From 612d74fd2c2054c17f96549238935ebab4c2d4ab Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 28 Jul 2020 22:04:01 +0100 Subject: [PATCH 01/51] Migration of home and librarymenu to ES6 modules --- package.json | 2 + .../dashboard/plugins/available/index.js | 2 + .../dashboard/plugins/installed/index.js | 2 + src/controllers/home.js | 118 +++++----- src/controllers/itemDetails/index.js | 2 + src/controllers/movies/moviesrecommended.js | 2 + src/controllers/music/musicrecommended.js | 2 + src/scripts/libraryMenu.js | 216 ++++++++++-------- 8 files changed, 190 insertions(+), 156 deletions(-) diff --git a/package.json b/package.json index edda337f2c..6276da0e43 100644 --- a/package.json +++ b/package.json @@ -188,6 +188,7 @@ "src/controllers/dashboard/users/userparentalcontrol.js", "src/controllers/dashboard/users/userpasswordpage.js", "src/controllers/dashboard/users/userprofilespage.js", + "src/controllers/home.js", "src/controllers/playback/queue/index.js", "src/controllers/playback/video/index.js", "src/controllers/searchpage.js", @@ -248,6 +249,7 @@ "src/scripts/imagehelper.js", "src/scripts/inputManager.js", "src/scripts/keyboardNavigation.js", + "src/scripts/libraryMenu.js", "src/scripts/playlists.js", "src/scripts/settings/appSettings.js", "src/scripts/settings/userSettings.js", diff --git a/src/controllers/dashboard/plugins/available/index.js b/src/controllers/dashboard/plugins/available/index.js index 1f202d6ff6..473b1b8961 100644 --- a/src/controllers/dashboard/plugins/available/index.js +++ b/src/controllers/dashboard/plugins/available/index.js @@ -1,6 +1,8 @@ define(['loading', 'libraryMenu', 'globalize', 'cardStyle', 'emby-button', 'emby-checkbox', 'emby-select'], function (loading, libraryMenu, globalize) { 'use strict'; + libraryMenu = LibraryMenu.default || libraryMenu; + function reloadList(page) { loading.show(); var promise1 = ApiClient.getAvailablePlugins(); diff --git a/src/controllers/dashboard/plugins/installed/index.js b/src/controllers/dashboard/plugins/installed/index.js index d28a04cb2b..014fbf16c2 100644 --- a/src/controllers/dashboard/plugins/installed/index.js +++ b/src/controllers/dashboard/plugins/installed/index.js @@ -1,6 +1,8 @@ define(['loading', 'libraryMenu', 'dom', 'globalize', 'cardStyle', 'emby-button'], function (loading, libraryMenu, dom, globalize) { 'use strict'; + libraryMenu = LibraryMenu.default || libraryMenu; + function deletePlugin(page, uniqueid, name) { var msg = globalize.translate('UninstallPluginConfirmation', name); diff --git a/src/controllers/home.js b/src/controllers/home.js index 9a4cea2227..f3e9e8e22b 100644 --- a/src/controllers/home.js +++ b/src/controllers/home.js @@ -1,75 +1,77 @@ -define(['tabbedView', 'globalize', 'require', 'emby-tabs', 'emby-button', 'emby-scroller'], function (TabbedView, globalize, require) { - 'use strict'; +import TabbedView from 'tabbedView'; +import globalize from 'globalize'; +import require from 'require'; +import 'emby-tabs'; +import 'emby-button'; +import 'emby-scroller'; - function getTabs() { - return [{ - name: globalize.translate('Home') - }, { - name: globalize.translate('Favorites') - }]; +function getTabs() { + return [{ + name: globalize.translate('Home') + }, { + name: globalize.translate('Favorites') + }]; +} + +function getDefaultTabIndex() { + return 0; +} + +function getRequirePromise(deps) { + return new Promise(function (resolve, reject) { + require(deps, resolve); + }); +} + +function getTabController(index) { + if (null == index) { + throw new Error('index cannot be null'); } - function getDefaultTabIndex() { - return 0; + const depends = []; + + switch (index) { + case 0: + depends.push('controllers/hometab'); + break; + + case 1: + depends.push('controllers/favorites'); } - function getRequirePromise(deps) { - return new Promise(function (resolve, reject) { - require(deps, resolve); - }); - } + const instance = this; + return getRequirePromise(depends).then(function (controllerFactory) { + let controller = instance.tabControllers[index]; - function getTabController(index) { - if (null == index) { - throw new Error('index cannot be null'); + if (!controller) { + controller = new controllerFactory(instance.view.querySelector(".tabContent[data-index='" + index + "']"), instance.params); + instance.tabControllers[index] = controller; } - var depends = []; + return controller; + }); +} - switch (index) { - case 0: - depends.push('controllers/hometab'); - break; - - case 1: - depends.push('controllers/favorites'); - } - - var instance = this; - return getRequirePromise(depends).then(function (controllerFactory) { - var controller = instance.tabControllers[index]; - - if (!controller) { - controller = new controllerFactory(instance.view.querySelector(".tabContent[data-index='" + index + "']"), instance.params); - instance.tabControllers[index] = controller; - } - - return controller; - }); - } - - function HomeView(view, params) { +class HomeView { + constructor(view, params) { TabbedView.call(this, view, params); } - - Object.assign(HomeView.prototype, TabbedView.prototype); - HomeView.prototype.getTabs = getTabs; - HomeView.prototype.getDefaultTabIndex = getDefaultTabIndex; - HomeView.prototype.getTabController = getTabController; - - HomeView.prototype.setTitle = function () { + setTitle() { Emby.Page.setTitle(null); - }; - - HomeView.prototype.onPause = function () { + } + onPause() { TabbedView.prototype.onPause.call(this); document.querySelector('.skinHeader').classList.remove('noHomeButtonHeader'); - }; - - HomeView.prototype.onResume = function (options) { + } + onResume(options) { TabbedView.prototype.onResume.call(this, options); document.querySelector('.skinHeader').classList.add('noHomeButtonHeader'); - }; + } +} - return HomeView; -}); +Object.assign(HomeView.prototype, TabbedView.prototype); +HomeView.prototype.getTabs = getTabs; +HomeView.prototype.getDefaultTabIndex = getDefaultTabIndex; +HomeView.prototype.getTabController = getTabController; + +export default HomeView; diff --git a/src/controllers/itemDetails/index.js b/src/controllers/itemDetails/index.js index 29aa8ad1e1..49412c4ac4 100644 --- a/src/controllers/itemDetails/index.js +++ b/src/controllers/itemDetails/index.js @@ -1,6 +1,8 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSettings', 'cardBuilder', 'datetime', 'mediaInfo', 'backdrop', 'listView', 'itemContextMenu', 'itemHelper', 'dom', 'indicators', 'imageLoader', 'libraryMenu', 'globalize', 'browser', 'events', 'playbackManager', 'scrollStyles', 'emby-itemscontainer', 'emby-checkbox', 'emby-button', 'emby-playstatebutton', 'emby-ratingbutton', 'emby-scroller', 'emby-select'], function (loading, appRouter, layoutManager, connectionManager, userSettings, cardBuilder, datetime, mediaInfo, backdrop, listView, itemContextMenu, itemHelper, dom, indicators, imageLoader, libraryMenu, globalize, browser, events, playbackManager) { 'use strict'; + libraryMenu = LibraryMenu.default || libraryMenu; + function getPromise(apiClient, params) { var id = params.id; diff --git a/src/controllers/movies/moviesrecommended.js b/src/controllers/movies/moviesrecommended.js index 4ffe7888cf..96e98452db 100644 --- a/src/controllers/movies/moviesrecommended.js +++ b/src/controllers/movies/moviesrecommended.js @@ -1,6 +1,8 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu', 'mainTabsManager', 'cardBuilder', 'dom', 'imageLoader', 'playbackManager', 'globalize', 'emby-scroller', 'emby-itemscontainer', 'emby-tabs', 'emby-button'], function (events, layoutManager, inputManager, userSettings, libraryMenu, mainTabsManager, cardBuilder, dom, imageLoader, playbackManager, globalize) { 'use strict'; + libraryMenu = LibraryMenu.default || libraryMenu; + function enableScrollX() { return !layoutManager.desktop; } diff --git a/src/controllers/music/musicrecommended.js b/src/controllers/music/musicrecommended.js index 0eecef0bfb..01b907a893 100644 --- a/src/controllers/music/musicrecommended.js +++ b/src/controllers/music/musicrecommended.js @@ -1,6 +1,8 @@ define(['browser', 'layoutManager', 'userSettings', 'inputManager', 'loading', 'cardBuilder', 'dom', 'apphost', 'imageLoader', 'libraryMenu', 'playbackManager', 'mainTabsManager', 'globalize', 'scrollStyles', 'emby-itemscontainer', 'emby-tabs', 'emby-button', 'flexStyles'], function (browser, layoutManager, userSettings, inputManager, loading, cardBuilder, dom, appHost, imageLoader, libraryMenu, playbackManager, mainTabsManager, globalize) { 'use strict'; + libraryMenu = LibraryMenu.default || libraryMenu; + function itemsPerRow() { var screenWidth = dom.getWindowSize().innerWidth; diff --git a/src/scripts/libraryMenu.js b/src/scripts/libraryMenu.js index 961d89dc05..86a8d38079 100644 --- a/src/scripts/libraryMenu.js +++ b/src/scripts/libraryMenu.js @@ -1,8 +1,26 @@ -define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', 'viewManager', 'libraryBrowser', 'appRouter', 'apphost', 'playbackManager', 'syncPlayManager', 'groupSelectionMenu', 'browser', 'globalize', 'scripts/imagehelper', 'paper-icon-button-light', 'material-icons', 'scrollStyles', 'flexStyles'], function (dom, layoutManager, inputManager, connectionManager, events, viewManager, libraryBrowser, appRouter, appHost, playbackManager, syncPlayManager, groupSelectionMenu, browser, globalize, imageHelper) { - 'use strict'; +import dom from 'dom'; +import layoutManager from 'layoutManager'; +import inputManager from 'inputManager'; +import connectionManager from 'connectionManager'; +import events from 'events'; +import viewManager from 'viewManager'; +import appRouter from 'appRouter'; +import appHost from 'apphost'; +import playbackManager from 'playbackManager'; +import syncPlayManager from 'syncPlayManager'; +import groupSelectionMenu from 'groupSelectionMenu'; +import browser from 'browser'; +import globalize from 'globalize'; +import imageHelper from 'scripts/imagehelper'; +import 'paper-icon-button-light'; +import 'material-icons'; +import 'scrollStyles'; +import 'flexStyles'; + +/* eslint-disable indent */ function renderHeader() { - var html = ''; + let html = ''; html += '
'; html += '
'; html += ''; @@ -56,11 +74,11 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' } function updateUserInHeader(user) { - var hasImage; + let hasImage; if (user && user.name) { if (user.imageUrl) { - var url = user.imageUrl; + const url = user.imageUrl; updateHeaderUserButton(url); hasImage = true; } @@ -87,9 +105,9 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' headerCastButton.classList.remove('hide'); } - var policy = user.Policy ? user.Policy : user.localUser.Policy; + const policy = user.Policy ? user.Policy : user.localUser.Policy; - var apiClient = getCurrentApiClient(); + const apiClient = getCurrentApiClient(); if (headerSyncButton && policy && policy.SyncPlayAccess !== 'None' && apiClient.isMinServerVersion('10.6.0')) { headerSyncButton.classList.remove('hide'); } @@ -139,7 +157,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' mainDrawerButton.addEventListener('click', toggleMainDrawer); } - var headerBackButton = skinHeader.querySelector('.headerBackButton'); + const headerBackButton = skinHeader.querySelector('.headerBackButton'); if (headerBackButton) { headerBackButton.addEventListener('click', onBackClick); @@ -181,7 +199,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' } function onCastButtonClicked() { - var btn = this; + const btn = this; require(['playerSelectionMenu'], function (playerSelectionMenu) { playerSelectionMenu.show(btn); @@ -189,12 +207,12 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' } function onSyncButtonClicked() { - var btn = this; + const btn = this; groupSelectionMenu.show(btn); } function onSyncPlayEnabled(event, enabled) { - var icon = headerSyncButton.querySelector('span'); + const icon = headerSyncButton.querySelector('span'); icon.classList.remove('sync', 'sync_disabled', 'sync_problem'); if (enabled) { icon.classList.add('sync'); @@ -204,7 +222,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' } function onSyncPlaySyncing(event, is_syncing, syncMethod) { - var icon = headerSyncButton.querySelector('span'); + const icon = headerSyncButton.querySelector('span'); icon.classList.remove('sync', 'sync_disabled', 'sync_problem'); if (is_syncing) { icon.classList.add('sync_problem'); @@ -250,7 +268,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' } function refreshLibraryInfoInDrawer(user, drawer) { - var html = ''; + let html = ''; html += '
'; html += '' + globalize.translate('ButtonHome') + ''; @@ -286,12 +304,12 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' // add buttons to navigation drawer navDrawerScrollContainer.innerHTML = html; - var btnSettings = navDrawerScrollContainer.querySelector('.btnSettings'); + const btnSettings = navDrawerScrollContainer.querySelector('.btnSettings'); if (btnSettings) { btnSettings.addEventListener('click', onSettingsClick); } - var btnLogout = navDrawerScrollContainer.querySelector('.btnLogout'); + const btnLogout = navDrawerScrollContainer.querySelector('.btnLogout'); if (btnLogout) { btnLogout.addEventListener('click', onLogoutClick); } @@ -313,20 +331,20 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' } function updateDashboardMenuSelectedItem() { - var links = navDrawerScrollContainer.querySelectorAll('.navMenuOption'); - var currentViewId = viewManager.currentView().id; + const links = navDrawerScrollContainer.querySelectorAll('.navMenuOption'); + const currentViewId = viewManager.currentView().id; - for (var i = 0, length = links.length; i < length; i++) { - var link = links[i]; - var selected = false; - var pageIds = link.getAttribute('data-pageids'); + for (let i = 0, length = links.length; i < length; i++) { + let link = links[i]; + let selected = false; + let pageIds = link.getAttribute('data-pageids'); if (pageIds) { pageIds = pageIds.split('|'); selected = -1 != pageIds.indexOf(currentViewId); } - var pageUrls = link.getAttribute('data-pageurls'); + let pageUrls = link.getAttribute('data-pageurls'); if (pageUrls) { pageUrls = pageUrls.split('|'); @@ -335,7 +353,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' if (selected) { link.classList.add('navMenuOption-selected'); - var title = ''; + let title = ''; link = link.querySelector('.navMenuOptionText') || link; title += (link.innerText || link.textContent).trim(); LibraryMenu.setTitle(title); @@ -346,7 +364,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' } function createToolsMenuList(pluginItems) { - var links = [{ + const links = [{ name: globalize.translate('TabServer') }, { name: globalize.translate('TabDashboard'), @@ -458,8 +476,8 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' } function addPluginPagesToMainMenu(links, pluginItems, section) { - for (var i = 0, length = pluginItems.length; i < length; i++) { - var pluginItem = pluginItems[i]; + for (let i = 0, length = pluginItems.length; i < length; i++) { + const pluginItem = pluginItems[i]; if (pluginItem.EnableInMainMenu && pluginItem.MenuSection === section) { links.push({ @@ -479,10 +497,10 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' } function getToolsLinkHtml(item) { - var menuHtml = ''; - var pageIds = item.pageIds ? item.pageIds.join('|') : ''; + let menuHtml = ''; + let pageIds = item.pageIds ? item.pageIds.join('|') : ''; pageIds = pageIds ? ' data-pageids="' + pageIds + '"' : ''; - var pageUrls = item.pageUrls ? item.pageUrls.join('|') : ''; + let pageUrls = item.pageUrls ? item.pageUrls.join('|') : ''; pageUrls = pageUrls ? ' data-pageurls="' + pageUrls + '"' : ''; menuHtml += ''; @@ -498,11 +516,11 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' function getToolsMenuHtml(apiClient) { return getToolsMenuLinks(apiClient).then(function (items) { - var item; - var menuHtml = ''; + let item; + let menuHtml = ''; menuHtml += '
'; - for (var i = 0; i < items.length; i++) { + for (let i = 0; i < items.length; i++) { item = items[i]; if (item.href) { @@ -520,7 +538,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' function createDashboardMenu(apiClient) { return getToolsMenuHtml(apiClient).then(function (toolsMenuHtml) { - var html = ''; + let html = ''; html += ''; @@ -531,24 +549,24 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' } function onSidebarLinkClick() { - var section = this.getElementsByClassName('sectionName')[0]; - var text = section ? section.innerHTML : this.innerHTML; + const section = this.getElementsByClassName('sectionName')[0]; + const text = section ? section.innerHTML : this.innerHTML; LibraryMenu.setTitle(text); } function getUserViews(apiClient, userId) { return apiClient.getUserViews({}, userId).then(function (result) { - var items = result.Items; - var list = []; + const items = result.Items; + const list = []; - for (var i = 0, length = items.length; i < length; i++) { - var view = items[i]; + for (let i = 0, length = items.length; i < length; i++) { + const view = items[i]; list.push(view); if ('livetv' == view.CollectionType) { view.ImageTags = {}; view.icon = 'live_tv'; - var guideView = Object.assign({}, view); + const guideView = Object.assign({}, view); guideView.Name = globalize.translate('ButtonGuide'); guideView.ImageTags = {}; guideView.icon = 'dvr'; @@ -562,7 +580,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' } function showBySelector(selector, show) { - var elem = document.querySelector(selector); + const elem = document.querySelector(selector); if (elem) { if (show) { @@ -592,17 +610,17 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' showBySelector('.libraryMenuDownloads', false); } - var userId = Dashboard.getCurrentUserId(); - var apiClient = getCurrentApiClient(); - var libraryMenuOptions = document.querySelector('.libraryMenuOptions'); + const userId = Dashboard.getCurrentUserId(); + const apiClient = getCurrentApiClient(); + const libraryMenuOptions = document.querySelector('.libraryMenuOptions'); if (libraryMenuOptions) { getUserViews(apiClient, userId).then(function (result) { - var items = result; - var html = `

${globalize.translate('HeaderMedia')}

`; + const items = result; + let html = `

${globalize.translate('HeaderMedia')}

`; html += items.map(function (i) { - var icon = i.icon || imageHelper.getLibraryIcon(i.CollectionType); - var itemId = i.Id; + const icon = i.icon || imageHelper.getLibraryIcon(i.CollectionType); + const itemId = i.Id; const linkHtml = ` @@ -612,8 +630,8 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' return linkHtml; }).join(''); libraryMenuOptions.innerHTML = html; - var elem = libraryMenuOptions; - var sidebarLinks = elem.querySelectorAll('.navMenuOption'); + const elem = libraryMenuOptions; + const sidebarLinks = elem.querySelectorAll('.navMenuOption'); for (const sidebarLink of sidebarLinks) { sidebarLink.removeEventListener('click', onSidebarLinkClick); @@ -642,9 +660,9 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' } function updateCastIcon() { - var context = document; - var info = playbackManager.getPlayerInfo(); - var icon = headerCastButton.querySelector('.material-icons'); + const context = document; + const info = playbackManager.getPlayerInfo(); + const icon = headerCastButton.querySelector('.material-icons'); icon.classList.remove('cast_connected', 'cast'); @@ -660,18 +678,16 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' } function updateLibraryNavLinks(page) { - var i; - var length; - var isLiveTvPage = page.classList.contains('liveTvPage'); - var isChannelsPage = page.classList.contains('channelsPage'); - var isEditorPage = page.classList.contains('metadataEditorPage'); - var isMySyncPage = page.classList.contains('mySyncPage'); - var id = isLiveTvPage || isChannelsPage || isEditorPage || isMySyncPage || page.classList.contains('allLibraryPage') ? '' : getTopParentId() || ''; - var elems = document.getElementsByClassName('lnkMediaFolder'); + const isLiveTvPage = page.classList.contains('liveTvPage'); + const isChannelsPage = page.classList.contains('channelsPage'); + const isEditorPage = page.classList.contains('metadataEditorPage'); + const isMySyncPage = page.classList.contains('mySyncPage'); + const id = isLiveTvPage || isChannelsPage || isEditorPage || isMySyncPage || page.classList.contains('allLibraryPage') ? '' : getTopParentId() || ''; + const elems = document.getElementsByClassName('lnkMediaFolder'); - for (var i = 0, length = elems.length; i < length; i++) { - var lnkMediaFolder = elems[i]; - var itemId = lnkMediaFolder.getAttribute('data-itemid'); + for (let i = 0, length = elems.length; i < length; i++) { + const lnkMediaFolder = elems[i]; + const itemId = lnkMediaFolder.getAttribute('data-itemid'); if (isChannelsPage && 'channels' === itemId) { lnkMediaFolder.classList.add('navMenuOption-selected'); @@ -692,7 +708,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' } function updateMenuForPageType(isDashboardPage, isLibraryPage) { - var newPageType = isDashboardPage ? 2 : isLibraryPage ? 1 : 3; + const newPageType = isDashboardPage ? 2 : isLibraryPage ? 1 : 3; if (currentPageType !== newPageType) { currentPageType = newPageType; @@ -703,7 +719,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' skinHeader.classList.remove('headroomDisabled'); } - var bodyClassList = document.body.classList; + const bodyClassList = document.body.classList; if (isLibraryPage) { bodyClassList.add('libraryDocument'); @@ -740,7 +756,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' } function updateTitle(page) { - var title = page.getAttribute('data-title'); + const title = page.getAttribute('data-title'); if (title) { LibraryMenu.setTitle(title); @@ -765,7 +781,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' function initHeadRoom(elem) { require(['headroom'], function (Headroom) { - var headroom = new Headroom(elem); + const headroom = new Headroom(elem); headroom.init(); }); } @@ -785,7 +801,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' } function getNavDrawerOptions() { - var drawerWidth = screen.availWidth - 50; + let drawerWidth = screen.availWidth - 50; drawerWidth = Math.max(drawerWidth, 240); drawerWidth = Math.min(drawerWidth, 320); return { @@ -816,25 +832,25 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' }); } - var navDrawerElement; - var navDrawerScrollContainer; - var navDrawerInstance; - var mainDrawerButton; - var headerHomeButton; - var currentDrawerType; - var pageTitleElement; - var headerBackButton; - var headerUserButton; - var currentUser; - var headerCastButton; - var headerSearchButton; - var headerAudioPlayerButton; - var headerSyncButton; - var enableLibraryNavDrawer = layoutManager.desktop; - var enableLibraryNavDrawerHome = !layoutManager.tv; - var skinHeader = document.querySelector('.skinHeader'); - var requiresUserRefresh = true; - window.LibraryMenu = { + let navDrawerElement; + let navDrawerScrollContainer; + let navDrawerInstance; + let mainDrawerButton; + let headerHomeButton; + let currentDrawerType; + let pageTitleElement; + let headerBackButton; + let headerUserButton; + let currentUser; + let headerCastButton; + let headerSearchButton; + let headerAudioPlayerButton; + let headerSyncButton; + const enableLibraryNavDrawer = layoutManager.desktop; + const enableLibraryNavDrawerHome = !layoutManager.tv; + const skinHeader = document.querySelector('.skinHeader'); + let requiresUserRefresh = true; + const LibraryMenu = { getTopParentId: getTopParentId, onHardwareMenuButtonClick: function () { toggleMainDrawer(); @@ -873,7 +889,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' title = ''; } - var html = title; + const html = title; if (!pageTitleElement) { pageTitleElement = document.querySelector('.pageTitle'); @@ -896,18 +912,18 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' } } }; - var currentPageType; + let currentPageType; pageClassOn('pagebeforeshow', 'page', function (e) { if (!this.classList.contains('withTabs')) { LibraryMenu.setTabs(null); } }); pageClassOn('pageshow', 'page', function (e) { - var page = this; - var isDashboardPage = page.classList.contains('type-interior'); - var isHomePage = page.classList.contains('homePage'); - var isLibraryPage = !isDashboardPage && page.classList.contains('libraryPage'); - var apiClient = getCurrentApiClient(); + const page = this; + const isDashboardPage = page.classList.contains('type-interior'); + const isHomePage = page.classList.contains('homePage'); + const isLibraryPage = !isDashboardPage && page.classList.contains('libraryPage'); + const apiClient = getCurrentApiClient(); if (isDashboardPage) { if (mainDrawerButton) { @@ -944,7 +960,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' renderHeader(); events.on(connectionManager, 'localusersignedin', function (e, user) { - var currentApiClient = connectionManager.getApiClient(user.ServerId); + const currentApiClient = connectionManager.getApiClient(user.ServerId); currentDrawerType = null; currentUser = { @@ -968,5 +984,9 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' events.on(syncPlayManager, 'syncing', onSyncPlaySyncing); loadNavDrawer(); - return LibraryMenu; -}); + + window.LibraryMenu = LibraryMenu; + +export default LibraryMenu; + +/* eslint-enable indent */ From 00a545dae350daf20b671ee184ebb9f7e5430534 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 28 Jul 2020 22:09:56 +0100 Subject: [PATCH 02/51] extraction of functions --- src/scripts/libraryMenu.js | 129 ++++++++++++++++++++----------------- 1 file changed, 71 insertions(+), 58 deletions(-) diff --git a/src/scripts/libraryMenu.js b/src/scripts/libraryMenu.js index 86a8d38079..594bb79f20 100644 --- a/src/scripts/libraryMenu.js +++ b/src/scripts/libraryMenu.js @@ -850,74 +850,74 @@ import 'flexStyles'; const enableLibraryNavDrawerHome = !layoutManager.tv; const skinHeader = document.querySelector('.skinHeader'); let requiresUserRefresh = true; - const LibraryMenu = { - getTopParentId: getTopParentId, - onHardwareMenuButtonClick: function () { - toggleMainDrawer(); - }, - setTabs: function (type, selectedIndex, builder) { - require(['mainTabsManager'], function (mainTabsManager) { - if (type) { - mainTabsManager.setTabs(viewManager.currentView(), selectedIndex, builder, function () { - return []; - }); - } else { - mainTabsManager.setTabs(null); - } - }); - }, - setDefaultTitle: function () { - if (!pageTitleElement) { - pageTitleElement = document.querySelector('.pageTitle'); - } - if (pageTitleElement) { - pageTitleElement.classList.add('pageTitleWithLogo'); - pageTitleElement.classList.add('pageTitleWithDefaultLogo'); - pageTitleElement.style.backgroundImage = null; - pageTitleElement.innerHTML = ''; - } - - document.title = 'Jellyfin'; - }, - setTitle: function (title) { - if (null == title) { - return void LibraryMenu.setDefaultTitle(); - } - - if ('-' === title) { - title = ''; - } - - const html = title; - - if (!pageTitleElement) { - pageTitleElement = document.querySelector('.pageTitle'); - } - - if (pageTitleElement) { - pageTitleElement.classList.remove('pageTitleWithLogo'); - pageTitleElement.classList.remove('pageTitleWithDefaultLogo'); - pageTitleElement.style.backgroundImage = null; - pageTitleElement.innerHTML = html || ''; - } - - document.title = title || 'Jellyfin'; - }, - setTransparentMenu: function (transparent) { - if (transparent) { - skinHeader.classList.add('semiTransparent'); + function setTabs (type, selectedIndex, builder) { + require(['mainTabsManager'], function (mainTabsManager) { + if (type) { + mainTabsManager.setTabs(viewManager.currentView(), selectedIndex, builder, function () { + return []; + }); } else { - skinHeader.classList.remove('semiTransparent'); + mainTabsManager.setTabs(null); } + }); + } + + function setDefaultTitle () { + if (!pageTitleElement) { + pageTitleElement = document.querySelector('.pageTitle'); } - }; + + if (pageTitleElement) { + pageTitleElement.classList.add('pageTitleWithLogo'); + pageTitleElement.classList.add('pageTitleWithDefaultLogo'); + pageTitleElement.style.backgroundImage = null; + pageTitleElement.innerHTML = ''; + } + + document.title = 'Jellyfin'; + } + + function setTitle (title) { + if (null == title) { + return void LibraryMenu.setDefaultTitle(); + } + + if ('-' === title) { + title = ''; + } + + const html = title; + + if (!pageTitleElement) { + pageTitleElement = document.querySelector('.pageTitle'); + } + + if (pageTitleElement) { + pageTitleElement.classList.remove('pageTitleWithLogo'); + pageTitleElement.classList.remove('pageTitleWithDefaultLogo'); + pageTitleElement.style.backgroundImage = null; + pageTitleElement.innerHTML = html || ''; + } + + document.title = title || 'Jellyfin'; + } + + function setTransparentMenu (transparent) { + if (transparent) { + skinHeader.classList.add('semiTransparent'); + } else { + skinHeader.classList.remove('semiTransparent'); + } + } + let currentPageType; pageClassOn('pagebeforeshow', 'page', function (e) { if (!this.classList.contains('withTabs')) { LibraryMenu.setTabs(null); } }); + pageClassOn('pageshow', 'page', function (e) { const page = this; const isDashboardPage = page.classList.contains('type-interior'); @@ -974,10 +974,12 @@ import 'flexStyles'; updateUserInHeader(user); }); }); + events.on(connectionManager, 'localusersignedout', function () { currentUser = {}; updateUserInHeader(); }); + events.on(playbackManager, 'playerchange', updateCastIcon); events.on(syncPlayManager, 'enabled', onSyncPlayEnabled); @@ -985,6 +987,17 @@ import 'flexStyles'; loadNavDrawer(); + const LibraryMenu = { + getTopParentId: getTopParentId, + onHardwareMenuButtonClick: function () { + toggleMainDrawer(); + }, + setTabs: setTabs, + setDefaultTitle: setDefaultTitle, + setTitle: setTitle, + setTransparentMenu: setTransparentMenu + }; + window.LibraryMenu = LibraryMenu; export default LibraryMenu; From 215a623a18cf859414cadb4051fefbce7051812e Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 28 Jul 2020 22:13:49 +0100 Subject: [PATCH 03/51] remove require --- src/scripts/libraryMenu.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/scripts/libraryMenu.js b/src/scripts/libraryMenu.js index 594bb79f20..f010fa15e3 100644 --- a/src/scripts/libraryMenu.js +++ b/src/scripts/libraryMenu.js @@ -64,7 +64,7 @@ import 'flexStyles'; } function lazyLoadViewMenuBarImages() { - require(['imageLoader'], function (imageLoader) { + import('imageLoader').then(({default: imageLoader}) => { imageLoader.lazyChildren(skinHeader); }); } @@ -201,7 +201,7 @@ import 'flexStyles'; function onCastButtonClicked() { const btn = this; - require(['playerSelectionMenu'], function (playerSelectionMenu) { + import('playerSelectionMenu').then(({default: playerSelectionMenu}) => { playerSelectionMenu.show(btn); }); } @@ -780,7 +780,7 @@ import 'flexStyles'; } function initHeadRoom(elem) { - require(['headroom'], function (Headroom) { + import('headroom').then(({default: Headroom}) => { const headroom = new Headroom(elem); headroom.init(); }); @@ -820,7 +820,7 @@ import 'flexStyles'; navDrawerScrollContainer = navDrawerElement.querySelector('.scrollContainer'); navDrawerScrollContainer.addEventListener('click', onMainDrawerClick); return new Promise(function (resolve, reject) { - require(['navdrawer'], function (navdrawer) { + import('navdrawer').then(({default: navdrawer}) => { navDrawerInstance = new navdrawer(getNavDrawerOptions()); if (!layoutManager.tv) { @@ -852,7 +852,7 @@ import 'flexStyles'; let requiresUserRefresh = true; function setTabs (type, selectedIndex, builder) { - require(['mainTabsManager'], function (mainTabsManager) { + import('mainTabsManager').then(({default: mainTabsManager}) => { if (type) { mainTabsManager.setTabs(viewManager.currentView(), selectedIndex, builder, function () { return []; From 997054ab12572c52000cc1ec0186bfbf4f609679 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 31 Jul 2020 15:54:47 +0100 Subject: [PATCH 04/51] remove require This reverts commit bef8a4509d0b151e7a2e85f549bfca49ce952341. --- src/controllers/home.js | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/controllers/home.js b/src/controllers/home.js index f3e9e8e22b..392da0e2d2 100644 --- a/src/controllers/home.js +++ b/src/controllers/home.js @@ -1,6 +1,5 @@ import TabbedView from 'tabbedView'; import globalize from 'globalize'; -import require from 'require'; import 'emby-tabs'; import 'emby-button'; import 'emby-scroller'; @@ -17,30 +16,24 @@ function getDefaultTabIndex() { return 0; } -function getRequirePromise(deps) { - return new Promise(function (resolve, reject) { - require(deps, resolve); - }); -} - function getTabController(index) { if (null == index) { throw new Error('index cannot be null'); } - const depends = []; + let depends = ''; switch (index) { case 0: - depends.push('controllers/hometab'); + depends = 'controllers/hometab'; break; case 1: - depends.push('controllers/favorites'); + depends = 'controllers/favorites'; } const instance = this; - return getRequirePromise(depends).then(function (controllerFactory) { + return import(depends).then(({ default: controllerFactory }) => { let controller = instance.tabControllers[index]; if (!controller) { From 39627f364a52bd1103ca72496a3a3a4e1fd0340e Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 31 Jul 2020 16:12:49 +0100 Subject: [PATCH 05/51] Migration of tabbedview to ES6 module --- package.json | 1 + src/components/tabbedview/tabbedview.js | 60 +++++++++++++------------ 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/package.json b/package.json index 2fd4b90ce5..07ac3f4b54 100644 --- a/package.json +++ b/package.json @@ -158,6 +158,7 @@ "src/components/syncPlay/playbackPermissionManager.js", "src/components/syncPlay/syncPlayManager.js", "src/components/syncPlay/timeSyncManager.js", + "src/components/tabbedview/tabbedview.js", "src/controllers/session/addServer/index.js", "src/controllers/session/forgotPassword/index.js", "src/controllers/session/redeemPassword/index.js", diff --git a/src/components/tabbedview/tabbedview.js b/src/components/tabbedview/tabbedview.js index 8bd3afd372..710a0e3c40 100644 --- a/src/components/tabbedview/tabbedview.js +++ b/src/components/tabbedview/tabbedview.js @@ -1,30 +1,33 @@ -define(['backdrop', 'mainTabsManager', 'layoutManager', 'emby-tabs'], function (backdrop, mainTabsManager, layoutManager) { - 'use strict'; +import backdrop from 'backdrop'; +import * as mainTabsManager from 'mainTabsManager'; +import layoutManager from 'layoutManager'; +import 'emby-tabs'; - function onViewDestroy(e) { - var tabControllers = this.tabControllers; +function onViewDestroy(e) { + var tabControllers = this.tabControllers; - if (tabControllers) { - tabControllers.forEach(function (t) { - if (t.destroy) { - t.destroy(); - } - }); + if (tabControllers) { + tabControllers.forEach(function (t) { + if (t.destroy) { + t.destroy(); + } + }); - this.tabControllers = null; - } - - this.view = null; - this.params = null; - this.currentTabController = null; - this.initialTabIndex = null; + this.tabControllers = null; } - function onBeforeTabChange() { + this.view = null; + this.params = null; + this.currentTabController = null; + this.initialTabIndex = null; +} - } +function onBeforeTabChange() { - function TabbedView(view, params) { +} + +class TabbedView { + constructor(view, params) { this.tabControllers = []; this.view = view; this.params = params; @@ -85,7 +88,7 @@ define(['backdrop', 'mainTabsManager', 'layoutManager', 'emby-tabs'], function ( view.addEventListener('viewdestroy', onViewDestroy.bind(this)); } - TabbedView.prototype.onResume = function (options) { + onResume(options) { this.setTitle(); backdrop.clearBackdrop(); @@ -96,19 +99,18 @@ define(['backdrop', 'mainTabsManager', 'layoutManager', 'emby-tabs'], function ( } else if (currentTabController && currentTabController.onResume) { currentTabController.onResume({}); } - }; + } - TabbedView.prototype.onPause = function () { + onPause() { var currentTabController = this.currentTabController; if (currentTabController && currentTabController.onPause) { currentTabController.onPause(); } - }; - - TabbedView.prototype.setTitle = function () { + } + setTitle() { Emby.Page.setTitle(''); - }; + } +} - return TabbedView; -}); +export default TabbedView; From 38d25172d5def42c19de33153fed8e7accaee286 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 31 Jul 2020 17:06:04 +0100 Subject: [PATCH 06/51] Fix type and applysuggestions to home.js --- .../dashboard/plugins/available/index.js | 2 +- .../dashboard/plugins/installed/index.js | 2 +- src/controllers/home.js | 98 +++++++++---------- src/controllers/itemDetails/index.js | 2 +- src/controllers/movies/moviesrecommended.js | 2 +- 5 files changed, 52 insertions(+), 54 deletions(-) diff --git a/src/controllers/dashboard/plugins/available/index.js b/src/controllers/dashboard/plugins/available/index.js index 473b1b8961..274cde8824 100644 --- a/src/controllers/dashboard/plugins/available/index.js +++ b/src/controllers/dashboard/plugins/available/index.js @@ -1,7 +1,7 @@ define(['loading', 'libraryMenu', 'globalize', 'cardStyle', 'emby-button', 'emby-checkbox', 'emby-select'], function (loading, libraryMenu, globalize) { 'use strict'; - libraryMenu = LibraryMenu.default || libraryMenu; + libraryMenu = libraryMenu.default || libraryMenu; function reloadList(page) { loading.show(); diff --git a/src/controllers/dashboard/plugins/installed/index.js b/src/controllers/dashboard/plugins/installed/index.js index 014fbf16c2..02fd954620 100644 --- a/src/controllers/dashboard/plugins/installed/index.js +++ b/src/controllers/dashboard/plugins/installed/index.js @@ -1,7 +1,7 @@ define(['loading', 'libraryMenu', 'dom', 'globalize', 'cardStyle', 'emby-button'], function (loading, libraryMenu, dom, globalize) { 'use strict'; - libraryMenu = LibraryMenu.default || libraryMenu; + libraryMenu = libraryMenu.default || libraryMenu; function deletePlugin(page, uniqueid, name) { var msg = globalize.translate('UninstallPluginConfirmation', name); diff --git a/src/controllers/home.js b/src/controllers/home.js index 392da0e2d2..bfee2c50fb 100644 --- a/src/controllers/home.js +++ b/src/controllers/home.js @@ -4,67 +4,65 @@ import 'emby-tabs'; import 'emby-button'; import 'emby-scroller'; -function getTabs() { - return [{ - name: globalize.translate('Home') - }, { - name: globalize.translate('Favorites') - }]; -} - -function getDefaultTabIndex() { - return 0; -} - -function getTabController(index) { - if (null == index) { - throw new Error('index cannot be null'); - } - - let depends = ''; - - switch (index) { - case 0: - depends = 'controllers/hometab'; - break; - - case 1: - depends = 'controllers/favorites'; - } - - const instance = this; - return import(depends).then(({ default: controllerFactory }) => { - let controller = instance.tabControllers[index]; - - if (!controller) { - controller = new controllerFactory(instance.view.querySelector(".tabContent[data-index='" + index + "']"), instance.params); - instance.tabControllers[index] = controller; - } - - return controller; - }); -} - -class HomeView { +class HomeView extends TabbedView { constructor(view, params) { - TabbedView.call(this, view, params); + super(view, params); } + setTitle() { Emby.Page.setTitle(null); } + onPause() { - TabbedView.prototype.onPause.call(this); + super.onPause(this); document.querySelector('.skinHeader').classList.remove('noHomeButtonHeader'); } + onResume(options) { - TabbedView.prototype.onResume.call(this, options); + super.onResume(this, options); document.querySelector('.skinHeader').classList.add('noHomeButtonHeader'); } + + getDefaultTabIndex() { + return 0; + } + + getTabs() { + return [{ + name: globalize.translate('Home') + }, { + name: globalize.translate('Favorites') + }]; + } + + getTabController(index) { + if (null == index) { + throw new Error('index cannot be null'); + } + + let depends = ''; + + switch (index) { + case 0: + depends = 'controllers/hometab'; + break; + + case 1: + depends = 'controllers/favorites'; + } + + const instance = this; + return import(depends).then(({ default: controllerFactory }) => { + let controller = instance.tabControllers[index]; + + if (!controller) { + controller = new controllerFactory(instance.view.querySelector(".tabContent[data-index='" + index + "']"), instance.params); + instance.tabControllers[index] = controller; + } + + return controller; + }); + } } -Object.assign(HomeView.prototype, TabbedView.prototype); -HomeView.prototype.getTabs = getTabs; -HomeView.prototype.getDefaultTabIndex = getDefaultTabIndex; -HomeView.prototype.getTabController = getTabController; - export default HomeView; diff --git a/src/controllers/itemDetails/index.js b/src/controllers/itemDetails/index.js index 49412c4ac4..d02268b9d4 100644 --- a/src/controllers/itemDetails/index.js +++ b/src/controllers/itemDetails/index.js @@ -1,7 +1,7 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSettings', 'cardBuilder', 'datetime', 'mediaInfo', 'backdrop', 'listView', 'itemContextMenu', 'itemHelper', 'dom', 'indicators', 'imageLoader', 'libraryMenu', 'globalize', 'browser', 'events', 'playbackManager', 'scrollStyles', 'emby-itemscontainer', 'emby-checkbox', 'emby-button', 'emby-playstatebutton', 'emby-ratingbutton', 'emby-scroller', 'emby-select'], function (loading, appRouter, layoutManager, connectionManager, userSettings, cardBuilder, datetime, mediaInfo, backdrop, listView, itemContextMenu, itemHelper, dom, indicators, imageLoader, libraryMenu, globalize, browser, events, playbackManager) { 'use strict'; - libraryMenu = LibraryMenu.default || libraryMenu; + libraryMenu = libraryMenu.default || libraryMenu; function getPromise(apiClient, params) { var id = params.id; diff --git a/src/controllers/movies/moviesrecommended.js b/src/controllers/movies/moviesrecommended.js index 96e98452db..09ecd3c034 100644 --- a/src/controllers/movies/moviesrecommended.js +++ b/src/controllers/movies/moviesrecommended.js @@ -1,7 +1,7 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu', 'mainTabsManager', 'cardBuilder', 'dom', 'imageLoader', 'playbackManager', 'globalize', 'emby-scroller', 'emby-itemscontainer', 'emby-tabs', 'emby-button'], function (events, layoutManager, inputManager, userSettings, libraryMenu, mainTabsManager, cardBuilder, dom, imageLoader, playbackManager, globalize) { 'use strict'; - libraryMenu = LibraryMenu.default || libraryMenu; + libraryMenu = libraryMenu.default || libraryMenu; function enableScrollX() { return !layoutManager.desktop; From 36f389e8f9e9d41db4c900ddb85452d06bd6f665 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 3 Aug 2020 20:57:54 +0100 Subject: [PATCH 07/51] Migration of htmlAudioPlayer to ES6 module --- package.json | 1 + src/plugins/htmlAudioPlayer/plugin.js | 342 +++++++++++++------------- 2 files changed, 173 insertions(+), 170 deletions(-) diff --git a/package.json b/package.json index 1d7cf770d3..3de5ede240 100644 --- a/package.json +++ b/package.json @@ -155,6 +155,7 @@ "src/components/refreshdialog/refreshdialog.js", "src/components/sanatizefilename.js", "src/components/scrollManager.js", + "src/plugins/htmlAudioPlayer/plugin.js", "src/plugins/htmlVideoPlayer/plugin.js", "src/components/search/searchfields.js", "src/components/search/searchresults.js", diff --git a/src/plugins/htmlAudioPlayer/plugin.js b/src/plugins/htmlAudioPlayer/plugin.js index 16fce8c9d1..c60349c5af 100644 --- a/src/plugins/htmlAudioPlayer/plugin.js +++ b/src/plugins/htmlAudioPlayer/plugin.js @@ -1,91 +1,94 @@ -define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelper'], function (events, browser, require, appHost, appSettings, htmlMediaHelper) { - 'use strict'; +import events from 'events'; +import browser from 'browser'; +import appHost from 'apphost'; +import * as htmlMediaHelper from 'htmlMediaHelper'; - function getDefaultProfile() { - return new Promise(function (resolve, reject) { - require(['browserdeviceprofile'], function (profileBuilder) { - resolve(profileBuilder({})); - }); +function getDefaultProfile() { + return new Promise(function (resolve, reject) { + import('browserdeviceprofile').then(({default: profileBuilder}) => { + resolve(profileBuilder({})); }); + }); +} + +let fadeTimeout; +function fade(instance, elem, startingVolume) { + instance._isFadingOut = true; + + // Need to record the starting volume on each pass rather than querying elem.volume + // This is due to iOS safari not allowing volume changes and always returning the system volume value + const newVolume = Math.max(0, startingVolume - 0.15); + console.debug('fading volume to ' + newVolume); + elem.volume = newVolume; + + if (newVolume <= 0) { + instance._isFadingOut = false; + return Promise.resolve(); } - var fadeTimeout; - function fade(instance, elem, startingVolume) { - instance._isFadingOut = true; + return new Promise(function (resolve, reject) { + cancelFadeTimeout(); + fadeTimeout = setTimeout(function () { + fade(instance, elem, newVolume).then(resolve, reject); + }, 100); + }); +} - // Need to record the starting volume on each pass rather than querying elem.volume - // This is due to iOS safari not allowing volume changes and always returning the system volume value - var newVolume = Math.max(0, startingVolume - 0.15); - console.debug('fading volume to ' + newVolume); - elem.volume = newVolume; +function cancelFadeTimeout() { + const timeout = fadeTimeout; + if (timeout) { + clearTimeout(timeout); + fadeTimeout = null; + } +} - if (newVolume <= 0) { - instance._isFadingOut = false; - return Promise.resolve(); - } +function supportsFade() { + if (browser.tv) { + // Not working on tizen. + // We could possibly enable on other tv's, but all smart tv browsers tend to be pretty primitive + return false; + } - return new Promise(function (resolve, reject) { - cancelFadeTimeout(); - fadeTimeout = setTimeout(function () { - fade(instance, elem, newVolume).then(resolve, reject); - }, 100); + return true; +} + +function requireHlsPlayer(callback) { + import('hlsjs').then(({default: hls}) => { + window.Hls = hls; + callback(); + }); +} + +function enableHlsPlayer(url, item, mediaSource, mediaType) { + if (!htmlMediaHelper.enableHlsJsPlayer(mediaSource.RunTimeTicks, mediaType)) { + return Promise.reject(); + } + + if (url.indexOf('.m3u8') !== -1) { + return Promise.resolve(); + } + + // issue head request to get content type + return new Promise(function (resolve, reject) { + import('fetchHelper').then(({default: fetchHelper}) => { + fetchHelper.ajax({ + url: url, + type: 'HEAD' + }).then(function (response) { + const contentType = (response.headers.get('Content-Type') || '').toLowerCase(); + if (contentType === 'application/x-mpegurl') { + resolve(); + } else { + reject(); + } + }, reject); }); - } + }); +} - function cancelFadeTimeout() { - var timeout = fadeTimeout; - if (timeout) { - clearTimeout(timeout); - fadeTimeout = null; - } - } - - function supportsFade() { - if (browser.tv) { - // Not working on tizen. - // We could possibly enable on other tv's, but all smart tv browsers tend to be pretty primitive - return false; - } - - return true; - } - - function requireHlsPlayer(callback) { - require(['hlsjs'], function (hls) { - window.Hls = hls; - callback(); - }); - } - - function enableHlsPlayer(url, item, mediaSource, mediaType) { - if (!htmlMediaHelper.enableHlsJsPlayer(mediaSource.RunTimeTicks, mediaType)) { - return Promise.reject(); - } - - if (url.indexOf('.m3u8') !== -1) { - return Promise.resolve(); - } - - // issue head request to get content type - return new Promise(function (resolve, reject) { - require(['fetchHelper'], function (fetchHelper) { - fetchHelper.ajax({ - url: url, - type: 'HEAD' - }).then(function (response) { - var contentType = (response.headers.get('Content-Type') || '').toLowerCase(); - if (contentType === 'application/x-mpegurl') { - resolve(); - } else { - reject(); - } - }, reject); - }); - }); - } - - function HtmlAudioPlayer() { - var self = this; +class HtmlAudioPlayer { + constructor() { + const self = this; self.name = 'Html Audio Player'; self.type = 'mediaplayer'; @@ -99,7 +102,7 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp self._timeUpdated = false; self._currentTime = null; - var elem = createMediaElement(); + const elem = createMediaElement(); return setCurrentSrc(elem, options); }; @@ -109,11 +112,11 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp unBindEvents(elem); bindEvents(elem); - var val = options.url; + let val = options.url; console.debug('playing url: ' + val); // Convert to seconds - var seconds = (options.playerStartPositionTicks || 0) / 10000000; + const seconds = (options.playerStartPositionTicks || 0) / 10000000; if (seconds) { val += '#t=' + seconds; } @@ -122,7 +125,7 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp self._currentPlayOptions = options; - var crossOrigin = htmlMediaHelper.getCrossOriginValue(options.mediaSource); + const crossOrigin = htmlMediaHelper.getCrossOriginValue(options.mediaSource); if (crossOrigin) { elem.crossOrigin = crossOrigin; } @@ -130,9 +133,9 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp return enableHlsPlayer(val, options.item, options.mediaSource, 'Audio').then(function () { return new Promise(function (resolve, reject) { requireHlsPlayer(function () { - var hls = new Hls({ + const hls = new Hls({ manifestLoadingTimeOut: 20000, - xhrSetup: function(xhr, url) { + xhrSetup: function (xhr, url) { xhr.withCredentials = true; } }); @@ -183,8 +186,8 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp self.stop = function (destroyPlayer) { cancelFadeTimeout(); - var elem = self._mediaElement; - var src = self._currentSrc; + const elem = self._mediaElement; + const src = self._currentSrc; if (elem && src) { if (!destroyPlayer || !supportsFade()) { @@ -198,7 +201,7 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp return Promise.resolve(); } - var originalVolume = elem.volume; + const originalVolume = elem.volume; return fade(self, elem, elem.volume).then(function () { elem.pause(); @@ -219,7 +222,7 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp }; function createMediaElement() { - var elem = self._mediaElement; + let elem = self._mediaElement; if (elem) { return elem; @@ -248,7 +251,7 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp function onTimeUpdate() { // Get the player position + the transcoding offset - var time = this.currentTime; + const time = this.currentTime; // Don't trigger events after user stop if (!self._isFadingOut) { @@ -287,11 +290,11 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp } function onError() { - var errorCode = this.error ? (this.error.code || 0) : 0; - var errorMessage = this.error ? (this.error.message || '') : ''; + const errorCode = this.error ? (this.error.code || 0) : 0; + const errorMessage = this.error ? (this.error.message || '') : ''; console.error('media element error: ' + errorCode.toString() + ' ' + errorMessage); - var type; + let type; switch (errorCode) { case 1: @@ -325,59 +328,59 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp } } - HtmlAudioPlayer.prototype.currentSrc = function () { + currentSrc() { return this._currentSrc; - }; + } - HtmlAudioPlayer.prototype.canPlayMediaType = function (mediaType) { + canPlayMediaType(mediaType) { return (mediaType || '').toLowerCase() === 'audio'; - }; + } - HtmlAudioPlayer.prototype.getDeviceProfile = function (item) { + getDeviceProfile(item) { if (appHost.getDeviceProfile) { return appHost.getDeviceProfile(item); } return getDefaultProfile(); - }; + } // Save this for when playback stops, because querying the time at that point might return 0 - HtmlAudioPlayer.prototype.currentTime = function (val) { - var mediaElement = this._mediaElement; + currentTime(val) { + const mediaElement = this._mediaElement; if (mediaElement) { if (val != null) { mediaElement.currentTime = val / 1000; return; } - var currentTime = this._currentTime; + const currentTime = this._currentTime; if (currentTime) { return currentTime * 1000; } return (mediaElement.currentTime || 0) * 1000; } - }; + } - HtmlAudioPlayer.prototype.duration = function (val) { - var mediaElement = this._mediaElement; + duration(val) { + const mediaElement = this._mediaElement; if (mediaElement) { - var duration = mediaElement.duration; + const duration = mediaElement.duration; if (htmlMediaHelper.isValidDuration(duration)) { return duration * 1000; } } return null; - }; + } - HtmlAudioPlayer.prototype.seekable = function () { - var mediaElement = this._mediaElement; + seekable() { + const mediaElement = this._mediaElement; if (mediaElement) { - var seekable = mediaElement.seekable; + const seekable = mediaElement.seekable; if (seekable && seekable.length) { - var start = seekable.start(0); - var end = seekable.end(0); + let start = seekable.start(0); + let end = seekable.end(0); if (!htmlMediaHelper.isValidDuration(start)) { start = 0; @@ -391,124 +394,123 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp return false; } - }; + } - HtmlAudioPlayer.prototype.getBufferedRanges = function () { - var mediaElement = this._mediaElement; + getBufferedRanges() { + const mediaElement = this._mediaElement; if (mediaElement) { return htmlMediaHelper.getBufferedRanges(this, mediaElement); } return []; - }; + } - HtmlAudioPlayer.prototype.pause = function () { - var mediaElement = this._mediaElement; + pause() { + const mediaElement = this._mediaElement; if (mediaElement) { mediaElement.pause(); } - }; + } // This is a retry after error - HtmlAudioPlayer.prototype.resume = function () { - var mediaElement = this._mediaElement; + resume() { + const mediaElement = this._mediaElement; if (mediaElement) { mediaElement.play(); } - }; + } - HtmlAudioPlayer.prototype.unpause = function () { - var mediaElement = this._mediaElement; + unpause() { + const mediaElement = this._mediaElement; if (mediaElement) { mediaElement.play(); } - }; + } - HtmlAudioPlayer.prototype.paused = function () { - var mediaElement = this._mediaElement; + paused() { + const mediaElement = this._mediaElement; if (mediaElement) { return mediaElement.paused; } return false; - }; + } - HtmlAudioPlayer.prototype.setPlaybackRate = function (value) { - var mediaElement = this._mediaElement; + setPlaybackRate(value) { + const mediaElement = this._mediaElement; if (mediaElement) { mediaElement.playbackRate = value; } - }; + } - HtmlAudioPlayer.prototype.getPlaybackRate = function () { - var mediaElement = this._mediaElement; + getPlaybackRate() { + const mediaElement = this._mediaElement; if (mediaElement) { return mediaElement.playbackRate; } return null; - }; + } - HtmlAudioPlayer.prototype.setVolume = function (val) { - var mediaElement = this._mediaElement; + setVolume(val) { + const mediaElement = this._mediaElement; if (mediaElement) { mediaElement.volume = val / 100; } - }; + } - HtmlAudioPlayer.prototype.getVolume = function () { - var mediaElement = this._mediaElement; + getVolume() { + const mediaElement = this._mediaElement; if (mediaElement) { return Math.min(Math.round(mediaElement.volume * 100), 100); } - }; + } - HtmlAudioPlayer.prototype.volumeUp = function () { + volumeUp() { this.setVolume(Math.min(this.getVolume() + 2, 100)); - }; + } - HtmlAudioPlayer.prototype.volumeDown = function () { + volumeDown() { this.setVolume(Math.max(this.getVolume() - 2, 0)); - }; + } - HtmlAudioPlayer.prototype.setMute = function (mute) { - var mediaElement = this._mediaElement; + setMute(mute) { + const mediaElement = this._mediaElement; if (mediaElement) { mediaElement.muted = mute; } - }; + } - HtmlAudioPlayer.prototype.isMuted = function () { - var mediaElement = this._mediaElement; + isMuted() { + const mediaElement = this._mediaElement; if (mediaElement) { return mediaElement.muted; } return false; - }; - - HtmlAudioPlayer.prototype.destroy = function () { - - }; - - var supportedFeatures; - - function getSupportedFeatures() { - var list = []; - var audio = document.createElement('audio'); - - if (typeof audio.playbackRate === 'number') { - list.push('PlaybackRate'); - } - - return list; } - HtmlAudioPlayer.prototype.supports = function (feature) { + destroy() { + } + + supports(feature) { if (!supportedFeatures) { supportedFeatures = getSupportedFeatures(); } return supportedFeatures.indexOf(feature) !== -1; - }; + } +} - return HtmlAudioPlayer; -}); +let supportedFeatures; + +function getSupportedFeatures() { + const list = []; + const audio = document.createElement('audio'); + + if (typeof audio.playbackRate === 'number') { + list.push('PlaybackRate'); + } + + return list; +} + +export default HtmlAudioPlayer; From 947f8ba05de15e4723d9317a28ea419c21c5d078 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 4 Aug 2020 08:55:54 +0100 Subject: [PATCH 08/51] Migration of livetvguide and livetvcomponents to ES6 moduoles --- package.json | 2 + src/controllers/livetv/livetvguide.js | 50 +++--- src/scripts/livetvcomponents.js | 209 ++++++++++++-------------- 3 files changed, 125 insertions(+), 136 deletions(-) diff --git a/package.json b/package.json index 22fd43308a..e3d0c4d144 100644 --- a/package.json +++ b/package.json @@ -209,6 +209,7 @@ "src/controllers/playback/queue/index.js", "src/controllers/playback/video/index.js", "src/controllers/searchpage.js", + "src/controllers/livetv/livetvguide.js", "src/controllers/livetvtuner.js", "src/controllers/livetvstatus.js", "src/controllers/livetvsettings.js", @@ -270,6 +271,7 @@ "src/scripts/inputManager.js", "src/scripts/keyboardNavigation.js", "src/scripts/libraryBrowser.js", + "src/scripts/livetvcomponents.js", "src/scripts/mouseManager.js", "src/scripts/multiDownload.js", "src/scripts/playlists.js", diff --git a/src/controllers/livetv/livetvguide.js b/src/controllers/livetv/livetvguide.js index ec7a7a3f81..77214a87b3 100644 --- a/src/controllers/livetv/livetvguide.js +++ b/src/controllers/livetv/livetvguide.js @@ -1,29 +1,27 @@ -define(['tvguide'], function (tvguide) { - 'use strict'; +import tvguide from 'tvguide'; - return function (view, params, tabContent) { - var guideInstance; - var self = this; +export default function (view, params, tabContent) { + let guideInstance; + const self = this; - self.renderTab = function () { - if (!guideInstance) { - guideInstance = new tvguide({ - element: tabContent, - serverId: ApiClient.serverId() - }); - } - }; - - self.onShow = function () { - if (guideInstance) { - guideInstance.resume(); - } - }; - - self.onHide = function () { - if (guideInstance) { - guideInstance.pause(); - } - }; + self.renderTab = function () { + if (!guideInstance) { + guideInstance = new tvguide({ + element: tabContent, + serverId: ApiClient.serverId() + }); + } }; -}); + + self.onShow = function () { + if (guideInstance) { + guideInstance.resume(); + } + }; + + self.onHide = function () { + if (guideInstance) { + guideInstance.pause(); + } + }; +}; diff --git a/src/scripts/livetvcomponents.js b/src/scripts/livetvcomponents.js index fd1b48d0eb..387972d996 100644 --- a/src/scripts/livetvcomponents.js +++ b/src/scripts/livetvcomponents.js @@ -1,115 +1,104 @@ -define(['layoutManager', 'datetime', 'cardBuilder', 'apphost'], function (layoutManager, datetime, cardBuilder, appHost) { - 'use strict'; +import layoutManager from 'layoutManager'; +import datetime from 'datetime'; +import cardBuilder from 'cardBuilder'; - function enableScrollX() { - return !layoutManager.desktop; +function enableScrollX() { + return !layoutManager.desktop; +} + +function getBackdropShape() { + return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; +} + +function getTimersHtml(timers, options) { + options = options || {}; + const items = timers.map(function (t) { + t.Type = 'Timer'; + return t; + }); + const groups = []; + let currentGroupName = ''; + let currentGroup = []; + + for (let i = 0, length = items.length; i < length; i++) { + const item = items[i]; + let dateText = ''; + + if (options.indexByDate !== false && item.StartDate) { + try { + const premiereDate = datetime.parseISO8601Date(item.StartDate, true); + dateText = datetime.toLocaleDateString(premiereDate, { + weekday: 'long', + month: 'short', + day: 'numeric' + }); + } catch (err) { + console.error('error parsing premiereDate:' + item.StartDate + '; error: ' + err); + } + } + + if (dateText != currentGroupName) { + if (currentGroup.length) { + groups.push({ + name: currentGroupName, + items: currentGroup + }); + } + + currentGroupName = dateText; + currentGroup = [item]; + } else { + currentGroup.push(item); + } } - - function getBackdropShape() { - return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; - } - - function getTimersHtml(timers, options) { - options = options || {}; - var i; - var length; - var items = timers.map(function (t) { - t.Type = 'Timer'; - return t; + if (currentGroup.length) { + groups.push({ + name: currentGroupName, + items: currentGroup }); - var groups = []; - var currentGroupName = ''; - var currentGroup = []; - - for (i = 0, length = items.length; i < length; i++) { - var item = items[i]; - var dateText = ''; - - if (options.indexByDate !== false && item.StartDate) { - try { - var premiereDate = datetime.parseISO8601Date(item.StartDate, true); - dateText = datetime.toLocaleDateString(premiereDate, { - weekday: 'long', - month: 'short', - day: 'numeric' - }); - } catch (err) { - console.error('error parsing premiereDate:' + item.StartDate + '; error: ' + err); - } - } - - if (dateText != currentGroupName) { - if (currentGroup.length) { - groups.push({ - name: currentGroupName, - items: currentGroup - }); - } - - currentGroupName = dateText; - currentGroup = [item]; - } else { - currentGroup.push(item); - } - } - - if (currentGroup.length) { - groups.push({ - name: currentGroupName, - items: currentGroup - }); - } - - var html = ''; - - for (i = 0, length = groups.length; i < length; i++) { - var group = groups[i]; - - if (group.name) { - html += '
'; - html += '

' + group.name + '

'; - } - if (enableScrollX()) { - var scrollXClass = 'scrollX hiddenScrollX'; - - if (layoutManager.tv) { - scrollXClass += ' smoothScrollX'; - } - - html += '
'; - } else { - html += '
'; - } - - html += cardBuilder.getCardsHtml({ - items: group.items, - shape: getBackdropShape(), - showParentTitleOrTitle: true, - showAirTime: true, - showAirEndTime: true, - showChannelName: false, - cardLayout: true, - centerText: false, - action: 'edit', - cardFooterAside: 'none', - preferThumb: true, - defaultShape: null, - coverImage: true, - allowBottomPadding: false, - overlayText: false, - showChannelLogo: true - }); - html += '
'; - - if (group.name) { - html += '
'; - } - } - - return Promise.resolve(html); } + let html = ''; + for (let i = 0, length = groups.length; i < length; i++) { + const group = groups[i]; + if (group.name) { + html += '
'; + html += '

' + group.name + '

'; + } + if (enableScrollX()) { + let scrollXClass = 'scrollX hiddenScrollX'; + if (layoutManager.tv) { + scrollXClass += ' smoothScrollX'; + } + html += '
'; + } else { + html += '
'; + } + html += cardBuilder.getCardsHtml({ + items: group.items, + shape: getBackdropShape(), + showParentTitleOrTitle: true, + showAirTime: true, + showAirEndTime: true, + showChannelName: false, + cardLayout: true, + centerText: false, + action: 'edit', + cardFooterAside: 'none', + preferThumb: true, + defaultShape: null, + coverImage: true, + allowBottomPadding: false, + overlayText: false, + showChannelLogo: true + }); + html += '
'; + if (group.name) { + html += '
'; + } + } + return Promise.resolve(html); +} - window.LiveTvHelpers = { - getTimersHtml: getTimersHtml - }; -}); +window.LiveTvHelpers = { + getTimersHtml: getTimersHtml +}; From 44800858e5181abb1df1b875db7e571d8308acdb Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 4 Aug 2020 09:03:02 +0100 Subject: [PATCH 09/51] lint and replace for with for...of --- src/controllers/livetv/livetvguide.js | 2 +- src/scripts/livetvcomponents.js | 13 +++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/controllers/livetv/livetvguide.js b/src/controllers/livetv/livetvguide.js index 77214a87b3..f8b49bd22a 100644 --- a/src/controllers/livetv/livetvguide.js +++ b/src/controllers/livetv/livetvguide.js @@ -24,4 +24,4 @@ export default function (view, params, tabContent) { guideInstance.pause(); } }; -}; +} diff --git a/src/scripts/livetvcomponents.js b/src/scripts/livetvcomponents.js index 387972d996..f1d0b2d3da 100644 --- a/src/scripts/livetvcomponents.js +++ b/src/scripts/livetvcomponents.js @@ -12,16 +12,17 @@ function getBackdropShape() { function getTimersHtml(timers, options) { options = options || {}; + const items = timers.map(function (t) { t.Type = 'Timer'; return t; }); + const groups = []; let currentGroupName = ''; let currentGroup = []; - for (let i = 0, length = items.length; i < length; i++) { - const item = items[i]; + for (const item in items) { let dateText = ''; if (options.indexByDate !== false && item.StartDate) { @@ -51,6 +52,7 @@ function getTimersHtml(timers, options) { currentGroup.push(item); } } + if (currentGroup.length) { groups.push({ name: currentGroupName, @@ -58,12 +60,12 @@ function getTimersHtml(timers, options) { }); } let html = ''; - for (let i = 0, length = groups.length; i < length; i++) { - const group = groups[i]; + for (const group in groups) { if (group.name) { html += '
'; html += '

' + group.name + '

'; } + if (enableScrollX()) { let scrollXClass = 'scrollX hiddenScrollX'; if (layoutManager.tv) { @@ -73,6 +75,7 @@ function getTimersHtml(timers, options) { } else { html += '
'; } + html += cardBuilder.getCardsHtml({ items: group.items, shape: getBackdropShape(), @@ -91,7 +94,9 @@ function getTimersHtml(timers, options) { overlayText: false, showChannelLogo: true }); + html += '
'; + if (group.name) { html += '
'; } From 2feb4c966f8473b95fca2742a1dc29ec3f57c3e3 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 4 Aug 2020 10:33:16 +0100 Subject: [PATCH 10/51] Migration of recordingbutton and recordingcreator to ES6 modules --- package.json | 2 + .../recordingcreator/recordingbutton.js | 129 +++---- .../recordingcreator/recordingcreator.js | 333 +++++++++--------- 3 files changed, 245 insertions(+), 219 deletions(-) diff --git a/package.json b/package.json index 5ad12d2011..72036d04e6 100644 --- a/package.json +++ b/package.json @@ -152,6 +152,8 @@ "src/components/playlisteditor/playlisteditor.js", "src/components/playmenu.js", "src/components/prompt/prompt.js", + "src/components/recordingcreator/recordingbutton.js", + "src/components/recordingcreator/recordingcreator.js", "src/components/refreshdialog/refreshdialog.js", "src/components/sanatizefilename.js", "src/components/scrollManager.js", diff --git a/src/components/recordingcreator/recordingbutton.js b/src/components/recordingcreator/recordingbutton.js index 3ad4ffa594..78fb0563cf 100644 --- a/src/components/recordingcreator/recordingbutton.js +++ b/src/components/recordingcreator/recordingbutton.js @@ -1,35 +1,42 @@ -define(['globalize', 'connectionManager', 'require', 'loading', 'apphost', 'dom', 'recordingHelper', 'events', 'paper-icon-button-light', 'emby-button', 'css!./recordingfields'], function (globalize, connectionManager, require, loading, appHost, dom, recordingHelper, events) { - 'use strict'; +import connectionManager from 'connectionManager'; +import dom from 'dom'; +import recordingHelper from 'recordingHelper'; +import 'paper-icon-button-light'; +import 'emby-button'; +import 'css!./recordingfields'; - function onRecordingButtonClick(e) { - var item = this.item; +/*eslint prefer-const: "error"*/ - if (item) { - var serverId = item.ServerId; - var programId = item.Id; - var timerId = item.TimerId; - var timerStatus = item.Status; - var seriesTimerId = item.SeriesTimerId; +function onRecordingButtonClick(e) { + const item = this.item; - var instance = this; + if (item) { + const serverId = item.ServerId; + const programId = item.Id; + const timerId = item.TimerId; + const timerStatus = item.Status; + const seriesTimerId = item.SeriesTimerId; - recordingHelper.toggleRecording(serverId, programId, timerId, timerStatus, seriesTimerId).then(function () { - instance.refresh(serverId, programId); - }); - } + const instance = this; + + recordingHelper.toggleRecording(serverId, programId, timerId, timerStatus, seriesTimerId).then(function () { + instance.refresh(serverId, programId); + }); } +} - function setButtonIcon(button, icon) { - var inner = button.querySelector('.material-icons'); - inner.classList.remove('fiber_smart_record'); - inner.classList.remove('fiber_manual_record'); - inner.classList.add(icon); - } +function setButtonIcon(button, icon) { + const inner = button.querySelector('.material-icons'); + inner.classList.remove('fiber_smart_record'); + inner.classList.remove('fiber_manual_record'); + inner.classList.add(icon); +} - function RecordingButton(options) { +class RecordingButton { + constructor(options) { this.options = options; - var button = options.button; + const button = options.button; setButtonIcon(button, 'fiber_manual_record'); @@ -39,7 +46,7 @@ define(['globalize', 'connectionManager', 'require', 'loading', 'apphost', 'dom' this.refresh(options.itemId, options.serverId); } - var clickFn = onRecordingButtonClick.bind(this); + const clickFn = onRecordingButtonClick.bind(this); this.clickFn = clickFn; dom.addEventListener(button, 'click', clickFn, { @@ -47,39 +54,17 @@ define(['globalize', 'connectionManager', 'require', 'loading', 'apphost', 'dom' }); } - function getIndicatorIcon(item) { - var status; - - if (item.Type === 'SeriesTimer') { - return 'fiber_smart_record'; - } else if (item.TimerId || item.SeriesTimerId) { - status = item.Status || 'Cancelled'; - } else if (item.Type === 'Timer') { - status = item.Status; - } else { - return 'fiber_manual_record'; - } - - if (item.SeriesTimerId) { - if (status !== 'Cancelled') { - return 'fiber_smart_record'; - } - } - - return 'fiber_manual_record'; - } - - RecordingButton.prototype.refresh = function (serverId, itemId) { - var apiClient = connectionManager.getApiClient(serverId); - var self = this; + refresh(serverId, itemId) { + const apiClient = connectionManager.getApiClient(serverId); + const self = this; apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(function (item) { self.refreshItem(item); }); - }; + } - RecordingButton.prototype.refreshItem = function (item) { - var options = this.options; - var button = options.button; + refreshItem(item) { + const options = this.options; + const button = options.button; this.item = item; setButtonIcon(button, getIndicatorIcon(item)); @@ -88,15 +73,15 @@ define(['globalize', 'connectionManager', 'require', 'loading', 'apphost', 'dom' } else { button.classList.remove('recordingIcon-active'); } - }; + } - RecordingButton.prototype.destroy = function () { - var options = this.options; + destroy() { + const options = this.options; if (options) { - var button = options.button; + const button = options.button; - var clickFn = this.clickFn; + const clickFn = this.clickFn; if (clickFn) { dom.removeEventListener(button, 'click', clickFn, { @@ -107,7 +92,29 @@ define(['globalize', 'connectionManager', 'require', 'loading', 'apphost', 'dom' this.options = null; this.item = null; - }; + } +} - return RecordingButton; -}); +function getIndicatorIcon(item) { + let status; + + if (item.Type === 'SeriesTimer') { + return 'fiber_smart_record'; + } else if (item.TimerId || item.SeriesTimerId) { + status = item.Status || 'Cancelled'; + } else if (item.Type === 'Timer') { + status = item.Status; + } else { + return 'fiber_manual_record'; + } + + if (item.SeriesTimerId) { + if (status !== 'Cancelled') { + return 'fiber_smart_record'; + } + } + + return 'fiber_manual_record'; +} + +export default RecordingButton; diff --git a/src/components/recordingcreator/recordingcreator.js b/src/components/recordingcreator/recordingcreator.js index ca5c475829..b8001fc35e 100644 --- a/src/components/recordingcreator/recordingcreator.js +++ b/src/components/recordingcreator/recordingcreator.js @@ -1,187 +1,204 @@ -define(['dialogHelper', 'globalize', 'layoutManager', 'mediaInfo', 'apphost', 'connectionManager', 'require', 'loading', 'scrollHelper', 'datetime', 'imageLoader', 'recordingFields', 'events', 'emby-checkbox', 'emby-button', 'emby-collapse', 'emby-input', 'paper-icon-button-light', 'css!./../formdialog', 'css!./recordingcreator', 'material-icons'], function (dialogHelper, globalize, layoutManager, mediaInfo, appHost, connectionManager, require, loading, scrollHelper, datetime, imageLoader, recordingFields, events) { - 'use strict'; +import dialogHelper from 'dialogHelper'; +import globalize from 'globalize'; +import layoutManager from 'layoutManager'; +import mediaInfo from 'mediaInfo'; +import connectionManager from 'connectionManager'; +import require from 'require'; +import loading from 'loading'; +import scrollHelper from 'scrollHelper'; +import datetime from 'datetime'; +import imageLoader from 'imageLoader'; +import recordingFields from 'recordingFields'; +import events from 'events'; +import 'emby-checkbox'; +import 'emby-button'; +import 'emby-collapse'; +import 'emby-input'; +import 'paper-icon-button-light'; +import 'css!./../formdialog'; +import 'css!./recordingcreator'; +import 'material-icons'; - var currentDialog; - var closeAction; - var currentRecordingFields; +let currentDialog; +let closeAction; +let currentRecordingFields; - function closeDialog() { - dialogHelper.close(currentDialog); +function closeDialog() { + dialogHelper.close(currentDialog); +} + +function init(context) { + context.querySelector('.btnPlay').addEventListener('click', function () { + closeAction = 'play'; + closeDialog(); + }); + + context.querySelector('.btnCancel').addEventListener('click', function () { + closeAction = null; + closeDialog(); + }); +} + +function getImageUrl(item, apiClient, imageHeight) { + const imageTags = item.ImageTags || {}; + + if (item.PrimaryImageTag) { + imageTags.Primary = item.PrimaryImageTag; } - function init(context) { - context.querySelector('.btnPlay').addEventListener('click', function () { - closeAction = 'play'; - closeDialog(); + if (imageTags.Primary) { + return apiClient.getScaledImageUrl(item.Id, { + type: 'Primary', + maxHeight: imageHeight, + tag: item.ImageTags.Primary }); - - context.querySelector('.btnCancel').addEventListener('click', function () { - closeAction = null; - closeDialog(); + } else if (imageTags.Thumb) { + return apiClient.getScaledImageUrl(item.Id, { + type: 'Thumb', + maxHeight: imageHeight, + tag: item.ImageTags.Thumb }); } - function getImageUrl(item, apiClient, imageHeight) { - var imageTags = item.ImageTags || {}; + return null; +} - if (item.PrimaryImageTag) { - imageTags.Primary = item.PrimaryImageTag; +function renderRecording(context, defaultTimer, program, apiClient, refreshRecordingStateOnly) { + if (!refreshRecordingStateOnly) { + const imgUrl = getImageUrl(program, apiClient, 200); + const imageContainer = context.querySelector('.recordingDialog-imageContainer'); + + if (imgUrl) { + imageContainer.innerHTML = ''; + imageContainer.classList.remove('hide'); + + imageLoader.lazyChildren(imageContainer); + } else { + imageContainer.innerHTML = ''; + imageContainer.classList.add('hide'); } - if (imageTags.Primary) { - return apiClient.getScaledImageUrl(item.Id, { - type: 'Primary', - maxHeight: imageHeight, - tag: item.ImageTags.Primary - }); - } else if (imageTags.Thumb) { - return apiClient.getScaledImageUrl(item.Id, { - type: 'Thumb', - maxHeight: imageHeight, - tag: item.ImageTags.Thumb - }); + context.querySelector('.recordingDialog-itemName').innerHTML = program.Name; + context.querySelector('.formDialogHeaderTitle').innerHTML = program.Name; + context.querySelector('.itemGenres').innerHTML = (program.Genres || []).join(' / '); + context.querySelector('.itemOverview').innerHTML = program.Overview || ''; + + const formDialogFooter = context.querySelector('.formDialogFooter'); + const now = new Date(); + if (now >= datetime.parseISO8601Date(program.StartDate, true) && now < datetime.parseISO8601Date(program.EndDate, true)) { + formDialogFooter.classList.remove('hide'); + } else { + formDialogFooter.classList.add('hide'); } - return null; + context.querySelector('.itemMiscInfoPrimary').innerHTML = mediaInfo.getPrimaryMediaInfoHtml(program); } - function renderRecording(context, defaultTimer, program, apiClient, refreshRecordingStateOnly) { - if (!refreshRecordingStateOnly) { - var imgUrl = getImageUrl(program, apiClient, 200); - var imageContainer = context.querySelector('.recordingDialog-imageContainer'); + context.querySelector('.itemMiscInfoSecondary').innerHTML = mediaInfo.getSecondaryMediaInfoHtml(program, { + }); - if (imgUrl) { - imageContainer.innerHTML = ''; - imageContainer.classList.remove('hide'); + loading.hide(); +} - imageLoader.lazyChildren(imageContainer); - } else { - imageContainer.innerHTML = ''; - imageContainer.classList.add('hide'); - } +function reload(context, programId, serverId, refreshRecordingStateOnly) { + loading.show(); - context.querySelector('.recordingDialog-itemName').innerHTML = program.Name; - context.querySelector('.formDialogHeaderTitle').innerHTML = program.Name; - context.querySelector('.itemGenres').innerHTML = (program.Genres || []).join(' / '); - context.querySelector('.itemOverview').innerHTML = program.Overview || ''; + const apiClient = connectionManager.getApiClient(serverId); - var formDialogFooter = context.querySelector('.formDialogFooter'); - var now = new Date(); - if (now >= datetime.parseISO8601Date(program.StartDate, true) && now < datetime.parseISO8601Date(program.EndDate, true)) { - formDialogFooter.classList.remove('hide'); - } else { - formDialogFooter.classList.add('hide'); - } + const promise1 = apiClient.getNewLiveTvTimerDefaults({ programId: programId }); + const promise2 = apiClient.getLiveTvProgram(programId, apiClient.getCurrentUserId()); - context.querySelector('.itemMiscInfoPrimary').innerHTML = mediaInfo.getPrimaryMediaInfoHtml(program); - } + Promise.all([promise1, promise2]).then(function (responses) { + const defaults = responses[0]; + const program = responses[1]; - context.querySelector('.itemMiscInfoSecondary').innerHTML = mediaInfo.getSecondaryMediaInfoHtml(program, { - }); + renderRecording(context, defaults, program, apiClient, refreshRecordingStateOnly); + }); +} - loading.hide(); - } +function executeCloseAction(action, programId, serverId) { + if (action === 'play') { + import('playbackManager').then(({default: playbackManager}) => { + const apiClient = connectionManager.getApiClient(serverId); - function reload(context, programId, serverId, refreshRecordingStateOnly) { - loading.show(); - - var apiClient = connectionManager.getApiClient(serverId); - - var promise1 = apiClient.getNewLiveTvTimerDefaults({ programId: programId }); - var promise2 = apiClient.getLiveTvProgram(programId, apiClient.getCurrentUserId()); - - Promise.all([promise1, promise2]).then(function (responses) { - var defaults = responses[0]; - var program = responses[1]; - - renderRecording(context, defaults, program, apiClient, refreshRecordingStateOnly); - }); - } - - function executeCloseAction(action, programId, serverId) { - if (action === 'play') { - require(['playbackManager'], function (playbackManager) { - var apiClient = connectionManager.getApiClient(serverId); - - apiClient.getLiveTvProgram(programId, apiClient.getCurrentUserId()).then(function (item) { - playbackManager.default.play({ - ids: [item.ChannelId], - serverId: serverId - }); - }); - }); - return; - } - } - - function showEditor(itemId, serverId) { - return new Promise(function (resolve, reject) { - closeAction = null; - - loading.show(); - - require(['text!./recordingcreator.template.html'], function (template) { - var dialogOptions = { - removeOnClose: true, - scrollY: false - }; - - if (layoutManager.tv) { - dialogOptions.size = 'fullscreen'; - } else { - dialogOptions.size = 'small'; - } - - var dlg = dialogHelper.createDialog(dialogOptions); - - dlg.classList.add('formDialog'); - dlg.classList.add('recordingDialog'); - - var html = ''; - - html += globalize.translateHtml(template, 'core'); - - dlg.innerHTML = html; - - currentDialog = dlg; - - function onRecordingChanged() { - reload(dlg, itemId, serverId, true); - } - - dlg.addEventListener('close', function () { - events.off(currentRecordingFields, 'recordingchanged', onRecordingChanged); - executeCloseAction(closeAction, itemId, serverId); - - if (currentRecordingFields && currentRecordingFields.hasChanged()) { - resolve(); - } else { - reject(); - } - }); - - if (layoutManager.tv) { - scrollHelper.centerFocus.on(dlg.querySelector('.formDialogContent'), false); - } - - init(dlg); - - reload(dlg, itemId, serverId); - - currentRecordingFields = new recordingFields({ - parent: dlg.querySelector('.recordingFields'), - programId: itemId, + apiClient.getLiveTvProgram(programId, apiClient.getCurrentUserId()).then(function (item) { + playbackManager.default.play({ + ids: [item.ChannelId], serverId: serverId }); - - events.on(currentRecordingFields, 'recordingchanged', onRecordingChanged); - - dialogHelper.open(dlg); }); }); + return; } +} - return { - show: showEditor - }; -}); +function showEditor(itemId, serverId) { + return new Promise(function (resolve, reject) { + closeAction = null; + + loading.show(); + + import('text!./recordingcreator.template.html').then(({default: template}) => { + const dialogOptions = { + removeOnClose: true, + scrollY: false + }; + + if (layoutManager.tv) { + dialogOptions.size = 'fullscreen'; + } else { + dialogOptions.size = 'small'; + } + + const dlg = dialogHelper.createDialog(dialogOptions); + + dlg.classList.add('formDialog'); + dlg.classList.add('recordingDialog'); + + let html = ''; + + html += globalize.translateHtml(template, 'core'); + + dlg.innerHTML = html; + + currentDialog = dlg; + + function onRecordingChanged() { + reload(dlg, itemId, serverId, true); + } + + dlg.addEventListener('close', function () { + events.off(currentRecordingFields, 'recordingchanged', onRecordingChanged); + executeCloseAction(closeAction, itemId, serverId); + + if (currentRecordingFields && currentRecordingFields.hasChanged()) { + resolve(); + } else { + reject(); + } + }); + + if (layoutManager.tv) { + scrollHelper.centerFocus.on(dlg.querySelector('.formDialogContent'), false); + } + + init(dlg); + + reload(dlg, itemId, serverId); + + currentRecordingFields = new recordingFields({ + parent: dlg.querySelector('.recordingFields'), + programId: itemId, + serverId: serverId + }); + + events.on(currentRecordingFields, 'recordingchanged', onRecordingChanged); + + dialogHelper.open(dlg); + }); + }); +} + +export default { + show: showEditor +}; From 3a35a9c5f2ee664fd9409de1d47acdaac54be1d2 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 4 Aug 2020 11:55:00 +0100 Subject: [PATCH 11/51] lint --- src/controllers/itemDetails/index.js | 2 -- src/controllers/music/musicrecommended.js | 2 -- src/scripts/libraryMenu.js | 3 --- 3 files changed, 7 deletions(-) diff --git a/src/controllers/itemDetails/index.js b/src/controllers/itemDetails/index.js index 15c437d684..df2855d69a 100644 --- a/src/controllers/itemDetails/index.js +++ b/src/controllers/itemDetails/index.js @@ -29,8 +29,6 @@ import 'emby-select'; /* eslint-disable indent */ - libraryMenu = libraryMenu.default || libraryMenu; - function getPromise(apiClient, params) { const id = params.id; diff --git a/src/controllers/music/musicrecommended.js b/src/controllers/music/musicrecommended.js index ad031e42b4..db7dac9547 100644 --- a/src/controllers/music/musicrecommended.js +++ b/src/controllers/music/musicrecommended.js @@ -17,8 +17,6 @@ import 'flexStyles'; /* eslint-disable indent */ - libraryMenu = LibraryMenu.default || libraryMenu; - function itemsPerRow() { const screenWidth = dom.getWindowSize().innerWidth; diff --git a/src/scripts/libraryMenu.js b/src/scripts/libraryMenu.js index 8b5fce14d3..0780916a7c 100644 --- a/src/scripts/libraryMenu.js +++ b/src/scripts/libraryMenu.js @@ -19,9 +19,6 @@ import 'flexStyles'; /* eslint-disable indent */ - playbackManager = playbackManager.default || playbackManager; - browser = browser.default || browser; - function renderHeader() { let html = ''; html += '
'; From 234a70875df1cc047c5fc546291036ccfb0cb20f Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 4 Aug 2020 15:15:59 +0100 Subject: [PATCH 12/51] Remove Missed Required --- src/components/imageDownloader/imageDownloader.js | 2 +- src/components/metadataEditor/metadataEditor.js | 9 ++++----- src/scripts/libraryBrowser.js | 5 ++++- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/components/imageDownloader/imageDownloader.js b/src/components/imageDownloader/imageDownloader.js index 2be2ef09b2..3a5f2f56ce 100644 --- a/src/components/imageDownloader/imageDownloader.js +++ b/src/components/imageDownloader/imageDownloader.js @@ -317,7 +317,7 @@ import 'cardStyle'; function showEditor(itemId, serverId, itemType) { loading.show(); - require(['text!./imageDownloader.template.html'], function (template) { + import('text!./imageDownloader.template.html').then(({default: template}) => { const apiClient = connectionManager.getApiClient(serverId); currentItemId = itemId; diff --git a/src/components/metadataEditor/metadataEditor.js b/src/components/metadataEditor/metadataEditor.js index be44e86b44..162da8984a 100644 --- a/src/components/metadataEditor/metadataEditor.js +++ b/src/components/metadataEditor/metadataEditor.js @@ -6,7 +6,6 @@ import loading from 'loading'; import focusManager from 'focusManager'; import connectionManager from 'connectionManager'; import globalize from 'globalize'; -import require from 'require'; import shell from 'shell'; import 'emby-checkbox'; import 'emby-input'; @@ -37,7 +36,7 @@ import 'flexStyles'; function submitUpdatedItem(form, item) { function afterContentTypeUpdated() { - require(['toast'], function (toast) { + import('toast').then(({default: toast}) => { toast(globalize.translate('MessageItemSaved')); }); @@ -227,7 +226,7 @@ import 'flexStyles'; } function editPerson(context, person, index) { - require(['personEditor'], function (personEditor) { + import('personEditor').then(({default: personEditor}) => { personEditor.show(person).then(function (updatedPerson) { const isNew = index === -1; @@ -246,14 +245,14 @@ import 'flexStyles'; if (parentId) { reload(context, parentId, item.ServerId); } else { - require(['appRouter'], function (appRouter) { + import('appRouter').then(({default: appRouter}) => { appRouter.goHome(); }); } } function showMoreMenu(context, button, user) { - require(['itemContextMenu'], function (itemContextMenu) { + import('itemContextMenu').then(({default: itemContextMenu}) => { var item = currentItem; itemContextMenu.show({ diff --git a/src/scripts/libraryBrowser.js b/src/scripts/libraryBrowser.js index 46cda51e55..4f2a0fdb47 100644 --- a/src/scripts/libraryBrowser.js +++ b/src/scripts/libraryBrowser.js @@ -119,7 +119,10 @@ export function getQueryPagingHtml (options) { } export function showSortMenu (options) { - require(['dialogHelper', 'emby-radio'], function (dialogHelper) { + Promise.All([ + import('dialogHelper'), + import('emby-radio') + ]).then(({default: dialogHelper}) => { function onSortByChange() { var newValue = this.value; From 89f0fe8564ceb9996e63534c0fcc0fb74b876517 Mon Sep 17 00:00:00 2001 From: matjaz321 Date: Tue, 4 Aug 2020 19:33:20 +0200 Subject: [PATCH 13/51] Migrates itembynamedetailpage.js to es6 module --- src/scripts/itembynamedetailpage.js | 713 ++++++++++++++-------------- 1 file changed, 359 insertions(+), 354 deletions(-) diff --git a/src/scripts/itembynamedetailpage.js b/src/scripts/itembynamedetailpage.js index eaeecba275..f464f3b9e9 100644 --- a/src/scripts/itembynamedetailpage.js +++ b/src/scripts/itembynamedetailpage.js @@ -1,369 +1,374 @@ -define(['connectionManager', 'listView', 'cardBuilder', 'imageLoader', 'libraryBrowser', 'globalize', 'emby-itemscontainer', 'emby-button'], function (connectionManager, listView, cardBuilder, imageLoader, libraryBrowser, globalize) { - 'use strict'; +import connectionManager from 'connectionManager'; +import listView from 'listView'; +import cardBuilder from 'cardBuilder'; +import imageLoader from 'imageLoader'; +import globalize from 'globalize'; +import 'emby-itemscontainer'; +import 'emby-button'; - function renderItems(page, item) { - var sections = []; +function renderItems(page, item) { + const sections = []; - if (item.ArtistCount) { - sections.push({ - name: globalize.translate('TabArtists'), - type: 'MusicArtist' - }); - } - - if (item.ProgramCount && item.Type == 'Person') { - sections.push({ - name: globalize.translate('HeaderUpcomingOnTV'), - type: 'Program' - }); - } - - if (item.MovieCount) { - sections.push({ - name: globalize.translate('TabMovies'), - type: 'Movie' - }); - } - - if (item.SeriesCount) { - sections.push({ - name: globalize.translate('TabShows'), - type: 'Series' - }); - } - - if (item.EpisodeCount) { - sections.push({ - name: globalize.translate('TabEpisodes'), - type: 'Episode' - }); - } - - if (item.TrailerCount) { - sections.push({ - name: globalize.translate('TabTrailers'), - type: 'Trailer' - }); - } - - if (item.AlbumCount) { - sections.push({ - name: globalize.translate('TabAlbums'), - type: 'MusicAlbum' - }); - } - - if (item.MusicVideoCount) { - sections.push({ - name: globalize.translate('TabMusicVideos'), - type: 'MusicVideo' - }); - } - - var elem = page.querySelector('#childrenContent'); - elem.innerHTML = sections.map(function (section) { - var html = ''; - var sectionClass = 'verticalSection'; - - if (section.type === 'Audio') { - sectionClass += ' verticalSection-extrabottompadding'; - } - - html += ''; - }).join(''); - var sectionElems = elem.querySelectorAll('.verticalSection'); - - for (var i = 0, length = sectionElems.length; i < length; i++) { - renderSection(page, item, sectionElems[i], sectionElems[i].getAttribute('data-type')); - } - } - - function renderSection(page, item, element, type) { - switch (type) { - case 'Program': - loadItems(element, item, type, { - MediaTypes: '', - IncludeItemTypes: 'Program', - PersonTypes: '', - ArtistIds: '', - AlbumArtistIds: '', - Limit: 10, - SortBy: 'StartDate' - }, { - shape: 'overflowBackdrop', - showTitle: true, - centerText: true, - overlayMoreButton: true, - preferThumb: true, - overlayText: false, - showAirTime: true, - showAirDateTime: true, - showChannelName: true - }); - break; - - case 'Movie': - loadItems(element, item, type, { - MediaTypes: '', - IncludeItemTypes: 'Movie', - PersonTypes: '', - ArtistIds: '', - AlbumArtistIds: '', - Limit: 10, - SortBy: 'SortName' - }, { - shape: 'overflowPortrait', - showTitle: true, - centerText: true, - overlayMoreButton: true, - overlayText: false, - showYear: true - }); - break; - - case 'MusicVideo': - loadItems(element, item, type, { - MediaTypes: '', - IncludeItemTypes: 'MusicVideo', - PersonTypes: '', - ArtistIds: '', - AlbumArtistIds: '', - Limit: 10, - SortBy: 'SortName' - }, { - shape: 'overflowPortrait', - showTitle: true, - centerText: true, - overlayPlayButton: true - }); - break; - - case 'Trailer': - loadItems(element, item, type, { - MediaTypes: '', - IncludeItemTypes: 'Trailer', - PersonTypes: '', - ArtistIds: '', - AlbumArtistIds: '', - Limit: 10, - SortBy: 'SortName' - }, { - shape: 'overflowPortrait', - showTitle: true, - centerText: true, - overlayPlayButton: true - }); - break; - - case 'Series': - loadItems(element, item, type, { - MediaTypes: '', - IncludeItemTypes: 'Series', - PersonTypes: '', - ArtistIds: '', - AlbumArtistIds: '', - Limit: 10, - SortBy: 'SortName' - }, { - shape: 'overflowPortrait', - showTitle: true, - centerText: true, - overlayMoreButton: true - }); - break; - - case 'MusicAlbum': - loadItems(element, item, type, { - MediaTypes: '', - IncludeItemTypes: 'MusicAlbum', - PersonTypes: '', - ArtistIds: '', - AlbumArtistIds: '', - SortOrder: 'Descending', - SortBy: 'ProductionYear,Sortname' - }, { - shape: 'overflowSquare', - playFromHere: true, - showTitle: true, - showYear: true, - coverImage: true, - centerText: true, - overlayPlayButton: true - }); - break; - - case 'MusicArtist': - loadItems(element, item, type, { - MediaTypes: '', - IncludeItemTypes: 'MusicArtist', - PersonTypes: '', - ArtistIds: '', - AlbumArtistIds: '', - Limit: 8, - SortBy: 'SortName' - }, { - shape: 'overflowSquare', - playFromHere: true, - showTitle: true, - showParentTitle: true, - coverImage: true, - centerText: true, - overlayPlayButton: true - }); - break; - - case 'Episode': - loadItems(element, item, type, { - MediaTypes: '', - IncludeItemTypes: 'Episode', - PersonTypes: '', - ArtistIds: '', - AlbumArtistIds: '', - Limit: 6, - SortBy: 'SortName' - }, { - shape: 'overflowBackdrop', - showTitle: true, - showParentTitle: true, - centerText: true, - overlayPlayButton: true - }); - break; - - case 'Audio': - loadItems(element, item, type, { - MediaTypes: '', - IncludeItemTypes: 'Audio', - PersonTypes: '', - ArtistIds: '', - AlbumArtistIds: '', - SortBy: 'AlbumArtist,Album,SortName' - }, { - playFromHere: true, - action: 'playallfromhere', - smallIcon: true, - artist: true - }); - } - } - - function loadItems(element, item, type, query, listOptions) { - query = getQuery(query, item); - getItemsFunction(query, item)(query.StartIndex, query.Limit, query.Fields).then(function (result) { - var html = ''; - - if (query.Limit && result.TotalRecordCount > query.Limit) { - var link = element.querySelector('a'); - link.classList.remove('hide'); - link.setAttribute('href', getMoreItemsHref(item, type)); - } else { - element.querySelector('a').classList.add('hide'); - } - - listOptions.items = result.Items; - var itemsContainer = element.querySelector('.itemsContainer'); - - if (type == 'Audio') { - html = listView.getListViewHtml(listOptions); - itemsContainer.classList.remove('vertical-wrap'); - itemsContainer.classList.add('vertical-list'); - } else { - html = cardBuilder.getCardsHtml(listOptions); - itemsContainer.classList.add('vertical-wrap'); - itemsContainer.classList.remove('vertical-list'); - } - - itemsContainer.innerHTML = html; - imageLoader.lazyChildren(itemsContainer); + if (item.ArtistCount) { + sections.push({ + name: globalize.translate('TabArtists'), + type: 'MusicArtist' }); } - function getMoreItemsHref(item, type) { - if (item.Type == 'Genre') { - return 'list.html?type=' + type + '&genreId=' + item.Id + '&serverId=' + item.ServerId; - } - - if (item.Type == 'MusicGenre') { - return 'list.html?type=' + type + '&musicGenreId=' + item.Id + '&serverId=' + item.ServerId; - } - - if (item.Type == 'Studio') { - return 'list.html?type=' + type + '&studioId=' + item.Id + '&serverId=' + item.ServerId; - } - - if (item.Type == 'MusicArtist') { - return 'list.html?type=' + type + '&artistId=' + item.Id + '&serverId=' + item.ServerId; - } - - if (item.Type == 'Person') { - return 'list.html?type=' + type + '&personId=' + item.Id + '&serverId=' + item.ServerId; - } - - return 'list.html?type=' + type + '&parentId=' + item.Id + '&serverId=' + item.ServerId; + if (item.ProgramCount && item.Type === 'Person') { + sections.push({ + name: globalize.translate('HeaderUpcomingOnTV'), + type: 'Program' + }); } - function addCurrentItemToQuery(query, item) { - if (item.Type == 'Person') { - query.PersonIds = item.Id; - } else if (item.Type == 'Genre') { - query.Genres = item.Name; - } else if (item.Type == 'MusicGenre') { - query.Genres = item.Name; - } else if (item.Type == 'GameGenre') { - query.Genres = item.Name; - } else if (item.Type == 'Studio') { - query.StudioIds = item.Id; - } else if (item.Type == 'MusicArtist') { - query.AlbumArtistIds = item.Id; + if (item.MovieCount) { + sections.push({ + name: globalize.translate('TabMovies'), + type: 'Movie' + }); + } + + if (item.SeriesCount) { + sections.push({ + name: globalize.translate('TabShows'), + type: 'Series' + }); + } + + if (item.EpisodeCount) { + sections.push({ + name: globalize.translate('TabEpisodes'), + type: 'Episode' + }); + } + + if (item.TrailerCount) { + sections.push({ + name: globalize.translate('TabTrailers'), + type: 'Trailer' + }); + } + + if (item.AlbumCount) { + sections.push({ + name: globalize.translate('TabAlbums'), + type: 'MusicAlbum' + }); + } + + if (item.MusicVideoCount) { + sections.push({ + name: globalize.translate('TabMusicVideos'), + type: 'MusicVideo' + }); + } + + const elem = page.querySelector('#childrenContent'); + elem.innerHTML = sections.map(function (section) { + let html = ''; + let sectionClass = 'verticalSection'; + + if (section.type === 'Audio') { + sectionClass += ' verticalSection-extrabottompadding'; } + + html += '
'; + html += '
'; + html += '

'; + html += section.name; + html += '

'; + html += ''; + html += '
'; + html += '
'; + html += '
'; + html += '
'; + return html; + }).join(''); + const sectionElems = elem.querySelectorAll('.verticalSection'); + + for (let i = 0, length = sectionElems.length; i < length; i++) { + renderSection(page, item, sectionElems[i], sectionElems[i].getAttribute('data-type')); + } +} + +function renderSection(page, item, element, type) { + switch (type) { + case 'Program': + loadItems(element, item, type, { + MediaTypes: '', + IncludeItemTypes: 'Program', + PersonTypes: '', + ArtistIds: '', + AlbumArtistIds: '', + Limit: 10, + SortBy: 'StartDate' + }, { + shape: 'overflowBackdrop', + showTitle: true, + centerText: true, + overlayMoreButton: true, + preferThumb: true, + overlayText: false, + showAirTime: true, + showAirDateTime: true, + showChannelName: true + }); + break; + + case 'Movie': + loadItems(element, item, type, { + MediaTypes: '', + IncludeItemTypes: 'Movie', + PersonTypes: '', + ArtistIds: '', + AlbumArtistIds: '', + Limit: 10, + SortBy: 'SortName' + }, { + shape: 'overflowPortrait', + showTitle: true, + centerText: true, + overlayMoreButton: true, + overlayText: false, + showYear: true + }); + break; + + case 'MusicVideo': + loadItems(element, item, type, { + MediaTypes: '', + IncludeItemTypes: 'MusicVideo', + PersonTypes: '', + ArtistIds: '', + AlbumArtistIds: '', + Limit: 10, + SortBy: 'SortName' + }, { + shape: 'overflowPortrait', + showTitle: true, + centerText: true, + overlayPlayButton: true + }); + break; + + case 'Trailer': + loadItems(element, item, type, { + MediaTypes: '', + IncludeItemTypes: 'Trailer', + PersonTypes: '', + ArtistIds: '', + AlbumArtistIds: '', + Limit: 10, + SortBy: 'SortName' + }, { + shape: 'overflowPortrait', + showTitle: true, + centerText: true, + overlayPlayButton: true + }); + break; + + case 'Series': + loadItems(element, item, type, { + MediaTypes: '', + IncludeItemTypes: 'Series', + PersonTypes: '', + ArtistIds: '', + AlbumArtistIds: '', + Limit: 10, + SortBy: 'SortName' + }, { + shape: 'overflowPortrait', + showTitle: true, + centerText: true, + overlayMoreButton: true + }); + break; + + case 'MusicAlbum': + loadItems(element, item, type, { + MediaTypes: '', + IncludeItemTypes: 'MusicAlbum', + PersonTypes: '', + ArtistIds: '', + AlbumArtistIds: '', + SortOrder: 'Descending', + SortBy: 'ProductionYear,Sortname' + }, { + shape: 'overflowSquare', + playFromHere: true, + showTitle: true, + showYear: true, + coverImage: true, + centerText: true, + overlayPlayButton: true + }); + break; + + case 'MusicArtist': + loadItems(element, item, type, { + MediaTypes: '', + IncludeItemTypes: 'MusicArtist', + PersonTypes: '', + ArtistIds: '', + AlbumArtistIds: '', + Limit: 8, + SortBy: 'SortName' + }, { + shape: 'overflowSquare', + playFromHere: true, + showTitle: true, + showParentTitle: true, + coverImage: true, + centerText: true, + overlayPlayButton: true + }); + break; + + case 'Episode': + loadItems(element, item, type, { + MediaTypes: '', + IncludeItemTypes: 'Episode', + PersonTypes: '', + ArtistIds: '', + AlbumArtistIds: '', + Limit: 6, + SortBy: 'SortName' + }, { + shape: 'overflowBackdrop', + showTitle: true, + showParentTitle: true, + centerText: true, + overlayPlayButton: true + }); + break; + + case 'Audio': + loadItems(element, item, type, { + MediaTypes: '', + IncludeItemTypes: 'Audio', + PersonTypes: '', + ArtistIds: '', + AlbumArtistIds: '', + SortBy: 'AlbumArtist,Album,SortName' + }, { + playFromHere: true, + action: 'playallfromhere', + smallIcon: true, + artist: true + }); + } +} + +function loadItems(element, item, type, query, listOptions) { + query = getQuery(query, item); + getItemsFunction(query, item)(query.StartIndex, query.Limit, query.Fields).then(function (result) { + let html = ''; + + if (query.Limit && result.TotalRecordCount > query.Limit) { + const link = element.querySelector('a'); + link.classList.remove('hide'); + link.setAttribute('href', getMoreItemsHref(item, type)); + } else { + element.querySelector('a').classList.add('hide'); + } + + listOptions.items = result.Items; + const itemsContainer = element.querySelector('.itemsContainer'); + + if (type === 'Audio') { + html = listView.getListViewHtml(listOptions); + itemsContainer.classList.remove('vertical-wrap'); + itemsContainer.classList.add('vertical-list'); + } else { + html = cardBuilder.getCardsHtml(listOptions); + itemsContainer.classList.add('vertical-wrap'); + itemsContainer.classList.remove('vertical-list'); + } + + itemsContainer.innerHTML = html; + imageLoader.lazyChildren(itemsContainer); + }); +} + +function getMoreItemsHref(item, type) { + if (item.Type === 'Genre') { + return 'list.html?type=' + type + '&genreId=' + item.Id + '&serverId=' + item.ServerId; } - function getQuery(options, item) { - var query = { - SortOrder: 'Ascending', - IncludeItemTypes: '', - Recursive: true, - Fields: 'AudioInfo,SeriesInfo,ParentId,PrimaryImageAspectRatio,BasicSyncInfo', - Limit: 100, - StartIndex: 0, - CollapseBoxSetItems: false - }; - query = Object.assign(query, options || {}); - addCurrentItemToQuery(query, item); - return query; + if (item.Type === 'MusicGenre') { + return 'list.html?type=' + type + '&musicGenreId=' + item.Id + '&serverId=' + item.ServerId; } - function getItemsFunction(options, item) { - var query = getQuery(options, item); - return function (index, limit, fields) { - query.StartIndex = index; - query.Limit = limit; - - if (fields) { - query.Fields += ',' + fields; - } - - var apiClient = connectionManager.getApiClient(item.ServerId); - - if (query.IncludeItemTypes === 'MusicArtist') { - query.IncludeItemTypes = null; - return apiClient.getAlbumArtists(apiClient.getCurrentUserId(), query); - } - - return apiClient.getItems(apiClient.getCurrentUserId(), query); - }; + if (item.Type === 'Studio') { + return 'list.html?type=' + type + '&studioId=' + item.Id + '&serverId=' + item.ServerId; } - window.ItemsByName = { - renderItems: renderItems + if (item.Type === 'MusicArtist') { + return 'list.html?type=' + type + '&artistId=' + item.Id + '&serverId=' + item.ServerId; + } + + if (item.Type === 'Person') { + return 'list.html?type=' + type + '&personId=' + item.Id + '&serverId=' + item.ServerId; + } + + return 'list.html?type=' + type + '&parentId=' + item.Id + '&serverId=' + item.ServerId; +} + +function addCurrentItemToQuery(query, item) { + if (item.Type === 'Person') { + query.PersonIds = item.Id; + } else if (item.Type === 'Genre') { + query.Genres = item.Name; + } else if (item.Type === 'MusicGenre') { + query.Genres = item.Name; + } else if (item.Type === 'GameGenre') { + query.Genres = item.Name; + } else if (item.Type === 'Studio') { + query.StudioIds = item.Id; + } else if (item.Type === 'MusicArtist') { + query.AlbumArtistIds = item.Id; + } +} + +function getQuery(options, item) { + let query = { + SortOrder: 'Ascending', + IncludeItemTypes: '', + Recursive: true, + Fields: 'AudioInfo,SeriesInfo,ParentId,PrimaryImageAspectRatio,BasicSyncInfo', + Limit: 100, + StartIndex: 0, + CollapseBoxSetItems: false }; -}); + query = Object.assign(query, options || {}); + addCurrentItemToQuery(query, item); + return query; +} + +function getItemsFunction(options, item) { + const query = getQuery(options, item); + return function (index, limit, fields) { + query.StartIndex = index; + query.Limit = limit; + + if (fields) { + query.Fields += ',' + fields; + } + + const apiClient = connectionManager.getApiClient(item.ServerId); + + if (query.IncludeItemTypes === 'MusicArtist') { + query.IncludeItemTypes = null; + return apiClient.getAlbumArtists(apiClient.getCurrentUserId(), query); + } + + return apiClient.getItems(apiClient.getCurrentUserId(), query); + }; +} + +window.ItemsByName = { + renderItems: renderItems +}; From f9c2a666237f967f9ff0a0d5e8852fb700fed426 Mon Sep 17 00:00:00 2001 From: matjaz321 Date: Tue, 4 Aug 2020 19:33:20 +0200 Subject: [PATCH 14/51] Migrates itembynamedetailpage.js to es6 module From fbb98bdd2f8adab4451793c998d543c4c03ba378 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 5 Aug 2020 09:00:24 +0100 Subject: [PATCH 15/51] Migration of guide and guide-settings to ES6 modules --- package.json | 2 + src/components/guide/guide-settings.js | 250 +-- src/components/guide/guide.js | 2306 ++++++++++++------------ src/controllers/livetv/livetvguide.js | 2 +- 4 files changed, 1294 insertions(+), 1266 deletions(-) diff --git a/package.json b/package.json index 5ad12d2011..ad9b19d242 100644 --- a/package.json +++ b/package.json @@ -111,6 +111,8 @@ "src/components/fetchhelper.js", "src/components/filterdialog/filterdialog.js", "src/components/groupedcards.js", + "src/components/guide/guide.js", + "src/components/guide/guide-settings.js", "src/components/homeScreenSettings/homeScreenSettings.js", "src/components/homesections/homesections.js", "src/components/htmlMediaHelper.js", diff --git a/src/components/guide/guide-settings.js b/src/components/guide/guide-settings.js index c3ba49f283..b62604ab98 100644 --- a/src/components/guide/guide-settings.js +++ b/src/components/guide/guide-settings.js @@ -1,147 +1,153 @@ -define(['dialogHelper', 'globalize', 'userSettings', 'layoutManager', 'connectionManager', 'require', 'loading', 'scrollHelper', 'emby-checkbox', 'emby-radio', 'css!./../formdialog', 'material-icons'], function (dialogHelper, globalize, userSettings, layoutManager, connectionManager, require, loading, scrollHelper) { - 'use strict'; +import dialogHelper from 'dialogHelper'; +import globalize from 'globalize'; +import * as userSettings from 'userSettings'; +import layoutManager from 'layoutManager'; +import scrollHelper from 'scrollHelper'; +import 'emby-checkbox'; +import 'emby-radio'; +import 'css!./../formdialog'; +import 'material-icons'; - function saveCategories(context, options) { - var categories = []; +function saveCategories(context, options) { + const categories = []; - var chkCategorys = context.querySelectorAll('.chkCategory'); - for (var i = 0, length = chkCategorys.length; i < length; i++) { - var type = chkCategorys[i].getAttribute('data-type'); + const chkCategorys = context.querySelectorAll('.chkCategory'); + for (let i = 0, length = chkCategorys.length; i < length; i++) { + const type = chkCategorys[i].getAttribute('data-type'); - if (chkCategorys[i].checked) { - categories.push(type); - } - } - - if (categories.length >= 4) { - categories.push('series'); - } - - // differentiate between none and all - categories.push('all'); - options.categories = categories; - } - - function loadCategories(context, options) { - var selectedCategories = options.categories || []; - - var chkCategorys = context.querySelectorAll('.chkCategory'); - for (var i = 0, length = chkCategorys.length; i < length; i++) { - var type = chkCategorys[i].getAttribute('data-type'); - - chkCategorys[i].checked = !selectedCategories.length || selectedCategories.indexOf(type) !== -1; + if (chkCategorys[i].checked) { + categories.push(type); } } - function save(context) { - var i; - var length; + if (categories.length >= 4) { + categories.push('series'); + } - var chkIndicators = context.querySelectorAll('.chkIndicator'); - for (i = 0, length = chkIndicators.length; i < length; i++) { - var type = chkIndicators[i].getAttribute('data-type'); - userSettings.set('guide-indicator-' + type, chkIndicators[i].checked); + // differentiate between none and all + categories.push('all'); + options.categories = categories; +} + +function loadCategories(context, options) { + const selectedCategories = options.categories || []; + + const chkCategorys = context.querySelectorAll('.chkCategory'); + for (let i = 0, length = chkCategorys.length; i < length; i++) { + const type = chkCategorys[i].getAttribute('data-type'); + + chkCategorys[i].checked = !selectedCategories.length || selectedCategories.indexOf(type) !== -1; + } +} + +function save(context) { + let i; + let length; + + const chkIndicators = context.querySelectorAll('.chkIndicator'); + for (i = 0, length = chkIndicators.length; i < length; i++) { + const type = chkIndicators[i].getAttribute('data-type'); + userSettings.set('guide-indicator-' + type, chkIndicators[i].checked); + } + + userSettings.set('guide-colorcodedbackgrounds', context.querySelector('.chkColorCodedBackgrounds').checked); + userSettings.set('livetv-favoritechannelsattop', context.querySelector('.chkFavoriteChannelsAtTop').checked); + + const sortBys = context.querySelectorAll('.chkSortOrder'); + for (i = 0, length = sortBys.length; i < length; i++) { + if (sortBys[i].checked) { + userSettings.set('livetv-channelorder', sortBys[i].value); + break; } + } +} - userSettings.set('guide-colorcodedbackgrounds', context.querySelector('.chkColorCodedBackgrounds').checked); - userSettings.set('livetv-favoritechannelsattop', context.querySelector('.chkFavoriteChannelsAtTop').checked); +function load(context) { + let i; + let length; - var sortBys = context.querySelectorAll('.chkSortOrder'); - for (i = 0, length = sortBys.length; i < length; i++) { - if (sortBys[i].checked) { - userSettings.set('livetv-channelorder', sortBys[i].value); - break; - } + const chkIndicators = context.querySelectorAll('.chkIndicator'); + for (i = 0, length = chkIndicators.length; i < length; i++) { + const type = chkIndicators[i].getAttribute('data-type'); + + if (chkIndicators[i].getAttribute('data-default') === 'true') { + chkIndicators[i].checked = userSettings.get('guide-indicator-' + type) !== 'false'; + } else { + chkIndicators[i].checked = userSettings.get('guide-indicator-' + type) === 'true'; } } - function load(context) { - var i; - var length; + context.querySelector('.chkColorCodedBackgrounds').checked = userSettings.get('guide-colorcodedbackgrounds') === 'true'; + context.querySelector('.chkFavoriteChannelsAtTop').checked = userSettings.get('livetv-favoritechannelsattop') !== 'false'; - var chkIndicators = context.querySelectorAll('.chkIndicator'); - for (i = 0, length = chkIndicators.length; i < length; i++) { - var type = chkIndicators[i].getAttribute('data-type'); + const sortByValue = userSettings.get('livetv-channelorder') || 'Number'; - if (chkIndicators[i].getAttribute('data-default') === 'true') { - chkIndicators[i].checked = userSettings.get('guide-indicator-' + type) !== 'false'; + const sortBys = context.querySelectorAll('.chkSortOrder'); + for (i = 0, length = sortBys.length; i < length; i++) { + sortBys[i].checked = sortBys[i].value === sortByValue; + } +} + +function showEditor(options) { + return new Promise(function (resolve, reject) { + let settingsChanged = false; + + import('text!./guide-settings.template.html').then(({default: template}) => { + const dialogOptions = { + removeOnClose: true, + scrollY: false + }; + + if (layoutManager.tv) { + dialogOptions.size = 'fullscreen'; } else { - chkIndicators[i].checked = userSettings.get('guide-indicator-' + type) === 'true'; + dialogOptions.size = 'small'; } - } - context.querySelector('.chkColorCodedBackgrounds').checked = userSettings.get('guide-colorcodedbackgrounds') === 'true'; - context.querySelector('.chkFavoriteChannelsAtTop').checked = userSettings.get('livetv-favoritechannelsattop') !== 'false'; + const dlg = dialogHelper.createDialog(dialogOptions); - var sortByValue = userSettings.get('livetv-channelorder') || 'Number'; + dlg.classList.add('formDialog'); - var sortBys = context.querySelectorAll('.chkSortOrder'); - for (i = 0, length = sortBys.length; i < length; i++) { - sortBys[i].checked = sortBys[i].value === sortByValue; - } - } + let html = ''; - function showEditor(options) { - return new Promise(function (resolve, reject) { - var settingsChanged = false; + html += globalize.translateHtml(template, 'core'); - require(['text!./guide-settings.template.html'], function (template) { - var dialogOptions = { - removeOnClose: true, - scrollY: false - }; + dlg.innerHTML = html; - if (layoutManager.tv) { - dialogOptions.size = 'fullscreen'; - } else { - dialogOptions.size = 'small'; - } - - var dlg = dialogHelper.createDialog(dialogOptions); - - dlg.classList.add('formDialog'); - - var html = ''; - - html += globalize.translateHtml(template, 'core'); - - dlg.innerHTML = html; - - dlg.addEventListener('change', function () { - settingsChanged = true; - }); - - dlg.addEventListener('close', function () { - if (layoutManager.tv) { - scrollHelper.centerFocus.off(dlg.querySelector('.formDialogContent'), false); - } - - save(dlg); - saveCategories(dlg, options); - - if (settingsChanged) { - resolve(); - } else { - reject(); - } - }); - - dlg.querySelector('.btnCancel').addEventListener('click', function () { - dialogHelper.close(dlg); - }); - - if (layoutManager.tv) { - scrollHelper.centerFocus.on(dlg.querySelector('.formDialogContent'), false); - } - - load(dlg); - loadCategories(dlg, options); - dialogHelper.open(dlg); + dlg.addEventListener('change', function () { + settingsChanged = true; }); - }); - } - return { - show: showEditor - }; -}); + dlg.addEventListener('close', function () { + if (layoutManager.tv) { + scrollHelper.centerFocus.off(dlg.querySelector('.formDialogContent'), false); + } + + save(dlg); + saveCategories(dlg, options); + + if (settingsChanged) { + resolve(); + } else { + reject(); + } + }); + + dlg.querySelector('.btnCancel').addEventListener('click', function () { + dialogHelper.close(dlg); + }); + + if (layoutManager.tv) { + scrollHelper.centerFocus.on(dlg.querySelector('.formDialogContent'), false); + } + + load(dlg); + loadCategories(dlg, options); + dialogHelper.open(dlg); + }); + }); +} + +export default { + show: showEditor +}; diff --git a/src/components/guide/guide.js b/src/components/guide/guide.js index 61caa9188f..e48d4646c0 100644 --- a/src/components/guide/guide.js +++ b/src/components/guide/guide.js @@ -1,1178 +1,1198 @@ -define(['require', 'inputManager', 'browser', 'globalize', 'connectionManager', 'scrollHelper', 'serverNotifications', 'loading', 'datetime', 'focusManager', 'playbackManager', 'userSettings', 'imageLoader', 'events', 'layoutManager', 'itemShortcuts', 'dom', 'css!./guide.css', 'programStyles', 'material-icons', 'scrollStyles', 'emby-programcell', 'emby-button', 'paper-icon-button-light', 'emby-tabs', 'emby-scroller', 'flexStyles', 'webcomponents'], function (require, inputManager, browser, globalize, connectionManager, scrollHelper, serverNotifications, loading, datetime, focusManager, playbackManager, userSettings, imageLoader, events, layoutManager, itemShortcuts, dom) { - 'use strict'; +import inputManager from 'inputManager'; +import browser from 'browser'; +import globalize from 'globalize'; +import connectionManager from 'connectionManager'; +import scrollHelper from 'scrollHelper'; +import serverNotifications from 'serverNotifications'; +import loading from 'loading'; +import datetime from 'datetime'; +import focusManager from 'focusManager'; +import playbackManager from 'playbackManager'; +import * as userSettings from 'userSettings'; +import imageLoader from 'imageLoader'; +import events from 'events'; +import layoutManager from 'layoutManager'; +import itemShortcuts from 'itemShortcuts'; +import dom from 'dom'; +import 'css!./guide.css'; +import 'programStyles'; +import 'material-icons'; +import 'scrollStyles'; +import 'emby-programcell'; +import 'emby-button'; +import 'paper-icon-button-light'; +import 'emby-tabs'; +import 'emby-scroller'; +import 'flexStyles'; +import 'webcomponents'; - playbackManager = playbackManager.default || playbackManager; - browser = browser.default || browser; - loading = loading.default || loading; - - function showViewSettings(instance) { - require(['guide-settings-dialog'], function (guideSettingsDialog) { - guideSettingsDialog.show(instance.categoryOptions).then(function () { - instance.refresh(); - }); +function showViewSettings(instance) { + import('guide-settings-dialog').then(({default: guideSettingsDialog}) => { + guideSettingsDialog.show(instance.categoryOptions).then(function () { + instance.refresh(); }); + }); +} + +function updateProgramCellOnScroll(cell, scrollPct) { + let left = cell.posLeft; + if (!left) { + left = parseFloat(cell.style.left.replace('%', '')); + cell.posLeft = left; + } + let width = cell.posWidth; + if (!width) { + width = parseFloat(cell.style.width.replace('%', '')); + cell.posWidth = width; } - function updateProgramCellOnScroll(cell, scrollPct) { - var left = cell.posLeft; - if (!left) { - left = parseFloat(cell.style.left.replace('%', '')); - cell.posLeft = left; - } - var width = cell.posWidth; - if (!width) { - width = parseFloat(cell.style.width.replace('%', '')); - cell.posWidth = width; - } + const right = left + width; + const newPct = Math.max(Math.min(scrollPct, right), left); - var right = left + width; - var newPct = Math.max(Math.min(scrollPct, right), left); + const offset = newPct - left; + const pctOfWidth = (offset / width) * 100; - var offset = newPct - left; - var pctOfWidth = (offset / width) * 100; - - var guideProgramName = cell.guideProgramName; - if (!guideProgramName) { - guideProgramName = cell.querySelector('.guideProgramName'); - cell.guideProgramName = guideProgramName; - } - - var caret = cell.caret; - if (!caret) { - caret = cell.querySelector('.guide-programNameCaret'); - cell.caret = caret; - } - - if (guideProgramName) { - if (pctOfWidth > 0 && pctOfWidth <= 100) { - guideProgramName.style.transform = 'translateX(' + pctOfWidth + '%)'; - caret.classList.remove('hide'); - } else { - guideProgramName.style.transform = 'none'; - caret.classList.add('hide'); - } - } + let guideProgramName = cell.guideProgramName; + if (!guideProgramName) { + guideProgramName = cell.querySelector('.guideProgramName'); + cell.guideProgramName = guideProgramName; } - var isUpdatingProgramCellScroll = false; - function updateProgramCellsOnScroll(programGrid, programCells) { - if (isUpdatingProgramCellScroll) { - return; - } - - isUpdatingProgramCellScroll = true; - - requestAnimationFrame(function () { - var scrollLeft = programGrid.scrollLeft; - - var scrollPct = scrollLeft ? (scrollLeft / programGrid.scrollWidth) * 100 : 0; - - for (var i = 0, length = programCells.length; i < length; i++) { - updateProgramCellOnScroll(programCells[i], scrollPct); - } - - isUpdatingProgramCellScroll = false; - }); + let caret = cell.caret; + if (!caret) { + caret = cell.querySelector('.guide-programNameCaret'); + cell.caret = caret; } - function onProgramGridClick(e) { - if (!layoutManager.tv) { - return; - } - - var programCell = dom.parentWithClass(e.target, 'programCell'); - if (programCell) { - var startDate = programCell.getAttribute('data-startdate'); - var endDate = programCell.getAttribute('data-enddate'); - startDate = datetime.parseISO8601Date(startDate, { toLocal: true }).getTime(); - endDate = datetime.parseISO8601Date(endDate, { toLocal: true }).getTime(); - - var now = new Date().getTime(); - if (now >= startDate && now < endDate) { - var channelId = programCell.getAttribute('data-channelid'); - var serverId = programCell.getAttribute('data-serverid'); - - e.preventDefault(); - e.stopPropagation(); - - playbackManager.play({ - ids: [channelId], - serverId: serverId - }); - } + if (guideProgramName) { + if (pctOfWidth > 0 && pctOfWidth <= 100) { + guideProgramName.style.transform = 'translateX(' + pctOfWidth + '%)'; + caret.classList.remove('hide'); + } else { + guideProgramName.style.transform = 'none'; + caret.classList.add('hide'); } } +} - function Guide(options) { - var self = this; - var items = {}; +let isUpdatingProgramCellScroll = false; +function updateProgramCellsOnScroll(programGrid, programCells) { + if (isUpdatingProgramCellScroll) { + return; + } - self.options = options; - self.categoryOptions = { categories: [] }; + isUpdatingProgramCellScroll = true; - // 30 mins - var cellCurationMinutes = 30; - var cellDurationMs = cellCurationMinutes * 60 * 1000; - var msPerDay = 86400000; + requestAnimationFrame(function () { + const scrollLeft = programGrid.scrollLeft; - var currentDate; - var currentStartIndex = 0; - var currentChannelLimit = 0; - var autoRefreshInterval; - var programCells; - var lastFocusDirection; - var programGrid; + const scrollPct = scrollLeft ? (scrollLeft / programGrid.scrollWidth) * 100 : 0; - self.refresh = function () { - currentDate = null; - reloadPage(options.element); - restartAutoRefresh(); - }; - - self.pause = function () { - stopAutoRefresh(); - }; - - self.resume = function (refreshData) { - if (refreshData) { - self.refresh(); - } else { - restartAutoRefresh(); - } - }; - - self.destroy = function () { - stopAutoRefresh(); - - events.off(serverNotifications, 'TimerCreated', onTimerCreated); - events.off(serverNotifications, 'SeriesTimerCreated', onSeriesTimerCreated); - events.off(serverNotifications, 'TimerCancelled', onTimerCancelled); - events.off(serverNotifications, 'SeriesTimerCancelled', onSeriesTimerCancelled); - - setScrollEvents(options.element, false); - itemShortcuts.off(options.element); - items = {}; - }; - - function restartAutoRefresh() { - stopAutoRefresh(); - - var intervalMs = 60000 * 15; // (minutes) - - autoRefreshInterval = setInterval(function () { - self.refresh(); - }, intervalMs); + for (let i = 0, length = programCells.length; i < length; i++) { + updateProgramCellOnScroll(programCells[i], scrollPct); } - function stopAutoRefresh() { - if (autoRefreshInterval) { - clearInterval(autoRefreshInterval); - autoRefreshInterval = null; - } - } - - function normalizeDateToTimeslot(date) { - var minutesOffset = date.getMinutes() - cellCurationMinutes; - - if (minutesOffset >= 0) { - date.setHours(date.getHours(), cellCurationMinutes, 0, 0); - } else { - date.setHours(date.getHours(), 0, 0, 0); - } - - return date; - } - - function showLoading() { - loading.show(); - } - - function hideLoading() { - loading.hide(); - } - - function reloadGuide(context, newStartDate, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, focusProgramOnRender) { - var apiClient = connectionManager.getApiClient(options.serverId); - - var channelQuery = { - - StartIndex: 0, - EnableFavoriteSorting: userSettings.get('livetv-favoritechannelsattop') !== 'false' - }; - - channelQuery.UserId = apiClient.getCurrentUserId(); - - var channelLimit = 500; - currentChannelLimit = channelLimit; - - showLoading(); - - channelQuery.StartIndex = currentStartIndex; - channelQuery.Limit = channelLimit; - channelQuery.AddCurrentProgram = false; - channelQuery.EnableUserData = false; - channelQuery.EnableImageTypes = 'Primary'; - - var categories = self.categoryOptions.categories || []; - var displayMovieContent = !categories.length || categories.indexOf('movies') !== -1; - var displaySportsContent = !categories.length || categories.indexOf('sports') !== -1; - var displayNewsContent = !categories.length || categories.indexOf('news') !== -1; - var displayKidsContent = !categories.length || categories.indexOf('kids') !== -1; - var displaySeriesContent = !categories.length || categories.indexOf('series') !== -1; - - if (displayMovieContent && displaySportsContent && displayNewsContent && displayKidsContent) { - channelQuery.IsMovie = null; - channelQuery.IsSports = null; - channelQuery.IsKids = null; - channelQuery.IsNews = null; - channelQuery.IsSeries = null; - } else { - if (displayNewsContent) { - channelQuery.IsNews = true; - } - if (displaySportsContent) { - channelQuery.IsSports = true; - } - if (displayKidsContent) { - channelQuery.IsKids = true; - } - if (displayMovieContent) { - channelQuery.IsMovie = true; - } - if (displaySeriesContent) { - channelQuery.IsSeries = true; - } - } - - if (userSettings.get('livetv-channelorder') === 'DatePlayed') { - channelQuery.SortBy = 'DatePlayed'; - channelQuery.SortOrder = 'Descending'; - } else { - channelQuery.SortBy = null; - channelQuery.SortOrder = null; - } - - var date = newStartDate; - // Add one second to avoid getting programs that are just ending - date = new Date(date.getTime() + 1000); - - // Subtract to avoid getting programs that are starting when the grid ends - var nextDay = new Date(date.getTime() + msPerDay - 2000); - - // Normally we'd want to just let responsive css handle this, - // but since mobile browsers are often underpowered, - // it can help performance to get them out of the markup - var allowIndicators = dom.getWindowSize().innerWidth >= 600; - - var renderOptions = { - showHdIcon: allowIndicators && userSettings.get('guide-indicator-hd') === 'true', - showLiveIndicator: allowIndicators && userSettings.get('guide-indicator-live') !== 'false', - showPremiereIndicator: allowIndicators && userSettings.get('guide-indicator-premiere') !== 'false', - showNewIndicator: allowIndicators && userSettings.get('guide-indicator-new') !== 'false', - showRepeatIndicator: allowIndicators && userSettings.get('guide-indicator-repeat') === 'true', - showEpisodeTitle: layoutManager.tv ? false : true - }; - - apiClient.getLiveTvChannels(channelQuery).then(function (channelsResult) { - var btnPreviousPage = context.querySelector('.btnPreviousPage'); - var btnNextPage = context.querySelector('.btnNextPage'); - - if (channelsResult.TotalRecordCount > channelLimit) { - context.querySelector('.guideOptions').classList.remove('hide'); - - btnPreviousPage.classList.remove('hide'); - btnNextPage.classList.remove('hide'); - - if (channelQuery.StartIndex) { - context.querySelector('.btnPreviousPage').disabled = false; - } else { - context.querySelector('.btnPreviousPage').disabled = true; - } - - if ((channelQuery.StartIndex + channelLimit) < channelsResult.TotalRecordCount) { - btnNextPage.disabled = false; - } else { - btnNextPage.disabled = true; - } - } else { - context.querySelector('.guideOptions').classList.add('hide'); - } - - var programFields = []; - - var programQuery = { - UserId: apiClient.getCurrentUserId(), - MaxStartDate: nextDay.toISOString(), - MinEndDate: date.toISOString(), - channelIds: channelsResult.Items.map(function (c) { - return c.Id; - }).join(','), - ImageTypeLimit: 1, - EnableImages: false, - //EnableImageTypes: layoutManager.tv ? "Primary,Backdrop" : "Primary", - SortBy: 'StartDate', - EnableTotalRecordCount: false, - EnableUserData: false - }; - - if (renderOptions.showHdIcon) { - programFields.push('IsHD'); - } - - if (programFields.length) { - programQuery.Fields = programFields.join(''); - } - - apiClient.getLiveTvPrograms(programQuery).then(function (programsResult) { - renderGuide(context, date, channelsResult.Items, programsResult.Items, renderOptions, apiClient, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, focusProgramOnRender); - - hideLoading(); - }); - }); - } - - function getDisplayTime(date) { - if ((typeof date).toString().toLowerCase() === 'string') { - try { - date = datetime.parseISO8601Date(date, { toLocal: true }); - } catch (err) { - return date; - } - } - - return datetime.getDisplayTime(date).toLowerCase(); - } - - function getTimeslotHeadersHtml(startDate, endDateTime) { - var html = ''; - - // clone - startDate = new Date(startDate.getTime()); - - html += '
'; - - while (startDate.getTime() < endDateTime) { - html += '
'; - - html += getDisplayTime(startDate); - html += '
'; - - // Add 30 mins - startDate.setTime(startDate.getTime() + cellDurationMs); - } - - return html; - } - - function parseDates(program) { - if (!program.StartDateLocal) { - try { - program.StartDateLocal = datetime.parseISO8601Date(program.StartDate, { toLocal: true }); - } catch (err) { - console.error('error parsing timestamp for start date'); - } - } - - if (!program.EndDateLocal) { - try { - program.EndDateLocal = datetime.parseISO8601Date(program.EndDate, { toLocal: true }); - } catch (err) { - console.error('error parsing timestamp for end date'); - } - } - - return null; - } - - function getTimerIndicator(item) { - var status; - - if (item.Type === 'SeriesTimer') { - return ''; - } else if (item.TimerId || item.SeriesTimerId) { - status = item.Status || 'Cancelled'; - } else if (item.Type === 'Timer') { - status = item.Status; - } else { - return ''; - } - - if (item.SeriesTimerId) { - if (status !== 'Cancelled') { - return ''; - } - - return ''; - } - - return ''; - } - - function getChannelProgramsHtml(context, date, channel, programs, options, listInfo) { - var html = ''; - - var startMs = date.getTime(); - var endMs = startMs + msPerDay - 1; - - var outerCssClass = layoutManager.tv ? 'channelPrograms channelPrograms-tv' : 'channelPrograms'; - - html += '
'; - - var clickAction = layoutManager.tv ? 'link' : 'programdialog'; - - var categories = self.categoryOptions.categories || []; - var displayMovieContent = !categories.length || categories.indexOf('movies') !== -1; - var displaySportsContent = !categories.length || categories.indexOf('sports') !== -1; - var displayNewsContent = !categories.length || categories.indexOf('news') !== -1; - var displayKidsContent = !categories.length || categories.indexOf('kids') !== -1; - var displaySeriesContent = !categories.length || categories.indexOf('series') !== -1; - var enableColorCodedBackgrounds = userSettings.get('guide-colorcodedbackgrounds') === 'true'; - - var programsFound; - var now = new Date().getTime(); - - for (var i = listInfo.startIndex, length = programs.length; i < length; i++) { - var program = programs[i]; - - if (program.ChannelId !== channel.Id) { - if (programsFound) { - break; - } - - continue; - } - - programsFound = true; - listInfo.startIndex++; - - parseDates(program); - - var startDateLocalMs = program.StartDateLocal.getTime(); - var endDateLocalMs = program.EndDateLocal.getTime(); - - if (endDateLocalMs < startMs) { - continue; - } - - if (startDateLocalMs > endMs) { - break; - } - - items[program.Id] = program; - - var renderStartMs = Math.max(startDateLocalMs, startMs); - var startPercent = (startDateLocalMs - startMs) / msPerDay; - startPercent *= 100; - startPercent = Math.max(startPercent, 0); - - var renderEndMs = Math.min(endDateLocalMs, endMs); - var endPercent = (renderEndMs - renderStartMs) / msPerDay; - endPercent *= 100; - - var cssClass = 'programCell itemAction'; - var accentCssClass = null; - var displayInnerContent = true; - - if (program.IsKids) { - displayInnerContent = displayKidsContent; - accentCssClass = 'kids'; - } else if (program.IsSports) { - displayInnerContent = displaySportsContent; - accentCssClass = 'sports'; - } else if (program.IsNews) { - displayInnerContent = displayNewsContent; - accentCssClass = 'news'; - } else if (program.IsMovie) { - displayInnerContent = displayMovieContent; - accentCssClass = 'movie'; - } else if (program.IsSeries) { - displayInnerContent = displaySeriesContent; - } else { - displayInnerContent = displayMovieContent && displayNewsContent && displaySportsContent && displayKidsContent && displaySeriesContent; - } - - if (displayInnerContent && enableColorCodedBackgrounds && accentCssClass) { - cssClass += ' programCell-' + accentCssClass; - } - - if (now >= startDateLocalMs && now < endDateLocalMs) { - cssClass += ' programCell-active'; - } - - var timerAttributes = ''; - if (program.TimerId) { - timerAttributes += ' data-timerid="' + program.TimerId + '"'; - } - if (program.SeriesTimerId) { - timerAttributes += ' data-seriestimerid="' + program.SeriesTimerId + '"'; - } - - var isAttribute = endPercent >= 2 ? ' is="emby-programcell"' : ''; - - html += ''; - - if (displayInnerContent) { - var guideProgramNameClass = 'guideProgramName'; - - html += '
'; - - html += '
'; - - html += '
' + program.Name; - - var indicatorHtml = null; - if (program.IsLive && options.showLiveIndicator) { - indicatorHtml = '' + globalize.translate('Live') + ''; - } else if (program.IsPremiere && options.showPremiereIndicator) { - indicatorHtml = '' + globalize.translate('Premiere') + ''; - } else if (program.IsSeries && !program.IsRepeat && options.showNewIndicator) { - indicatorHtml = '' + globalize.translate('AttributeNew') + ''; - } else if (program.IsSeries && program.IsRepeat && options.showRepeatIndicator) { - indicatorHtml = '' + globalize.translate('Repeat') + ''; - } - html += indicatorHtml || ''; - - if ((program.EpisodeTitle && options.showEpisodeTitle)) { - html += '
'; - - if (program.EpisodeTitle && options.showEpisodeTitle) { - html += '' + program.EpisodeTitle + ''; - } - html += '
'; - } - - html += '
'; - - if (program.IsHD && options.showHdIcon) { - if (layoutManager.tv) { - html += '
HD
'; - } else { - html += '
HD
'; - } - } - - html += getTimerIndicator(program); - - html += '
'; - } - - html += ''; - } - - html += '
'; - - return html; - } - - function renderChannelHeaders(context, channels, apiClient) { - var html = ''; - - for (var i = 0, length = channels.length; i < length; i++) { - var channel = channels[i]; - var hasChannelImage = channel.ImageTags.Primary; - - var cssClass = 'guide-channelHeaderCell itemAction'; - - if (layoutManager.tv) { - cssClass += ' guide-channelHeaderCell-tv'; - } - - var title = []; - if (channel.ChannelNumber) { - title.push(channel.ChannelNumber); - } - if (channel.Name) { - title.push(channel.Name); - } - - html += ''; - } - - var channelList = context.querySelector('.channelsContainer'); - channelList.innerHTML = html; - imageLoader.lazyChildren(channelList); - } - - function renderPrograms(context, date, channels, programs, options) { - var listInfo = { - startIndex: 0 - }; - - var html = []; - - for (var i = 0, length = channels.length; i < length; i++) { - html.push(getChannelProgramsHtml(context, date, channels[i], programs, options, listInfo)); - } - - programGrid.innerHTML = html.join(''); - - programCells = programGrid.querySelectorAll('[is=emby-programcell]'); - - updateProgramCellsOnScroll(programGrid, programCells); - } - - function getProgramSortOrder(program, channels) { - var channelId = program.ChannelId; - var channelIndex = -1; - - for (var i = 0, length = channels.length; i < length; i++) { - if (channelId === channels[i].Id) { - channelIndex = i; - break; - } - } - - var start = datetime.parseISO8601Date(program.StartDate, { toLocal: true }); - - return (channelIndex * 10000000) + (start.getTime() / 60000); - } - - function renderGuide(context, date, channels, programs, renderOptions, apiClient, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, focusProgramOnRender) { - programs.sort(function (a, b) { - return getProgramSortOrder(a, channels) - getProgramSortOrder(b, channels); - }); - - var activeElement = document.activeElement; - var itemId = activeElement && activeElement.getAttribute ? activeElement.getAttribute('data-id') : null; - var channelRowId = null; - - if (activeElement) { - channelRowId = dom.parentWithClass(activeElement, 'channelPrograms'); - channelRowId = channelRowId && channelRowId.getAttribute ? channelRowId.getAttribute('data-channelid') : null; - } - - renderChannelHeaders(context, channels, apiClient); - - var startDate = date; - var endDate = new Date(startDate.getTime() + msPerDay); - context.querySelector('.timeslotHeaders').innerHTML = getTimeslotHeadersHtml(startDate, endDate); - items = {}; - renderPrograms(context, date, channels, programs, renderOptions); - - if (focusProgramOnRender) { - focusProgram(context, itemId, channelRowId, focusToTimeMs, startTimeOfDayMs); - } - - scrollProgramGridToTimeMs(context, scrollToTimeMs, startTimeOfDayMs); - } - - function scrollProgramGridToTimeMs(context, scrollToTimeMs, startTimeOfDayMs) { - scrollToTimeMs -= startTimeOfDayMs; - - var pct = scrollToTimeMs / msPerDay; - - programGrid.scrollTop = 0; - - var scrollPos = pct * programGrid.scrollWidth; - - nativeScrollTo(programGrid, scrollPos, true); - } - - function focusProgram(context, itemId, channelRowId, focusToTimeMs, startTimeOfDayMs) { - var focusElem; - if (itemId) { - focusElem = context.querySelector('[data-id="' + itemId + '"]'); - } - - if (focusElem) { - focusManager.focus(focusElem); - } else { - var autoFocusParent; - - if (channelRowId) { - autoFocusParent = context.querySelector('[data-channelid="' + channelRowId + '"]'); - } - - if (!autoFocusParent) { - autoFocusParent = programGrid; - } - - focusToTimeMs -= startTimeOfDayMs; - - var pct = (focusToTimeMs / msPerDay) * 100; - - var programCell = autoFocusParent.querySelector('.programCell'); - - while (programCell) { - var left = (programCell.style.left || '').replace('%', ''); - left = left ? parseFloat(left) : 0; - var width = (programCell.style.width || '').replace('%', ''); - width = width ? parseFloat(width) : 0; - - if (left >= pct || (left + width) >= pct) { - break; - } - programCell = programCell.nextSibling; - } - - if (programCell) { - focusManager.focus(programCell); - } else { - focusManager.autoFocus(autoFocusParent, true); - } - } - } - - function nativeScrollTo(container, pos, horizontal) { - if (container.scrollTo) { - if (horizontal) { - container.scrollTo(pos, 0); - } else { - container.scrollTo(0, pos); - } - } else { - if (horizontal) { - container.scrollLeft = Math.round(pos); - } else { - container.scrollTop = Math.round(pos); - } - } - } - - var lastGridScroll = 0; - var lastHeaderScroll = 0; - var scrollXPct = 0; - function onProgramGridScroll(context, elem, timeslotHeaders) { - if ((new Date().getTime() - lastHeaderScroll) >= 1000) { - lastGridScroll = new Date().getTime(); - - var scrollLeft = elem.scrollLeft; - scrollXPct = (scrollLeft * 100) / elem.scrollWidth; - nativeScrollTo(timeslotHeaders, scrollLeft, true); - } - - updateProgramCellsOnScroll(elem, programCells); - } - - function onTimeslotHeadersScroll(context, elem) { - if ((new Date().getTime() - lastGridScroll) >= 1000) { - lastHeaderScroll = new Date().getTime(); - nativeScrollTo(programGrid, elem.scrollLeft, true); - } - } - - function changeDate(page, date, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, focusProgramOnRender) { - var newStartDate = normalizeDateToTimeslot(date); - currentDate = newStartDate; - - reloadGuide(page, newStartDate, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, focusProgramOnRender); - } - - function getDateTabText(date, isActive, tabIndex) { - var cssClass = isActive ? 'emby-tab-button guide-date-tab-button emby-tab-button-active' : 'emby-tab-button guide-date-tab-button'; - - var html = ''; - - return html; - } - - function setDateRange(page, guideInfo) { - var today = new Date(); - var nowHours = today.getHours(); - today.setHours(nowHours, 0, 0, 0); - - var start = datetime.parseISO8601Date(guideInfo.StartDate, { toLocal: true }); - var end = datetime.parseISO8601Date(guideInfo.EndDate, { toLocal: true }); - - start.setHours(nowHours, 0, 0, 0); - end.setHours(0, 0, 0, 0); - - if (start.getTime() >= end.getTime()) { - end.setDate(start.getDate() + 1); - } - - start = new Date(Math.max(today, start)); - - var dateTabsHtml = ''; - var tabIndex = 0; - - // TODO: Use date-fns - var date = new Date(); - - if (currentDate) { - date.setTime(currentDate.getTime()); - } - - date.setHours(nowHours, 0, 0, 0); - - var startTimeOfDayMs = (start.getHours() * 60 * 60 * 1000); - startTimeOfDayMs += start.getMinutes() * 60 * 1000; - - while (start <= end) { - var isActive = date.getDate() === start.getDate() && date.getMonth() === start.getMonth() && date.getFullYear() === start.getFullYear(); - - dateTabsHtml += getDateTabText(start, isActive, tabIndex); - - start.setDate(start.getDate() + 1); - start.setHours(0, 0, 0, 0); - tabIndex++; - } - - page.querySelector('.emby-tabs-slider').innerHTML = dateTabsHtml; - page.querySelector('.guideDateTabs').refresh(); - - var newDate = new Date(); - var newDateHours = newDate.getHours(); - var scrollToTimeMs = newDateHours * 60 * 60 * 1000; - - var minutes = newDate.getMinutes(); - if (minutes >= 30) { - scrollToTimeMs += 30 * 60 * 1000; - } - - var focusToTimeMs = ((newDateHours * 60) + minutes) * 60 * 1000; - changeDate(page, date, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, layoutManager.tv); - } - - function reloadPage(page) { - showLoading(); - - var apiClient = connectionManager.getApiClient(options.serverId); - - apiClient.getLiveTvGuideInfo().then(function (guideInfo) { - setDateRange(page, guideInfo); - }); - } - - function getChannelProgramsFocusableElements(container) { - var elements = container.querySelectorAll('.programCell'); - - var list = []; - // add 1 to avoid programs that are out of view to the left - var currentScrollXPct = scrollXPct + 1; - - for (var i = 0, length = elements.length; i < length; i++) { - var elem = elements[i]; - - var left = (elem.style.left || '').replace('%', ''); - left = left ? parseFloat(left) : 0; - var width = (elem.style.width || '').replace('%', ''); - width = width ? parseFloat(width) : 0; - - if ((left + width) >= currentScrollXPct) { - list.push(elem); - } - } - - return list; - } - - function onInputCommand(e) { - var target = e.target; - var programCell = dom.parentWithClass(target, 'programCell'); - var container; - var channelPrograms; - var focusableElements; - var newRow; - - switch (e.detail.command) { - case 'up': - if (programCell) { - container = programGrid; - channelPrograms = dom.parentWithClass(programCell, 'channelPrograms'); - - newRow = channelPrograms.previousSibling; - if (newRow) { - focusableElements = getChannelProgramsFocusableElements(newRow); - if (focusableElements.length) { - container = newRow; - } else { - focusableElements = null; - } - } else { - container = null; - } - } else { - container = null; - } - lastFocusDirection = e.detail.command; - - focusManager.moveUp(target, { - container: container, - focusableElements: focusableElements - }); - break; - case 'down': - if (programCell) { - container = programGrid; - channelPrograms = dom.parentWithClass(programCell, 'channelPrograms'); - - newRow = channelPrograms.nextSibling; - if (newRow) { - focusableElements = getChannelProgramsFocusableElements(newRow); - if (focusableElements.length) { - container = newRow; - } else { - focusableElements = null; - } - } else { - container = null; - } - } else { - container = null; - } - lastFocusDirection = e.detail.command; - - focusManager.moveDown(target, { - container: container, - focusableElements: focusableElements - }); - break; - case 'left': - container = programCell ? dom.parentWithClass(programCell, 'channelPrograms') : null; - // allow left outside the channelProgramsContainer when the first child is currently focused - if (container && !programCell.previousSibling) { - container = null; - } - lastFocusDirection = e.detail.command; - - focusManager.moveLeft(target, { - container: container - }); - break; - case 'right': - container = programCell ? dom.parentWithClass(programCell, 'channelPrograms') : null; - lastFocusDirection = e.detail.command; - - focusManager.moveRight(target, { - container: container - }); - break; - default: - return; - } + isUpdatingProgramCellScroll = false; + }); +} + +function onProgramGridClick(e) { + if (!layoutManager.tv) { + return; + } + + const programCell = dom.parentWithClass(e.target, 'programCell'); + if (programCell) { + let startDate = programCell.getAttribute('data-startdate'); + let endDate = programCell.getAttribute('data-enddate'); + startDate = datetime.parseISO8601Date(startDate, { toLocal: true }).getTime(); + endDate = datetime.parseISO8601Date(endDate, { toLocal: true }).getTime(); + + const now = new Date().getTime(); + if (now >= startDate && now < endDate) { + const channelId = programCell.getAttribute('data-channelid'); + const serverId = programCell.getAttribute('data-serverid'); e.preventDefault(); e.stopPropagation(); - } - function onScrollerFocus(e) { - var target = e.target; - var programCell = dom.parentWithClass(target, 'programCell'); - - if (programCell) { - var focused = target; - - var id = focused.getAttribute('data-id'); - var item = items[id]; - - if (item) { - events.trigger(self, 'focus', [ - { - item: item - }]); - } - } - - if (lastFocusDirection === 'left') { - if (programCell) { - scrollHelper.toStart(programGrid, programCell, true, true); - } - } else if (lastFocusDirection === 'right') { - if (programCell) { - scrollHelper.toCenter(programGrid, programCell, true, true); - } - } else if (lastFocusDirection === 'up' || lastFocusDirection === 'down') { - var verticalScroller = dom.parentWithClass(target, 'guideVerticalScroller'); - if (verticalScroller) { - var focusedElement = programCell || dom.parentWithTag(target, 'BUTTON'); - verticalScroller.toCenter(focusedElement, true); - } - } - } - - function setScrollEvents(view, enabled) { - if (layoutManager.tv) { - var guideVerticalScroller = view.querySelector('.guideVerticalScroller'); - - if (enabled) { - inputManager.on(guideVerticalScroller, onInputCommand); - } else { - inputManager.off(guideVerticalScroller, onInputCommand); - } - } - } - - function onTimerCreated(e, apiClient, data) { - var programId = data.ProgramId; - // This could be null, not supported by all tv providers - var newTimerId = data.Id; - - // find guide cells by program id, ensure timer icon - var cells = options.element.querySelectorAll('.programCell[data-id="' + programId + '"]'); - for (var i = 0, length = cells.length; i < length; i++) { - var cell = cells[i]; - - var icon = cell.querySelector('.timerIcon'); - if (!icon) { - cell.querySelector('.guideProgramName').insertAdjacentHTML('beforeend', ''); - } - - if (newTimerId) { - cell.setAttribute('data-timerid', newTimerId); - } - } - } - - function onSeriesTimerCreated(e, apiClient, data) { - } - - function onTimerCancelled(e, apiClient, data) { - var id = data.Id; - // find guide cells by timer id, remove timer icon - var cells = options.element.querySelectorAll('.programCell[data-timerid="' + id + '"]'); - for (var i = 0, length = cells.length; i < length; i++) { - var cell = cells[i]; - var icon = cell.querySelector('.timerIcon'); - if (icon) { - icon.parentNode.removeChild(icon); - } - cell.removeAttribute('data-timerid'); - } - } - - function onSeriesTimerCancelled(e, apiClient, data) { - var id = data.Id; - // find guide cells by timer id, remove timer icon - var cells = options.element.querySelectorAll('.programCell[data-seriestimerid="' + id + '"]'); - for (var i = 0, length = cells.length; i < length; i++) { - var cell = cells[i]; - var icon = cell.querySelector('.seriesTimerIcon'); - if (icon) { - icon.parentNode.removeChild(icon); - } - cell.removeAttribute('data-seriestimerid'); - } - } - - require(['text!./tvguide.template.html'], function (template) { - var context = options.element; - - context.classList.add('tvguide'); - - context.innerHTML = globalize.translateHtml(template, 'core'); - - programGrid = context.querySelector('.programGrid'); - var timeslotHeaders = context.querySelector('.timeslotHeaders'); - - if (layoutManager.tv) { - dom.addEventListener(context.querySelector('.guideVerticalScroller'), 'focus', onScrollerFocus, { - capture: true, - passive: true - }); - } else if (layoutManager.desktop) { - timeslotHeaders.classList.add('timeslotHeaders-desktop'); - } - - if (browser.iOS || browser.osx) { - context.querySelector('.channelsContainer').classList.add('noRubberBanding'); - - programGrid.classList.add('noRubberBanding'); - } - - dom.addEventListener(programGrid, 'scroll', function (e) { - onProgramGridScroll(context, this, timeslotHeaders); - }, { - passive: true + playbackManager.play({ + ids: [channelId], + serverId: serverId }); + } + } +} - dom.addEventListener(timeslotHeaders, 'scroll', function () { - onTimeslotHeadersScroll(context, this); - }, { - passive: true - }); +function Guide(options) { + const self = this; + let items = {}; - programGrid.addEventListener('click', onProgramGridClick); + self.options = options; + self.categoryOptions = { categories: [] }; - context.querySelector('.btnNextPage').addEventListener('click', function () { - currentStartIndex += currentChannelLimit; - reloadPage(context); - restartAutoRefresh(); - }); + // 30 mins + const cellCurationMinutes = 30; + const cellDurationMs = cellCurationMinutes * 60 * 1000; + const msPerDay = 86400000; - context.querySelector('.btnPreviousPage').addEventListener('click', function () { - currentStartIndex = Math.max(currentStartIndex - currentChannelLimit, 0); - reloadPage(context); - restartAutoRefresh(); - }); + let currentDate; + let currentStartIndex = 0; + let currentChannelLimit = 0; + let autoRefreshInterval; + let programCells; + let lastFocusDirection; + let programGrid; - context.querySelector('.btnGuideViewSettings').addEventListener('click', function () { - showViewSettings(self); - restartAutoRefresh(); - }); + self.refresh = function () { + currentDate = null; + reloadPage(options.element); + restartAutoRefresh(); + }; - context.querySelector('.guideDateTabs').addEventListener('tabchange', function (e) { - var allTabButtons = e.target.querySelectorAll('.guide-date-tab-button'); - - var tabButton = allTabButtons[parseInt(e.detail.selectedTabIndex)]; - if (tabButton) { - var previousButton = e.detail.previousIndex == null ? null : allTabButtons[parseInt(e.detail.previousIndex)]; - - var date = new Date(); - date.setTime(parseInt(tabButton.getAttribute('data-date'))); - - var scrollWidth = programGrid.scrollWidth; - var scrollToTimeMs; - if (scrollWidth) { - scrollToTimeMs = (programGrid.scrollLeft / scrollWidth) * msPerDay; - } else { - scrollToTimeMs = 0; - } - - if (previousButton) { - var previousDate = new Date(); - previousDate.setTime(parseInt(previousButton.getAttribute('data-date'))); - - scrollToTimeMs += (previousDate.getHours() * 60 * 60 * 1000); - scrollToTimeMs += (previousDate.getMinutes() * 60 * 1000); - } - - var startTimeOfDayMs = (date.getHours() * 60 * 60 * 1000); - startTimeOfDayMs += (date.getMinutes() * 60 * 1000); - - changeDate(context, date, scrollToTimeMs, scrollToTimeMs, startTimeOfDayMs, false); - } - }); - - setScrollEvents(context, true); - itemShortcuts.on(context); - - events.trigger(self, 'load'); - - events.on(serverNotifications, 'TimerCreated', onTimerCreated); - events.on(serverNotifications, 'SeriesTimerCreated', onSeriesTimerCreated); - events.on(serverNotifications, 'TimerCancelled', onTimerCancelled); - events.on(serverNotifications, 'SeriesTimerCancelled', onSeriesTimerCancelled); + self.pause = function () { + stopAutoRefresh(); + }; + self.resume = function (refreshData) { + if (refreshData) { self.refresh(); + } else { + restartAutoRefresh(); + } + }; + + self.destroy = function () { + stopAutoRefresh(); + + events.off(serverNotifications, 'TimerCreated', onTimerCreated); + events.off(serverNotifications, 'SeriesTimerCreated', onSeriesTimerCreated); + events.off(serverNotifications, 'TimerCancelled', onTimerCancelled); + events.off(serverNotifications, 'SeriesTimerCancelled', onSeriesTimerCancelled); + + setScrollEvents(options.element, false); + itemShortcuts.off(options.element); + items = {}; + }; + + function restartAutoRefresh() { + stopAutoRefresh(); + + const intervalMs = 60000 * 15; // (minutes) + + autoRefreshInterval = setInterval(function () { + self.refresh(); + }, intervalMs); + } + + function stopAutoRefresh() { + if (autoRefreshInterval) { + clearInterval(autoRefreshInterval); + autoRefreshInterval = null; + } + } + + function normalizeDateToTimeslot(date) { + const minutesOffset = date.getMinutes() - cellCurationMinutes; + + if (minutesOffset >= 0) { + date.setHours(date.getHours(), cellCurationMinutes, 0, 0); + } else { + date.setHours(date.getHours(), 0, 0, 0); + } + + return date; + } + + function showLoading() { + loading.show(); + } + + function hideLoading() { + loading.hide(); + } + + function reloadGuide(context, newStartDate, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, focusProgramOnRender) { + const apiClient = connectionManager.getApiClient(options.serverId); + + const channelQuery = { + + StartIndex: 0, + EnableFavoriteSorting: userSettings.get('livetv-favoritechannelsattop') !== 'false' + }; + + channelQuery.UserId = apiClient.getCurrentUserId(); + + const channelLimit = 500; + currentChannelLimit = channelLimit; + + showLoading(); + + channelQuery.StartIndex = currentStartIndex; + channelQuery.Limit = channelLimit; + channelQuery.AddCurrentProgram = false; + channelQuery.EnableUserData = false; + channelQuery.EnableImageTypes = 'Primary'; + + const categories = self.categoryOptions.categories || []; + const displayMovieContent = !categories.length || categories.indexOf('movies') !== -1; + const displaySportsContent = !categories.length || categories.indexOf('sports') !== -1; + const displayNewsContent = !categories.length || categories.indexOf('news') !== -1; + const displayKidsContent = !categories.length || categories.indexOf('kids') !== -1; + const displaySeriesContent = !categories.length || categories.indexOf('series') !== -1; + + if (displayMovieContent && displaySportsContent && displayNewsContent && displayKidsContent) { + channelQuery.IsMovie = null; + channelQuery.IsSports = null; + channelQuery.IsKids = null; + channelQuery.IsNews = null; + channelQuery.IsSeries = null; + } else { + if (displayNewsContent) { + channelQuery.IsNews = true; + } + if (displaySportsContent) { + channelQuery.IsSports = true; + } + if (displayKidsContent) { + channelQuery.IsKids = true; + } + if (displayMovieContent) { + channelQuery.IsMovie = true; + } + if (displaySeriesContent) { + channelQuery.IsSeries = true; + } + } + + if (userSettings.get('livetv-channelorder') === 'DatePlayed') { + channelQuery.SortBy = 'DatePlayed'; + channelQuery.SortOrder = 'Descending'; + } else { + channelQuery.SortBy = null; + channelQuery.SortOrder = null; + } + + let date = newStartDate; + // Add one second to avoid getting programs that are just ending + date = new Date(date.getTime() + 1000); + + // Subtract to avoid getting programs that are starting when the grid ends + const nextDay = new Date(date.getTime() + msPerDay - 2000); + + // Normally we'd want to just let responsive css handle this, + // but since mobile browsers are often underpowered, + // it can help performance to get them out of the markup + const allowIndicators = dom.getWindowSize().innerWidth >= 600; + + const renderOptions = { + showHdIcon: allowIndicators && userSettings.get('guide-indicator-hd') === 'true', + showLiveIndicator: allowIndicators && userSettings.get('guide-indicator-live') !== 'false', + showPremiereIndicator: allowIndicators && userSettings.get('guide-indicator-premiere') !== 'false', + showNewIndicator: allowIndicators && userSettings.get('guide-indicator-new') !== 'false', + showRepeatIndicator: allowIndicators && userSettings.get('guide-indicator-repeat') === 'true', + showEpisodeTitle: layoutManager.tv ? false : true + }; + + apiClient.getLiveTvChannels(channelQuery).then(function (channelsResult) { + const btnPreviousPage = context.querySelector('.btnPreviousPage'); + const btnNextPage = context.querySelector('.btnNextPage'); + + if (channelsResult.TotalRecordCount > channelLimit) { + context.querySelector('.guideOptions').classList.remove('hide'); + + btnPreviousPage.classList.remove('hide'); + btnNextPage.classList.remove('hide'); + + if (channelQuery.StartIndex) { + context.querySelector('.btnPreviousPage').disabled = false; + } else { + context.querySelector('.btnPreviousPage').disabled = true; + } + + if ((channelQuery.StartIndex + channelLimit) < channelsResult.TotalRecordCount) { + btnNextPage.disabled = false; + } else { + btnNextPage.disabled = true; + } + } else { + context.querySelector('.guideOptions').classList.add('hide'); + } + + const programFields = []; + + const programQuery = { + UserId: apiClient.getCurrentUserId(), + MaxStartDate: nextDay.toISOString(), + MinEndDate: date.toISOString(), + channelIds: channelsResult.Items.map(function (c) { + return c.Id; + }).join(','), + ImageTypeLimit: 1, + EnableImages: false, + //EnableImageTypes: layoutManager.tv ? "Primary,Backdrop" : "Primary", + SortBy: 'StartDate', + EnableTotalRecordCount: false, + EnableUserData: false + }; + + if (renderOptions.showHdIcon) { + programFields.push('IsHD'); + } + + if (programFields.length) { + programQuery.Fields = programFields.join(''); + } + + apiClient.getLiveTvPrograms(programQuery).then(function (programsResult) { + renderGuide(context, date, channelsResult.Items, programsResult.Items, renderOptions, apiClient, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, focusProgramOnRender); + + hideLoading(); + }); }); } - return Guide; -}); + function getDisplayTime(date) { + if ((typeof date).toString().toLowerCase() === 'string') { + try { + date = datetime.parseISO8601Date(date, { toLocal: true }); + } catch (err) { + return date; + } + } + + return datetime.getDisplayTime(date).toLowerCase(); + } + + function getTimeslotHeadersHtml(startDate, endDateTime) { + let html = ''; + + // clone + startDate = new Date(startDate.getTime()); + + html += '
'; + + while (startDate.getTime() < endDateTime) { + html += '
'; + + html += getDisplayTime(startDate); + html += '
'; + + // Add 30 mins + startDate.setTime(startDate.getTime() + cellDurationMs); + } + + return html; + } + + function parseDates(program) { + if (!program.StartDateLocal) { + try { + program.StartDateLocal = datetime.parseISO8601Date(program.StartDate, { toLocal: true }); + } catch (err) { + console.error('error parsing timestamp for start date'); + } + } + + if (!program.EndDateLocal) { + try { + program.EndDateLocal = datetime.parseISO8601Date(program.EndDate, { toLocal: true }); + } catch (err) { + console.error('error parsing timestamp for end date'); + } + } + + return null; + } + + function getTimerIndicator(item) { + let status; + + if (item.Type === 'SeriesTimer') { + return ''; + } else if (item.TimerId || item.SeriesTimerId) { + status = item.Status || 'Cancelled'; + } else if (item.Type === 'Timer') { + status = item.Status; + } else { + return ''; + } + + if (item.SeriesTimerId) { + if (status !== 'Cancelled') { + return ''; + } + + return ''; + } + + return ''; + } + + function getChannelProgramsHtml(context, date, channel, programs, options, listInfo) { + let html = ''; + + const startMs = date.getTime(); + const endMs = startMs + msPerDay - 1; + + const outerCssClass = layoutManager.tv ? 'channelPrograms channelPrograms-tv' : 'channelPrograms'; + + html += '
'; + + const clickAction = layoutManager.tv ? 'link' : 'programdialog'; + + const categories = self.categoryOptions.categories || []; + const displayMovieContent = !categories.length || categories.indexOf('movies') !== -1; + const displaySportsContent = !categories.length || categories.indexOf('sports') !== -1; + const displayNewsContent = !categories.length || categories.indexOf('news') !== -1; + const displayKidsContent = !categories.length || categories.indexOf('kids') !== -1; + const displaySeriesContent = !categories.length || categories.indexOf('series') !== -1; + const enableColorCodedBackgrounds = userSettings.get('guide-colorcodedbackgrounds') === 'true'; + + let programsFound; + const now = new Date().getTime(); + + for (let i = listInfo.startIndex, length = programs.length; i < length; i++) { + const program = programs[i]; + + if (program.ChannelId !== channel.Id) { + if (programsFound) { + break; + } + + continue; + } + + programsFound = true; + listInfo.startIndex++; + + parseDates(program); + + const startDateLocalMs = program.StartDateLocal.getTime(); + const endDateLocalMs = program.EndDateLocal.getTime(); + + if (endDateLocalMs < startMs) { + continue; + } + + if (startDateLocalMs > endMs) { + break; + } + + items[program.Id] = program; + + const renderStartMs = Math.max(startDateLocalMs, startMs); + let startPercent = (startDateLocalMs - startMs) / msPerDay; + startPercent *= 100; + startPercent = Math.max(startPercent, 0); + + const renderEndMs = Math.min(endDateLocalMs, endMs); + let endPercent = (renderEndMs - renderStartMs) / msPerDay; + endPercent *= 100; + + let cssClass = 'programCell itemAction'; + let accentCssClass = null; + let displayInnerContent = true; + + if (program.IsKids) { + displayInnerContent = displayKidsContent; + accentCssClass = 'kids'; + } else if (program.IsSports) { + displayInnerContent = displaySportsContent; + accentCssClass = 'sports'; + } else if (program.IsNews) { + displayInnerContent = displayNewsContent; + accentCssClass = 'news'; + } else if (program.IsMovie) { + displayInnerContent = displayMovieContent; + accentCssClass = 'movie'; + } else if (program.IsSeries) { + displayInnerContent = displaySeriesContent; + } else { + displayInnerContent = displayMovieContent && displayNewsContent && displaySportsContent && displayKidsContent && displaySeriesContent; + } + + if (displayInnerContent && enableColorCodedBackgrounds && accentCssClass) { + cssClass += ' programCell-' + accentCssClass; + } + + if (now >= startDateLocalMs && now < endDateLocalMs) { + cssClass += ' programCell-active'; + } + + let timerAttributes = ''; + if (program.TimerId) { + timerAttributes += ' data-timerid="' + program.TimerId + '"'; + } + if (program.SeriesTimerId) { + timerAttributes += ' data-seriestimerid="' + program.SeriesTimerId + '"'; + } + + const isAttribute = endPercent >= 2 ? ' is="emby-programcell"' : ''; + + html += ''; + + if (displayInnerContent) { + const guideProgramNameClass = 'guideProgramName'; + + html += '
'; + + html += '
'; + + html += '
' + program.Name; + + let indicatorHtml = null; + if (program.IsLive && options.showLiveIndicator) { + indicatorHtml = '' + globalize.translate('Live') + ''; + } else if (program.IsPremiere && options.showPremiereIndicator) { + indicatorHtml = '' + globalize.translate('Premiere') + ''; + } else if (program.IsSeries && !program.IsRepeat && options.showNewIndicator) { + indicatorHtml = '' + globalize.translate('AttributeNew') + ''; + } else if (program.IsSeries && program.IsRepeat && options.showRepeatIndicator) { + indicatorHtml = '' + globalize.translate('Repeat') + ''; + } + html += indicatorHtml || ''; + + if ((program.EpisodeTitle && options.showEpisodeTitle)) { + html += '
'; + + if (program.EpisodeTitle && options.showEpisodeTitle) { + html += '' + program.EpisodeTitle + ''; + } + html += '
'; + } + + html += '
'; + + if (program.IsHD && options.showHdIcon) { + if (layoutManager.tv) { + html += '
HD
'; + } else { + html += '
HD
'; + } + } + + html += getTimerIndicator(program); + + html += '
'; + } + + html += ''; + } + + html += '
'; + + return html; + } + + function renderChannelHeaders(context, channels, apiClient) { + let html = ''; + + for (let i = 0, length = channels.length; i < length; i++) { + const channel = channels[i]; + const hasChannelImage = channel.ImageTags.Primary; + + let cssClass = 'guide-channelHeaderCell itemAction'; + + if (layoutManager.tv) { + cssClass += ' guide-channelHeaderCell-tv'; + } + + const title = []; + if (channel.ChannelNumber) { + title.push(channel.ChannelNumber); + } + if (channel.Name) { + title.push(channel.Name); + } + + html += ''; + } + + const channelList = context.querySelector('.channelsContainer'); + channelList.innerHTML = html; + imageLoader.lazyChildren(channelList); + } + + function renderPrograms(context, date, channels, programs, options) { + const listInfo = { + startIndex: 0 + }; + + const html = []; + + for (let i = 0, length = channels.length; i < length; i++) { + html.push(getChannelProgramsHtml(context, date, channels[i], programs, options, listInfo)); + } + + programGrid.innerHTML = html.join(''); + + programCells = programGrid.querySelectorAll('[is=emby-programcell]'); + + updateProgramCellsOnScroll(programGrid, programCells); + } + + function getProgramSortOrder(program, channels) { + const channelId = program.ChannelId; + let channelIndex = -1; + + for (let i = 0, length = channels.length; i < length; i++) { + if (channelId === channels[i].Id) { + channelIndex = i; + break; + } + } + + const start = datetime.parseISO8601Date(program.StartDate, { toLocal: true }); + + return (channelIndex * 10000000) + (start.getTime() / 60000); + } + + function renderGuide(context, date, channels, programs, renderOptions, apiClient, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, focusProgramOnRender) { + programs.sort(function (a, b) { + return getProgramSortOrder(a, channels) - getProgramSortOrder(b, channels); + }); + + const activeElement = document.activeElement; + const itemId = activeElement && activeElement.getAttribute ? activeElement.getAttribute('data-id') : null; + let channelRowId = null; + + if (activeElement) { + channelRowId = dom.parentWithClass(activeElement, 'channelPrograms'); + channelRowId = channelRowId && channelRowId.getAttribute ? channelRowId.getAttribute('data-channelid') : null; + } + + renderChannelHeaders(context, channels, apiClient); + + const startDate = date; + const endDate = new Date(startDate.getTime() + msPerDay); + context.querySelector('.timeslotHeaders').innerHTML = getTimeslotHeadersHtml(startDate, endDate); + items = {}; + renderPrograms(context, date, channels, programs, renderOptions); + + if (focusProgramOnRender) { + focusProgram(context, itemId, channelRowId, focusToTimeMs, startTimeOfDayMs); + } + + scrollProgramGridToTimeMs(context, scrollToTimeMs, startTimeOfDayMs); + } + + function scrollProgramGridToTimeMs(context, scrollToTimeMs, startTimeOfDayMs) { + scrollToTimeMs -= startTimeOfDayMs; + + const pct = scrollToTimeMs / msPerDay; + + programGrid.scrollTop = 0; + + const scrollPos = pct * programGrid.scrollWidth; + + nativeScrollTo(programGrid, scrollPos, true); + } + + function focusProgram(context, itemId, channelRowId, focusToTimeMs, startTimeOfDayMs) { + let focusElem; + if (itemId) { + focusElem = context.querySelector('[data-id="' + itemId + '"]'); + } + + if (focusElem) { + focusManager.focus(focusElem); + } else { + let autoFocusParent; + + if (channelRowId) { + autoFocusParent = context.querySelector('[data-channelid="' + channelRowId + '"]'); + } + + if (!autoFocusParent) { + autoFocusParent = programGrid; + } + + focusToTimeMs -= startTimeOfDayMs; + + const pct = (focusToTimeMs / msPerDay) * 100; + + let programCell = autoFocusParent.querySelector('.programCell'); + + while (programCell) { + let left = (programCell.style.left || '').replace('%', ''); + left = left ? parseFloat(left) : 0; + let width = (programCell.style.width || '').replace('%', ''); + width = width ? parseFloat(width) : 0; + + if (left >= pct || (left + width) >= pct) { + break; + } + programCell = programCell.nextSibling; + } + + if (programCell) { + focusManager.focus(programCell); + } else { + focusManager.autoFocus(autoFocusParent, true); + } + } + } + + function nativeScrollTo(container, pos, horizontal) { + if (container.scrollTo) { + if (horizontal) { + container.scrollTo(pos, 0); + } else { + container.scrollTo(0, pos); + } + } else { + if (horizontal) { + container.scrollLeft = Math.round(pos); + } else { + container.scrollTop = Math.round(pos); + } + } + } + + let lastGridScroll = 0; + let lastHeaderScroll = 0; + let scrollXPct = 0; + function onProgramGridScroll(context, elem, timeslotHeaders) { + if ((new Date().getTime() - lastHeaderScroll) >= 1000) { + lastGridScroll = new Date().getTime(); + + const scrollLeft = elem.scrollLeft; + scrollXPct = (scrollLeft * 100) / elem.scrollWidth; + nativeScrollTo(timeslotHeaders, scrollLeft, true); + } + + updateProgramCellsOnScroll(elem, programCells); + } + + function onTimeslotHeadersScroll(context, elem) { + if ((new Date().getTime() - lastGridScroll) >= 1000) { + lastHeaderScroll = new Date().getTime(); + nativeScrollTo(programGrid, elem.scrollLeft, true); + } + } + + function changeDate(page, date, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, focusProgramOnRender) { + const newStartDate = normalizeDateToTimeslot(date); + currentDate = newStartDate; + + reloadGuide(page, newStartDate, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, focusProgramOnRender); + } + + function getDateTabText(date, isActive, tabIndex) { + const cssClass = isActive ? 'emby-tab-button guide-date-tab-button emby-tab-button-active' : 'emby-tab-button guide-date-tab-button'; + + let html = ''; + + return html; + } + + function setDateRange(page, guideInfo) { + const today = new Date(); + const nowHours = today.getHours(); + today.setHours(nowHours, 0, 0, 0); + + let start = datetime.parseISO8601Date(guideInfo.StartDate, { toLocal: true }); + const end = datetime.parseISO8601Date(guideInfo.EndDate, { toLocal: true }); + + start.setHours(nowHours, 0, 0, 0); + end.setHours(0, 0, 0, 0); + + if (start.getTime() >= end.getTime()) { + end.setDate(start.getDate() + 1); + } + + start = new Date(Math.max(today, start)); + + let dateTabsHtml = ''; + let tabIndex = 0; + + // TODO: Use date-fns + const date = new Date(); + + if (currentDate) { + date.setTime(currentDate.getTime()); + } + + date.setHours(nowHours, 0, 0, 0); + + let startTimeOfDayMs = (start.getHours() * 60 * 60 * 1000); + startTimeOfDayMs += start.getMinutes() * 60 * 1000; + + while (start <= end) { + const isActive = date.getDate() === start.getDate() && date.getMonth() === start.getMonth() && date.getFullYear() === start.getFullYear(); + + dateTabsHtml += getDateTabText(start, isActive, tabIndex); + + start.setDate(start.getDate() + 1); + start.setHours(0, 0, 0, 0); + tabIndex++; + } + + page.querySelector('.emby-tabs-slider').innerHTML = dateTabsHtml; + page.querySelector('.guideDateTabs').refresh(); + + const newDate = new Date(); + const newDateHours = newDate.getHours(); + let scrollToTimeMs = newDateHours * 60 * 60 * 1000; + + const minutes = newDate.getMinutes(); + if (minutes >= 30) { + scrollToTimeMs += 30 * 60 * 1000; + } + + const focusToTimeMs = ((newDateHours * 60) + minutes) * 60 * 1000; + changeDate(page, date, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, layoutManager.tv); + } + + function reloadPage(page) { + showLoading(); + + const apiClient = connectionManager.getApiClient(options.serverId); + + apiClient.getLiveTvGuideInfo().then(function (guideInfo) { + setDateRange(page, guideInfo); + }); + } + + function getChannelProgramsFocusableElements(container) { + const elements = container.querySelectorAll('.programCell'); + + const list = []; + // add 1 to avoid programs that are out of view to the left + const currentScrollXPct = scrollXPct + 1; + + for (let i = 0, length = elements.length; i < length; i++) { + const elem = elements[i]; + + let left = (elem.style.left || '').replace('%', ''); + left = left ? parseFloat(left) : 0; + let width = (elem.style.width || '').replace('%', ''); + width = width ? parseFloat(width) : 0; + + if ((left + width) >= currentScrollXPct) { + list.push(elem); + } + } + + return list; + } + + function onInputCommand(e) { + const target = e.target; + const programCell = dom.parentWithClass(target, 'programCell'); + let container; + let channelPrograms; + let focusableElements; + let newRow; + + switch (e.detail.command) { + case 'up': + if (programCell) { + container = programGrid; + channelPrograms = dom.parentWithClass(programCell, 'channelPrograms'); + + newRow = channelPrograms.previousSibling; + if (newRow) { + focusableElements = getChannelProgramsFocusableElements(newRow); + if (focusableElements.length) { + container = newRow; + } else { + focusableElements = null; + } + } else { + container = null; + } + } else { + container = null; + } + lastFocusDirection = e.detail.command; + + focusManager.moveUp(target, { + container: container, + focusableElements: focusableElements + }); + break; + case 'down': + if (programCell) { + container = programGrid; + channelPrograms = dom.parentWithClass(programCell, 'channelPrograms'); + + newRow = channelPrograms.nextSibling; + if (newRow) { + focusableElements = getChannelProgramsFocusableElements(newRow); + if (focusableElements.length) { + container = newRow; + } else { + focusableElements = null; + } + } else { + container = null; + } + } else { + container = null; + } + lastFocusDirection = e.detail.command; + + focusManager.moveDown(target, { + container: container, + focusableElements: focusableElements + }); + break; + case 'left': + container = programCell ? dom.parentWithClass(programCell, 'channelPrograms') : null; + // allow left outside the channelProgramsContainer when the first child is currently focused + if (container && !programCell.previousSibling) { + container = null; + } + lastFocusDirection = e.detail.command; + + focusManager.moveLeft(target, { + container: container + }); + break; + case 'right': + container = programCell ? dom.parentWithClass(programCell, 'channelPrograms') : null; + lastFocusDirection = e.detail.command; + + focusManager.moveRight(target, { + container: container + }); + break; + default: + return; + } + + e.preventDefault(); + e.stopPropagation(); + } + + function onScrollerFocus(e) { + const target = e.target; + const programCell = dom.parentWithClass(target, 'programCell'); + + if (programCell) { + const focused = target; + + const id = focused.getAttribute('data-id'); + const item = items[id]; + + if (item) { + events.trigger(self, 'focus', [ + { + item: item + }]); + } + } + + if (lastFocusDirection === 'left') { + if (programCell) { + scrollHelper.toStart(programGrid, programCell, true, true); + } + } else if (lastFocusDirection === 'right') { + if (programCell) { + scrollHelper.toCenter(programGrid, programCell, true, true); + } + } else if (lastFocusDirection === 'up' || lastFocusDirection === 'down') { + const verticalScroller = dom.parentWithClass(target, 'guideVerticalScroller'); + if (verticalScroller) { + const focusedElement = programCell || dom.parentWithTag(target, 'BUTTON'); + verticalScroller.toCenter(focusedElement, true); + } + } + } + + function setScrollEvents(view, enabled) { + if (layoutManager.tv) { + const guideVerticalScroller = view.querySelector('.guideVerticalScroller'); + + if (enabled) { + inputManager.on(guideVerticalScroller, onInputCommand); + } else { + inputManager.off(guideVerticalScroller, onInputCommand); + } + } + } + + function onTimerCreated(e, apiClient, data) { + const programId = data.ProgramId; + // This could be null, not supported by all tv providers + const newTimerId = data.Id; + + // find guide cells by program id, ensure timer icon + const cells = options.element.querySelectorAll('.programCell[data-id="' + programId + '"]'); + for (let i = 0, length = cells.length; i < length; i++) { + const cell = cells[i]; + + const icon = cell.querySelector('.timerIcon'); + if (!icon) { + cell.querySelector('.guideProgramName').insertAdjacentHTML('beforeend', ''); + } + + if (newTimerId) { + cell.setAttribute('data-timerid', newTimerId); + } + } + } + + function onSeriesTimerCreated(e, apiClient, data) { + } + + function onTimerCancelled(e, apiClient, data) { + const id = data.Id; + // find guide cells by timer id, remove timer icon + const cells = options.element.querySelectorAll('.programCell[data-timerid="' + id + '"]'); + for (let i = 0, length = cells.length; i < length; i++) { + const cell = cells[i]; + const icon = cell.querySelector('.timerIcon'); + if (icon) { + icon.parentNode.removeChild(icon); + } + cell.removeAttribute('data-timerid'); + } + } + + function onSeriesTimerCancelled(e, apiClient, data) { + const id = data.Id; + // find guide cells by timer id, remove timer icon + const cells = options.element.querySelectorAll('.programCell[data-seriestimerid="' + id + '"]'); + for (let i = 0, length = cells.length; i < length; i++) { + const cell = cells[i]; + const icon = cell.querySelector('.seriesTimerIcon'); + if (icon) { + icon.parentNode.removeChild(icon); + } + cell.removeAttribute('data-seriestimerid'); + } + } + + import('text!./tvguide.template.html').then(({default: template}) => { + const context = options.element; + + context.classList.add('tvguide'); + + context.innerHTML = globalize.translateHtml(template, 'core'); + + programGrid = context.querySelector('.programGrid'); + const timeslotHeaders = context.querySelector('.timeslotHeaders'); + + if (layoutManager.tv) { + dom.addEventListener(context.querySelector('.guideVerticalScroller'), 'focus', onScrollerFocus, { + capture: true, + passive: true + }); + } else if (layoutManager.desktop) { + timeslotHeaders.classList.add('timeslotHeaders-desktop'); + } + + if (browser.iOS || browser.osx) { + context.querySelector('.channelsContainer').classList.add('noRubberBanding'); + + programGrid.classList.add('noRubberBanding'); + } + + dom.addEventListener(programGrid, 'scroll', function (e) { + onProgramGridScroll(context, this, timeslotHeaders); + }, { + passive: true + }); + + dom.addEventListener(timeslotHeaders, 'scroll', function () { + onTimeslotHeadersScroll(context, this); + }, { + passive: true + }); + + programGrid.addEventListener('click', onProgramGridClick); + + context.querySelector('.btnNextPage').addEventListener('click', function () { + currentStartIndex += currentChannelLimit; + reloadPage(context); + restartAutoRefresh(); + }); + + context.querySelector('.btnPreviousPage').addEventListener('click', function () { + currentStartIndex = Math.max(currentStartIndex - currentChannelLimit, 0); + reloadPage(context); + restartAutoRefresh(); + }); + + context.querySelector('.btnGuideViewSettings').addEventListener('click', function () { + showViewSettings(self); + restartAutoRefresh(); + }); + + context.querySelector('.guideDateTabs').addEventListener('tabchange', function (e) { + const allTabButtons = e.target.querySelectorAll('.guide-date-tab-button'); + + const tabButton = allTabButtons[parseInt(e.detail.selectedTabIndex)]; + if (tabButton) { + const previousButton = e.detail.previousIndex == null ? null : allTabButtons[parseInt(e.detail.previousIndex)]; + + const date = new Date(); + date.setTime(parseInt(tabButton.getAttribute('data-date'))); + + const scrollWidth = programGrid.scrollWidth; + let scrollToTimeMs; + if (scrollWidth) { + scrollToTimeMs = (programGrid.scrollLeft / scrollWidth) * msPerDay; + } else { + scrollToTimeMs = 0; + } + + if (previousButton) { + const previousDate = new Date(); + previousDate.setTime(parseInt(previousButton.getAttribute('data-date'))); + + scrollToTimeMs += (previousDate.getHours() * 60 * 60 * 1000); + scrollToTimeMs += (previousDate.getMinutes() * 60 * 1000); + } + + let startTimeOfDayMs = (date.getHours() * 60 * 60 * 1000); + startTimeOfDayMs += (date.getMinutes() * 60 * 1000); + + changeDate(context, date, scrollToTimeMs, scrollToTimeMs, startTimeOfDayMs, false); + } + }); + + setScrollEvents(context, true); + itemShortcuts.on(context); + + events.trigger(self, 'load'); + + events.on(serverNotifications, 'TimerCreated', onTimerCreated); + events.on(serverNotifications, 'SeriesTimerCreated', onSeriesTimerCreated); + events.on(serverNotifications, 'TimerCancelled', onTimerCancelled); + events.on(serverNotifications, 'SeriesTimerCancelled', onSeriesTimerCancelled); + + self.refresh(); + }); +} + +export default Guide; diff --git a/src/controllers/livetv/livetvguide.js b/src/controllers/livetv/livetvguide.js index ec7a7a3f81..f89c2693be 100644 --- a/src/controllers/livetv/livetvguide.js +++ b/src/controllers/livetv/livetvguide.js @@ -7,7 +7,7 @@ define(['tvguide'], function (tvguide) { self.renderTab = function () { if (!guideInstance) { - guideInstance = new tvguide({ + guideInstance = new tvguide.default({ element: tabContent, serverId: ApiClient.serverId() }); From 1b69c89b3119b8b4017d94b0ec048ff76a0ca1a1 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 5 Aug 2020 09:13:46 +0100 Subject: [PATCH 16/51] replace for with for...of --- src/components/guide/guide-settings.js | 48 ++++++++++++-------------- src/components/guide/guide.js | 32 ++++++++--------- 2 files changed, 38 insertions(+), 42 deletions(-) diff --git a/src/components/guide/guide-settings.js b/src/components/guide/guide-settings.js index b62604ab98..35f0d3e06e 100644 --- a/src/components/guide/guide-settings.js +++ b/src/components/guide/guide-settings.js @@ -12,10 +12,10 @@ function saveCategories(context, options) { const categories = []; const chkCategorys = context.querySelectorAll('.chkCategory'); - for (let i = 0, length = chkCategorys.length; i < length; i++) { - const type = chkCategorys[i].getAttribute('data-type'); + for (const chkCategory of chkCategorys) { + const type = chkCategory.getAttribute('data-type'); - if (chkCategorys[i].checked) { + if (chkCategory.checked) { categories.push(type); } } @@ -33,47 +33,43 @@ function loadCategories(context, options) { const selectedCategories = options.categories || []; const chkCategorys = context.querySelectorAll('.chkCategory'); - for (let i = 0, length = chkCategorys.length; i < length; i++) { - const type = chkCategorys[i].getAttribute('data-type'); + for (const chkCategory of chkCategorys) { + const type = chkCategory.getAttribute('data-type'); - chkCategorys[i].checked = !selectedCategories.length || selectedCategories.indexOf(type) !== -1; + chkCategory.checked = !selectedCategories.length || selectedCategories.indexOf(type) !== -1; } } function save(context) { - let i; - let length; - const chkIndicators = context.querySelectorAll('.chkIndicator'); - for (i = 0, length = chkIndicators.length; i < length; i++) { - const type = chkIndicators[i].getAttribute('data-type'); - userSettings.set('guide-indicator-' + type, chkIndicators[i].checked); + + for (const chkIndicator of chkIndicators) { + const type = chkIndicator.getAttribute('data-type'); + userSettings.set('guide-indicator-' + type, chkIndicator.checked); } userSettings.set('guide-colorcodedbackgrounds', context.querySelector('.chkColorCodedBackgrounds').checked); userSettings.set('livetv-favoritechannelsattop', context.querySelector('.chkFavoriteChannelsAtTop').checked); const sortBys = context.querySelectorAll('.chkSortOrder'); - for (i = 0, length = sortBys.length; i < length; i++) { - if (sortBys[i].checked) { - userSettings.set('livetv-channelorder', sortBys[i].value); + for (const sortBy of sortBys) { + if (sortBy.checked) { + userSettings.set('livetv-channelorder', sortBy.value); break; } } } function load(context) { - let i; - let length; - const chkIndicators = context.querySelectorAll('.chkIndicator'); - for (i = 0, length = chkIndicators.length; i < length; i++) { - const type = chkIndicators[i].getAttribute('data-type'); - if (chkIndicators[i].getAttribute('data-default') === 'true') { - chkIndicators[i].checked = userSettings.get('guide-indicator-' + type) !== 'false'; + for (const chkIndicator of chkIndicators) { + const type = chkIndicator.getAttribute('data-type'); + + if (chkIndicator.getAttribute('data-default') === 'true') { + chkIndicator.checked = userSettings.get('guide-indicator-' + type) !== 'false'; } else { - chkIndicators[i].checked = userSettings.get('guide-indicator-' + type) === 'true'; + chkIndicator.checked = userSettings.get('guide-indicator-' + type) === 'true'; } } @@ -83,8 +79,8 @@ function load(context) { const sortByValue = userSettings.get('livetv-channelorder') || 'Number'; const sortBys = context.querySelectorAll('.chkSortOrder'); - for (i = 0, length = sortBys.length; i < length; i++) { - sortBys[i].checked = sortBys[i].value === sortByValue; + for (const sortBy of sortBys) { + sortBy.checked = sortBy.value === sortByValue; } } @@ -92,7 +88,7 @@ function showEditor(options) { return new Promise(function (resolve, reject) { let settingsChanged = false; - import('text!./guide-settings.template.html').then(({default: template}) => { + import('text!./guide-settings.template.html').then(({ default: template }) => { const dialogOptions = { removeOnClose: true, scrollY: false diff --git a/src/components/guide/guide.js b/src/components/guide/guide.js index e48d4646c0..a7b32d887d 100644 --- a/src/components/guide/guide.js +++ b/src/components/guide/guide.js @@ -88,8 +88,8 @@ function updateProgramCellsOnScroll(programGrid, programCells) { const scrollPct = scrollLeft ? (scrollLeft / programGrid.scrollWidth) * 100 : 0; - for (let i = 0, length = programCells.length; i < length; i++) { - updateProgramCellOnScroll(programCells[i], scrollPct); + for (const programCell of programCells) { + updateProgramCellOnScroll(programCell, scrollPct); } isUpdatingProgramCellScroll = false; @@ -588,8 +588,7 @@ function Guide(options) { function renderChannelHeaders(context, channels, apiClient) { let html = ''; - for (let i = 0, length = channels.length; i < length; i++) { - const channel = channels[i]; + for (const channel of channels) { const hasChannelImage = channel.ImageTags.Primary; let cssClass = 'guide-channelHeaderCell itemAction'; @@ -641,8 +640,8 @@ function Guide(options) { const html = []; - for (let i = 0, length = channels.length; i < length; i++) { - html.push(getChannelProgramsHtml(context, date, channels[i], programs, options, listInfo)); + for (const channel of channels) { + html.push(getChannelProgramsHtml(context, date, channel, programs, options, listInfo)); } programGrid.innerHTML = html.join(''); @@ -888,11 +887,10 @@ function Guide(options) { // add 1 to avoid programs that are out of view to the left const currentScrollXPct = scrollXPct + 1; - for (let i = 0, length = elements.length; i < length; i++) { - const elem = elements[i]; - + for (const elem of elements) { let left = (elem.style.left || '').replace('%', ''); left = left ? parseFloat(left) : 0; + let width = (elem.style.width || '').replace('%', ''); width = width ? parseFloat(width) : 0; @@ -1047,9 +1045,7 @@ function Guide(options) { // find guide cells by program id, ensure timer icon const cells = options.element.querySelectorAll('.programCell[data-id="' + programId + '"]'); - for (let i = 0, length = cells.length; i < length; i++) { - const cell = cells[i]; - + for (const cell of cells) { const icon = cell.querySelector('.timerIcon'); if (!icon) { cell.querySelector('.guideProgramName').insertAdjacentHTML('beforeend', ''); @@ -1068,12 +1064,14 @@ function Guide(options) { const id = data.Id; // find guide cells by timer id, remove timer icon const cells = options.element.querySelectorAll('.programCell[data-timerid="' + id + '"]'); - for (let i = 0, length = cells.length; i < length; i++) { - const cell = cells[i]; + + for (const cell of cells) { const icon = cell.querySelector('.timerIcon'); + if (icon) { icon.parentNode.removeChild(icon); } + cell.removeAttribute('data-timerid'); } } @@ -1082,12 +1080,14 @@ function Guide(options) { const id = data.Id; // find guide cells by timer id, remove timer icon const cells = options.element.querySelectorAll('.programCell[data-seriestimerid="' + id + '"]'); - for (let i = 0, length = cells.length; i < length; i++) { - const cell = cells[i]; + + for (const cell of cells) { const icon = cell.querySelector('.seriesTimerIcon'); + if (icon) { icon.parentNode.removeChild(icon); } + cell.removeAttribute('data-seriestimerid'); } } From 3925c8eddb93854e0bce6aa8aa96bedd45e3ff2a Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 5 Aug 2020 19:25:22 +0100 Subject: [PATCH 17/51] remove empty destory --- src/plugins/htmlAudioPlayer/plugin.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/plugins/htmlAudioPlayer/plugin.js b/src/plugins/htmlAudioPlayer/plugin.js index c60349c5af..86d59aa54d 100644 --- a/src/plugins/htmlAudioPlayer/plugin.js +++ b/src/plugins/htmlAudioPlayer/plugin.js @@ -488,9 +488,6 @@ class HtmlAudioPlayer { return false; } - destroy() { - } - supports(feature) { if (!supportedFeatures) { supportedFeatures = getSupportedFeatures(); From e694f0918aa10a211a4b12a8930de0dfd4667334 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 6 Aug 2020 15:55:10 +0100 Subject: [PATCH 18/51] Migration of remotecontrol to ES6 module --- package.json | 1 + src/components/remotecontrol/remotecontrol.js | 1713 +++++++++-------- 2 files changed, 865 insertions(+), 849 deletions(-) diff --git a/package.json b/package.json index 246b4b5cf5..271fc8e601 100644 --- a/package.json +++ b/package.json @@ -154,6 +154,7 @@ "src/components/playmenu.js", "src/components/prompt/prompt.js", "src/components/refreshdialog/refreshdialog.js", + "src/components/remotecontrol/remotecontrol.js", "src/components/sanatizefilename.js", "src/components/scrollManager.js", "src/plugins/htmlVideoPlayer/plugin.js", diff --git a/src/components/remotecontrol/remotecontrol.js b/src/components/remotecontrol/remotecontrol.js index b5ac4c9a8b..b9f33b4e1a 100644 --- a/src/components/remotecontrol/remotecontrol.js +++ b/src/components/remotecontrol/remotecontrol.js @@ -1,939 +1,954 @@ -define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageLoader', 'playbackManager', 'nowPlayingHelper', 'events', 'connectionManager', 'apphost', 'globalize', 'layoutManager', 'userSettings', 'cardBuilder', 'itemContextMenu', 'cardStyle', 'emby-itemscontainer', 'css!./remotecontrol.css', 'emby-ratingbutton'], function (browser, datetime, backdrop, libraryBrowser, listView, imageLoader, playbackManager, nowPlayingHelper, events, connectionManager, appHost, globalize, layoutManager, userSettings, cardBuilder, itemContextMenu) { - 'use strict'; +import datetime from 'datetime'; +import backdrop from 'backdrop'; +import listView from 'listView'; +import imageLoader from 'imageLoader'; +import playbackManager from 'playbackManager'; +import nowPlayingHelper from 'nowPlayingHelper'; +import events from 'events'; +import connectionManager from 'connectionManager'; +import appHost from 'apphost'; +import globalize from 'globalize'; +import layoutManager from 'layoutManager'; +import * as userSettings from 'userSettings'; +import cardBuilder from 'cardBuilder'; +import itemContextMenu from 'itemContextMenu'; +import 'cardStyle'; +import 'emby-itemscontainer'; +import 'css!./remotecontrol.css'; +import 'emby-ratingbutton'; - playbackManager = playbackManager.default || playbackManager; +/*eslint prefer-const: "error"*/ - var showMuteButton = true; - var showVolumeSlider = true; +let showMuteButton = true; +let showVolumeSlider = true; - function showAudioMenu(context, player, button, item) { - var currentIndex = playbackManager.getAudioStreamIndex(player); - var streams = playbackManager.audioTracks(player); - var menuItems = streams.map(function (s) { - var menuItem = { - name: s.DisplayTitle, - id: s.Index - }; +function showAudioMenu(context, player, button, item) { + const currentIndex = playbackManager.getAudioStreamIndex(player); + const streams = playbackManager.audioTracks(player); + const menuItems = streams.map(function (s) { + const menuItem = { + name: s.DisplayTitle, + id: s.Index + }; - if (s.Index == currentIndex) { - menuItem.selected = true; - } - - return menuItem; - }); - - require(['actionsheet'], function (actionsheet) { - actionsheet.show({ - items: menuItems, - positionTo: button, - callback: function (id) { - playbackManager.setAudioStreamIndex(parseInt(id), player); - } - }); - }); - } - - function showSubtitleMenu(context, player, button, item) { - var currentIndex = playbackManager.getSubtitleStreamIndex(player); - var streams = playbackManager.subtitleTracks(player); - var menuItems = streams.map(function (s) { - var menuItem = { - name: s.DisplayTitle, - id: s.Index - }; - - if (s.Index == currentIndex) { - menuItem.selected = true; - } - - return menuItem; - }); - menuItems.unshift({ - id: -1, - name: globalize.translate('ButtonOff'), - selected: currentIndex == null - }); - - require(['actionsheet'], function (actionsheet) { - actionsheet.show({ - items: menuItems, - positionTo: button, - callback: function (id) { - playbackManager.setSubtitleStreamIndex(parseInt(id), player); - } - }); - }); - } - - function getNowPlayingNameHtml(nowPlayingItem, includeNonNameInfo) { - return nowPlayingHelper.getNowPlayingNames(nowPlayingItem, includeNonNameInfo).map(function (i) { - return i.text; - }).join('
'); - } - - function seriesImageUrl(item, options) { - if (item.Type !== 'Episode') { - return null; + if (s.Index == currentIndex) { + menuItem.selected = true; } - options = options || {}; - options.type = options.type || 'Primary'; - if (options.type === 'Primary' && item.SeriesPrimaryImageTag) { - options.tag = item.SeriesPrimaryImageTag; + return menuItem; + }); + + import('actionsheet').then(({ default: actionsheet }) => { + actionsheet.show({ + items: menuItems, + positionTo: button, + callback: function (id) { + playbackManager.setAudioStreamIndex(parseInt(id), player); + } + }); + }); +} + +function showSubtitleMenu(context, player, button, item) { + const currentIndex = playbackManager.getSubtitleStreamIndex(player); + const streams = playbackManager.subtitleTracks(player); + const menuItems = streams.map(function (s) { + const menuItem = { + name: s.DisplayTitle, + id: s.Index + }; + + if (s.Index == currentIndex) { + menuItem.selected = true; + } + + return menuItem; + }); + menuItems.unshift({ + id: -1, + name: globalize.translate('ButtonOff'), + selected: currentIndex == null + }); + + import('actionsheet').then(({ default: actionsheet }) => { + actionsheet.show({ + items: menuItems, + positionTo: button, + callback: function (id) { + playbackManager.setSubtitleStreamIndex(parseInt(id), player); + } + }); + }); +} + +function getNowPlayingNameHtml(nowPlayingItem, includeNonNameInfo) { + return nowPlayingHelper.getNowPlayingNames(nowPlayingItem, includeNonNameInfo).map(function (i) { + return i.text; + }).join('
'); +} + +function seriesImageUrl(item, options) { + if (item.Type !== 'Episode') { + return null; + } + + options = options || {}; + options.type = options.type || 'Primary'; + if (options.type === 'Primary' && item.SeriesPrimaryImageTag) { + options.tag = item.SeriesPrimaryImageTag; + return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); + } + + if (options.type === 'Thumb') { + if (item.SeriesThumbImageTag) { + options.tag = item.SeriesThumbImageTag; return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); } - if (options.type === 'Thumb') { - if (item.SeriesThumbImageTag) { - options.tag = item.SeriesThumbImageTag; - return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); - } - - if (item.ParentThumbImageTag) { - options.tag = item.ParentThumbImageTag; - return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.ParentThumbItemId, options); - } + if (item.ParentThumbImageTag) { + options.tag = item.ParentThumbImageTag; + return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.ParentThumbItemId, options); } - - return null; } - function imageUrl(item, options) { - options = options || {}; - options.type = options.type || 'Primary'; + return null; +} - if (item.ImageTags && item.ImageTags[options.type]) { - options.tag = item.ImageTags[options.type]; - return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.PrimaryImageItemId || item.Id, options); - } +function imageUrl(item, options) { + options = options || {}; + options.type = options.type || 'Primary'; - if (item.AlbumId && item.AlbumPrimaryImageTag) { - options.tag = item.AlbumPrimaryImageTag; - return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.AlbumId, options); - } - - return null; + if (item.ImageTags && item.ImageTags[options.type]) { + options.tag = item.ImageTags[options.type]; + return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.PrimaryImageItemId || item.Id, options); } - function updateNowPlayingInfo(context, state, serverId) { - var item = state.NowPlayingItem; - var displayName = item ? getNowPlayingNameHtml(item).replace('
', ' - ') : ''; - if (typeof item !== 'undefined') { - var nowPlayingServerId = (item.ServerId || serverId); - if (item.Type == 'Audio' || item.MediaStreams[0].Type == 'Audio') { - var songName = item.Name; - var artistsSeries = ''; - var albumName = ''; - if (item.Artists != null) { - if (item.ArtistItems != null) { - for (const artist of item.ArtistItems) { - let artistName = artist.Name; - let artistId = artist.Id; - artistsSeries += `${artistName}`; - if (artist !== item.ArtistItems.slice(-1)[0]) { - artistsSeries += ', '; - } + if (item.AlbumId && item.AlbumPrimaryImageTag) { + options.tag = item.AlbumPrimaryImageTag; + return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.AlbumId, options); + } + + return null; +} + +function updateNowPlayingInfo(context, state, serverId) { + const item = state.NowPlayingItem; + const displayName = item ? getNowPlayingNameHtml(item).replace('
', ' - ') : ''; + if (typeof item !== 'undefined') { + const nowPlayingServerId = (item.ServerId || serverId); + if (item.Type == 'Audio' || item.MediaStreams[0].Type == 'Audio') { + const songName = item.Name; + let artistsSeries = ''; + let albumName = ''; + if (item.Artists != null) { + if (item.ArtistItems != null) { + for (const artist of item.ArtistItems) { + const artistName = artist.Name; + const artistId = artist.Id; + artistsSeries += `${artistName}`; + if (artist !== item.ArtistItems.slice(-1)[0]) { + artistsSeries += ', '; } - } else if (item.Artists) { - // For some reason, Chromecast Player doesn't return a item.ArtistItems object, so we need to fallback - // to normal item.Artists item. - // TODO: Normalise fields returned by all the players - for (const artist of item.Artists) { - artistsSeries += `${artist}`; - if (artist !== item.Artists.slice(-1)[0]) { - artistsSeries += ', '; - } + } + } else if (item.Artists) { + // For some reason, Chromecast Player doesn't return a item.ArtistItems object, so we need to fallback + // to normal item.Artists item. + // TODO: Normalise fields returned by all the players + for (const artist of item.Artists) { + artistsSeries += `${artist}`; + if (artist !== item.Artists.slice(-1)[0]) { + artistsSeries += ', '; } } } - if (item.Album != null) { - albumName = '` + item.Album + ''; - } - context.querySelector('.nowPlayingAlbum').innerHTML = albumName; - context.querySelector('.nowPlayingArtist').innerHTML = artistsSeries; - context.querySelector('.nowPlayingSongName').innerHTML = songName; - } else if (item.Type == 'Episode') { - if (item.SeasonName != null) { - var seasonName = item.SeasonName; - context.querySelector('.nowPlayingSeason').innerHTML = '${seasonName}`; - } - if (item.SeriesName != null) { - var seriesName = item.SeriesName; - if (item.SeriesId != null) { - context.querySelector('.nowPlayingSerie').innerHTML = '${seriesName}`; - } else { - context.querySelector('.nowPlayingSerie').innerHTML = seriesName; - } - } - context.querySelector('.nowPlayingEpisode').innerHTML = item.Name; - } else { - context.querySelector('.nowPlayingPageTitle').innerHTML = displayName; } - - if (displayName.length > 0 && item.Type != 'Audio' && item.Type != 'Episode') { - context.querySelector('.nowPlayingPageTitle').classList.remove('hide'); - } else { - context.querySelector('.nowPlayingPageTitle').classList.add('hide'); + if (item.Album != null) { + albumName = '` + item.Album + ''; } - - var url = item ? seriesImageUrl(item, { - maxHeight: 300 - }) || imageUrl(item, { - maxHeight: 300 - }) : null; - - let contextButton = context.querySelector('.btnToggleContextMenu'); - // We remove the previous event listener by replacing the item in each update event - const autoFocusContextButton = document.activeElement === contextButton; - let contextButtonClone = contextButton.cloneNode(true); - contextButton.parentNode.replaceChild(contextButtonClone, contextButton); - contextButton = context.querySelector('.btnToggleContextMenu'); - if (autoFocusContextButton) { - contextButton.focus(); + context.querySelector('.nowPlayingAlbum').innerHTML = albumName; + context.querySelector('.nowPlayingArtist').innerHTML = artistsSeries; + context.querySelector('.nowPlayingSongName').innerHTML = songName; + } else if (item.Type == 'Episode') { + if (item.SeasonName != null) { + const seasonName = item.SeasonName; + context.querySelector('.nowPlayingSeason').innerHTML = '${seasonName}`; } - const stopPlayback = !!layoutManager.mobile; - var options = { - play: false, - queue: false, - stopPlayback: stopPlayback, - clearQueue: true, - openAlbum: false, - positionTo: contextButton - }; - var apiClient = connectionManager.getApiClient(item.ServerId); - apiClient.getItem(apiClient.getCurrentUserId(), item.Id).then(function (fullItem) { - apiClient.getCurrentUser().then(function (user) { - contextButton.addEventListener('click', function () { - itemContextMenu.show(Object.assign({ - item: fullItem, - user: user - }, options)); - }); - }); - }); - setImageUrl(context, state, url); - if (item) { - backdrop.setBackdrops([item]); - apiClient.getItem(apiClient.getCurrentUserId(), item.Id).then(function (fullItem) { - var userData = fullItem.UserData || {}; - var likes = userData.Likes == null ? '' : userData.Likes; - context.querySelector('.nowPlayingPageUserDataButtonsTitle').innerHTML = ''; - context.querySelector('.nowPlayingPageUserDataButtons').innerHTML = ''; - }); - } else { - backdrop.clearBackdrop(); - context.querySelector('.nowPlayingPageUserDataButtons').innerHTML = ''; - } - } - } - - function setImageUrl(context, state, url) { - var item = state.NowPlayingItem; - var imgContainer = context.querySelector('.nowPlayingPageImageContainer'); - - if (url) { - imgContainer.innerHTML = ''; - if (item.Type == 'Audio') { - context.querySelector('.nowPlayingPageImage').classList.add('nowPlayingPageImageAudio'); - context.querySelector('.nowPlayingPageImageContainer').classList.remove('nowPlayingPageImageAudio'); - } else { - context.querySelector('.nowPlayingPageImageContainer').classList.add('nowPlayingPageImagePoster'); - context.querySelector('.nowPlayingPageImage').classList.remove('nowPlayingPageImageAudio'); - } - } else { - imgContainer.innerHTML = '
'; - } - } - - function buttonVisible(btn, enabled) { - if (enabled) { - btn.classList.remove('hide'); - } else { - btn.classList.add('hide'); - } - } - - function updateSupportedCommands(context, commands) { - var all = context.querySelectorAll('.btnCommand'); - - for (var i = 0, length = all.length; i < length; i++) { - var enableButton = commands.indexOf(all[i].getAttribute('data-command')) !== -1; - all[i].disabled = !enableButton; - } - } - - return function () { - function toggleRepeat() { - switch (playbackManager.getRepeatMode()) { - case 'RepeatAll': - playbackManager.setRepeatMode('RepeatOne'); - break; - case 'RepeatOne': - playbackManager.setRepeatMode('RepeatNone'); - break; - case 'RepeatNone': - playbackManager.setRepeatMode('RepeatAll'); - } - } - - function updatePlayerState(player, context, state) { - lastPlayerState = state; - var item = state.NowPlayingItem; - var playerInfo = playbackManager.getPlayerInfo(); - var supportedCommands = playerInfo.supportedCommands; - currentPlayerSupportedCommands = supportedCommands; - var playState = state.PlayState || {}; - var isSupportedCommands = supportedCommands.includes('DisplayMessage') || supportedCommands.includes('SendString') || supportedCommands.includes('Select'); - buttonVisible(context.querySelector('.btnToggleFullscreen'), item && item.MediaType == 'Video' && supportedCommands.includes('ToggleFullscreen')); - updateAudioTracksDisplay(player, context); - updateSubtitleTracksDisplay(player, context); - - if (supportedCommands.includes('DisplayMessage') && !currentPlayer.isLocalPlayer) { - context.querySelector('.sendMessageSection').classList.remove('hide'); - } else { - context.querySelector('.sendMessageSection').classList.add('hide'); - } - - if (supportedCommands.includes('SendString') && !currentPlayer.isLocalPlayer) { - context.querySelector('.sendTextSection').classList.remove('hide'); - } else { - context.querySelector('.sendTextSection').classList.add('hide'); - } - - if (supportedCommands.includes('Select') && !currentPlayer.isLocalPlayer) { - context.querySelector('.navigationSection').classList.remove('hide'); - } else { - context.querySelector('.navigationSection').classList.add('hide'); - } - - if (isSupportedCommands && !currentPlayer.isLocalPlayer) { - context.querySelector('.remoteControlSection').classList.remove('hide'); - } else { - context.querySelector('.remoteControlSection').classList.add('hide'); - } - - buttonVisible(context.querySelector('.btnStop'), item != null); - buttonVisible(context.querySelector('.btnNextTrack'), item != null); - buttonVisible(context.querySelector('.btnPreviousTrack'), item != null); - if (layoutManager.mobile) { - buttonVisible(context.querySelector('.btnRewind'), false); - buttonVisible(context.querySelector('.btnFastForward'), false); - } else { - buttonVisible(context.querySelector('.btnRewind'), item != null); - buttonVisible(context.querySelector('.btnFastForward'), item != null); - } - var positionSlider = context.querySelector('.nowPlayingPositionSlider'); - - if (positionSlider && item && item.RunTimeTicks) { - positionSlider.setKeyboardSteps(userSettings.skipBackLength() * 1000000 / item.RunTimeTicks, - userSettings.skipForwardLength() * 1000000 / item.RunTimeTicks); - } - - if (positionSlider && !positionSlider.dragging) { - positionSlider.disabled = !playState.CanSeek; - var isProgressClear = state.MediaSource && state.MediaSource.RunTimeTicks == null; - positionSlider.setIsClear(isProgressClear); - } - - updatePlayPauseState(playState.IsPaused, item != null); - updateTimeDisplay(playState.PositionTicks, item ? item.RunTimeTicks : null); - updatePlayerVolumeState(context, playState.IsMuted, playState.VolumeLevel); - - if (item && item.MediaType == 'Video') { - context.classList.remove('hideVideoButtons'); - } else { - context.classList.add('hideVideoButtons'); - } - - updateRepeatModeDisplay(playbackManager.getRepeatMode()); - onShuffleQueueModeChange(false); - updateNowPlayingInfo(context, state); - } - - function updateAudioTracksDisplay(player, context) { - var supportedCommands = currentPlayerSupportedCommands; - buttonVisible(context.querySelector('.btnAudioTracks'), playbackManager.audioTracks(player).length > 1 && supportedCommands.indexOf('SetAudioStreamIndex') != -1); - } - - function updateSubtitleTracksDisplay(player, context) { - var supportedCommands = currentPlayerSupportedCommands; - buttonVisible(context.querySelector('.btnSubtitles'), playbackManager.subtitleTracks(player).length && supportedCommands.indexOf('SetSubtitleStreamIndex') != -1); - } - - function updateRepeatModeDisplay(repeatMode) { - var context = dlg; - let toggleRepeatButtons = context.querySelectorAll('.repeatToggleButton'); - const cssClass = 'buttonActive'; - let innHtml = ''; - let repeatOn = true; - - switch (repeatMode) { - case 'RepeatAll': - break; - case 'RepeatOne': - innHtml = ''; - break; - case 'RepeatNone': - default: - repeatOn = false; - break; - } - - for (const toggleRepeatButton of toggleRepeatButtons) { - toggleRepeatButton.classList.toggle(cssClass, repeatOn); - toggleRepeatButton.innerHTML = innHtml; - } - } - - function updatePlayerVolumeState(context, isMuted, volumeLevel) { - var view = context; - var supportedCommands = currentPlayerSupportedCommands; - - if (supportedCommands.indexOf('Mute') === -1) { - showMuteButton = false; - } - - if (supportedCommands.indexOf('SetVolume') === -1) { - showVolumeSlider = false; - } - - if (currentPlayer.isLocalPlayer && appHost.supports('physicalvolumecontrol')) { - showMuteButton = false; - showVolumeSlider = false; - } - - const buttonMute = view.querySelector('.buttonMute'); - const buttonMuteIcon = buttonMute.querySelector('.material-icons'); - - buttonMuteIcon.classList.remove('volume_off', 'volume_up'); - - if (isMuted) { - buttonMute.setAttribute('title', globalize.translate('Unmute')); - buttonMuteIcon.classList.add('volume_off'); - } else { - buttonMute.setAttribute('title', globalize.translate('Mute')); - buttonMuteIcon.classList.add('volume_up'); - } - - if (!showMuteButton && !showVolumeSlider) { - context.querySelector('.volumecontrol').classList.add('hide'); - } else { - buttonMute.classList.toggle('hide', !showMuteButton); - - var nowPlayingVolumeSlider = context.querySelector('.nowPlayingVolumeSlider'); - var nowPlayingVolumeSliderContainer = context.querySelector('.nowPlayingVolumeSliderContainer'); - - if (nowPlayingVolumeSlider) { - nowPlayingVolumeSliderContainer.classList.toggle('hide', !showVolumeSlider); - - if (!nowPlayingVolumeSlider.dragging) { - nowPlayingVolumeSlider.value = volumeLevel || 0; - } - } - } - } - - function updatePlayPauseState(isPaused, isActive) { - var context = dlg; - var btnPlayPause = context.querySelector('.btnPlayPause'); - const btnPlayPauseIcon = btnPlayPause.querySelector('.material-icons'); - - btnPlayPauseIcon.classList.remove('play_circle_filled', 'pause_circle_filled'); - btnPlayPauseIcon.classList.add(isPaused ? 'play_circle_filled' : 'pause_circle_filled'); - - buttonVisible(btnPlayPause, isActive); - } - - function updateTimeDisplay(positionTicks, runtimeTicks) { - var context = dlg; - var positionSlider = context.querySelector('.nowPlayingPositionSlider'); - - if (positionSlider && !positionSlider.dragging) { - if (runtimeTicks) { - var pct = positionTicks / runtimeTicks; - pct *= 100; - positionSlider.value = pct; + if (item.SeriesName != null) { + const seriesName = item.SeriesName; + if (item.SeriesId != null) { + context.querySelector('.nowPlayingSerie').innerHTML = '${seriesName}`; } else { - positionSlider.value = 0; + context.querySelector('.nowPlayingSerie').innerHTML = seriesName; } } - - context.querySelector('.positionTime').innerHTML = positionTicks == null ? '--:--' : datetime.getDisplayRunningTime(positionTicks); - context.querySelector('.runtime').innerHTML = runtimeTicks != null ? datetime.getDisplayRunningTime(runtimeTicks) : '--:--'; + context.querySelector('.nowPlayingEpisode').innerHTML = item.Name; + } else { + context.querySelector('.nowPlayingPageTitle').innerHTML = displayName; } - function getPlaylistItems(player) { - return playbackManager.getPlaylist(player); + if (displayName.length > 0 && item.Type != 'Audio' && item.Type != 'Episode') { + context.querySelector('.nowPlayingPageTitle').classList.remove('hide'); + } else { + context.querySelector('.nowPlayingPageTitle').classList.add('hide'); } - function loadPlaylist(context, player) { - getPlaylistItems(player).then(function (items) { - var html = ''; - let favoritesEnabled = true; - if (layoutManager.mobile) { - if (items.length > 0) { - context.querySelector('.btnTogglePlaylist').classList.remove('hide'); - } else { - context.querySelector('.btnTogglePlaylist').classList.add('hide'); - } - favoritesEnabled = false; - } + const url = item ? seriesImageUrl(item, { + maxHeight: 300 + }) || imageUrl(item, { + maxHeight: 300 + }) : null; - html += listView.getListViewHtml({ - items: items, - smallIcon: true, - action: 'setplaylistindex', - enableUserDataButtons: favoritesEnabled, - rightButtons: [{ - icon: 'remove_circle_outline', - title: globalize.translate('ButtonRemove'), - id: 'remove' - }], - dragHandle: true + let contextButton = context.querySelector('.btnToggleContextMenu'); + // We remove the previous event listener by replacing the item in each update event + const autoFocusContextButton = document.activeElement === contextButton; + const contextButtonClone = contextButton.cloneNode(true); + contextButton.parentNode.replaceChild(contextButtonClone, contextButton); + contextButton = context.querySelector('.btnToggleContextMenu'); + if (autoFocusContextButton) { + contextButton.focus(); + } + const stopPlayback = !!layoutManager.mobile; + const options = { + play: false, + queue: false, + stopPlayback: stopPlayback, + clearQueue: true, + openAlbum: false, + positionTo: contextButton + }; + const apiClient = connectionManager.getApiClient(item.ServerId); + apiClient.getItem(apiClient.getCurrentUserId(), item.Id).then(function (fullItem) { + apiClient.getCurrentUser().then(function (user) { + contextButton.addEventListener('click', function () { + itemContextMenu.show(Object.assign({ + item: fullItem, + user: user + }, options)); }); - - var itemsContainer = context.querySelector('.playlist'); - let focusedItemPlaylistId = itemsContainer.querySelector('button:focus'); - itemsContainer.innerHTML = html; - if (focusedItemPlaylistId !== null) { - focusedItemPlaylistId = focusedItemPlaylistId.getAttribute('data-playlistitemid'); - const newFocusedItem = itemsContainer.querySelector(`button[data-playlistitemid="${focusedItemPlaylistId}"]`); - if (newFocusedItem !== null) { - newFocusedItem.focus(); - } - } - - var playlistItemId = playbackManager.getCurrentPlaylistItemId(player); - - if (playlistItemId) { - var img = itemsContainer.querySelector(`.listItem[data-playlistItemId="${playlistItemId}"] .listItemImage`); - - if (img) { - img.classList.remove('lazy'); - img.classList.add('playlistIndexIndicatorImage'); - } - } - - imageLoader.lazyChildren(itemsContainer); }); + }); + setImageUrl(context, state, url); + if (item) { + backdrop.setBackdrops([item]); + apiClient.getItem(apiClient.getCurrentUserId(), item.Id).then(function (fullItem) { + const userData = fullItem.UserData || {}; + const likes = userData.Likes == null ? '' : userData.Likes; + context.querySelector('.nowPlayingPageUserDataButtonsTitle').innerHTML = ''; + context.querySelector('.nowPlayingPageUserDataButtons').innerHTML = ''; + }); + } else { + backdrop.clearBackdrop(); + context.querySelector('.nowPlayingPageUserDataButtons').innerHTML = ''; + } + } +} + +function setImageUrl(context, state, url) { + const item = state.NowPlayingItem; + const imgContainer = context.querySelector('.nowPlayingPageImageContainer'); + + if (url) { + imgContainer.innerHTML = ''; + if (item.Type == 'Audio') { + context.querySelector('.nowPlayingPageImage').classList.add('nowPlayingPageImageAudio'); + context.querySelector('.nowPlayingPageImageContainer').classList.remove('nowPlayingPageImageAudio'); + } else { + context.querySelector('.nowPlayingPageImageContainer').classList.add('nowPlayingPageImagePoster'); + context.querySelector('.nowPlayingPageImage').classList.remove('nowPlayingPageImageAudio'); + } + } else { + imgContainer.innerHTML = '
'; + } +} + +function buttonVisible(btn, enabled) { + if (enabled) { + btn.classList.remove('hide'); + } else { + btn.classList.add('hide'); + } +} + +function updateSupportedCommands(context, commands) { + const all = context.querySelectorAll('.btnCommand'); + + for (let i = 0, length = all.length; i < length; i++) { + const enableButton = commands.indexOf(all[i].getAttribute('data-command')) !== -1; + all[i].disabled = !enableButton; + } +} + +export default function () { + function toggleRepeat() { + switch (playbackManager.getRepeatMode()) { + case 'RepeatAll': + playbackManager.setRepeatMode('RepeatOne'); + break; + case 'RepeatOne': + playbackManager.setRepeatMode('RepeatNone'); + break; + case 'RepeatNone': + playbackManager.setRepeatMode('RepeatAll'); + } + } + + function updatePlayerState(player, context, state) { + lastPlayerState = state; + const item = state.NowPlayingItem; + const playerInfo = playbackManager.getPlayerInfo(); + const supportedCommands = playerInfo.supportedCommands; + currentPlayerSupportedCommands = supportedCommands; + const playState = state.PlayState || {}; + const isSupportedCommands = supportedCommands.includes('DisplayMessage') || supportedCommands.includes('SendString') || supportedCommands.includes('Select'); + buttonVisible(context.querySelector('.btnToggleFullscreen'), item && item.MediaType == 'Video' && supportedCommands.includes('ToggleFullscreen')); + updateAudioTracksDisplay(player, context); + updateSubtitleTracksDisplay(player, context); + + if (supportedCommands.includes('DisplayMessage') && !currentPlayer.isLocalPlayer) { + context.querySelector('.sendMessageSection').classList.remove('hide'); + } else { + context.querySelector('.sendMessageSection').classList.add('hide'); } - function onPlaybackStart(e, state) { - console.debug('remotecontrol event: ' + e.type); - var player = this; - onStateChanged.call(player, e, state); + if (supportedCommands.includes('SendString') && !currentPlayer.isLocalPlayer) { + context.querySelector('.sendTextSection').classList.remove('hide'); + } else { + context.querySelector('.sendTextSection').classList.add('hide'); } - function onRepeatModeChange() { - updateRepeatModeDisplay(playbackManager.getRepeatMode()); + if (supportedCommands.includes('Select') && !currentPlayer.isLocalPlayer) { + context.querySelector('.navigationSection').classList.remove('hide'); + } else { + context.querySelector('.navigationSection').classList.add('hide'); } - function onShuffleQueueModeChange(updateView = true) { - let shuffleMode = playbackManager.getQueueShuffleMode(this); - let context = dlg; - const cssClass = 'buttonActive'; - let shuffleButtons = context.querySelectorAll('.btnShuffleQueue'); + if (isSupportedCommands && !currentPlayer.isLocalPlayer) { + context.querySelector('.remoteControlSection').classList.remove('hide'); + } else { + context.querySelector('.remoteControlSection').classList.add('hide'); + } - for (let shuffleButton of shuffleButtons) { - switch (shuffleMode) { - case 'Shuffle': - shuffleButton.classList.add(cssClass); - break; - case 'Sorted': - default: - shuffleButton.classList.remove(cssClass); - break; + buttonVisible(context.querySelector('.btnStop'), item != null); + buttonVisible(context.querySelector('.btnNextTrack'), item != null); + buttonVisible(context.querySelector('.btnPreviousTrack'), item != null); + if (layoutManager.mobile) { + buttonVisible(context.querySelector('.btnRewind'), false); + buttonVisible(context.querySelector('.btnFastForward'), false); + } else { + buttonVisible(context.querySelector('.btnRewind'), item != null); + buttonVisible(context.querySelector('.btnFastForward'), item != null); + } + const positionSlider = context.querySelector('.nowPlayingPositionSlider'); + + if (positionSlider && item && item.RunTimeTicks) { + positionSlider.setKeyboardSteps(userSettings.skipBackLength() * 1000000 / item.RunTimeTicks, + userSettings.skipForwardLength() * 1000000 / item.RunTimeTicks); + } + + if (positionSlider && !positionSlider.dragging) { + positionSlider.disabled = !playState.CanSeek; + const isProgressClear = state.MediaSource && state.MediaSource.RunTimeTicks == null; + positionSlider.setIsClear(isProgressClear); + } + + updatePlayPauseState(playState.IsPaused, item != null); + updateTimeDisplay(playState.PositionTicks, item ? item.RunTimeTicks : null); + updatePlayerVolumeState(context, playState.IsMuted, playState.VolumeLevel); + + if (item && item.MediaType == 'Video') { + context.classList.remove('hideVideoButtons'); + } else { + context.classList.add('hideVideoButtons'); + } + + updateRepeatModeDisplay(playbackManager.getRepeatMode()); + onShuffleQueueModeChange(false); + updateNowPlayingInfo(context, state); + } + + function updateAudioTracksDisplay(player, context) { + const supportedCommands = currentPlayerSupportedCommands; + buttonVisible(context.querySelector('.btnAudioTracks'), playbackManager.audioTracks(player).length > 1 && supportedCommands.indexOf('SetAudioStreamIndex') != -1); + } + + function updateSubtitleTracksDisplay(player, context) { + const supportedCommands = currentPlayerSupportedCommands; + buttonVisible(context.querySelector('.btnSubtitles'), playbackManager.subtitleTracks(player).length && supportedCommands.indexOf('SetSubtitleStreamIndex') != -1); + } + + function updateRepeatModeDisplay(repeatMode) { + const context = dlg; + const toggleRepeatButtons = context.querySelectorAll('.repeatToggleButton'); + const cssClass = 'buttonActive'; + let innHtml = ''; + let repeatOn = true; + + switch (repeatMode) { + case 'RepeatAll': + break; + case 'RepeatOne': + innHtml = ''; + break; + case 'RepeatNone': + default: + repeatOn = false; + break; + } + + for (const toggleRepeatButton of toggleRepeatButtons) { + toggleRepeatButton.classList.toggle(cssClass, repeatOn); + toggleRepeatButton.innerHTML = innHtml; + } + } + + function updatePlayerVolumeState(context, isMuted, volumeLevel) { + const view = context; + const supportedCommands = currentPlayerSupportedCommands; + + if (supportedCommands.indexOf('Mute') === -1) { + showMuteButton = false; + } + + if (supportedCommands.indexOf('SetVolume') === -1) { + showVolumeSlider = false; + } + + if (currentPlayer.isLocalPlayer && appHost.supports('physicalvolumecontrol')) { + showMuteButton = false; + showVolumeSlider = false; + } + + const buttonMute = view.querySelector('.buttonMute'); + const buttonMuteIcon = buttonMute.querySelector('.material-icons'); + + buttonMuteIcon.classList.remove('volume_off', 'volume_up'); + + if (isMuted) { + buttonMute.setAttribute('title', globalize.translate('Unmute')); + buttonMuteIcon.classList.add('volume_off'); + } else { + buttonMute.setAttribute('title', globalize.translate('Mute')); + buttonMuteIcon.classList.add('volume_up'); + } + + if (!showMuteButton && !showVolumeSlider) { + context.querySelector('.volumecontrol').classList.add('hide'); + } else { + buttonMute.classList.toggle('hide', !showMuteButton); + + const nowPlayingVolumeSlider = context.querySelector('.nowPlayingVolumeSlider'); + const nowPlayingVolumeSliderContainer = context.querySelector('.nowPlayingVolumeSliderContainer'); + + if (nowPlayingVolumeSlider) { + nowPlayingVolumeSliderContainer.classList.toggle('hide', !showVolumeSlider); + + if (!nowPlayingVolumeSlider.dragging) { + nowPlayingVolumeSlider.value = volumeLevel || 0; } } - - if (updateView) { - onPlaylistUpdate(); - } } + } - function onPlaylistUpdate(e) { - loadPlaylist(dlg, this); - } + function updatePlayPauseState(isPaused, isActive) { + const context = dlg; + const btnPlayPause = context.querySelector('.btnPlayPause'); + const btnPlayPauseIcon = btnPlayPause.querySelector('.material-icons'); - function onPlaylistItemRemoved(e, info) { - var context = dlg; - if (info !== undefined) { - var playlistItemIds = info.playlistItemIds; + btnPlayPauseIcon.classList.remove('play_circle_filled', 'pause_circle_filled'); + btnPlayPauseIcon.classList.add(isPaused ? 'play_circle_filled' : 'pause_circle_filled'); - for (var i = 0, length = playlistItemIds.length; i < length; i++) { - var listItem = context.querySelector('.listItem[data-playlistItemId="' + playlistItemIds[i] + '"]'); + buttonVisible(btnPlayPause, isActive); + } - if (listItem) { - listItem.parentNode.removeChild(listItem); - } - } + function updateTimeDisplay(positionTicks, runtimeTicks) { + const context = dlg; + const positionSlider = context.querySelector('.nowPlayingPositionSlider'); + + if (positionSlider && !positionSlider.dragging) { + if (runtimeTicks) { + let pct = positionTicks / runtimeTicks; + pct *= 100; + positionSlider.value = pct; } else { - onPlaylistUpdate(); + positionSlider.value = 0; } } - function onPlaybackStopped(e, state) { - console.debug('remotecontrol event: ' + e.type); - var player = this; + context.querySelector('.positionTime').innerHTML = positionTicks == null ? '--:--' : datetime.getDisplayRunningTime(positionTicks); + context.querySelector('.runtime').innerHTML = runtimeTicks != null ? datetime.getDisplayRunningTime(runtimeTicks) : '--:--'; + } - if (!state.NextMediaType) { - updatePlayerState(player, dlg, {}); - Emby.Page.back(); + function getPlaylistItems(player) { + return playbackManager.getPlaylist(player); + } + + function loadPlaylist(context, player) { + getPlaylistItems(player).then(function (items) { + let html = ''; + let favoritesEnabled = true; + if (layoutManager.mobile) { + if (items.length > 0) { + context.querySelector('.btnTogglePlaylist').classList.remove('hide'); + } else { + context.querySelector('.btnTogglePlaylist').classList.add('hide'); + } + favoritesEnabled = false; + } + + html += listView.getListViewHtml({ + items: items, + smallIcon: true, + action: 'setplaylistindex', + enableUserDataButtons: favoritesEnabled, + rightButtons: [{ + icon: 'remove_circle_outline', + title: globalize.translate('ButtonRemove'), + id: 'remove' + }], + dragHandle: true + }); + + const itemsContainer = context.querySelector('.playlist'); + let focusedItemPlaylistId = itemsContainer.querySelector('button:focus'); + itemsContainer.innerHTML = html; + if (focusedItemPlaylistId !== null) { + focusedItemPlaylistId = focusedItemPlaylistId.getAttribute('data-playlistitemid'); + const newFocusedItem = itemsContainer.querySelector(`button[data-playlistitemid="${focusedItemPlaylistId}"]`); + if (newFocusedItem !== null) { + newFocusedItem.focus(); + } + } + + const playlistItemId = playbackManager.getCurrentPlaylistItemId(player); + + if (playlistItemId) { + const img = itemsContainer.querySelector(`.listItem[data-playlistItemId="${playlistItemId}"] .listItemImage`); + + if (img) { + img.classList.remove('lazy'); + img.classList.add('playlistIndexIndicatorImage'); + } + } + + imageLoader.lazyChildren(itemsContainer); + }); + } + + function onPlaybackStart(e, state) { + console.debug('remotecontrol event: ' + e.type); + const player = this; + onStateChanged.call(player, e, state); + } + + function onRepeatModeChange() { + updateRepeatModeDisplay(playbackManager.getRepeatMode()); + } + + function onShuffleQueueModeChange(updateView = true) { + const shuffleMode = playbackManager.getQueueShuffleMode(this); + const context = dlg; + const cssClass = 'buttonActive'; + const shuffleButtons = context.querySelectorAll('.btnShuffleQueue'); + + for (const shuffleButton of shuffleButtons) { + switch (shuffleMode) { + case 'Shuffle': + shuffleButton.classList.add(cssClass); + break; + case 'Sorted': + default: + shuffleButton.classList.remove(cssClass); + break; } } - function onPlayPauseStateChanged(e) { - updatePlayPauseState(this.paused(), true); - } - - function onStateChanged(event, state) { - var player = this; - updatePlayerState(player, dlg, state); + if (updateView) { onPlaylistUpdate(); } + } - function onTimeUpdate(e) { - var now = new Date().getTime(); + function onPlaylistUpdate(e) { + loadPlaylist(dlg, this); + } - if (!(now - lastUpdateTime < 700)) { - lastUpdateTime = now; - var player = this; - currentRuntimeTicks = playbackManager.duration(player); - updateTimeDisplay(playbackManager.currentTime(player), currentRuntimeTicks); + function onPlaylistItemRemoved(e, info) { + const context = dlg; + if (info !== undefined) { + const playlistItemIds = info.playlistItemIds; + + for (let i = 0, length = playlistItemIds.length; i < length; i++) { + const listItem = context.querySelector('.listItem[data-playlistItemId="' + playlistItemIds[i] + '"]'); + + if (listItem) { + listItem.parentNode.removeChild(listItem); + } + } + } else { + onPlaylistUpdate(); + } + } + + function onPlaybackStopped(e, state) { + console.debug('remotecontrol event: ' + e.type); + const player = this; + + if (!state.NextMediaType) { + updatePlayerState(player, dlg, {}); + Emby.Page.back(); + } + } + + function onPlayPauseStateChanged(e) { + updatePlayPauseState(this.paused(), true); + } + + function onStateChanged(event, state) { + const player = this; + updatePlayerState(player, dlg, state); + onPlaylistUpdate(); + } + + function onTimeUpdate(e) { + const now = new Date().getTime(); + + if (!(now - lastUpdateTime < 700)) { + lastUpdateTime = now; + const player = this; + currentRuntimeTicks = playbackManager.duration(player); + updateTimeDisplay(playbackManager.currentTime(player), currentRuntimeTicks); + } + } + + function onVolumeChanged(e) { + const player = this; + updatePlayerVolumeState(dlg, player.isMuted(), player.getVolume()); + } + + function releaseCurrentPlayer() { + const player = currentPlayer; + + if (player) { + events.off(player, 'playbackstart', onPlaybackStart); + events.off(player, 'statechange', onStateChanged); + events.off(player, 'repeatmodechange', onRepeatModeChange); + events.off(player, 'shufflequeuemodechange', onShuffleQueueModeChange); + events.off(player, 'playlistitemremove', onPlaylistItemRemoved); + events.off(player, 'playlistitemmove', onPlaylistUpdate); + events.off(player, 'playlistitemadd', onPlaylistUpdate); + events.off(player, 'playbackstop', onPlaybackStopped); + events.off(player, 'volumechange', onVolumeChanged); + events.off(player, 'pause', onPlayPauseStateChanged); + events.off(player, 'unpause', onPlayPauseStateChanged); + events.off(player, 'timeupdate', onTimeUpdate); + currentPlayer = null; + } + } + + function bindToPlayer(context, player) { + if (releaseCurrentPlayer(), currentPlayer = player, player) { + const state = playbackManager.getPlayerState(player); + onStateChanged.call(player, { + type: 'init' + }, state); + events.on(player, 'playbackstart', onPlaybackStart); + events.on(player, 'statechange', onStateChanged); + events.on(player, 'repeatmodechange', onRepeatModeChange); + events.on(player, 'shufflequeuemodechange', onShuffleQueueModeChange); + events.on(player, 'playlistitemremove', onPlaylistItemRemoved); + events.on(player, 'playlistitemmove', onPlaylistUpdate); + events.on(player, 'playlistitemadd', onPlaylistUpdate); + events.on(player, 'playbackstop', onPlaybackStopped); + events.on(player, 'volumechange', onVolumeChanged); + events.on(player, 'pause', onPlayPauseStateChanged); + events.on(player, 'unpause', onPlayPauseStateChanged); + events.on(player, 'timeupdate', onTimeUpdate); + const playerInfo = playbackManager.getPlayerInfo(); + const supportedCommands = playerInfo.supportedCommands; + currentPlayerSupportedCommands = supportedCommands; + updateSupportedCommands(context, supportedCommands); + } + } + + function onBtnCommandClick() { + if (currentPlayer) { + if (this.classList.contains('repeatToggleButton')) { + toggleRepeat(); + } else { + playbackManager.sendCommand({ + Name: this.getAttribute('data-command') + }, currentPlayer); } } + } - function onVolumeChanged(e) { - var player = this; - updatePlayerVolumeState(dlg, player.isMuted(), player.getVolume()); + function getSaveablePlaylistItems() { + return getPlaylistItems(currentPlayer).then(function (items) { + return items.filter(function (i) { + return i.Id && i.ServerId; + }); + }); + } + + function savePlaylist() { + import('playlistEditor').then(({ default: playlistEditor }) => { + getSaveablePlaylistItems().then(function (items) { + const serverId = items.length ? items[0].ServerId : ApiClient.serverId(); + new playlistEditor.showEditor({ + items: items.map(function (i) { + return i.Id; + }), + serverId: serverId, + enableAddToPlayQueue: false, + defaultValue: 'new' + }); + }); + }); + } + + function bindEvents(context) { + const btnCommand = context.querySelectorAll('.btnCommand'); + const positionSlider = context.querySelector('.nowPlayingPositionSlider'); + + for (let i = 0, length = btnCommand.length; i < length; i++) { + btnCommand[i].addEventListener('click', onBtnCommandClick); } - function releaseCurrentPlayer() { - var player = currentPlayer; - - if (player) { - events.off(player, 'playbackstart', onPlaybackStart); - events.off(player, 'statechange', onStateChanged); - events.off(player, 'repeatmodechange', onRepeatModeChange); - events.off(player, 'shufflequeuemodechange', onShuffleQueueModeChange); - events.off(player, 'playlistitemremove', onPlaylistItemRemoved); - events.off(player, 'playlistitemmove', onPlaylistUpdate); - events.off(player, 'playlistitemadd', onPlaylistUpdate); - events.off(player, 'playbackstop', onPlaybackStopped); - events.off(player, 'volumechange', onVolumeChanged); - events.off(player, 'pause', onPlayPauseStateChanged); - events.off(player, 'unpause', onPlayPauseStateChanged); - events.off(player, 'timeupdate', onTimeUpdate); - currentPlayer = null; - } - } - - function bindToPlayer(context, player) { - if (releaseCurrentPlayer(), currentPlayer = player, player) { - var state = playbackManager.getPlayerState(player); - onStateChanged.call(player, { - type: 'init' - }, state); - events.on(player, 'playbackstart', onPlaybackStart); - events.on(player, 'statechange', onStateChanged); - events.on(player, 'repeatmodechange', onRepeatModeChange); - events.on(player, 'shufflequeuemodechange', onShuffleQueueModeChange); - events.on(player, 'playlistitemremove', onPlaylistItemRemoved); - events.on(player, 'playlistitemmove', onPlaylistUpdate); - events.on(player, 'playlistitemadd', onPlaylistUpdate); - events.on(player, 'playbackstop', onPlaybackStopped); - events.on(player, 'volumechange', onVolumeChanged); - events.on(player, 'pause', onPlayPauseStateChanged); - events.on(player, 'unpause', onPlayPauseStateChanged); - events.on(player, 'timeupdate', onTimeUpdate); - var playerInfo = playbackManager.getPlayerInfo(); - var supportedCommands = playerInfo.supportedCommands; - currentPlayerSupportedCommands = supportedCommands; - updateSupportedCommands(context, supportedCommands); - } - } - - function onBtnCommandClick() { + context.querySelector('.btnToggleFullscreen').addEventListener('click', function (e) { if (currentPlayer) { - if (this.classList.contains('repeatToggleButton')) { - toggleRepeat(); + playbackManager.sendCommand({ + Name: e.target.getAttribute('data-command') + }, currentPlayer); + } + }); + context.querySelector('.btnAudioTracks').addEventListener('click', function (e) { + if (currentPlayer && lastPlayerState && lastPlayerState.NowPlayingItem) { + showAudioMenu(context, currentPlayer, e.target, lastPlayerState.NowPlayingItem); + } + }); + context.querySelector('.btnSubtitles').addEventListener('click', function (e) { + if (currentPlayer && lastPlayerState && lastPlayerState.NowPlayingItem) { + showSubtitleMenu(context, currentPlayer, e.target, lastPlayerState.NowPlayingItem); + } + }); + context.querySelector('.btnStop').addEventListener('click', function () { + if (currentPlayer) { + playbackManager.stop(currentPlayer); + } + }); + context.querySelector('.btnPlayPause').addEventListener('click', function () { + if (currentPlayer) { + playbackManager.playPause(currentPlayer); + } + }); + context.querySelector('.btnNextTrack').addEventListener('click', function () { + if (currentPlayer) { + playbackManager.nextTrack(currentPlayer); + } + }); + context.querySelector('.btnRewind').addEventListener('click', function () { + if (currentPlayer) { + playbackManager.rewind(currentPlayer); + } + }); + context.querySelector('.btnFastForward').addEventListener('click', function () { + if (currentPlayer) { + playbackManager.fastForward(currentPlayer); + } + }); + for (const shuffleButton of context.querySelectorAll('.btnShuffleQueue')) { + shuffleButton.addEventListener('click', function () { + if (currentPlayer) { + playbackManager.toggleQueueShuffleMode(currentPlayer); + } + }); + } + + context.querySelector('.btnPreviousTrack').addEventListener('click', function (e) { + if (currentPlayer) { + if (lastPlayerState.NowPlayingItem.MediaType === 'Audio' && (currentPlayer._currentTime >= 5 || !playbackManager.previousTrack(currentPlayer))) { + // Cancel this event if doubleclick is fired + if (e.detail > 1 && playbackManager.previousTrack(currentPlayer)) { + return; + } + playbackManager.seekPercent(0, currentPlayer); + // This is done automatically by playbackManager. However, setting this here gives instant visual feedback. + // TODO: Check why seekPercentage doesn't reflect the changes inmmediately, so we can remove this workaround. + positionSlider.value = 0; } else { - playbackManager.sendCommand({ - Name: this.getAttribute('data-command') - }, currentPlayer); - } - } - } - - function getSaveablePlaylistItems() { - return getPlaylistItems(currentPlayer).then(function (items) { - return items.filter(function (i) { - return i.Id && i.ServerId; - }); - }); - } - - function savePlaylist() { - require(['playlistEditor'], function (playlistEditor) { - getSaveablePlaylistItems().then(function (items) { - var serverId = items.length ? items[0].ServerId : ApiClient.serverId(); - new playlistEditor.showEditor({ - items: items.map(function (i) { - return i.Id; - }), - serverId: serverId, - enableAddToPlayQueue: false, - defaultValue: 'new' - }); - }); - }); - } - - function bindEvents(context) { - var btnCommand = context.querySelectorAll('.btnCommand'); - var positionSlider = context.querySelector('.nowPlayingPositionSlider'); - - for (var i = 0, length = btnCommand.length; i < length; i++) { - btnCommand[i].addEventListener('click', onBtnCommandClick); - } - - context.querySelector('.btnToggleFullscreen').addEventListener('click', function (e) { - if (currentPlayer) { - playbackManager.sendCommand({ - Name: e.target.getAttribute('data-command') - }, currentPlayer); - } - }); - context.querySelector('.btnAudioTracks').addEventListener('click', function (e) { - if (currentPlayer && lastPlayerState && lastPlayerState.NowPlayingItem) { - showAudioMenu(context, currentPlayer, e.target, lastPlayerState.NowPlayingItem); - } - }); - context.querySelector('.btnSubtitles').addEventListener('click', function (e) { - if (currentPlayer && lastPlayerState && lastPlayerState.NowPlayingItem) { - showSubtitleMenu(context, currentPlayer, e.target, lastPlayerState.NowPlayingItem); - } - }); - context.querySelector('.btnStop').addEventListener('click', function () { - if (currentPlayer) { - playbackManager.stop(currentPlayer); - } - }); - context.querySelector('.btnPlayPause').addEventListener('click', function () { - if (currentPlayer) { - playbackManager.playPause(currentPlayer); - } - }); - context.querySelector('.btnNextTrack').addEventListener('click', function () { - if (currentPlayer) { - playbackManager.nextTrack(currentPlayer); - } - }); - context.querySelector('.btnRewind').addEventListener('click', function () { - if (currentPlayer) { - playbackManager.rewind(currentPlayer); - } - }); - context.querySelector('.btnFastForward').addEventListener('click', function () { - if (currentPlayer) { - playbackManager.fastForward(currentPlayer); - } - }); - for (const shuffleButton of context.querySelectorAll('.btnShuffleQueue')) { - shuffleButton.addEventListener('click', function () { - if (currentPlayer) { - playbackManager.toggleQueueShuffleMode(currentPlayer); - } - }); - } - - context.querySelector('.btnPreviousTrack').addEventListener('click', function (e) { - if (currentPlayer) { - if (lastPlayerState.NowPlayingItem.MediaType === 'Audio' && (currentPlayer._currentTime >= 5 || !playbackManager.previousTrack(currentPlayer))) { - // Cancel this event if doubleclick is fired - if (e.detail > 1 && playbackManager.previousTrack(currentPlayer)) { - return; - } - playbackManager.seekPercent(0, currentPlayer); - // This is done automatically by playbackManager. However, setting this here gives instant visual feedback. - // TODO: Check why seekPercentage doesn't reflect the changes inmmediately, so we can remove this workaround. - positionSlider.value = 0; - } else { - playbackManager.previousTrack(currentPlayer); - } - } - }); - - context.querySelector('.btnPreviousTrack').addEventListener('dblclick', function () { - if (currentPlayer) { playbackManager.previousTrack(currentPlayer); } - }); - positionSlider.addEventListener('change', function () { - var value = this.value; + } + }); - if (currentPlayer) { - var newPercent = parseFloat(value); - playbackManager.seekPercent(newPercent, currentPlayer); - } - }); + context.querySelector('.btnPreviousTrack').addEventListener('dblclick', function () { + if (currentPlayer) { + playbackManager.previousTrack(currentPlayer); + } + }); + positionSlider.addEventListener('change', function () { + const value = this.value; - positionSlider.getBubbleText = function (value) { - var state = lastPlayerState; + if (currentPlayer) { + const newPercent = parseFloat(value); + playbackManager.seekPercent(newPercent, currentPlayer); + } + }); - if (!state || !state.NowPlayingItem || !currentRuntimeTicks) { - return '--:--'; - } + positionSlider.getBubbleText = function (value) { + const state = lastPlayerState; - var ticks = currentRuntimeTicks; - ticks /= 100; - ticks *= value; - return datetime.getDisplayRunningTime(ticks); - }; + if (!state || !state.NowPlayingItem || !currentRuntimeTicks) { + return '--:--'; + } - context.querySelector('.nowPlayingVolumeSlider').addEventListener('input', (e) => { - playbackManager.setVolume(e.target.value, currentPlayer); - }); + let ticks = currentRuntimeTicks; + ticks /= 100; + ticks *= value; + return datetime.getDisplayRunningTime(ticks); + }; - context.querySelector('.buttonMute').addEventListener('click', function () { - playbackManager.toggleMute(currentPlayer); - }); - var playlistContainer = context.querySelector('.playlist'); - playlistContainer.addEventListener('action-remove', function (e) { - playbackManager.removeFromPlaylist([e.detail.playlistItemId], currentPlayer); - }); - playlistContainer.addEventListener('itemdrop', function (e) { - var newIndex = e.detail.newIndex; - var playlistItemId = e.detail.playlistItemId; - playbackManager.movePlaylistItem(playlistItemId, newIndex, currentPlayer); - }); - context.querySelector('.btnSavePlaylist').addEventListener('click', savePlaylist); - context.querySelector('.btnTogglePlaylist').addEventListener('click', function () { - if (context.querySelector('.playlist').classList.contains('hide')) { - context.querySelector('.playlist').classList.remove('hide'); - context.querySelector('.btnSavePlaylist').classList.remove('hide'); - context.querySelector('.volumecontrol').classList.add('hide'); - if (layoutManager.mobile) { - context.querySelector('.playlistSectionButton').classList.remove('playlistSectionButtonTransparent'); - } - } else { - context.querySelector('.playlist').classList.add('hide'); - context.querySelector('.btnSavePlaylist').classList.add('hide'); - if (showMuteButton || showVolumeSlider) { - context.querySelector('.volumecontrol').classList.remove('hide'); - } - if (layoutManager.mobile) { - context.querySelector('.playlistSectionButton').classList.add('playlistSectionButtonTransparent'); - } - } - }); - } + context.querySelector('.nowPlayingVolumeSlider').addEventListener('input', (e) => { + playbackManager.setVolume(e.target.value, currentPlayer); + }); - function onPlayerChange() { - bindToPlayer(dlg, playbackManager.getCurrentPlayer()); - } - - function onMessageSubmit(e) { - var form = e.target; - playbackManager.sendCommand({ - Name: 'DisplayMessage', - Arguments: { - Header: form.querySelector('#txtMessageTitle').value, - Text: form.querySelector('#txtMessageText', form).value - } - }, currentPlayer); - form.querySelector('input').value = ''; - - require(['toast'], function (toast) { - toast('Message sent.'); - }); - - e.preventDefault(); - e.stopPropagation(); - return false; - } - - function onSendStringSubmit(e) { - var form = e.target; - playbackManager.sendCommand({ - Name: 'SendString', - Arguments: { - String: form.querySelector('#txtTypeText', form).value - } - }, currentPlayer); - form.querySelector('input').value = ''; - - require(['toast'], function (toast) { - toast('Text sent.'); - }); - - e.preventDefault(); - e.stopPropagation(); - return false; - } - - function init(ownerView, context) { - let volumecontrolHtml = '
'; - volumecontrolHtml += ``; - volumecontrolHtml += '
'; - volumecontrolHtml += '
'; - let optionsSection = context.querySelector('.playlistSectionButton'); - if (!layoutManager.mobile) { - context.querySelector('.nowPlayingSecondaryButtons').insertAdjacentHTML('beforeend', volumecontrolHtml); - optionsSection.classList.remove('align-items-center', 'justify-content-center'); - optionsSection.classList.add('align-items-right', 'justify-content-flex-end'); + context.querySelector('.buttonMute').addEventListener('click', function () { + playbackManager.toggleMute(currentPlayer); + }); + const playlistContainer = context.querySelector('.playlist'); + playlistContainer.addEventListener('action-remove', function (e) { + playbackManager.removeFromPlaylist([e.detail.playlistItemId], currentPlayer); + }); + playlistContainer.addEventListener('itemdrop', function (e) { + const newIndex = e.detail.newIndex; + const playlistItemId = e.detail.playlistItemId; + playbackManager.movePlaylistItem(playlistItemId, newIndex, currentPlayer); + }); + context.querySelector('.btnSavePlaylist').addEventListener('click', savePlaylist); + context.querySelector('.btnTogglePlaylist').addEventListener('click', function () { + if (context.querySelector('.playlist').classList.contains('hide')) { context.querySelector('.playlist').classList.remove('hide'); context.querySelector('.btnSavePlaylist').classList.remove('hide'); - context.classList.add('padded-bottom'); + context.querySelector('.volumecontrol').classList.add('hide'); + if (layoutManager.mobile) { + context.querySelector('.playlistSectionButton').classList.remove('playlistSectionButtonTransparent'); + } } else { - optionsSection.querySelector('.btnTogglePlaylist').insertAdjacentHTML('afterend', volumecontrolHtml); - optionsSection.classList.add('playlistSectionButtonTransparent'); - context.querySelector('.btnTogglePlaylist').classList.remove('hide'); - context.querySelector('.playlistSectionButton').classList.remove('justify-content-center'); - context.querySelector('.playlistSectionButton').classList.add('justify-content-space-between'); + context.querySelector('.playlist').classList.add('hide'); + context.querySelector('.btnSavePlaylist').classList.add('hide'); + if (showMuteButton || showVolumeSlider) { + context.querySelector('.volumecontrol').classList.remove('hide'); + } + if (layoutManager.mobile) { + context.querySelector('.playlistSectionButton').classList.add('playlistSectionButtonTransparent'); + } } + }); + } - bindEvents(context); - context.querySelector('.sendMessageForm').addEventListener('submit', onMessageSubmit); - context.querySelector('.typeTextForm').addEventListener('submit', onSendStringSubmit); - events.on(playbackManager, 'playerchange', onPlayerChange); + function onPlayerChange() { + bindToPlayer(dlg, playbackManager.getCurrentPlayer()); + } - if (layoutManager.tv) { - var positionSlider = context.querySelector('.nowPlayingPositionSlider'); - positionSlider.classList.add('focusable'); - positionSlider.enableKeyboardDragging(); + function onMessageSubmit(e) { + const form = e.target; + playbackManager.sendCommand({ + Name: 'DisplayMessage', + Arguments: { + Header: form.querySelector('#txtMessageTitle').value, + Text: form.querySelector('#txtMessageText', form).value } + }, currentPlayer); + form.querySelector('input').value = ''; + + import('toast').then(({ default: toast }) => { + toast('Message sent.'); + }); + + e.preventDefault(); + e.stopPropagation(); + return false; + } + + function onSendStringSubmit(e) { + const form = e.target; + playbackManager.sendCommand({ + Name: 'SendString', + Arguments: { + String: form.querySelector('#txtTypeText', form).value + } + }, currentPlayer); + form.querySelector('input').value = ''; + + import('toast').then(({ default: toast }) => { + toast('Text sent.'); + }); + + e.preventDefault(); + e.stopPropagation(); + return false; + } + + function init(ownerView, context) { + let volumecontrolHtml = '
'; + volumecontrolHtml += ``; + volumecontrolHtml += '
'; + volumecontrolHtml += '
'; + const optionsSection = context.querySelector('.playlistSectionButton'); + if (!layoutManager.mobile) { + context.querySelector('.nowPlayingSecondaryButtons').insertAdjacentHTML('beforeend', volumecontrolHtml); + optionsSection.classList.remove('align-items-center', 'justify-content-center'); + optionsSection.classList.add('align-items-right', 'justify-content-flex-end'); + context.querySelector('.playlist').classList.remove('hide'); + context.querySelector('.btnSavePlaylist').classList.remove('hide'); + context.classList.add('padded-bottom'); + } else { + optionsSection.querySelector('.btnTogglePlaylist').insertAdjacentHTML('afterend', volumecontrolHtml); + optionsSection.classList.add('playlistSectionButtonTransparent'); + context.querySelector('.btnTogglePlaylist').classList.remove('hide'); + context.querySelector('.playlistSectionButton').classList.remove('justify-content-center'); + context.querySelector('.playlistSectionButton').classList.add('justify-content-space-between'); } - function onDialogClosed(e) { - releaseCurrentPlayer(); - events.off(playbackManager, 'playerchange', onPlayerChange); - lastPlayerState = null; + bindEvents(context); + context.querySelector('.sendMessageForm').addEventListener('submit', onMessageSubmit); + context.querySelector('.typeTextForm').addEventListener('submit', onSendStringSubmit); + events.on(playbackManager, 'playerchange', onPlayerChange); + + if (layoutManager.tv) { + const positionSlider = context.querySelector('.nowPlayingPositionSlider'); + positionSlider.classList.add('focusable'); + positionSlider.enableKeyboardDragging(); } + } - function onShow(context, tab) { - bindToPlayer(context, playbackManager.getCurrentPlayer()); - } + function onDialogClosed(e) { + releaseCurrentPlayer(); + events.off(playbackManager, 'playerchange', onPlayerChange); + lastPlayerState = null; + } - var dlg; - var currentPlayer; - var lastPlayerState; - var currentPlayerSupportedCommands = []; - var lastUpdateTime = 0; - var currentRuntimeTicks = 0; - var self = this; + function onShow(context, tab) { + bindToPlayer(context, playbackManager.getCurrentPlayer()); + } - self.init = function (ownerView, context) { - dlg = context; - init(ownerView, dlg); - }; + let dlg; + let currentPlayer; + let lastPlayerState; + let currentPlayerSupportedCommands = []; + let lastUpdateTime = 0; + let currentRuntimeTicks = 0; + const self = this; - self.onShow = function () { - onShow(dlg, window.location.hash); - }; - - self.destroy = function () { - onDialogClosed(); - }; + self.init = function (ownerView, context) { + dlg = context; + init(ownerView, dlg); }; -}); + + self.onShow = function () { + onShow(dlg, window.location.hash); + }; + + self.destroy = function () { + onDialogClosed(); + }; +} From 9f46f21b7f522ca5d6bc9be39c56cc44bbe51227 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 7 Aug 2020 08:29:07 +0100 Subject: [PATCH 19/51] Migration of chromecastPlayer to ES6 module --- package.json | 1 + src/plugins/chromecastPlayer/plugin.js | 761 +++++++++++++------------ 2 files changed, 382 insertions(+), 380 deletions(-) diff --git a/package.json b/package.json index 1d7cf770d3..33be2cac8a 100644 --- a/package.json +++ b/package.json @@ -155,6 +155,7 @@ "src/components/refreshdialog/refreshdialog.js", "src/components/sanatizefilename.js", "src/components/scrollManager.js", + "src/plugins/chromecastPlayer/plugin.js", "src/plugins/htmlVideoPlayer/plugin.js", "src/components/search/searchfields.js", "src/components/search/searchresults.js", diff --git a/src/plugins/chromecastPlayer/plugin.js b/src/plugins/chromecastPlayer/plugin.js index 2741a6f0e3..88c460c444 100644 --- a/src/plugins/chromecastPlayer/plugin.js +++ b/src/plugins/chromecastPlayer/plugin.js @@ -1,66 +1,71 @@ -define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', 'globalize', 'events', 'require', 'castSenderApiLoader'], function (appSettings, userSettings, playbackManager, connectionManager, globalize, events, require, castSenderApiLoader) { - 'use strict'; +import appSettings from 'appSettings'; +import * as userSettings from 'userSettings'; +import playbackManager from 'playbackManager'; +import connectionManager from 'connectionManager'; +import globalize from 'globalize'; +import events from 'events'; +import castSenderApiLoader from 'castSenderApiLoader'; - playbackManager = playbackManager.default || playbackManager; +// Based on https://github.com/googlecast/CastVideos-chrome/blob/master/CastVideos.js - // Based on https://github.com/googlecast/CastVideos-chrome/blob/master/CastVideos.js - var currentResolve; - var currentReject; +let currentResolve; +let currentReject; - var PlayerName = 'Google Cast'; +const PlayerName = 'Google Cast'; - function sendConnectionResult(isOk) { - var resolve = currentResolve; - var reject = currentReject; +function sendConnectionResult(isOk) { + const resolve = currentResolve; + const reject = currentReject; - currentResolve = null; - currentReject = null; + currentResolve = null; + currentReject = null; - if (isOk) { - if (resolve) { - resolve(); - } + if (isOk) { + if (resolve) { + resolve(); + } + } else { + if (reject) { + reject(); } else { - if (reject) { - reject(); - } else { - playbackManager.removeActivePlayer(PlayerName); - } + playbackManager.removeActivePlayer(PlayerName); } } +} - /** - * Constants of states for Chromecast device - **/ - var DEVICE_STATE = { - 'IDLE': 0, - 'ACTIVE': 1, - 'WARNING': 2, - 'ERROR': 3 - }; +/** + * Constants of states for Chromecast device + **/ +const DEVICE_STATE = { + 'IDLE': 0, + 'ACTIVE': 1, + 'WARNING': 2, + 'ERROR': 3 +}; - /** - * Constants of states for CastPlayer - **/ - var PLAYER_STATE = { - 'IDLE': 'IDLE', - 'LOADING': 'LOADING', - 'LOADED': 'LOADED', - 'PLAYING': 'PLAYING', - 'PAUSED': 'PAUSED', - 'STOPPED': 'STOPPED', - 'SEEKING': 'SEEKING', - 'ERROR': 'ERROR' - }; +/** + * Constants of states for CastPlayer + **/ +const PLAYER_STATE = { + 'IDLE': 'IDLE', + 'LOADING': 'LOADING', + 'LOADED': 'LOADED', + 'PLAYING': 'PLAYING', + 'PAUSED': 'PAUSED', + 'STOPPED': 'STOPPED', + 'SEEKING': 'SEEKING', + 'ERROR': 'ERROR' +}; - // production version registered with google - // replace this value if you want to test changes on another instance - var applicationStable = 'F007D354'; - var applicationUnstable = '6F511C87'; +// production version registered with google +// replace this value if you want to test changes on another instance +const applicationStable = 'F007D354'; +const applicationUnstable = '6F511C87'; - var messageNamespace = 'urn:x-cast:com.connectsdk'; +const messageNamespace = 'urn:x-cast:com.connectsdk'; - var CastPlayer = function () { +class CastPlayer { + constructor() { /* device variables */ // @type {DEVICE_STATE} A state for device this.deviceState = DEVICE_STATE.IDLE; @@ -81,7 +86,7 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' this.mediaStatusUpdateHandler = this.onMediaStatusUpdate.bind(this); this.initializeCastPlayer(); - }; + } /** * Initialize Cast media player @@ -89,8 +94,8 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' * invoked once the API has finished initialization. The sessionListener and * receiverListener may be invoked at any time afterwards, and possibly more than once. */ - CastPlayer.prototype.initializeCastPlayer = function () { - var chrome = window.chrome; + initializeCastPlayer() { + const chrome = window.chrome; if (!chrome) { return; } @@ -100,35 +105,35 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' return; } - var applicationID = applicationStable; + let applicationID = applicationStable; if (userSettings.chromecastVersion() === 'unstable') { applicationID = applicationUnstable; } // request session - var sessionRequest = new chrome.cast.SessionRequest(applicationID); - var apiConfig = new chrome.cast.ApiConfig(sessionRequest, + const sessionRequest = new chrome.cast.SessionRequest(applicationID); + const apiConfig = new chrome.cast.ApiConfig(sessionRequest, this.sessionListener.bind(this), this.receiverListener.bind(this)); console.debug('chromecast.initialize'); chrome.cast.initialize(apiConfig, this.onInitSuccess.bind(this), this.errorHandler); - }; + } /** * Callback function for init success */ - CastPlayer.prototype.onInitSuccess = function () { + onInitSuccess() { this.isInitialized = true; console.debug('chromecast init success'); - }; + } /** * Generic error callback function */ - CastPlayer.prototype.onError = function () { + onError() { console.debug('chromecast error'); - }; + } /** * @param {!Object} e A new session @@ -137,7 +142,7 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' * join existing session and occur in Cast mode and media * status gets synced up with current media of the session */ - CastPlayer.prototype.sessionListener = function (e) { + sessionListener(e) { this.session = e; if (this.session) { if (this.session.media[0]) { @@ -146,24 +151,15 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' this.onSessionConnected(e); } - }; - - function alertText(text, title) { - require(['alert'], function (alert) { - alert.default({ - text: text, - title: title - }); - }); } - CastPlayer.prototype.messageListener = function (namespace, message) { + messageListener(namespace, message) { if (typeof (message) === 'string') { message = JSON.parse(message); } if (message.type === 'playbackerror') { - var errorCode = message.data; + const errorCode = message.data; setTimeout(function () { alertText(globalize.translate('MessagePlaybackError' + errorCode), globalize.translate('HeaderPlaybackError')); }, 300); @@ -174,14 +170,14 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' } else if (message.type) { events.trigger(this, message.type, [message.data]); } - }; + } /** * @param {string} e Receiver availability * This indicates availability of receivers but * does not provide a list of device IDs */ - CastPlayer.prototype.receiverListener = function (e) { + receiverListener(e) { if (e === 'available') { console.debug('chromecast receiver found'); this.hasReceivers = true; @@ -189,12 +185,12 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' console.debug('chromecast receiver list empty'); this.hasReceivers = false; } - }; + } /** * session update listener */ - CastPlayer.prototype.sessionUpdateListener = function (isAlive) { + sessionUpdateListener(isAlive) { if (isAlive) { console.debug('sessionUpdateListener: already alive'); } else { @@ -209,28 +205,28 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' sendConnectionResult(false); } - }; + } /** * Requests that a receiver application session be created or joined. By default, the SessionRequest * passed to the API at initialization time is used; this may be overridden by passing a different * session request in opt_sessionRequest. */ - CastPlayer.prototype.launchApp = function () { + launchApp() { console.debug('chromecast launching app...'); chrome.cast.requestSession(this.onRequestSessionSuccess.bind(this), this.onLaunchError.bind(this)); - }; + } /** * Callback function for request session success * @param {Object} e A chrome.cast.Session object */ - CastPlayer.prototype.onRequestSessionSuccess = function (e) { + onRequestSessionSuccess(e) { console.debug('chromecast session success: ' + e.sessionId); this.onSessionConnected(e); - }; + } - CastPlayer.prototype.onSessionConnected = function (session) { + onSessionConnected(session) { this.session = session; this.deviceState = DEVICE_STATE.ACTIVE; @@ -246,46 +242,38 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' options: {}, command: 'Identify' }); - }; - - function onVolumeUpKeyDown() { - playbackManager.volumeUp(); - } - - function onVolumeDownKeyDown() { - playbackManager.volumeDown(); } /** * session update listener */ - CastPlayer.prototype.sessionMediaListener = function (e) { + sessionMediaListener(e) { this.currentMediaSession = e; this.currentMediaSession.addUpdateListener(this.mediaStatusUpdateHandler); - }; + } /** * Callback function for launch error */ - CastPlayer.prototype.onLaunchError = function () { + onLaunchError() { console.debug('chromecast launch error'); this.deviceState = DEVICE_STATE.ERROR; sendConnectionResult(false); - }; + } /** * Stops the running receiver application associated with the session. */ - CastPlayer.prototype.stopApp = function () { + stopApp() { if (this.session) { this.session.stop(this.onStopAppSuccess.bind(this, 'Session stopped'), this.errorHandler); } - }; + } /** * Callback function for stop app success */ - CastPlayer.prototype.onStopAppSuccess = function (message) { + onStopAppSuccess(message) { console.debug(message); this.deviceState = DEVICE_STATE.IDLE; @@ -294,13 +282,13 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' document.removeEventListener('volumedownbutton', onVolumeDownKeyDown, false); this.currentMediaSession = null; - }; + } /** * Loads media into a running receiver application * @param {Number} mediaIndex An index number to indicate current media content */ - CastPlayer.prototype.loadMedia = function (options, command) { + loadMedia(options, command) { if (!this.session) { console.debug('no session'); return Promise.reject(); @@ -322,20 +310,20 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' options: options, command: command }); - }; + } - CastPlayer.prototype.sendMessage = function (message) { - var player = this; + sendMessage(message) { + const player = this; - var receiverName = null; + let receiverName = null; - var session = player.session; + const session = player.session; if (session && session.receiver && session.receiver.friendlyName) { receiverName = session.receiver.friendlyName; } - var apiClient; + let apiClient; if (message.options && message.options.ServerId) { apiClient = connectionManager.getApiClient(message.options.ServerId); } else if (message.options && message.options.items && message.options.items.length) { @@ -354,7 +342,7 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' receiverName: receiverName }); - var bitrateSetting = appSettings.maxChromecastBitrate(); + const bitrateSetting = appSettings.maxChromecastBitrate(); if (bitrateSetting) { message.maxBitrate = bitrateSetting; } @@ -365,31 +353,31 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' } return new Promise(function (resolve, reject) { - require(['./chromecastHelper'], function (chromecastHelper) { + import('./chromecastHelper').then(({ default: chromecastHelper }) => { chromecastHelper.getServerAddress(apiClient).then(function (serverAddress) { message.serverAddress = serverAddress; player.sendMessageInternal(message).then(resolve, reject); }, reject); }); }); - }; + } - CastPlayer.prototype.sendMessageInternal = function (message) { + sendMessageInternal(message) { message = JSON.stringify(message); this.session.sendMessage(messageNamespace, message, this.onPlayCommandSuccess.bind(this), this.errorHandler); return Promise.resolve(); - }; + } - CastPlayer.prototype.onPlayCommandSuccess = function () { + onPlayCommandSuccess() { console.debug('Message was sent to receiver ok.'); - }; + } /** * Callback function for loadMedia success * @param {Object} mediaSession A new media object. */ - CastPlayer.prototype.onMediaDiscovered = function (how, mediaSession) { + onMediaDiscovered(how, mediaSession) { console.debug('chromecast new media session ID:' + mediaSession.mediaSessionId + ' (' + how + ')'); this.currentMediaSession = mediaSession; @@ -402,24 +390,24 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' } this.currentMediaSession.addUpdateListener(this.mediaStatusUpdateHandler); - }; + } /** * Callback function for media status update from receiver * @param {!Boolean} e true/false */ - CastPlayer.prototype.onMediaStatusUpdate = function (e) { + onMediaStatusUpdate(e) { console.debug('chromecast updating media: ' + e); if (e === false) { this.castPlayerState = PLAYER_STATE.IDLE; } - }; + } /** * Set media volume in Cast mode * @param {Boolean} mute A boolean */ - CastPlayer.prototype.setReceiverVolume = function (mute, vol) { + setReceiverVolume(mute, vol) { if (!this.currentMediaSession) { console.debug('this.currentMediaSession is null'); return; @@ -434,142 +422,161 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' this.mediaCommandSuccessCallback.bind(this), this.errorHandler); } - }; + } /** * Mute CC */ - CastPlayer.prototype.mute = function () { + mute() { this.setReceiverVolume(true); - }; + } /** * Callback function for media command success */ - CastPlayer.prototype.mediaCommandSuccessCallback = function (info, e) { + mediaCommandSuccessCallback(info, e) { console.debug(info); - }; + } +} - function normalizeImages(state) { - if (state && state.NowPlayingItem) { - var item = state.NowPlayingItem; +function alertText(text, title) { + import('alert').then(({default: alert}) => { + alert({ + text: text, + title: title + }); + }); +} - if (!item.ImageTags || !item.ImageTags.Primary) { - if (item.PrimaryImageTag) { - item.ImageTags = item.ImageTags || {}; - item.ImageTags.Primary = item.PrimaryImageTag; - } - } - if (item.BackdropImageTag && item.BackdropItemId === item.Id) { - item.BackdropImageTags = [item.BackdropImageTag]; - } - if (item.BackdropImageTag && item.BackdropItemId !== item.Id) { - item.ParentBackdropImageTags = [item.BackdropImageTag]; - item.ParentBackdropItemId = item.BackdropItemId; +function onVolumeUpKeyDown() { + playbackManager.volumeUp(); +} + +function onVolumeDownKeyDown() { + playbackManager.volumeDown(); +} + +function normalizeImages(state) { + if (state && state.NowPlayingItem) { + const item = state.NowPlayingItem; + + if (!item.ImageTags || !item.ImageTags.Primary) { + if (item.PrimaryImageTag) { + item.ImageTags = item.ImageTags || {}; + item.ImageTags.Primary = item.PrimaryImageTag; } } + if (item.BackdropImageTag && item.BackdropItemId === item.Id) { + item.BackdropImageTags = [item.BackdropImageTag]; + } + if (item.BackdropImageTag && item.BackdropItemId !== item.Id) { + item.ParentBackdropImageTags = [item.BackdropImageTag]; + item.ParentBackdropItemId = item.BackdropItemId; + } } +} - function getItemsForPlayback(apiClient, query) { - var userId = apiClient.getCurrentUserId(); +function getItemsForPlayback(apiClient, query) { + const userId = apiClient.getCurrentUserId(); - if (query.Ids && query.Ids.split(',').length === 1) { - return apiClient.getItem(userId, query.Ids.split(',')).then(function (item) { - return { - Items: [item], - TotalRecordCount: 1 - }; - }); + if (query.Ids && query.Ids.split(',').length === 1) { + return apiClient.getItem(userId, query.Ids.split(',')).then(function (item) { + return { + Items: [item], + TotalRecordCount: 1 + }; + }); + } else { + query.Limit = query.Limit || 100; + query.ExcludeLocationTypes = 'Virtual'; + query.EnableTotalRecordCount = false; + + return apiClient.getItems(userId, query); + } +} + +function bindEventForRelay(instance, eventName) { + events.on(instance._castPlayer, eventName, function (e, data) { + console.debug('cc: ' + eventName); + const state = instance.getPlayerStateInternal(data); + + events.trigger(instance, eventName, [state]); + }); +} + +function initializeChromecast() { + const instance = this; + instance._castPlayer = new CastPlayer(); + + // To allow the native android app to override + document.dispatchEvent(new CustomEvent('chromecastloaded', { + detail: { + player: instance + } + })); + + events.on(instance._castPlayer, 'connect', function (e) { + if (currentResolve) { + sendConnectionResult(true); } else { - query.Limit = query.Limit || 100; - query.ExcludeLocationTypes = 'Virtual'; - query.EnableTotalRecordCount = false; - - return apiClient.getItems(userId, query); + playbackManager.setActivePlayer(PlayerName, instance.getCurrentTargetInfo()); } - } - function bindEventForRelay(instance, eventName) { - events.on(instance._castPlayer, eventName, function (e, data) { - console.debug('cc: ' + eventName); - var state = instance.getPlayerStateInternal(data); + console.debug('cc: connect'); + // Reset this so that statechange will fire + instance.lastPlayerData = null; + }); - events.trigger(instance, eventName, [state]); - }); - } + events.on(instance._castPlayer, 'playbackstart', function (e, data) { + console.debug('cc: playbackstart'); - function initializeChromecast() { - var instance = this; - instance._castPlayer = new CastPlayer(); + instance._castPlayer.initializeCastPlayer(); - // To allow the native android app to override - document.dispatchEvent(new CustomEvent('chromecastloaded', { - detail: { - player: instance - } - })); + const state = instance.getPlayerStateInternal(data); + events.trigger(instance, 'playbackstart', [state]); + }); - events.on(instance._castPlayer, 'connect', function (e) { - if (currentResolve) { - sendConnectionResult(true); - } else { - playbackManager.setActivePlayer(PlayerName, instance.getCurrentTargetInfo()); - } + events.on(instance._castPlayer, 'playbackstop', function (e, data) { + console.debug('cc: playbackstop'); + let state = instance.getPlayerStateInternal(data); - console.debug('cc: connect'); - // Reset this so that statechange will fire - instance.lastPlayerData = null; - }); + events.trigger(instance, 'playbackstop', [state]); - events.on(instance._castPlayer, 'playbackstart', function (e, data) { - console.debug('cc: playbackstart'); + state = instance.lastPlayerData.PlayState || {}; + const volume = state.VolumeLevel || 0.5; + const mute = state.IsMuted || false; - instance._castPlayer.initializeCastPlayer(); + // Reset this so the next query doesn't make it appear like content is playing. + instance.lastPlayerData = {}; + instance.lastPlayerData.PlayState = {}; + instance.lastPlayerData.PlayState.VolumeLevel = volume; + instance.lastPlayerData.PlayState.IsMuted = mute; + }); - var state = instance.getPlayerStateInternal(data); - events.trigger(instance, 'playbackstart', [state]); - }); + events.on(instance._castPlayer, 'playbackprogress', function (e, data) { + console.debug('cc: positionchange'); + const state = instance.getPlayerStateInternal(data); - events.on(instance._castPlayer, 'playbackstop', function (e, data) { - console.debug('cc: playbackstop'); - var state = instance.getPlayerStateInternal(data); + events.trigger(instance, 'timeupdate', [state]); + }); - events.trigger(instance, 'playbackstop', [state]); + bindEventForRelay(instance, 'timeupdate'); + bindEventForRelay(instance, 'pause'); + bindEventForRelay(instance, 'unpause'); + bindEventForRelay(instance, 'volumechange'); + bindEventForRelay(instance, 'repeatmodechange'); + bindEventForRelay(instance, 'shufflequeuemodechange'); - state = instance.lastPlayerData.PlayState || {}; - var volume = state.VolumeLevel || 0.5; - var mute = state.IsMuted || false; + events.on(instance._castPlayer, 'playstatechange', function (e, data) { + console.debug('cc: playstatechange'); + const state = instance.getPlayerStateInternal(data); - // Reset this so the next query doesn't make it appear like content is playing. - instance.lastPlayerData = {}; - instance.lastPlayerData.PlayState = {}; - instance.lastPlayerData.PlayState.VolumeLevel = volume; - instance.lastPlayerData.PlayState.IsMuted = mute; - }); + events.trigger(instance, 'pause', [state]); + }); +} - events.on(instance._castPlayer, 'playbackprogress', function (e, data) { - console.debug('cc: positionchange'); - var state = instance.getPlayerStateInternal(data); - - events.trigger(instance, 'timeupdate', [state]); - }); - - bindEventForRelay(instance, 'timeupdate'); - bindEventForRelay(instance, 'pause'); - bindEventForRelay(instance, 'unpause'); - bindEventForRelay(instance, 'volumechange'); - bindEventForRelay(instance, 'repeatmodechange'); - bindEventForRelay(instance, 'shufflequeuemodechange'); - - events.on(instance._castPlayer, 'playstatechange', function (e, data) { - console.debug('cc: playstatechange'); - var state = instance.getPlayerStateInternal(data); - - events.trigger(instance, 'pause', [state]); - }); - } - - function ChromecastPlayer() { +class ChromecastPlayer { + constructor() { // playbackManager needs this this.name = PlayerName; this.type = 'mediaplayer'; @@ -580,8 +587,8 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' castSenderApiLoader.load().then(initializeChromecast.bind(this)); } - ChromecastPlayer.prototype.tryPair = function (target) { - var castPlayer = this._castPlayer; + tryPair(target) { + const castPlayer = this._castPlayer; if (castPlayer.deviceState !== DEVICE_STATE.ACTIVE && castPlayer.isInitialized) { return new Promise(function (resolve, reject) { @@ -595,23 +602,23 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' return Promise.reject(); } - }; + } - ChromecastPlayer.prototype.getTargets = function () { - var targets = []; + getTargets() { + const targets = []; if (this._castPlayer && this._castPlayer.hasReceivers) { targets.push(this.getCurrentTargetInfo()); } return Promise.resolve(targets); - }; + } // This is a privately used method - ChromecastPlayer.prototype.getCurrentTargetInfo = function () { - var appName = null; + getCurrentTargetInfo() { + let appName = null; - var castPlayer = this._castPlayer; + const castPlayer = this._castPlayer; if (castPlayer.session && castPlayer.session.receiver && castPlayer.session.receiver.friendlyName) { appName = castPlayer.session.receiver.friendlyName; @@ -642,10 +649,10 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' 'PlayTrailers' ] }; - }; + } - ChromecastPlayer.prototype.getPlayerStateInternal = function (data) { - var triggerStateChange = false; + getPlayerStateInternal(data) { + let triggerStateChange = false; if (data && !this.lastPlayerData) { triggerStateChange = true; } @@ -662,12 +669,12 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' } return data; - }; + } - ChromecastPlayer.prototype.playWithCommand = function (options, command) { + playWithCommand(options, command) { if (!options.items) { - var apiClient = connectionManager.getApiClient(options.serverId); - var instance = this; + const apiClient = connectionManager.getApiClient(options.serverId); + const instance = this; return apiClient.getItem(apiClient.getCurrentUserId(), options.ids[0]).then(function (item) { options.items = [item]; @@ -683,9 +690,9 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' } return this._castPlayer.loadMedia(options, command); - }; + } - ChromecastPlayer.prototype.seek = function (position) { + seek(position) { position = parseInt(position); position = position / 10000000; @@ -696,55 +703,55 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' }, command: 'Seek' }); - }; + } - ChromecastPlayer.prototype.setAudioStreamIndex = function (index) { + setAudioStreamIndex(index) { this._castPlayer.sendMessage({ options: { index: index }, command: 'SetAudioStreamIndex' }); - }; + } - ChromecastPlayer.prototype.setSubtitleStreamIndex = function (index) { + setSubtitleStreamIndex(index) { this._castPlayer.sendMessage({ options: { index: index }, command: 'SetSubtitleStreamIndex' }); - }; + } - ChromecastPlayer.prototype.setMaxStreamingBitrate = function (options) { + setMaxStreamingBitrate(options) { this._castPlayer.sendMessage({ options: options, command: 'SetMaxStreamingBitrate' }); - }; + } - ChromecastPlayer.prototype.isFullscreen = function () { - var state = this.lastPlayerData || {}; + isFullscreen() { + let state = this.lastPlayerData || {}; state = state.PlayState || {}; return state.IsFullscreen; - }; + } - ChromecastPlayer.prototype.nextTrack = function () { + nextTrack() { this._castPlayer.sendMessage({ options: {}, command: 'NextTrack' }); - }; + } - ChromecastPlayer.prototype.previousTrack = function () { + previousTrack() { this._castPlayer.sendMessage({ options: {}, command: 'PreviousTrack' }); - }; + } - ChromecastPlayer.prototype.volumeDown = function () { - var vol = this._castPlayer.session.receiver.volume.level; + volumeDown() { + let vol = this._castPlayer.session.receiver.volume.level; if (vol == null) { vol = 0.5; } @@ -752,20 +759,20 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' vol = Math.max(vol, 0); this._castPlayer.session.setReceiverVolumeLevel(vol); - }; + } - ChromecastPlayer.prototype.endSession = function () { - var instance = this; + endSession() { + const instance = this; this.stop().then(function () { setTimeout(function () { instance._castPlayer.stopApp(); }, 1000); }); - }; + } - ChromecastPlayer.prototype.volumeUp = function () { - var vol = this._castPlayer.session.receiver.volume.level; + volumeUp() { + let vol = this._castPlayer.session.receiver.volume.level; if (vol == null) { vol = 0.5; } @@ -773,53 +780,53 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' vol = Math.min(vol, 1); this._castPlayer.session.setReceiverVolumeLevel(vol); - }; + } - ChromecastPlayer.prototype.setVolume = function (vol) { + setVolume(vol) { vol = Math.min(vol, 100); vol = Math.max(vol, 0); vol = vol / 100; this._castPlayer.session.setReceiverVolumeLevel(vol); - }; + } - ChromecastPlayer.prototype.unpause = function () { + unpause() { this._castPlayer.sendMessage({ options: {}, command: 'Unpause' }); - }; + } - ChromecastPlayer.prototype.playPause = function () { + playPause() { this._castPlayer.sendMessage({ options: {}, command: 'PlayPause' }); - }; + } - ChromecastPlayer.prototype.pause = function () { + pause() { this._castPlayer.sendMessage({ options: {}, command: 'Pause' }); - }; + } - ChromecastPlayer.prototype.stop = function () { + stop() { return this._castPlayer.sendMessage({ options: {}, command: 'Stop' }); - }; + } - ChromecastPlayer.prototype.displayContent = function (options) { + displayContent(options) { this._castPlayer.sendMessage({ options: options, command: 'DisplayContent' }); - }; + } - ChromecastPlayer.prototype.setMute = function (isMuted) { - var castPlayer = this._castPlayer; + setMute(isMuted) { + const castPlayer = this._castPlayer; if (isMuted) { castPlayer.sendMessage({ @@ -832,21 +839,21 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' command: 'Unmute' }); } - }; + } - ChromecastPlayer.prototype.getRepeatMode = function () { - var state = this.lastPlayerData || {}; + getRepeatMode() { + let state = this.lastPlayerData || {}; state = state.PlayState || {}; return state.RepeatMode; - }; + } - ChromecastPlayer.prototype.getQueueShuffleMode = function () { - var state = this.lastPlayerData || {}; + getQueueShuffleMode() { + let state = this.lastPlayerData || {}; state = state.PlayState || {}; return state.ShuffleMode; - }; + } - ChromecastPlayer.prototype.playTrailers = function (item) { + playTrailers(item) { this._castPlayer.sendMessage({ options: { ItemId: item.Id, @@ -854,177 +861,173 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' }, command: 'PlayTrailers' }); - }; + } - ChromecastPlayer.prototype.setRepeatMode = function (mode) { + setRepeatMode(mode) { this._castPlayer.sendMessage({ options: { RepeatMode: mode }, command: 'SetRepeatMode' }); - }; + } - ChromecastPlayer.prototype.setQueueShuffleMode = function (value) { + setQueueShuffleMode(value) { this._castPlayer.sendMessage({ options: { ShuffleMode: value }, command: 'SetShuffleQueue' }); - }; + } - ChromecastPlayer.prototype.toggleMute = function () { + toggleMute() { this._castPlayer.sendMessage({ options: {}, command: 'ToggleMute' }); - }; + } - ChromecastPlayer.prototype.audioTracks = function () { - var state = this.lastPlayerData || {}; + audioTracks() { + let state = this.lastPlayerData || {}; state = state.NowPlayingItem || {}; - var streams = state.MediaStreams || []; + const streams = state.MediaStreams || []; return streams.filter(function (s) { return s.Type === 'Audio'; }); - }; + } - ChromecastPlayer.prototype.getAudioStreamIndex = function () { - var state = this.lastPlayerData || {}; + getAudioStreamIndex() { + let state = this.lastPlayerData || {}; state = state.PlayState || {}; return state.AudioStreamIndex; - }; + } - ChromecastPlayer.prototype.subtitleTracks = function () { - var state = this.lastPlayerData || {}; + subtitleTracks() { + let state = this.lastPlayerData || {}; state = state.NowPlayingItem || {}; - var streams = state.MediaStreams || []; + const streams = state.MediaStreams || []; return streams.filter(function (s) { return s.Type === 'Subtitle'; }); - }; + } - ChromecastPlayer.prototype.getSubtitleStreamIndex = function () { - var state = this.lastPlayerData || {}; + getSubtitleStreamIndex() { + let state = this.lastPlayerData || {}; state = state.PlayState || {}; return state.SubtitleStreamIndex; - }; + } - ChromecastPlayer.prototype.getMaxStreamingBitrate = function () { - var state = this.lastPlayerData || {}; + getMaxStreamingBitrate() { + let state = this.lastPlayerData || {}; state = state.PlayState || {}; return state.MaxStreamingBitrate; - }; + } - ChromecastPlayer.prototype.getVolume = function () { - var state = this.lastPlayerData || {}; + getVolume() { + let state = this.lastPlayerData || {}; state = state.PlayState || {}; return state.VolumeLevel == null ? 100 : state.VolumeLevel; - }; + } - ChromecastPlayer.prototype.isPlaying = function () { - var state = this.lastPlayerData || {}; + isPlaying() { + const state = this.lastPlayerData || {}; return state.NowPlayingItem != null; - }; + } - ChromecastPlayer.prototype.isPlayingVideo = function () { - var state = this.lastPlayerData || {}; + isPlayingVideo() { + let state = this.lastPlayerData || {}; state = state.NowPlayingItem || {}; return state.MediaType === 'Video'; - }; + } - ChromecastPlayer.prototype.isPlayingAudio = function () { - var state = this.lastPlayerData || {}; + isPlayingAudio() { + let state = this.lastPlayerData || {}; state = state.NowPlayingItem || {}; return state.MediaType === 'Audio'; - }; + } - ChromecastPlayer.prototype.currentTime = function (val) { + currentTime(val) { if (val != null) { return this.seek(val); } - var state = this.lastPlayerData || {}; + let state = this.lastPlayerData || {}; state = state.PlayState || {}; return state.PositionTicks; - }; + } - ChromecastPlayer.prototype.duration = function () { - var state = this.lastPlayerData || {}; + duration() { + let state = this.lastPlayerData || {}; state = state.NowPlayingItem || {}; return state.RunTimeTicks; - }; + } - ChromecastPlayer.prototype.getBufferedRanges = function () { - var state = this.lastPlayerData || {}; + getBufferedRanges() { + let state = this.lastPlayerData || {}; state = state.PlayState || {}; return state.BufferedRanges || []; - }; + } - ChromecastPlayer.prototype.paused = function () { - var state = this.lastPlayerData || {}; + paused() { + let state = this.lastPlayerData || {}; state = state.PlayState || {}; return state.IsPaused; - }; + } - ChromecastPlayer.prototype.isMuted = function () { - var state = this.lastPlayerData || {}; + isMuted() { + let state = this.lastPlayerData || {}; state = state.PlayState || {}; return state.IsMuted; - }; + } - ChromecastPlayer.prototype.shuffle = function (item) { - var apiClient = connectionManager.getApiClient(item.ServerId); - var userId = apiClient.getCurrentUserId(); + shuffle(item) { + const apiClient = connectionManager.getApiClient(item.ServerId); + const userId = apiClient.getCurrentUserId(); - var instance = this; + const instance = this; apiClient.getItem(userId, item.Id).then(function (item) { instance.playWithCommand({ - items: [item] - }, 'Shuffle'); }); - }; + } - ChromecastPlayer.prototype.instantMix = function (item) { - var apiClient = connectionManager.getApiClient(item.ServerId); - var userId = apiClient.getCurrentUserId(); + instantMix(item) { + const apiClient = connectionManager.getApiClient(item.ServerId); + const userId = apiClient.getCurrentUserId(); - var instance = this; + const instance = this; apiClient.getItem(userId, item.Id).then(function (item) { instance.playWithCommand({ - items: [item] - }, 'InstantMix'); }); - }; + } - ChromecastPlayer.prototype.canPlayMediaType = function (mediaType) { + canPlayMediaType(mediaType) { mediaType = (mediaType || '').toLowerCase(); return mediaType === 'audio' || mediaType === 'video'; - }; + } - ChromecastPlayer.prototype.canQueueMediaType = function (mediaType) { + canQueueMediaType(mediaType) { return this.canPlayMediaType(mediaType); - }; + } - ChromecastPlayer.prototype.queue = function (options) { + queue(options) { this.playWithCommand(options, 'PlayLast'); - }; + } - ChromecastPlayer.prototype.queueNext = function (options) { + queueNext(options) { this.playWithCommand(options, 'PlayNext'); - }; + } - ChromecastPlayer.prototype.play = function (options) { + play(options) { if (options.items) { return this.playWithCommand(options, 'PlayNow'); } else { @@ -1032,50 +1035,48 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' throw new Error('serverId required!'); } - var instance = this; - var apiClient = connectionManager.getApiClient(options.serverId); + const instance = this; + const apiClient = connectionManager.getApiClient(options.serverId); return getItemsForPlayback(apiClient, { - Ids: options.ids.join(',') - }).then(function (result) { options.items = result.Items; return instance.playWithCommand(options, 'PlayNow'); }); } - }; + } - ChromecastPlayer.prototype.toggleFullscreen = function () { + toggleFullscreen() { // not supported - }; + } - ChromecastPlayer.prototype.beginPlayerUpdates = function () { + beginPlayerUpdates() { // Setup polling here - }; + } - ChromecastPlayer.prototype.endPlayerUpdates = function () { + endPlayerUpdates() { // Stop polling here - }; + } - ChromecastPlayer.prototype.getPlaylist = function () { + getPlaylist() { return Promise.resolve([]); - }; + } - ChromecastPlayer.prototype.getCurrentPlaylistItemId = function () { - }; + getCurrentPlaylistItemId() { + } - ChromecastPlayer.prototype.setCurrentPlaylistItem = function (playlistItemId) { + setCurrentPlaylistItem(playlistItemId) { return Promise.resolve(); - }; + } - ChromecastPlayer.prototype.removeFromPlaylist = function (playlistItemIds) { + removeFromPlaylist(playlistItemIds) { return Promise.resolve(); - }; + } - ChromecastPlayer.prototype.getPlayerState = function () { + getPlayerState() { return this.getPlayerStateInternal() || {}; - }; + } +} - return ChromecastPlayer; -}); +export default ChromecastPlayer; From 60dd3d009e245eb9318b468c347eef5b310f785b Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 7 Aug 2020 08:42:34 +0100 Subject: [PATCH 20/51] remove require --- src/components/imageDownloader/imageDownloader.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/imageDownloader/imageDownloader.js b/src/components/imageDownloader/imageDownloader.js index 3a5f2f56ce..3f78a5ecbb 100644 --- a/src/components/imageDownloader/imageDownloader.js +++ b/src/components/imageDownloader/imageDownloader.js @@ -8,7 +8,6 @@ import browser from 'browser'; import layoutManager from 'layoutManager'; import scrollHelper from 'scrollHelper'; import globalize from 'globalize'; -import require from 'require'; import 'emby-checkbox'; import 'paper-icon-button-light'; import 'emby-button'; From 2d72645aad0ae9dfa94fa9d7e32333d522129499 Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Sat, 8 Aug 2020 03:15:57 +0200 Subject: [PATCH 21/51] Move HTML files to the proper locations --- package.json | 2 +- src/{ => controllers/dashboard}/apikeys.html | 0 .../dashboard}/dashboard.html | 0 .../dashboard/devices}/device.html | 0 .../dashboard/devices}/devices.html | 0 .../dashboard/dlna/profile.html} | 0 .../dashboard/dlna/profiles.html} | 0 .../dashboard/dlna/settings.html} | 0 .../dashboard}/encodingsettings.html | 0 .../dashboard/general.html} | 0 src/{ => controllers/dashboard}/library.html | 0 .../dashboard/{mediaLibrary.js => library.js} | 0 .../dashboard}/librarydisplay.html | 0 .../dashboard/logs.html} | 0 .../dashboard}/metadataimages.html | 0 .../dashboard}/metadatanfo.html | 0 .../dashboard}/networking.html | 0 .../dashboard/playback.html} | 0 .../scheduledtasks}/scheduledtask.html | 0 .../scheduledtasks}/scheduledtasks.html | 0 .../dashboard}/serveractivity.html | 0 .../dashboard/streaming.html} | 0 .../dashboard/users}/useredit.html | 0 .../dashboard/users}/userlibraryaccess.html | 0 .../dashboard/users}/usernew.html | 0 .../dashboard/users}/userparentalcontrol.html | 0 .../dashboard/users}/userpassword.html | 0 .../dashboard/users}/userprofiles.html | 0 src/{ => controllers}/edititemmetadata.html | 0 src/{ => controllers}/home.html | 0 src/{ => controllers}/list.html | 0 src/{ => controllers}/livetv.html | 0 .../livetvguideprovider.html | 0 src/{ => controllers}/livetvsettings.html | 0 src/{ => controllers}/livetvstatus.html | 0 src/{ => controllers}/livetvtuner.html | 0 src/{ => controllers/movies}/movies.html | 0 src/{ => controllers/music}/music.html | 0 src/{ => controllers}/search.html | 0 .../shows/tvrecommended.html} | 0 .../wizard/library.html} | 0 src/index.html | 3 +- src/scripts/routes.js | 131 ++++++++++++------ 43 files changed, 87 insertions(+), 49 deletions(-) rename src/{ => controllers/dashboard}/apikeys.html (100%) rename src/{ => controllers/dashboard}/dashboard.html (100%) rename src/{ => controllers/dashboard/devices}/device.html (100%) rename src/{ => controllers/dashboard/devices}/devices.html (100%) rename src/{dlnaprofile.html => controllers/dashboard/dlna/profile.html} (100%) rename src/{dlnaprofiles.html => controllers/dashboard/dlna/profiles.html} (100%) rename src/{dlnasettings.html => controllers/dashboard/dlna/settings.html} (100%) rename src/{ => controllers/dashboard}/encodingsettings.html (100%) rename src/{dashboardgeneral.html => controllers/dashboard/general.html} (100%) rename src/{ => controllers/dashboard}/library.html (100%) rename src/controllers/dashboard/{mediaLibrary.js => library.js} (100%) rename src/{ => controllers/dashboard}/librarydisplay.html (100%) rename src/{log.html => controllers/dashboard/logs.html} (100%) rename src/{ => controllers/dashboard}/metadataimages.html (100%) rename src/{ => controllers/dashboard}/metadatanfo.html (100%) rename src/{ => controllers/dashboard}/networking.html (100%) rename src/{playbackconfiguration.html => controllers/dashboard/playback.html} (100%) rename src/{ => controllers/dashboard/scheduledtasks}/scheduledtask.html (100%) rename src/{ => controllers/dashboard/scheduledtasks}/scheduledtasks.html (100%) rename src/{ => controllers/dashboard}/serveractivity.html (100%) rename src/{streamingsettings.html => controllers/dashboard/streaming.html} (100%) rename src/{ => controllers/dashboard/users}/useredit.html (100%) rename src/{ => controllers/dashboard/users}/userlibraryaccess.html (100%) rename src/{ => controllers/dashboard/users}/usernew.html (100%) rename src/{ => controllers/dashboard/users}/userparentalcontrol.html (100%) rename src/{ => controllers/dashboard/users}/userpassword.html (100%) rename src/{ => controllers/dashboard/users}/userprofiles.html (100%) rename src/{ => controllers}/edititemmetadata.html (100%) rename src/{ => controllers}/home.html (100%) rename src/{ => controllers}/list.html (100%) rename src/{ => controllers}/livetv.html (100%) rename src/{ => controllers}/livetvguideprovider.html (100%) rename src/{ => controllers}/livetvsettings.html (100%) rename src/{ => controllers}/livetvstatus.html (100%) rename src/{ => controllers}/livetvtuner.html (100%) rename src/{ => controllers/movies}/movies.html (100%) rename src/{ => controllers/music}/music.html (100%) rename src/{ => controllers}/search.html (100%) rename src/{tv.html => controllers/shows/tvrecommended.html} (100%) rename src/{wizardlibrary.html => controllers/wizard/library.html} (100%) diff --git a/package.json b/package.json index ac2ad86e57..21b8ffbbd8 100644 --- a/package.json +++ b/package.json @@ -198,7 +198,7 @@ "src/controllers/music/musicplaylists.js", "src/controllers/music/musicrecommended.js", "src/controllers/music/songs.js", - "src/controllers/dashboard/mediaLibrary.js", + "src/controllers/dashboard/library.js", "src/controllers/dashboard/metadataImages.js", "src/controllers/dashboard/metadatanfo.js", "src/controllers/dashboard/networking.js", diff --git a/src/apikeys.html b/src/controllers/dashboard/apikeys.html similarity index 100% rename from src/apikeys.html rename to src/controllers/dashboard/apikeys.html diff --git a/src/dashboard.html b/src/controllers/dashboard/dashboard.html similarity index 100% rename from src/dashboard.html rename to src/controllers/dashboard/dashboard.html diff --git a/src/device.html b/src/controllers/dashboard/devices/device.html similarity index 100% rename from src/device.html rename to src/controllers/dashboard/devices/device.html diff --git a/src/devices.html b/src/controllers/dashboard/devices/devices.html similarity index 100% rename from src/devices.html rename to src/controllers/dashboard/devices/devices.html diff --git a/src/dlnaprofile.html b/src/controllers/dashboard/dlna/profile.html similarity index 100% rename from src/dlnaprofile.html rename to src/controllers/dashboard/dlna/profile.html diff --git a/src/dlnaprofiles.html b/src/controllers/dashboard/dlna/profiles.html similarity index 100% rename from src/dlnaprofiles.html rename to src/controllers/dashboard/dlna/profiles.html diff --git a/src/dlnasettings.html b/src/controllers/dashboard/dlna/settings.html similarity index 100% rename from src/dlnasettings.html rename to src/controllers/dashboard/dlna/settings.html diff --git a/src/encodingsettings.html b/src/controllers/dashboard/encodingsettings.html similarity index 100% rename from src/encodingsettings.html rename to src/controllers/dashboard/encodingsettings.html diff --git a/src/dashboardgeneral.html b/src/controllers/dashboard/general.html similarity index 100% rename from src/dashboardgeneral.html rename to src/controllers/dashboard/general.html diff --git a/src/library.html b/src/controllers/dashboard/library.html similarity index 100% rename from src/library.html rename to src/controllers/dashboard/library.html diff --git a/src/controllers/dashboard/mediaLibrary.js b/src/controllers/dashboard/library.js similarity index 100% rename from src/controllers/dashboard/mediaLibrary.js rename to src/controllers/dashboard/library.js diff --git a/src/librarydisplay.html b/src/controllers/dashboard/librarydisplay.html similarity index 100% rename from src/librarydisplay.html rename to src/controllers/dashboard/librarydisplay.html diff --git a/src/log.html b/src/controllers/dashboard/logs.html similarity index 100% rename from src/log.html rename to src/controllers/dashboard/logs.html diff --git a/src/metadataimages.html b/src/controllers/dashboard/metadataimages.html similarity index 100% rename from src/metadataimages.html rename to src/controllers/dashboard/metadataimages.html diff --git a/src/metadatanfo.html b/src/controllers/dashboard/metadatanfo.html similarity index 100% rename from src/metadatanfo.html rename to src/controllers/dashboard/metadatanfo.html diff --git a/src/networking.html b/src/controllers/dashboard/networking.html similarity index 100% rename from src/networking.html rename to src/controllers/dashboard/networking.html diff --git a/src/playbackconfiguration.html b/src/controllers/dashboard/playback.html similarity index 100% rename from src/playbackconfiguration.html rename to src/controllers/dashboard/playback.html diff --git a/src/scheduledtask.html b/src/controllers/dashboard/scheduledtasks/scheduledtask.html similarity index 100% rename from src/scheduledtask.html rename to src/controllers/dashboard/scheduledtasks/scheduledtask.html diff --git a/src/scheduledtasks.html b/src/controllers/dashboard/scheduledtasks/scheduledtasks.html similarity index 100% rename from src/scheduledtasks.html rename to src/controllers/dashboard/scheduledtasks/scheduledtasks.html diff --git a/src/serveractivity.html b/src/controllers/dashboard/serveractivity.html similarity index 100% rename from src/serveractivity.html rename to src/controllers/dashboard/serveractivity.html diff --git a/src/streamingsettings.html b/src/controllers/dashboard/streaming.html similarity index 100% rename from src/streamingsettings.html rename to src/controllers/dashboard/streaming.html diff --git a/src/useredit.html b/src/controllers/dashboard/users/useredit.html similarity index 100% rename from src/useredit.html rename to src/controllers/dashboard/users/useredit.html diff --git a/src/userlibraryaccess.html b/src/controllers/dashboard/users/userlibraryaccess.html similarity index 100% rename from src/userlibraryaccess.html rename to src/controllers/dashboard/users/userlibraryaccess.html diff --git a/src/usernew.html b/src/controllers/dashboard/users/usernew.html similarity index 100% rename from src/usernew.html rename to src/controllers/dashboard/users/usernew.html diff --git a/src/userparentalcontrol.html b/src/controllers/dashboard/users/userparentalcontrol.html similarity index 100% rename from src/userparentalcontrol.html rename to src/controllers/dashboard/users/userparentalcontrol.html diff --git a/src/userpassword.html b/src/controllers/dashboard/users/userpassword.html similarity index 100% rename from src/userpassword.html rename to src/controllers/dashboard/users/userpassword.html diff --git a/src/userprofiles.html b/src/controllers/dashboard/users/userprofiles.html similarity index 100% rename from src/userprofiles.html rename to src/controllers/dashboard/users/userprofiles.html diff --git a/src/edititemmetadata.html b/src/controllers/edititemmetadata.html similarity index 100% rename from src/edititemmetadata.html rename to src/controllers/edititemmetadata.html diff --git a/src/home.html b/src/controllers/home.html similarity index 100% rename from src/home.html rename to src/controllers/home.html diff --git a/src/list.html b/src/controllers/list.html similarity index 100% rename from src/list.html rename to src/controllers/list.html diff --git a/src/livetv.html b/src/controllers/livetv.html similarity index 100% rename from src/livetv.html rename to src/controllers/livetv.html diff --git a/src/livetvguideprovider.html b/src/controllers/livetvguideprovider.html similarity index 100% rename from src/livetvguideprovider.html rename to src/controllers/livetvguideprovider.html diff --git a/src/livetvsettings.html b/src/controllers/livetvsettings.html similarity index 100% rename from src/livetvsettings.html rename to src/controllers/livetvsettings.html diff --git a/src/livetvstatus.html b/src/controllers/livetvstatus.html similarity index 100% rename from src/livetvstatus.html rename to src/controllers/livetvstatus.html diff --git a/src/livetvtuner.html b/src/controllers/livetvtuner.html similarity index 100% rename from src/livetvtuner.html rename to src/controllers/livetvtuner.html diff --git a/src/movies.html b/src/controllers/movies/movies.html similarity index 100% rename from src/movies.html rename to src/controllers/movies/movies.html diff --git a/src/music.html b/src/controllers/music/music.html similarity index 100% rename from src/music.html rename to src/controllers/music/music.html diff --git a/src/search.html b/src/controllers/search.html similarity index 100% rename from src/search.html rename to src/controllers/search.html diff --git a/src/tv.html b/src/controllers/shows/tvrecommended.html similarity index 100% rename from src/tv.html rename to src/controllers/shows/tvrecommended.html diff --git a/src/wizardlibrary.html b/src/controllers/wizard/library.html similarity index 100% rename from src/wizardlibrary.html rename to src/controllers/wizard/library.html diff --git a/src/index.html b/src/index.html index 797fce8a94..c689a42f30 100644 --- a/src/index.html +++ b/src/index.html @@ -137,8 +137,7 @@ } @media screen - and (min-device-width: 992px) - and (-webkit-min-device-pixel-ratio: 1) { + and (min-device-width: 992px) { .splashLogo { background-image: url(assets/img/banner-light.png); } diff --git a/src/scripts/routes.js b/src/scripts/routes.js index 4094a2552f..4bbd5cff34 100644 --- a/src/scripts/routes.js +++ b/src/scripts/routes.js @@ -112,54 +112,69 @@ import 'detailtablecss'; }); defineRoute({ - path: '/dashboard.html', + alias: '/dashboard.html', + path: '/controllers/dashboard/dashboard.html', autoFocus: false, roles: 'admin', controller: 'dashboard/dashboard' }); defineRoute({ - path: '/dashboardgeneral.html', + alias: '/dashboardgeneral.html', + path: '/controllers/dashboard/general.html', controller: 'dashboard/general', autoFocus: false, roles: 'admin' }); defineRoute({ - path: '/networking.html', + alias: '/networking.html', + path: '/controllers/dashboard/networking.html', autoFocus: false, roles: 'admin', controller: 'dashboard/networking' }); defineRoute({ - path: '/devices.html', + alias: '/devices.html', + path: '/controllers/dashboard/devices/devices.html', autoFocus: false, roles: 'admin', controller: 'dashboard/devices/devices' }); defineRoute({ - path: '/device.html', + alias: '/device.html', + path: '/controllers/dashboard/devices/device.html', autoFocus: false, roles: 'admin', controller: 'dashboard/devices/device' }); defineRoute({ - path: '/dlnaprofile.html', + alias: '/dlnaprofile.html', + path: '/controllers/dashboard/dlna/profile.html', autoFocus: false, roles: 'admin', controller: 'dashboard/dlna/profile' }); defineRoute({ - path: '/dlnaprofiles.html', + alias: '/dlnaprofiles.html', + path: '/controllers/dashboard/dlna/profiles.html', autoFocus: false, roles: 'admin', controller: 'dashboard/dlna/profiles' }); + defineRoute({ + alias: '/dlnasettings.html', + path: '/controllers/dashboard/dlna/settings.html', + autoFocus: false, + roles: 'admin', + controller: 'dashboard/dlna/settings' + }); + defineRoute({ alias: '/addplugin.html', path: '/controllers/dashboard/plugins/add/index.html', @@ -169,54 +184,54 @@ import 'detailtablecss'; }); defineRoute({ - path: '/library.html', + alias: '/library.html', + path: '/controllers/dashboard/library.html', autoFocus: false, roles: 'admin', - controller: 'dashboard/mediaLibrary' + controller: 'dashboard/library' }); defineRoute({ - path: '/librarydisplay.html', + alias: '/librarydisplay.html', + path: '/controllers/dashboard/librarydisplay.html', autoFocus: false, roles: 'admin', controller: 'dashboard/librarydisplay' }); defineRoute({ - path: '/dlnasettings.html', - autoFocus: false, - roles: 'admin', - controller: 'dashboard/dlna/settings' - }); - - defineRoute({ - path: '/edititemmetadata.html', + alias: '/edititemmetadata.html', + path: '/controllers/edititemmetadata.html', controller: 'edititemmetadata', autoFocus: false }); defineRoute({ - path: '/encodingsettings.html', + alias: '/encodingsettings.html', + path: '/controllers/dashboard/encodingsettings.html', autoFocus: false, roles: 'admin', controller: 'dashboard/encodingsettings' }); defineRoute({ - path: '/log.html', + alias: '/log.html', + path: '/controllers/dashboard/logs.html', roles: 'admin', controller: 'dashboard/logs' }); defineRoute({ - path: '/metadataimages.html', + alias: '/metadataimages.html', + path: '/controllers/dashboard/metadataimages.html', autoFocus: false, roles: 'admin', controller: 'dashboard/metadataImages' }); defineRoute({ - path: '/metadatanfo.html', + alias: '/metadatanfo.html', + path: '/controllers/dashboard/metadatanfo.html', autoFocus: false, roles: 'admin', controller: 'dashboard/metadatanfo' @@ -240,6 +255,7 @@ import 'detailtablecss'; defineRoute({ path: '/playbackconfiguration.html', + path: '/controllers/dashboard/playback.html', autoFocus: false, roles: 'admin', controller: 'dashboard/playback' @@ -262,19 +278,22 @@ import 'detailtablecss'; }); defineRoute({ - path: '/home.html', + alias: '/home.html', + path: '/controllers/home.html', autoFocus: false, controller: 'home', type: 'home' }); defineRoute({ - path: '/search.html', + alias: '/search.html', + path: '/controllers/search.html', controller: 'searchpage' }); defineRoute({ - path: '/list.html', + alias: '/list.html', + path: '/controllers/list.html', autoFocus: false, controller: 'list' }); @@ -287,46 +306,53 @@ import 'detailtablecss'; }); defineRoute({ - path: '/livetv.html', + alias: '/livetv.html', + path: '/controllers/livetv.html', controller: 'livetv/livetvsuggested', autoFocus: false }); defineRoute({ - path: '/livetvguideprovider.html', + alias: '/livetvguideprovider.html', + path: '/controllers/livetvguideprovider.html', autoFocus: false, roles: 'admin', controller: 'livetvguideprovider' }); defineRoute({ - path: '/livetvsettings.html', + alias: '/livetvsettings.html', + path: '/controllers/livetvsettings.html', autoFocus: false, controller: 'livetvsettings' }); defineRoute({ - path: '/livetvstatus.html', + alias: '/livetvstatus.html', + path: '/controllers/livetvstatus.html', autoFocus: false, roles: 'admin', controller: 'livetvstatus' }); defineRoute({ - path: '/livetvtuner.html', + alias: '/livetvtuner.html', + path: '/controllers/livetvtuner.html', autoFocus: false, roles: 'admin', controller: 'livetvtuner' }); defineRoute({ - path: '/movies.html', + alias: '/movies.html', + path: '/controllers/movies/movies.html', autoFocus: false, controller: 'movies/moviesrecommended' }); defineRoute({ - path: '/music.html', + alias: '/music.html', + path: '/controllers/music/music.html', controller: 'music/musicrecommended', autoFocus: false }); @@ -340,82 +366,94 @@ import 'detailtablecss'; }); defineRoute({ - path: '/scheduledtask.html', + alias: '/scheduledtask.html', + path: '/controllers/dashboard/scheduledtasks/scheduledtask.html', autoFocus: false, roles: 'admin', controller: 'dashboard/scheduledtasks/scheduledtask' }); defineRoute({ - path: '/scheduledtasks.html', + alias: '/scheduledtasks.html', + path: '/controllers/dashboard/scheduledtasks/scheduledtasks.html', autoFocus: false, roles: 'admin', controller: 'dashboard/scheduledtasks/scheduledtasks' }); defineRoute({ - path: '/serveractivity.html', + alias: '/serveractivity.html', + path: '/controllers/dashboard/serveractivity.html', autoFocus: false, roles: 'admin', controller: 'dashboard/serveractivity' }); defineRoute({ - path: '/apikeys.html', + alias: '/apikeys.html', + path: '/controllers/dashboard/apikeys.html', autoFocus: false, roles: 'admin', controller: 'dashboard/apikeys' }); defineRoute({ - path: '/streamingsettings.html', + alias: '/streamingsettings.html', + path: '/controllers/dashboard/streaming.html', autoFocus: false, roles: 'admin', controller: 'dashboard/streaming' }); defineRoute({ - path: '/tv.html', + alias: '/tv.html', + path: '/controllers/shows/tvrecommended.html', autoFocus: false, controller: 'shows/tvrecommended' }); defineRoute({ - path: '/useredit.html', + alias: '/useredit.html', + path: '/controllers/dashboard/users/useredit.html', autoFocus: false, roles: 'admin', controller: 'dashboard/users/useredit' }); defineRoute({ - path: '/userlibraryaccess.html', + alias: '/userlibraryaccess.html', + path: '/controllers/dashboard/users/userlibraryaccess.html', autoFocus: false, roles: 'admin', controller: 'dashboard/users/userlibraryaccess' }); defineRoute({ - path: '/usernew.html', + alias: '/usernew.html', + path: '/controllers/dashboard/users/usernew.html', autoFocus: false, roles: 'admin', controller: 'dashboard/users/usernew' }); defineRoute({ - path: '/userparentalcontrol.html', + alias: '/userparentalcontrol.html', + path: '/controllers/dashboard/users/userparentalcontrol.html', autoFocus: false, roles: 'admin', controller: 'dashboard/users/userparentalcontrol' }); defineRoute({ - path: '/userpassword.html', + alias: '/userpassword.html', + path: '/controllers/dashboard/users/userpassword.html', autoFocus: false, controller: 'dashboard/users/userpasswordpage' }); defineRoute({ - path: '/userprofiles.html', + alias: '/userprofiles.html', + path: '/controllers/dashboard/users/userprofiles.html', autoFocus: false, roles: 'admin', controller: 'dashboard/users/userprofilespage' @@ -438,10 +476,11 @@ import 'detailtablecss'; }); defineRoute({ - path: '/wizardlibrary.html', + alias: '/wizardlibrary.html', + path: '/controllers/wizard/library.html', autoFocus: false, anonymous: true, - controller: 'dashboard/mediaLibrary' + controller: 'dashboard/library' }); defineRoute({ From 6b655e57ac334764f2e9d5aa263dbe851f9414b9 Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Sat, 8 Aug 2020 05:31:07 +0200 Subject: [PATCH 22/51] Fix lint --- src/scripts/routes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripts/routes.js b/src/scripts/routes.js index 4bbd5cff34..4bb3eb25d9 100644 --- a/src/scripts/routes.js +++ b/src/scripts/routes.js @@ -254,7 +254,7 @@ import 'detailtablecss'; }); defineRoute({ - path: '/playbackconfiguration.html', + alias: '/playbackconfiguration.html', path: '/controllers/dashboard/playback.html', autoFocus: false, roles: 'admin', From 23be95e450d4873eb5a938cfe3884ec60f083249 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 8 Aug 2020 17:51:10 +0100 Subject: [PATCH 23/51] Update src/components/remotecontrol/remotecontrol.js Co-authored-by: Dmitry Lyzo <56478732+dmitrylyzo@users.noreply.github.com> --- src/components/remotecontrol/remotecontrol.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/remotecontrol/remotecontrol.js b/src/components/remotecontrol/remotecontrol.js index b9f33b4e1a..6048c918c7 100644 --- a/src/components/remotecontrol/remotecontrol.js +++ b/src/components/remotecontrol/remotecontrol.js @@ -693,7 +693,7 @@ export default function () { import('playlistEditor').then(({ default: playlistEditor }) => { getSaveablePlaylistItems().then(function (items) { const serverId = items.length ? items[0].ServerId : ApiClient.serverId(); - new playlistEditor.showEditor({ + new playlistEditor({ items: items.map(function (i) { return i.Id; }), From be3dc8c697c2c34d6e8b2c0d046a6a59799eb7b2 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 8 Aug 2020 18:41:23 +0100 Subject: [PATCH 24/51] Update src/controllers/home.js Co-authored-by: Dmitry Lyzo <56478732+dmitrylyzo@users.noreply.github.com> --- src/controllers/home.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/home.js b/src/controllers/home.js index 1a4b315b09..72e326e46b 100644 --- a/src/controllers/home.js +++ b/src/controllers/home.js @@ -56,7 +56,7 @@ class HomeView extends TabbedView { let controller = instance.tabControllers[index]; if (!controller) { - controller = new controllerFactory.default(instance.view.querySelector(".tabContent[data-index='" + index + "']"), instance.params); + controller = new controllerFactory(instance.view.querySelector(".tabContent[data-index='" + index + "']"), instance.params); instance.tabControllers[index] = controller; } From c1a988f2c7e8a0bc70c3d2ec5af96ca11b1c240c Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 8 Aug 2020 19:23:56 +0100 Subject: [PATCH 25/51] add default --- src/controllers/livetv/livetvsuggested.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/controllers/livetv/livetvsuggested.js b/src/controllers/livetv/livetvsuggested.js index 79d9aac6e9..fdc6037850 100644 --- a/src/controllers/livetv/livetvsuggested.js +++ b/src/controllers/livetv/livetvsuggested.js @@ -261,6 +261,8 @@ define(['layoutManager', 'userSettings', 'inputManager', 'loading', 'globalize', } require(depends, function (controllerFactory) { + controllerFactory = controllerFactory.default || controllerFactory; + var tabContent; if (index == 0) { From 14ee172231d50be81fe3e59314d594b059085c9f Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sat, 8 Aug 2020 20:19:03 +0000 Subject: [PATCH 26/51] Bump @babel/eslint-parser from 7.11.0 to 7.11.3 Bumps [@babel/eslint-parser](https://github.com/babel/babel/tree/HEAD/eslint/babel-eslint-parser) from 7.11.0 to 7.11.3. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.11.3/eslint/babel-eslint-parser) Signed-off-by: dependabot-preview[bot] --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index d8164391cb..252c3e9008 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "license": "GPL-2.0-or-later", "devDependencies": { "@babel/core": "^7.11.1", - "@babel/eslint-parser": "^7.11.0", + "@babel/eslint-parser": "^7.11.3", "@babel/eslint-plugin": "^7.11.0", "@babel/plugin-proposal-class-properties": "^7.10.1", "@babel/plugin-proposal-private-methods": "^7.10.1", diff --git a/yarn.lock b/yarn.lock index 758ded53fc..4518e7af51 100644 --- a/yarn.lock +++ b/yarn.lock @@ -40,10 +40,10 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/eslint-parser@^7.11.0": - version "7.11.0" - resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.11.0.tgz#b123924edd44508782c030066c926f1b807151cd" - integrity sha512-dJDM2Pc01D9TwKL3Mmz2xgVF9X953RBHq9H4gywbN1q8MrfvXmNHfsCt06vvByBVQqm+9WxMs+doEH/R09TwWQ== +"@babel/eslint-parser@^7.11.3": + version "7.11.3" + resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.11.3.tgz#ceb94cb6e2457c4a4d2d87db29925e6b48d20786" + integrity sha512-OdCt/CVXdR/eTNTYDEobf4e55m/AAc04ki+/Oe2/GE8ivh2FxX4yDab48lA6t7ysP4M7luap6Fxx3hUVNTwzFQ== dependencies: eslint-scope "5.1.0" eslint-visitor-keys "^1.3.0" From 4ffded957dbb50a20b20e6a58324da58a9f55399 Mon Sep 17 00:00:00 2001 From: Moritz Date: Sat, 8 Aug 2020 20:57:10 +0000 Subject: [PATCH 27/51] Translated using Weblate (German) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/de/ --- src/strings/de.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/strings/de.json b/src/strings/de.json index 0aa4d2172f..b167c31707 100644 --- a/src/strings/de.json +++ b/src/strings/de.json @@ -1545,5 +1545,7 @@ "LabelUnstable": "Instabil", "SubtitleVerticalPositionHelp": "Zeilennummer, in der der Text angezeigt wird. Positive Zahlen geben die Zeile von oben an. Negative Zahlen geben die Zeile von unten an.", "Preview": "Vorschau", - "LabelSubtitleVerticalPosition": "Vertikale Position:" + "LabelSubtitleVerticalPosition": "Vertikale Position:", + "MessageGetInstalledPluginsError": "Beim Abrufen der Liste der derzeit installierten Plugins ist ein Fehler aufgetreten.", + "MessagePluginInstallError": "Bei der Installation des Plugins ist ein Fehler aufgetreten." } From cfb22c0fb91e58c75ec26d21db45e35b33a619d3 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 8 Aug 2020 23:23:53 +0100 Subject: [PATCH 28/51] Apply suggestions from code review Co-authored-by: Dmitry Lyzo <56478732+dmitrylyzo@users.noreply.github.com> --- src/scripts/livetvcomponents.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/scripts/livetvcomponents.js b/src/scripts/livetvcomponents.js index f1d0b2d3da..46fb714763 100644 --- a/src/scripts/livetvcomponents.js +++ b/src/scripts/livetvcomponents.js @@ -22,7 +22,7 @@ function getTimersHtml(timers, options) { let currentGroupName = ''; let currentGroup = []; - for (const item in items) { + for (const item of items) { let dateText = ''; if (options.indexByDate !== false && item.StartDate) { @@ -60,7 +60,7 @@ function getTimersHtml(timers, options) { }); } let html = ''; - for (const group in groups) { + for (const group of groups) { if (group.name) { html += '
'; html += '

' + group.name + '

'; From 1ede5e0d68afa4fc7ebeab1dc0f875eb1e9f5565 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 8 Aug 2020 23:48:24 +0100 Subject: [PATCH 29/51] Update src/scripts/libraryBrowser.js Co-authored-by: Dmitry Lyzo <56478732+dmitrylyzo@users.noreply.github.com> --- src/scripts/libraryBrowser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripts/libraryBrowser.js b/src/scripts/libraryBrowser.js index 4f2a0fdb47..460e1b95ed 100644 --- a/src/scripts/libraryBrowser.js +++ b/src/scripts/libraryBrowser.js @@ -119,7 +119,7 @@ export function getQueryPagingHtml (options) { } export function showSortMenu (options) { - Promise.All([ + Promise.all([ import('dialogHelper'), import('emby-radio') ]).then(({default: dialogHelper}) => { From 10100ce63b818670d36ee0344d6022085821c896 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 8 Aug 2020 23:53:34 +0100 Subject: [PATCH 30/51] Update src/scripts/libraryBrowser.js Co-authored-by: Dmitry Lyzo <56478732+dmitrylyzo@users.noreply.github.com> --- src/scripts/libraryBrowser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripts/libraryBrowser.js b/src/scripts/libraryBrowser.js index 460e1b95ed..83d683a690 100644 --- a/src/scripts/libraryBrowser.js +++ b/src/scripts/libraryBrowser.js @@ -122,7 +122,7 @@ export function showSortMenu (options) { Promise.all([ import('dialogHelper'), import('emby-radio') - ]).then(({default: dialogHelper}) => { + ]).then(([{default: dialogHelper}]) => { function onSortByChange() { var newValue = this.value; From ad7879ad4d481df0f1dac86b0baead1cc88e7707 Mon Sep 17 00:00:00 2001 From: Nicode Jiao Date: Sun, 9 Aug 2020 03:28:41 +0000 Subject: [PATCH 31/51] Translated using Weblate (Chinese (Simplified)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/zh_Hans/ --- src/strings/zh-cn.json | 76 +++++++++++++++++++++++------------------- 1 file changed, 42 insertions(+), 34 deletions(-) diff --git a/src/strings/zh-cn.json b/src/strings/zh-cn.json index 553445404a..eed9f95599 100644 --- a/src/strings/zh-cn.json +++ b/src/strings/zh-cn.json @@ -159,7 +159,7 @@ "DetectingDevices": "正在侦测设备", "DeviceAccessHelp": "这仅适用于可以唯一标识的设备,而不会阻止浏览器访问。限制用户设备访问会阻止使用未在此被批准的新增设备。", "DirectPlaying": "直接播放", - "DirectStreamHelp2": "直接串流只占用占用很少的CPU并且视频的品质不会有任何损失。", + "DirectStreamHelp2": "直接串流只占用占用很少的CPU并且视频的品质只会有极小程度的损失。", "DirectStreaming": "直接串流", "Director": "导演", "Disabled": "已禁用", @@ -261,7 +261,7 @@ "HeaderAllowMediaDeletionFrom": "允许从中删除媒体", "HeaderApiKey": "API 密钥", "HeaderApiKeys": "API 密钥", - "HeaderApiKeysHelp": "外部应用程序需要 API 密钥才能与 Jellyfin Server 进行通信。使用 Jellyfin 账户进行登录时密钥将会自动生成,您也可以手动为某个应用程序分配一个密钥。", + "HeaderApiKeysHelp": "外部应用程序需要 API 密钥才能与服务器进行通信。密钥会在使用普通账户登录时自动生成,或是手动为应用分配。", "HeaderAudioBooks": "有声读物", "HeaderAudioSettings": "声音设置", "HeaderBlockItemsWithNoRating": "通过没有评级和设置不允许的评级锁定内容:", @@ -327,7 +327,7 @@ "HeaderInstall": "安装", "HeaderInstantMix": "速成合辑", "HeaderItems": "项目", - "HeaderKodiMetadataHelp": "要启用或禁用 NFO 元数据, 请在 Jellyfin 库安装程序中编辑库, 然后找到“元数据储户”部分。", + "HeaderKodiMetadataHelp": "要启用或禁用 NFO 元数据, 请编辑库, 然后找到“元数据储户”部分。", "HeaderLatestEpisodes": "最新剧集", "HeaderLatestMedia": "最新媒体", "HeaderLatestMovies": "最新电影", @@ -372,7 +372,7 @@ "HeaderPreferredMetadataLanguage": "首选元数据语言", "HeaderProfile": "配置", "HeaderProfileInformation": "配置信息", - "HeaderProfileServerSettingsHelp": "这些参数将控制 Jellyfin 媒体服务器如何呈现给设备。", + "HeaderProfileServerSettingsHelp": "这些参数将控制服务器如何将自己呈现给客户端。", "HeaderRecentlyPlayed": "最近播放", "HeaderRecordingOptions": "录制选项", "HeaderRecordingPostProcessing": "记录后处理", @@ -396,7 +396,7 @@ "HeaderSelectServerCachePath": "选择服务器缓存路径", "HeaderSelectServerCachePathHelp": "浏览或输入一个路径用于服务器缓存文件,此文件夹必须可写。", "HeaderSelectTranscodingPath": "选择临时解码路径", - "HeaderSelectTranscodingPathHelp": "浏览或输入一个路径用于临时转码,此文件夹必须可写。", + "HeaderSelectTranscodingPathHelp": "浏览或输入一个路径用于转码文件,此文件夹必须可写。", "HeaderSendMessage": "发送消息", "HeaderSeries": "电视剧", "HeaderSeriesOptions": "系列选项", @@ -445,8 +445,8 @@ "HttpsRequiresCert": "要启用安全连接, 您需要提供一个受信任的 SSL 证书, 例如 Let's Encrypt 。请提供证书或禁用安全连接。", "Identify": "识别", "Images": "图片", - "ImportFavoriteChannelsHelp": "如果启用,只有在协调器设备中被标记为我的最爱的频道才会被导入。", - "ImportMissingEpisodesHelp": "如果启用,会将缺少的剧集信息导入到你的 Jellyfin 数据库并分季分剧显示。可能会大大延长媒体库扫描时间。", + "ImportFavoriteChannelsHelp": "只有在协调器设备中被标记为我的最爱的频道才会被导入。", + "ImportMissingEpisodesHelp": "缺少的剧集信息将被导入到你的数据库并分季分剧显示。可能会大大延长媒体库扫描时间。", "InstallingPackage": "正在安装 {0}(版本 {1})", "InstantMix": "即时混音", "ItemCount": "{0} 项", @@ -476,14 +476,14 @@ "LabelAppName": "APP名称", "LabelAppNameExample": "例如:Sickbeard, Sonarr", "LabelArtists": "艺术家:", - "LabelArtistsHelp": "独立多功能 ;", + "LabelArtistsHelp": "将多个艺术家用分号分隔", "LabelAudioLanguagePreference": "首选音频语言:", "LabelAutomaticallyRefreshInternetMetadataEvery": "自动从互联网获取元数据并刷新:", "LabelBindToLocalNetworkAddress": "监听的本地网络地址:", - "LabelBindToLocalNetworkAddressHelp": "(可选的)覆盖 HTTP 服务器绑定的本地 IP 地址。如果留空,服务器将会监听所有可用的地址。改变这个值需要重启 Jellyfin 服务器。", + "LabelBindToLocalNetworkAddressHelp": "覆盖 HTTP 服务器绑定的本地 IP 地址。如果留空,服务器将会监听所有可用的地址。改变这个值需要重启 Jellyfin 服务器。", "LabelBirthDate": "出生日期:", "LabelBirthYear": "出生年份:", - "LabelBlastMessageInterval": "活动信号的时间间隔(秒)", + "LabelBlastMessageInterval": "活动信号的时间间隔", "LabelBlastMessageIntervalHelp": "确定爆炸活动消息之间的持续时间(以秒为单位)。", "LabelBlockContentWithTags": "通过标签锁定内容:", "LabelBurnSubtitles": "烧录字幕:", @@ -541,7 +541,7 @@ "LabelEnableAutomaticPortMapHelp": "通过UPnP将路由器端口自动转发到服务器端口。这可能不适用于某些型号的路由器和网络配置。需要服务器重新启动后才会应用更改。", "LabelEnableBlastAliveMessages": "爆发活动信号", "LabelEnableBlastAliveMessagesHelp": "如果该服务器不能被网络中的其他UPnP设备检测到,请启用此选项。", - "LabelEnableDlnaClientDiscoveryInterval": "客户端搜寻时间间隔(秒)", + "LabelEnableDlnaClientDiscoveryInterval": "客户端搜寻时间间隔", "LabelEnableDlnaClientDiscoveryIntervalHelp": "确定由 Jellyfin 执行的 SSDP 搜索之间的持续时间 (以秒为单位)。", "LabelEnableDlnaDebugLogging": "启用 DLNA 调试日志", "LabelEnableDlnaDebugLoggingHelp": "创建一个很大的日志文件,仅应在排除故障时使用。", @@ -567,9 +567,9 @@ "LabelForgotPasswordUsernameHelp": "输入你的用户名,如果你还记得。", "LabelFormat": "格式:", "LabelFriendlyName": "好记的名称:", - "LabelServerNameHelp": "此名称将用做服务器名,如果留空,将使用计算机名。", + "LabelServerNameHelp": "此名称将用做服务器名,默认使用服务器的主机名。", "LabelGroupMoviesIntoCollections": "批量添加电影到收藏", - "LabelGroupMoviesIntoCollectionsHelp": "显示电影列表时,属于一个收藏的电影将显示为一个分组。", + "LabelGroupMoviesIntoCollectionsHelp": "显示电影列表时,同一收藏的电影将显示为一个分组。", "LabelH264Crf": "H264 CRF 编码质量等级:", "LabelEncoderPreset": "H264 和 H265 编码预设:", "LabelHardwareAccelerationType": "硬件加速:", @@ -577,7 +577,7 @@ "LabelHomeNetworkQuality": "家庭网络质量:", "LabelHomeScreenSectionValue": "主屏幕模块{0}:", "LabelHttpsPort": "本地 HTTPS 端口号:", - "LabelHttpsPortHelp": "Jellyfin HTTPS 服务器监听端口。", + "LabelHttpsPortHelp": "HTTPS 服务器监听的 TCP 端口号。", "LabelIconMaxHeight": "图标最大高度:", "LabelIconMaxHeightHelp": "通过UPnP显示的图标最大分辨率。", "LabelIconMaxWidth": "图标最大宽度:", @@ -604,7 +604,7 @@ "LabelLanguage": "语言:", "LabelLineup": "排队:", "LabelLocalHttpServerPortNumber": "本地 HTTP 端口号:", - "LabelLocalHttpServerPortNumberHelp": "Jellyfin HTTP 服务器监听的 TCP 端口。", + "LabelLocalHttpServerPortNumberHelp": "HTTP 服务器监听的 TCP 端口号。", "LabelLockItemToPreventChanges": "锁定此项目防止改动", "LabelLoginDisclaimer": "登录声明:", "LabelLoginDisclaimerHelp": "将在登录页面底部显示的信息。", @@ -646,9 +646,9 @@ "LabelMovieCategories": "电影分类:", "LabelMoviePrefix": "电影前缀:", "LabelMoviePrefixHelp": "如果将前缀应用于影片标题, 请在此处输入它, 以便服务器可以正确处理它。", - "LabelMovieRecordingPath": "电影录制路径 (可选的):", + "LabelMovieRecordingPath": "电影录制路径:", "LabelMusicStreamingTranscodingBitrate": "音乐转码的比特率:", - "LabelMusicStreamingTranscodingBitrateHelp": "请指定一个音乐媒体串流时的最大比特率。", + "LabelMusicStreamingTranscodingBitrateHelp": "请指定音乐媒体串流时的最大比特率。", "LabelName": "名字:", "LabelNewName": "新名字:", "LabelNewPassword": "新密码:", @@ -659,7 +659,7 @@ "LabelNumber": "编号:", "LabelNumberOfGuideDays": "下载几天的节目指南:", "LabelNumberOfGuideDaysHelp": "下载更多天的节目指南可以帮你进一步查看节目列表并做出提前安排,但下载过程也将耗时更久。它将基于频道数量自动选择。", - "LabelOptionalNetworkPath": "(可选的)共享的网络文件夹:", + "LabelOptionalNetworkPath": "共享的网络文件夹:", "LabelOptionalNetworkPathHelp": "如果这个文件夹在你的网络上是共享的,提供这个网络共享地址能够允许其他设备上的 Jellyfin 应用程序直接访问媒体文件,例如 {0} 或者 {1}。", "LabelOriginalAspectRatio": "原始长宽比:", "LabelOriginalTitle": "原标题:", @@ -704,7 +704,7 @@ "LabelReleaseDate": "发行日期:", "LabelRemoteClientBitrateLimit": "互联网流媒体传输比特率限制(Mbps):", "LabelRemoteClientBitrateLimitHelp": "所有网络设备都有一个可选的每流比特率限制。这对于防止设备请求比 internet 连接所能处理的更高的比特率非常有用。这可能会导致服务器上的 CPU 负载增加, 以便将视频转码到较低的比特率。", - "LabelRuntimeMinutes": "播放时长(分钟):", + "LabelRuntimeMinutes": "播放时长:", "LabelSaveLocalMetadata": "将媒体图像保存到媒体所在文件夹", "LabelSaveLocalMetadataHelp": "直接将媒体图像保存到媒体所在文件夹以方便编辑。", "LabelScheduledTaskLastRan": "最后运行 {0}, 花费时间 {1}.", @@ -714,7 +714,7 @@ "LabelSelectVersionToInstall": "选择安装版本:", "LabelSendNotificationToUsers": "发送通知至:", "LabelSerialNumber": "序列号", - "LabelSeriesRecordingPath": "电视剧录制路径 (可选的):", + "LabelSeriesRecordingPath": "电视剧录制路径:", "LabelServerHost": "主机:", "LabelServerHostHelp": "192.168.1.100:8096 或 https://myserver.com", "LabelSimultaneousConnectionLimit": "并发流限制:", @@ -786,7 +786,7 @@ "LabelYoureDone": "完成!", "LabelZipCode": "邮编:", "LabelffmpegPath": "FFmpeg 路径:", - "LabelffmpegPathHelp": "FFmpeg 应用程序的文件,或者包含了 FFmpeg 的文件夹的路径。", + "LabelffmpegPathHelp": "FFmpeg 应用文件或包含 FFmpeg 的文件夹的路径。", "LanNetworksHelp": "在强制带宽限制时,认作本地网络上的 IP 地址或 IP/网络掩码条目的逗号分隔列表。如果设置此项,所有其它 IP 地址将被视为在外部网络上,并且将受到外部带宽限制。如果保留为空,则只将服务器的子网视为本地网络。", "Large": "大", "LatestFromLibrary": "最新的{0}", @@ -918,7 +918,7 @@ "OptionAllowLinkSharingHelp": "只有网页包含的媒体信息会被共享。媒体文件不会被公开共享。共享是有时间限制的并且会在 {0} 天后到期。", "OptionAllowManageLiveTv": "允许电视直播录制管理", "OptionAllowMediaPlayback": "允许播放媒体", - "OptionAllowMediaPlaybackTranscodingHelp": "由于不支持的媒体格式, 限制对代码转换的访问可能会导致 Jellyfin 应用程序中的播放失败。", + "OptionAllowMediaPlaybackTranscodingHelp": "限制对转码的访问可能会由于不支持的媒体格式导致客户端中播放失败。", "OptionAllowRemoteControlOthers": "允许其他用户全程控制", "OptionAllowRemoteSharedDevices": "允许远程控制共享的设备", "OptionAllowRemoteSharedDevicesHelp": "DLNA 设备在用户对他们进行控制前都被视为是共享的。", @@ -931,7 +931,7 @@ "OptionAuto": "自动", "OptionAutomatic": "自动", "OptionAutomaticallyGroupSeries": "自动合并分布在不同文件夹的电视剧", - "OptionAutomaticallyGroupSeriesHelp": "如果启用,分布在这个媒体库的多个文件夹中的同一部电视剧将会自动整合成一部电视剧。", + "OptionAutomaticallyGroupSeriesHelp": "在这个媒体库的多个文件夹中的同一部电视剧将会自动整合成一部电视剧。", "OptionBlockBooks": "书籍", "OptionBlockChannelContent": "互联网频道内容", "OptionBlockLiveTvChannels": "电视直播频道", @@ -952,7 +952,7 @@ "OptionDatePlayed": "播放日期", "OptionDescending": "降序", "OptionDisableUser": "禁用此用户", - "OptionDisableUserHelp": "如果禁用该用户,服务器将不允许该用户连接。现有的连接将被终止。", + "OptionDisableUserHelp": "服务器将不允许来自该用户的任何连接。现有的连接将立即被终止。", "OptionDislikes": "不喜欢", "OptionDisplayFolderView": "显示一个“文件夹”类别用于按文件夹分类浏览你的媒体文件夹", "OptionDisplayFolderViewHelp": "在你的媒体库列表中显示文件夹。如果你有按文件夹分类进行浏览的需求,这会非常有用。", @@ -962,7 +962,7 @@ "OptionDownloadBoxImage": "包装", "OptionDownloadDiscImage": "光盘", "OptionDownloadImagesInAdvance": "提前下载图片", - "OptionDownloadImagesInAdvanceHelp": "默认下,大部分图片只有在 Jellyfin 应用程序请求时下载。开启此选项将随着媒体导入时下载所有图片。这可能需要更久媒体库扫描时间。", + "OptionDownloadImagesInAdvanceHelp": "默认大多数图片只在客户端请求时下载。开启此选项将在新媒体导入时预先下载所有图片。这可能大大延长媒体库扫描时间。", "OptionDownloadMenuImage": "菜单", "OptionDownloadPrimaryImage": "封面图", "OptionDownloadThumbImage": "缩略图", @@ -994,7 +994,7 @@ "OptionHlsSegmentedSubtitles": "HLS分段字幕", "OptionHomeVideos": "照片", "OptionIgnoreTranscodeByteRangeRequests": "忽略转码字节范围请求", - "OptionIgnoreTranscodeByteRangeRequestsHelp": "如果启用,这些请求会被兑现,但会忽略的字节范围标头。", + "OptionIgnoreTranscodeByteRangeRequestsHelp": "这些请求会被兑现,但会忽略的字节范围标头。", "OptionImdbRating": "IMDb 评分", "OptionIsHD": "HD高清", "OptionIsSD": "SD标清", @@ -1009,9 +1009,9 @@ "OptionOnInterval": "在一个期间", "OptionParentalRating": "家长分级", "OptionPlainStorageFolders": "显示所有文件夹作为一般存储文件夹", - "OptionPlainStorageFoldersHelp": "如果启用,所有文件夹在DIDL中显示为“ object.container.storageFolder ”,而不是一个更具体的类型,如“ object.container.person.musicArtist ” 。", + "OptionPlainStorageFoldersHelp": "所有文件夹在DIDL中显示为 \"object.container.storageFolder\" ,而不是一个更具体的类型,如 \"object.container.person.musicArtist\" 。", "OptionPlainVideoItems": "显示所有视频为一般视频项目", - "OptionPlainVideoItemsHelp": "如果启用,所有视频在DIDL中显示为“object.item.videoItem”,而不是一个更具体的类型,如“object.item.videoItem.movie ” 。", + "OptionPlainVideoItemsHelp": "所有视频在DIDL中显示为 \"object.item.videoItem\" ,而不是一个更具体的类型,如 \"object.item.videoItem.movie\" 。", "OptionPlayCount": "播放次数", "OptionPlayed": "已播放", "OptionPremiereDate": "首映日期", @@ -1316,7 +1316,7 @@ "ErrorDeletingItem": "从 Jellyfin Server 删除项目时出错。请确认 Jellyfin Server 是否拥有对媒体目录的写权限,然后重试。", "GroupBySeries": "按系列分组", "HeaderApp": "应用程序", - "DirectStreamHelp1": "该媒体文件的分辨率和编码(H.264、AC3 等)与您的设备兼容,但容器格式(.mkv、.avi、.wmv 等)不受支持。因此,视频在串流至您的设备之前将会被即时封装为另一种格式。", + "DirectStreamHelp1": "该媒体文件的分辨率和编码(H.264、AC3 等)与您的设备兼容,但文件格式(.mkv、.avi、.wmv 等)不受支持。因此,视频在串流至您的设备之前将会被即时封装为另一种格式。", "HeaderAppearsOn": "同时出现于", "HeaderCancelSeries": "取消系列", "HeaderFavoriteEpisodes": "最爱的剧集", @@ -1361,14 +1361,14 @@ "OptionDownloadLogoImage": "标志", "OptionLoginAttemptsBeforeLockout": "确定在锁定之前可以进行多少次不正确的登录尝试。", "OptionLoginAttemptsBeforeLockoutHelp": "如果值为0,则表示将允许普通用户尝试三次、管理员尝试五次的默认值。将此设置为-1将禁用此功能。", - "PasswordResetProviderHelp": "选择一个密码重置提供者用于密码重置", + "PasswordResetProviderHelp": "选择一个密码重置提供者用于此用户申请重置密码", "PlaceFavoriteChannelsAtBeginning": "将最喜爱的频道置顶", "PlayNext": "播放下一个", "PlayNextEpisodeAutomatically": "自动播放下一集", "Premieres": "首映", "Raised": "提高", "Recordings": "录音", - "RefreshDialogHelp": "元数据根据设置和Jellyfin服务器中启用的网络服务进行刷新。", + "RefreshDialogHelp": "元数据根据设置和仪表盘中启用的网络服务进行刷新。", "RepeatEpisodes": "重复剧集", "Schedule": "日程", "Screenshot": "屏幕截图", @@ -1421,7 +1421,7 @@ "ButtonAddImage": "添加图片", "LabelPlayer": "播放器:", "LabelBaseUrl": "基础 URL:", - "LabelBaseUrlHelp": "为服务器 URL添加自定义子目录,例如:http://example.com/<baseurl>。", + "LabelBaseUrlHelp": "为服务器 URL添加自定义子目录,例如:http://example.com/<baseurl>", "MusicLibraryHelp": "重播 {0}音乐命名指南{1}。", "HeaderFavoritePeople": "最喜欢的人物", "OptionRandom": "随机", @@ -1480,7 +1480,7 @@ "LabelRequireHttpsHelp": "开启后服务器将自动将所有 HTTP 请求重定向到 HTTPS。如果服务器没有启用 HTTPS 则不生效。", "LabelRequireHttps": "强制 HTTPS", "LabelStable": "稳定版", - "LabelEnableHttpsHelp": "开启服务器对所配置 HTTPS 端口的监听。必须配置有效的证书才会生效。", + "LabelEnableHttpsHelp": "监听已配置的 HTTPS 端口。必须配置有效的证书才会生效。", "LabelEnableHttps": "启用 HTTPS", "LabelChromecastVersion": "Chromecast版本", "HeaderDVR": "DVR", @@ -1539,5 +1539,13 @@ "ClearQueue": "清空队列", "StopPlayback": "停止播放", "Writers": "作者", - "ViewAlbumArtist": "查看专辑艺术家" + "ViewAlbumArtist": "查看专辑艺术家", + "Preview": "预览", + "SubtitleVerticalPositionHelp": "文字出现的行号。正数表示由上到下,负数表示由下到上。", + "LabelSubtitleVerticalPosition": "垂直位置:", + "PreviousTrack": "上一曲", + "MessageGetInstalledPluginsError": "获取已安装插件列表时出现错误。", + "MessagePluginInstallError": "安装插件时出现错误。", + "NextTrack": "下一曲", + "LabelUnstable": "不稳定" } From 754bf98d1645d24b3fdcd9b9c83b6c4c2ab04397 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 9 Aug 2020 10:46:37 +0100 Subject: [PATCH 32/51] Update src/components/recordingcreator/recordingcreator.js Co-authored-by: Dmitry Lyzo <56478732+dmitrylyzo@users.noreply.github.com> --- src/components/recordingcreator/recordingcreator.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/recordingcreator/recordingcreator.js b/src/components/recordingcreator/recordingcreator.js index b8001fc35e..f4ae64977d 100644 --- a/src/components/recordingcreator/recordingcreator.js +++ b/src/components/recordingcreator/recordingcreator.js @@ -122,7 +122,7 @@ function executeCloseAction(action, programId, serverId) { const apiClient = connectionManager.getApiClient(serverId); apiClient.getLiveTvProgram(programId, apiClient.getCurrentUserId()).then(function (item) { - playbackManager.default.play({ + playbackManager.play({ ids: [item.ChannelId], serverId: serverId }); From fa9c2757359a4f56a027049d3268b1c49211ec84 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 9 Aug 2020 12:41:16 +0100 Subject: [PATCH 33/51] fix ES6 errors --- src/plugins/chromecastPlayer/plugin.js | 2 +- src/scripts/libraryMenu.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/chromecastPlayer/plugin.js b/src/plugins/chromecastPlayer/plugin.js index f38f9b04ff..b7e6d05969 100644 --- a/src/plugins/chromecastPlayer/plugin.js +++ b/src/plugins/chromecastPlayer/plugin.js @@ -584,7 +584,7 @@ class ChromecastPlayer { this.isLocalPlayer = false; this.lastPlayerData = {}; - new castSenderApiLoader.default().load().then(initializeChromecast.bind(this)); + new castSenderApiLoader().load().then(initializeChromecast.bind(this)); } tryPair(target) { diff --git a/src/scripts/libraryMenu.js b/src/scripts/libraryMenu.js index 0780916a7c..ec572752b9 100644 --- a/src/scripts/libraryMenu.js +++ b/src/scripts/libraryMenu.js @@ -850,7 +850,7 @@ import 'flexStyles'; let requiresUserRefresh = true; function setTabs (type, selectedIndex, builder) { - import('mainTabsManager').then(({default: mainTabsManager}) => { + import('mainTabsManager').then((mainTabsManager) => { if (type) { mainTabsManager.setTabs(viewManager.currentView(), selectedIndex, builder, function () { return []; From 7d066a6fcfd8c645b5e9530309b9cb57004279ec Mon Sep 17 00:00:00 2001 From: Thomas Schwery Date: Sun, 9 Aug 2020 10:10:50 +0000 Subject: [PATCH 34/51] Translated using Weblate (French) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fr/ --- src/strings/fr.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/strings/fr.json b/src/strings/fr.json index 20b96f3a52..e66cdc0edc 100644 --- a/src/strings/fr.json +++ b/src/strings/fr.json @@ -1545,5 +1545,7 @@ "LabelUnstable": "Instable", "Preview": "Aperçu", "SubtitleVerticalPositionHelp": "Numéro de ligne où le texte apparaît. Un nombre positif compte les lignes de haut en bas. Un nombre négatif, de bas en haut.", - "LabelSubtitleVerticalPosition": "Position verticale :" + "LabelSubtitleVerticalPosition": "Position verticale :", + "MessageGetInstalledPluginsError": "Une erreur est survenue lors de la récupération de la liste des extensions installées.", + "MessagePluginInstallError": "Une erreur est survenue durant l'installation de l'extension." } From 475cc0e2532183e58466686f02f7e79e4bf1b39b Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sun, 9 Aug 2020 14:26:26 +0000 Subject: [PATCH 35/51] Bump @babel/eslint-plugin from 7.11.0 to 7.11.3 Bumps [@babel/eslint-plugin](https://github.com/babel/babel/tree/HEAD/eslint/babel-eslint-plugin) from 7.11.0 to 7.11.3. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.11.3/eslint/babel-eslint-plugin) Signed-off-by: dependabot-preview[bot] --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 508fbd1307..fa27f5b951 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "devDependencies": { "@babel/core": "^7.11.1", "@babel/eslint-parser": "^7.11.3", - "@babel/eslint-plugin": "^7.11.0", + "@babel/eslint-plugin": "^7.11.3", "@babel/plugin-proposal-class-properties": "^7.10.1", "@babel/plugin-proposal-private-methods": "^7.10.1", "@babel/plugin-transform-modules-amd": "^7.10.5", diff --git a/yarn.lock b/yarn.lock index 4518e7af51..269b3b93cd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -49,10 +49,10 @@ eslint-visitor-keys "^1.3.0" semver "^6.3.0" -"@babel/eslint-plugin@^7.11.0": - version "7.11.0" - resolved "https://registry.yarnpkg.com/@babel/eslint-plugin/-/eslint-plugin-7.11.0.tgz#55d5b6bd29859cabce152f16d01b3a8150d5b295" - integrity sha512-+gfPM0/T6d25jKBgmxWp38W0jqRs16Vt7DPBxGOcnN/7nS2A/6QoaXOYEaccvWS5a9UpWlMIAylivp6UtH8/sQ== +"@babel/eslint-plugin@^7.11.3": + version "7.11.3" + resolved "https://registry.yarnpkg.com/@babel/eslint-plugin/-/eslint-plugin-7.11.3.tgz#66b531f90592f8f0621d072b59ea2c37c91e8d0d" + integrity sha512-gmi3lgaWlYpNb+h7qPfv5GVz2ZVwzCDyV+kAGj+3il+Mv5uan5Yccvdw7m14UAAY2tdTbB0VgRF6ZLjUbrUm0g== dependencies: eslint-rule-composer "^0.3.0" From 988173fffc5e750e03a8c5b8e7199c787430b877 Mon Sep 17 00:00:00 2001 From: Felipe Date: Mon, 10 Aug 2020 01:25:23 +0000 Subject: [PATCH 36/51] Translated using Weblate (Spanish (Latin America)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/es_419/ --- src/strings/es_419.json | 50 +++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/src/strings/es_419.json b/src/strings/es_419.json index 54dd68e9ed..94a4db6aa2 100644 --- a/src/strings/es_419.json +++ b/src/strings/es_419.json @@ -183,7 +183,7 @@ "OptionPlayCount": "Contador de reproducciones", "OptionPlainVideoItemsHelp": "Si se habilita, todos los videos serán representados en DIDL como «object.item.videoItem» en lugar de un tipo más específico, como «object.item.videoItem.movie».", "OptionPlainVideoItems": "Mostrar todos los videos como elementos de video simples", - "OptionPlainStorageFoldersHelp": "Si se habilita, todos las carpetas serán representadas en DIDL como «object.container.storageFolder» en lugar de un tipo más específico, como «object.container.person.musicArtist».", + "OptionPlainStorageFoldersHelp": "Todos las carpetas serán representadas en DIDL como «object.container.storageFolder» en lugar de un tipo más específico, como «object.container.person.musicArtist».", "OptionPlainStorageFolders": "Mostrar todas las carpetas como carpetas de almacenamiento simples", "OptionParentalRating": "Clasificación parental", "OptionOnInterval": "En un intervalo", @@ -201,7 +201,7 @@ "OptionIsSD": "SD", "OptionIsHD": "HD", "OptionImdbRating": "Calificación de IMDb", - "OptionIgnoreTranscodeByteRangeRequestsHelp": "Si se habilita, estas solicitudes serán honradas pero se ignorará el encabezado de rango de bytes.", + "OptionIgnoreTranscodeByteRangeRequestsHelp": "Estas solicitudes serán consideradas pero se ignorará el encabezado de rango de bytes.", "OptionIgnoreTranscodeByteRangeRequests": "Ignorar solicitudes de transcodificación de rango de bytes", "OptionHomeVideos": "Fotos", "OptionHlsSegmentedSubtitles": "Subtítulos segmentados HLS", @@ -234,7 +234,7 @@ "OptionDownloadPrimaryImage": "Principal", "OptionDownloadMenuImage": "Menú", "OptionDownloadLogoImage": "Logo", - "OptionDownloadImagesInAdvanceHelp": "Por defecto, la mayoría de las imágenes solo son descargadas cuando son solicitadas por una aplicación Jellyfin. Habilita esta opción para descargar todas las imágenes por adelantado, a medida que se agreguen nuevos medios. Esto podría causar escaneos de bibliotecas significativamente más largos.", + "OptionDownloadImagesInAdvanceHelp": "Por defecto, la mayoría de las imágenes se descargan cuando son solicitadas por un cliente. Habilita esta opción para descargarlas todas por por adelantado a medida que se agreguen nuevos medios. Esto podría causar que los escaneos de bibliotecas sean significativamente más largos.", "OptionDownloadImagesInAdvance": "Descargar las imágenes con antelación", "OptionDownloadDiscImage": "Disco", "OptionDownloadBoxImage": "Caja", @@ -244,7 +244,7 @@ "OptionDisplayFolderViewHelp": "Muestra las carpetas junto con sus otras bibliotecas de medios. Esto puede ser útil si deseas tener una vista simple de carpeta.", "OptionDisplayFolderView": "Mostrar una vista de carpetas para mostrar las carpetas simples de los medios", "OptionDislikes": "No me gusta", - "OptionDisableUserHelp": "Si se desactiva, el servidor no aceptará conexiones de este usuario. Las conexiones existentes serán finalizadas abruptamente.", + "OptionDisableUserHelp": "El servidor no aceptará conexiones de este usuario. Las conexiones existentes serán finalizadas abruptamente.", "OptionDisableUser": "Desactivar este usuario", "OptionDescending": "Descendente", "OptionDatePlayed": "Fecha de reproducción", @@ -263,7 +263,7 @@ "OptionBlockChannelContent": "Contenido de canales de Internet", "OptionBlockBooks": "Libros", "OptionBanner": "Banner", - "OptionAutomaticallyGroupSeriesHelp": "Si se habilita, las series que se reparten a través de múltiples carpetas dentro de esta biblioteca serán fusionadas en una sola serie.", + "OptionAutomaticallyGroupSeriesHelp": "Series que estén repartidas en múltiples carpetas dentro de esta biblioteca serán fusionadas en una sola serie.", "OptionAutomaticallyGroupSeries": "Fusionar automáticamente series esparcidas a través de múltiples carpetas", "OptionAutomatic": "Automático", "OptionAuto": "Automático", @@ -276,7 +276,7 @@ "OptionAllowRemoteSharedDevicesHelp": "Los dispositivos DLNA se considerarán compartidos hasta que un usuario comience a controlarlos.", "OptionAllowRemoteSharedDevices": "Permitir control remoto de dispositivos compartidos", "OptionAllowRemoteControlOthers": "Permitir control remoto de otros usuarios", - "OptionAllowMediaPlaybackTranscodingHelp": "Restringir el acceso a la transcodificación podría causar fallas en la reproducción en las aplicaciones Jellyfin debido a los formatos de medios no soportados.", + "OptionAllowMediaPlaybackTranscodingHelp": "Restringir el acceso a la transcodificación podría causar fallas en reproducción en las aplicaciones debido a formatos de medios no soportados.", "OptionAllowMediaPlayback": "Permitir reproducción de medios", "OptionAllowManageLiveTv": "Permitir gestión de grabación de TV en vivo", "OptionAllowLinkSharingHelp": "Solo son compartidas páginas web que contienen información sobre los medios. Los archivos de medios nunca son compartidos públicamente. Los compartidos tienen un límite de tiempo y expirarán después de {0} días.", @@ -333,7 +333,7 @@ "Mobile": "Móvil", "MinutesBefore": "minutos antes", "MinutesAfter": "minutos después", - "MetadataSettingChangeHelp": "Cambiar la configuración de los metadatos afectará al nuevo contenido que se añada en el futuro. Para actualizar el contenido existente, abre la pantalla de detalles y haz clic en el botón actualizar, o realiza actualizaciones masivas usando el administrador de metadatos.", + "MetadataSettingChangeHelp": "Cambiar la configuración de los metadatos afectará al nuevo contenido que se añada en el futuro. Para actualizar el contenido existente, abre la pantalla de detalles y haz clic en el botón actualizar, o haz actualizaciones masivas usando el administrador de metadatos.", "MetadataManager": "Administrador de metadatos", "Metadata": "Metadatos", "MessageSyncPlayErrorMedia": "¡Fallo al activar SyncPlay! Error en el archivo de medios.", @@ -392,7 +392,7 @@ "LatestFromLibrary": "Últimas - {0}", "Large": "Grande", "LanNetworksHelp": "Lista separada por comas de direcciones IP o entradas de IP/máscara de red para las redes que se considerarán en la red local al aplicar las restricciones de ancho de banda. Si se establecen, todas las demás direcciones IP se considerarán como parte de la red externa y estarán sujetas a las restricciones de ancho de banda externa. Si se deja en blanco, solo se considera a la subred del servidor estar en la red local.", - "LabelffmpegPathHelp": "La ruta hacia el archivo de la aplicación ffmpeg, o la carpeta que contenga ffmpeg.", + "LabelffmpegPathHelp": "La ruta hacia el archivo ejecutable ffmpeg, o la carpeta que contenga ffmpeg.", "LabelffmpegPath": "Ruta del FFmpeg:", "LabelZipCode": "Código postal:", "LabelYoureDone": "¡Has terminado!", @@ -664,7 +664,7 @@ "LabelServerName": "Nombre del servidor:", "LabelServerHostHelp": "192.168.1.100:8096 o https://miservidor.com", "LabelServerHost": "Servidor:", - "LabelSeriesRecordingPath": "Ruta para las grabaciones de series (opcional):", + "LabelSeriesRecordingPath": "Ruta para las grabaciones de series:", "LabelSerialNumber": "Número de serie", "LabelSendNotificationToUsers": "Enviar la notificación a:", "LabelSelectVersionToInstall": "Seleccionar versión a instalar:", @@ -676,7 +676,7 @@ "LabelScheduledTaskLastRan": "Última ejecución {0}, tomando {1}.", "LabelSaveLocalMetadataHelp": "Guardar ilustraciones en las carpetas de los medios los colocará en un lugar donde se pueden editar fácilmente.", "LabelSaveLocalMetadata": "Guardar las ilustraciones en las carpetas de los medios", - "LabelRuntimeMinutes": "Duración (minutos):", + "LabelRuntimeMinutes": "Duración:", "LabelRequireHttpsHelp": "Si se marca, el servidor redirigirá automáticamente todas las solicitudes a través de HTTP a HTTPS. Esto no tiene efecto si el servidor no está escuchando en HTTPS.", "LabelRequireHttps": "Requerir HTTPS", "LabelRemoteClientBitrateLimitHelp": "Un límite opcional de velocidad de bits por transmisión para todos los dispositivos fuera de la red. Esto es útil para evitar que los dispositivos soliciten una tasa de bits más alta de la que puede manejar tu conexión a Internet. Esto puede provocar un aumento de la carga de la CPU en el servidor para transcodificar los videos sobre la marcha a una velocidad de bits inferior.", @@ -728,7 +728,7 @@ "LabelOriginalTitle": "Título original:", "LabelOriginalAspectRatio": "Relación de aspecto original:", "LabelOptionalNetworkPathHelp": "Si esta carpeta es compartida en su red, proveer la ruta del recurso compartido de red puede permitir a las aplicaciones Jellyfin en otros dispositivos acceder a los archivos de medios directamente. Por ejemplo, {0} o {1}.", - "LabelOptionalNetworkPath": "(Opcional) Carpeta de red compartida:", + "LabelOptionalNetworkPath": "Carpeta de red compartida:", "LabelNumberOfGuideDaysHelp": "Descargar más días de datos de programación permite programar con mayor anticipación y ver más listados, pero tomará más tiempo en descargar. Auto hará la selección basada en el número de canales.", "LabelNumberOfGuideDays": "Número de días de datos de la programación a descargar:", "LabelNumber": "Número:", @@ -741,9 +741,9 @@ "LabelStable": "Estable", "LabelChromecastVersion": "Versión de Chromecast", "LabelName": "Nombre:", - "LabelMusicStreamingTranscodingBitrateHelp": "Especifica la velocidad de bits máxima al transmitir música.", + "LabelMusicStreamingTranscodingBitrateHelp": "Especifica la máxima velocidad de bits al transmitir música.", "LabelMusicStreamingTranscodingBitrate": "Velocidad de bits de transcodificación de música:", - "LabelMovieRecordingPath": "Ruta para las grabaciones de películas (opcional):", + "LabelMovieRecordingPath": "Ruta para las grabaciones de películas:", "LabelMoviePrefixHelp": "Si un prefijo es aplicado al título de las películas, introdúcelo aquí para que el servidor pueda manejarlo correctamente.", "LabelMoviePrefix": "Prefijo de la película:", "LabelMovieCategories": "Categorías de películas:", @@ -941,11 +941,11 @@ "LabelBurnSubtitles": "Quemar subtítulos:", "LabelBlockContentWithTags": "Bloquear elementos con las etiquetas:", "LabelBlastMessageIntervalHelp": "Determina la duración en segundos del intervalo entre mensajes de vida.", - "LabelBlastMessageInterval": "Intervalo de mensajes de vida (segundos)", + "LabelBlastMessageInterval": "Intervalo de mensajes de vida", "LabelBitrate": "Velocidad de bits:", "LabelBirthYear": "Año de nacimiento:", "LabelBirthDate": "Fecha de nacimiento:", - "LabelBindToLocalNetworkAddressHelp": "Opcional. Sobrescribe la dirección IP local a la que se vincula el servidor http. Si se deja vacío, el servidor se vinculará a todas las direcciones disponibles. Cambiar este valor requiere reiniciar el servidor Jellyfin.", + "LabelBindToLocalNetworkAddressHelp": "Sobrescribe la dirección IP local del servidor HTTP. Si se deja vacío, el servidor se vinculará a todas las direcciones disponibles. Para cambiar este valor se necesita reiniciar el servidor.", "LabelBindToLocalNetworkAddress": "Vincular a la dirección de red local:", "LabelAutomaticallyRefreshInternetMetadataEvery": "Actualizar automáticamente los metadatos desde Internet:", "LabelAuthProvider": "Proveedor de autenticación:", @@ -956,7 +956,7 @@ "LabelAudioBitrate": "Velocidad de bits de audio:", "LabelAudioBitDepth": "Profundidad de bits de audio:", "LabelAudio": "Audio", - "LabelArtistsHelp": "Separar múltiples empleando ;", + "LabelArtistsHelp": "Separar múltiples artistas por punto y coma.", "LabelArtists": "Artistas:", "LabelAppNameExample": "Ejemplo: Sickbeard, Sonarr", "LabelAppName": "Nombre de la aplicación", @@ -987,8 +987,8 @@ "ItemCount": "{0} elementos", "InstantMix": "Mix instantáneo", "InstallingPackage": "Instalando {0} (versión {1})", - "ImportMissingEpisodesHelp": "Si se habilita, la información sobre los episodios faltantes se importará a la base de datos de Jellyfin y se mostrarán dentro de las temporadas y series. Esto puede causar escaneos de biblioteca significativamente más largos.", - "ImportFavoriteChannelsHelp": "Si se habilita, solo los canales marcados como favoritos en el dispositivo sintonizador serán importados.", + "ImportMissingEpisodesHelp": "La información sobre los episodios faltantes se importará a la base de datos y se mostrarán dentro de las temporadas y series. Esto puede causar escaneos de biblioteca significativamente más largos.", + "ImportFavoriteChannelsHelp": "Solo los canales marcados como favoritos en el dispositivo sintonizador serán importados.", "Images": "Imágenes", "Identify": "Identificar", "HttpsRequiresCert": "Para habilitar las conexiones seguras, necesitarás proporcionar un certificado SSL de confianza, como el de Let's Encrypt. Por favor, proporciona un certificado o desactiva las conexiones seguras.", @@ -1041,7 +1041,7 @@ "HeaderSelectServerCachePath": "Seleccionar ruta para la caché del servidor", "HeaderSelectServer": "Seleccionar servidor", "HeaderSelectPath": "Seleccionar ruta", - "HeaderSelectMetadataPathHelp": "Explora o introduce la ruta donde deseas almacenar los metadatos. Se debe tener permisos de escritura en dicha carpeta.", + "HeaderSelectMetadataPathHelp": "Explora o escribe la ruta donde deseas guardar los metadatos. Se tienen que tener permisos de escritura en esa carpeta.", "HeaderSelectMetadataPath": "Selecciona la ruta para los metadatos", "HeaderSelectCertificatePath": "Selecciona la ruta del certificado", "HeaderSecondsValue": "{0} segundos", @@ -1060,7 +1060,7 @@ "HeaderRecordingPostProcessing": "Post procesado de las grabaciones", "HeaderRecordingOptions": "Opciones de grabación", "HeaderRecentlyPlayed": "Reproducido recientemente", - "HeaderProfileServerSettingsHelp": "Estos valores controlan como el servidor Jellyfin se presentará al dispositivo.", + "HeaderProfileServerSettingsHelp": "Estos valores controlan cómo el servidor se presentará a los clientes.", "HeaderProfileInformation": "Información del perfil", "HeaderProfile": "Perfil", "HeaderPreferredMetadataLanguage": "Idioma preferido para los metadatos", @@ -1108,7 +1108,7 @@ "HeaderLatestMovies": "Últimas películas", "HeaderLatestMedia": "Últimos medios", "HeaderLatestEpisodes": "Últimos episodios", - "HeaderKodiMetadataHelp": "Para habilitar o deshabilitar los metadatos NFO, edite una biblioteca en la configuración de bibliotecas de Jellyfin y ubica la sección grabadores de metadatos.", + "HeaderKodiMetadataHelp": "Para habilitar o deshabilitar los metadatos NFO, edita una biblioteca y ubica la sección de grabadores de metadatos.", "HeaderKeepSeries": "Conservar serie", "HeaderKeepRecording": "Conservar grabación", "HeaderItems": "Elementos", @@ -1226,7 +1226,7 @@ "HeaderAppearsOn": "Aparece en", "HeaderApp": "Aplicación", "ApiKeysCaption": "Lista de claves API actualmente habilitadas", - "HeaderApiKeysHelp": "Las aplicaciones externas deben tener una clave API para poder comunicarse con el servidor Jellyfin. Las claves se emiten al iniciar sesión con una cuenta Jellyfin, o al otorgar manualmente una clave a la aplicación.", + "HeaderApiKeysHelp": "Las aplicaciones externas deben tener una clave API para poder comunicarse con el servidor. Las claves se emiten al iniciar sesión con una cuenta de usuario, o al otorgar manualmente una clave a la aplicación.", "HeaderApiKeys": "Claves API", "HeaderApiKey": "Clave API", "HeaderAllowMediaDeletionFrom": "Permitir eliminación de medios de", @@ -1372,7 +1372,7 @@ "HeaderSeriesOptions": "Opciones de serie", "HeaderSeries": "Series", "HeaderSendMessage": "Enviar mensaje", - "HeaderSelectTranscodingPathHelp": "Explora o introduce la ruta a utilizar para los archivos temporales de transcodificación. Se debe tener permisos de escritura en dicha carpeta.", + "HeaderSelectTranscodingPathHelp": "Explora o escribe la ruta para los archivos de transcodificación. Se tienen que tener permisos de escritura en esa carpeta.", "HeaderSelectTranscodingPath": "Selecciona la ruta para los archivos temporales de transcodificación", "ConfirmDeleteItems": "Eliminar estos elementos los eliminará tanto del sistema como de tu biblioteca de medios. ¿Estás seguro de querer continuar?", "ConfirmDeleteItem": "Eliminar este elemento lo eliminará tanto del sistema como de tu biblioteca de medios. ¿Estás seguro de querer continuar?", @@ -1539,5 +1539,7 @@ "ButtonCast": "Emitir", "Writers": "Escritores", "ViewAlbumArtist": "Ver Álbum de Artista", - "TabRepositories": "Repositorios" + "TabRepositories": "Repositorios", + "NextTrack": "Saltar al siguiente", + "LabelUnstable": "Inestable" } From 8121cf69b8c71fb7f2eda51b225ad89f0cb87293 Mon Sep 17 00:00:00 2001 From: Felipe Date: Mon, 10 Aug 2020 01:48:27 +0000 Subject: [PATCH 37/51] Translated using Weblate (Spanish (Latin America)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/es_419/ --- src/strings/es_419.json | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/strings/es_419.json b/src/strings/es_419.json index 94a4db6aa2..b1cc402d38 100644 --- a/src/strings/es_419.json +++ b/src/strings/es_419.json @@ -181,7 +181,7 @@ "OptionPoster": "Póster", "OptionPlayed": "Reproducido", "OptionPlayCount": "Contador de reproducciones", - "OptionPlainVideoItemsHelp": "Si se habilita, todos los videos serán representados en DIDL como «object.item.videoItem» en lugar de un tipo más específico, como «object.item.videoItem.movie».", + "OptionPlainVideoItemsHelp": "Todos los videos serán representados en DIDL como «object.item.videoItem» en vez de un tipo más específico, como «object.item.videoItem.movie».", "OptionPlainVideoItems": "Mostrar todos los videos como elementos de video simples", "OptionPlainStorageFoldersHelp": "Todos las carpetas serán representadas en DIDL como «object.container.storageFolder» en lugar de un tipo más específico, como «object.container.person.musicArtist».", "OptionPlainStorageFolders": "Mostrar todas las carpetas como carpetas de almacenamiento simples", @@ -563,7 +563,7 @@ "ReleaseDate": "Fecha de estreno", "RefreshQueued": "Actualización puesta en la cola.", "RefreshMetadata": "Actualizar metadatos", - "RefreshDialogHelp": "Los metadatos son actualizados basándose en las configuraciones y servicios de Internet que estén activados en el panel de control de tu servidor Jellyfin.", + "RefreshDialogHelp": "Los metadatos se actualizan según las configuraciones y servicios de internet que se habilitan en el panel de control.", "Refresh": "Actualizar", "Recordings": "Grabaciones", "RecordingScheduled": "Grabación programada.", @@ -1541,5 +1541,11 @@ "ViewAlbumArtist": "Ver Álbum de Artista", "TabRepositories": "Repositorios", "NextTrack": "Saltar al siguiente", - "LabelUnstable": "Inestable" + "LabelUnstable": "Inestable", + "Preview": "Vista previa", + "SubtitleVerticalPositionHelp": "Número de línea donde aparece el texto. Números positivos representan de arriba hacia abajo. Números negativos representan de abajo hacia arriba.", + "LabelSubtitleVerticalPosition": "Posición Vertical:", + "PreviousTrack": "Saltar al anterior", + "MessageGetInstalledPluginsError": "Ocurrió un error buscando la lista de plugins instalados.", + "MessagePluginInstallError": "Ocurrió un error instalando el plugin." } From 5d92a37134360c643aa6c7c29fcec4bca3afa519 Mon Sep 17 00:00:00 2001 From: Felipe Date: Mon, 10 Aug 2020 02:07:24 +0000 Subject: [PATCH 38/51] Translated using Weblate (Spanish (Latin America)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/es_419/ --- src/strings/es_419.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings/es_419.json b/src/strings/es_419.json index b1cc402d38..022d2c8635 100644 --- a/src/strings/es_419.json +++ b/src/strings/es_419.json @@ -1485,7 +1485,7 @@ "Backdrops": "Imágenes de fondo", "Backdrop": "Imagen de fondo", "Auto": "Auto", - "AuthProviderHelp": "Selecciona un proveedor de autenticación que se utilizará para autenticar la contraseña de este usuario.", + "AuthProviderHelp": "Selecciona el proveedor de autenticación que se utilizará para autenticar la contraseña de este usuario.", "Audio": "Audio", "AttributeNew": "Nuevo", "AspectRatio": "Relación de aspecto", From c37a59f02b7b002bc46b4991fd1a060e289a2c62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Rodr=C3=ADguez?= Date: Mon, 10 Aug 2020 07:50:49 +0000 Subject: [PATCH 39/51] Translated using Weblate (Spanish) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/es/ --- src/strings/es.json | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/strings/es.json b/src/strings/es.json index d720b515f4..7361015235 100644 --- a/src/strings/es.json +++ b/src/strings/es.json @@ -231,7 +231,7 @@ "HeaderAllowMediaDeletionFrom": "Permitir borrar contenido desde", "HeaderApiKey": "Clave API", "HeaderApiKeys": "Claves API", - "HeaderApiKeysHelp": "Las aplicaciones externas requieren de una clave API para comunicarse con el servidor Jellyfin. Las claves se facilitan iniciando sesión con una cuenta de Jellyfin, u otorgando manualmente una clave a la aplicación.", + "HeaderApiKeysHelp": "Las aplicaciones externas requieren de una clave API para comunicarse con el servidor. Las claves se facilitan iniciando sesión con una cuenta de usuario en Jellyfin, u otorgando manualmente una clave a la aplicación.", "HeaderAudioBooks": "Audiolibros", "HeaderAudioSettings": "Ajustes de audio", "HeaderBlockItemsWithNoRating": "Bloquear artículos sin valoraciones o si son desconocidas:", @@ -346,7 +346,7 @@ "HeaderPreferredMetadataLanguage": "Idioma preferido para las etiquetas", "HeaderProfile": "Perfil", "HeaderProfileInformation": "Información del perfil", - "HeaderProfileServerSettingsHelp": "Estos valores controlan como el servidor Jellyfin se presenta al dispositivo.", + "HeaderProfileServerSettingsHelp": "Estos valores controlan cómo el servidor será presentado a los clientes.", "HeaderRecentlyPlayed": "Reproducido recientemente", "HeaderRecordingOptions": "Ajustes de grabación", "HeaderRecordingPostProcessing": "Grabación post procesamiento", @@ -370,7 +370,7 @@ "HeaderSelectServerCachePath": "Seleccione la ruta para el caché del servidor", "HeaderSelectServerCachePathHelp": "Navega o introduce la ruta para alojar los archivos caché del servidor. Tienes que tener permisos de escritura en esa carpeta.", "HeaderSelectTranscodingPath": "Ruta para los archivos temporales de las conversiones", - "HeaderSelectTranscodingPathHelp": "Busca o escribe la ruta que se utilizará para guardar los archivos temporales que se generarán mientras se convierten los archivos. Jellyfin debe tener permisos de escritura en la carpeta.", + "HeaderSelectTranscodingPathHelp": "Busca o escribe la ruta que se utilizará para guardar los archivos que se generarán mientras se convierten los archivos. Jellyfin debe tener permisos de escritura en la carpeta.", "HeaderSendMessage": "Enviar mensaje", "HeaderSeries": "Series", "HeaderSeriesOptions": "Opciones de series", @@ -417,8 +417,8 @@ "HttpsRequiresCert": "Para activar la conexión segura, necesitas un certificado SSL de confianza, como Let's Encrypt. De lo contrario, desactive las conexiones seguras.", "Identify": "Identificar", "Images": "Imágenes", - "ImportFavoriteChannelsHelp": "Si está activado, sólo los canales guardados como favoritos en el sintonizador se importarán.", - "ImportMissingEpisodesHelp": "Si está activada, la información sobre los episodios que faltan se importará en su base de datos Jellyfin y se mostrará en temporadas y series. Esto puede causar exploraciones de bibliotecas significativamente más largas.", + "ImportFavoriteChannelsHelp": "Sólo los canales guardados como favoritos en el sintonizador se importarán.", + "ImportMissingEpisodesHelp": "La información sobre los episodios que faltan se importará en su base de datos y se mostrará en temporadas y series. Esto puede causar exploraciones de bibliotecas significativamente más largas.", "InstallingPackage": "Instalando {0} (versión {1})", "InstantMix": "Mix instantáneo", "ItemCount": "Elementos {0}", @@ -449,11 +449,11 @@ "LabelAppName": "Nombre de la aplicación", "LabelAppNameExample": "Ejemplo: Sickbeard, Sonarr", "LabelArtists": "Artistas:", - "LabelArtistsHelp": "Separar múltiples artistas usando ;", + "LabelArtistsHelp": "Separar múltiples artistas utilizando punto y coma.", "LabelAudioLanguagePreference": "Idioma de audio preferido:", "LabelAutomaticallyRefreshInternetMetadataEvery": "Actualizar las etiquetas automáticamente desde Internet:", "LabelBindToLocalNetworkAddress": "Vincular a la dirección de red local:", - "LabelBindToLocalNetworkAddressHelp": "Opcional. Anule la dirección IP local para enlazar el servidor HTTP. Si se deja vacío, el servidor se enlazará a todas las direcciones disponibles. Para cambiar este valor, debe reiniciar el servidor Jellyfin.", + "LabelBindToLocalNetworkAddressHelp": "Anule la dirección IP local para enlazar el servidor HTTP. Si se deja vacío, el servidor se enlazará a todas las direcciones disponibles. Para cambiar este valor, debe reiniciar el servidor Jellyfin.", "LabelBirthDate": "Fecha de nacimiento:", "LabelBirthYear": "Año de nacimiento:", "LabelBlastMessageInterval": "Intervalo para mensajes en vivo (segundos)", @@ -1228,7 +1228,7 @@ "DatePlayed": "Reproducido el", "Descending": "Descendiente", "DirectStreamHelp1": "El tipo de archivo (H.264, AC3, etc.) y la resolución son compatibles con el dispositivo, pero no el contenedor (mkv, avi, wmv, etc.). El vídeo será re-empaquetado al vuelo antes de transmitirlo al dispositivo.", - "DirectStreamHelp2": "La transmisión directa del archivo usa muy poco procesamiento sin ninguna pérdida de calidad en el vídeo.", + "DirectStreamHelp2": "La transmisión directa del archivo usa muy poco procesamiento sin mínima pérdida de calidad en el vídeo.", "Director": "Dirección de", "Directors": "Directores", "Display": "Mostrar", @@ -1539,5 +1539,11 @@ "MessageNoRepositories": "Sin repositorios.", "Writers": "Escritores", "StopPlayback": "Detener la reproducción", - "ClearQueue": "Borrar la cola" + "ClearQueue": "Borrar la cola", + "LabelSubtitleVerticalPosition": "Posición vertical:", + "PreviousTrack": "Saltar al anterior", + "MessageGetInstalledPluginsError": "Ha ocurrido un error al recuperar la lista de plugins instalados.", + "MessagePluginInstallError": "Ha ocurrido un error al instalar este plugin.", + "NextTrack": "Saltar al siguiente", + "LabelUnstable": "Inestable" } From e0f1ac833ea98b6b93aa3f3d5bd8f39f224ac848 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 10 Aug 2020 09:53:37 +0100 Subject: [PATCH 40/51] Apply suggestions --- src/plugins/htmlAudioPlayer/plugin.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/plugins/htmlAudioPlayer/plugin.js b/src/plugins/htmlAudioPlayer/plugin.js index 86d59aa54d..acce15df88 100644 --- a/src/plugins/htmlAudioPlayer/plugin.js +++ b/src/plugins/htmlAudioPlayer/plugin.js @@ -4,10 +4,8 @@ import appHost from 'apphost'; import * as htmlMediaHelper from 'htmlMediaHelper'; function getDefaultProfile() { - return new Promise(function (resolve, reject) { - import('browserdeviceprofile').then(({default: profileBuilder}) => { - resolve(profileBuilder({})); - }); + return import('browserdeviceprofile').then(({ default: profileBuilder }) => { + return profileBuilder({}); }); } @@ -53,7 +51,7 @@ function supportsFade() { } function requireHlsPlayer(callback) { - import('hlsjs').then(({default: hls}) => { + import('hlsjs').then(({ default: hls }) => { window.Hls = hls; callback(); }); @@ -70,7 +68,7 @@ function enableHlsPlayer(url, item, mediaSource, mediaType) { // issue head request to get content type return new Promise(function (resolve, reject) { - import('fetchHelper').then(({default: fetchHelper}) => { + import('fetchHelper').then((fetchHelper) => { fetchHelper.ajax({ url: url, type: 'HEAD' From 98e666c62dc769337a37f1456882f69e9bbf18df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Kucharczyk?= Date: Mon, 10 Aug 2020 08:49:33 +0000 Subject: [PATCH 41/51] Translated using Weblate (Czech) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/cs/ --- src/strings/cs.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/strings/cs.json b/src/strings/cs.json index 80415c24af..65a4b0ba3e 100644 --- a/src/strings/cs.json +++ b/src/strings/cs.json @@ -1542,5 +1542,10 @@ "ViewAlbumArtist": "Zobrazit interpreta alba", "PreviousTrack": "Předchozí", "NextTrack": "Další", - "LabelUnstable": "Nestabilní" + "LabelUnstable": "Nestabilní", + "Preview": "Náhled", + "SubtitleVerticalPositionHelp": "Číslo řádku, na kterém se zobrazí text. Kladná čísla znamenají směr shora dolů. Záporná čísla zdola nahoru.", + "LabelSubtitleVerticalPosition": "Svislé umístění:", + "MessageGetInstalledPluginsError": "Při načítání seznamu nainstalovaných zásuvných modulů došlo k chybě.", + "MessagePluginInstallError": "Při instalaci zásuvného modulu došlo k chybě." } From b46ec31afa5289b49d49679915fed5d782dbd7b9 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 10 Aug 2020 09:59:17 +0100 Subject: [PATCH 42/51] Update package.json --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 1d7cf770d3..36f1522538 100644 --- a/package.json +++ b/package.json @@ -277,6 +277,7 @@ "src/scripts/filesystem.js", "src/scripts/globalize.js", "src/scripts/imagehelper.js", + "src/scripts/itembynamedetailpage.js", "src/scripts/inputManager.js", "src/scripts/autoThemes.js", "src/scripts/themeManager.js", From 7133294b8b8296c0b9b66d9f71cbc405b79b89e8 Mon Sep 17 00:00:00 2001 From: Justin van der Krieken Date: Mon, 10 Aug 2020 12:21:30 +0000 Subject: [PATCH 43/51] Translated using Weblate (Dutch) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/nl/ --- src/strings/nl.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/strings/nl.json b/src/strings/nl.json index 36b947855d..1e318f92c2 100644 --- a/src/strings/nl.json +++ b/src/strings/nl.json @@ -160,7 +160,7 @@ "DetectingDevices": "Apparaten detecteren", "DeviceAccessHelp": "Dit geldt alleen voor apparaten die uniek geïdentificeerd kunnen worden en voorkomen niet toegang via een webbrowser. Filteren van apparaat toegang voor gebruikers voorkomt dat zij nieuwe apparaten gebruiken totdat deze hier zijn goedgekeurd.", "DirectPlaying": "Direct afspelen", - "DirectStreamHelp1": "De resolutie en codec (bijv. H.264, AC3, etc.) wordt ondersteund door het apparaat, maar het medium is in een niet-ondersteunde bestandscontainer (bijv. mkv, avi, wmv). De video zal tijdens het afspelen opnieuw verpakt worden naar een andere bestandscontainer.", + "DirectStreamHelp1": "De resolutie en codec (H.264, AC3, etc.) wordt ondersteund door het apparaat, maar het medium is in een niet-ondersteunde bestandscontainer (mkv, avi, wmv, etc.). De video zal tijdens het afspelen opnieuw verpakt worden naar een andere bestandscontainer.", "DirectStreamHelp2": "Direct streamen van een bestand gebruikt weinig processorkracht zonder verlies van beeldkwaliteit.", "DirectStreaming": "Direct streamen", "Director": "Regiseur", @@ -267,7 +267,7 @@ "HeaderAllowMediaDeletionFrom": "Wissen van media toestaan van", "HeaderApiKey": "API Sleutel", "HeaderApiKeys": "API Sleutels", - "HeaderApiKeysHelp": "Externe applicaties zijn verplicht om een API sleutel te hebben om te communiceren met Jellyfin Server. Sleutels worden uitgegeven door in te loggen met een Jellyfin account, of door het handmatig verlenen van een sleutel voor de toepassing.", + "HeaderApiKeysHelp": "Externe applicaties zijn verplicht om een API sleutel te hebben om te communiceren met de server. Sleutels kunnen verkregen worden door in te loggen met een Jellyfin account, of door er een handmatig te verlenen.", "HeaderApp": "Applicatie", "HeaderAppearsOn": "Verschijnt op", "HeaderAudioBooks": "Luisterboeken", From e8aeaea3eef0687255810a37e176a9b1640c2721 Mon Sep 17 00:00:00 2001 From: Gorrunyo Date: Tue, 11 Aug 2020 02:37:58 +0000 Subject: [PATCH 44/51] Translated using Weblate (Catalan) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/ca/ --- src/strings/ca.json | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/strings/ca.json b/src/strings/ca.json index 7250a6cb31..e0aa04ead1 100644 --- a/src/strings/ca.json +++ b/src/strings/ca.json @@ -787,12 +787,12 @@ "ButtonTogglePlaylist": "Llista de reproducció", "ButtonToggleContextMenu": "més", "ButtonOff": "Apagar", - "BurnSubtitlesHelp": "Determina si el servidor hauria de gravar-se en els subtítols en transcodificar vídeos. Evitar això millorarà molt el rendiment. Seleccioneu Automàtica per gravar formats basats en imatges (VOBSUB, PGS, SUB, IDX) i certs subtítols ASS o SSA.", + "BurnSubtitlesHelp": "Determina si el servidor hauria de gravar els subtítols en transcodificar vídeos. Evitar això millorarà molt el rendiment. Seleccioneu Automàtica per gravar formats basats en imatges (VOBSUB, PGS, SUB, IDX) i certs subtítols ASS o SSA.", "Browse": "Navega", "BoxRear": "Caixa (posterior)", "BoxSet": "conjunt de caixes", "Box": "Caixa", - "BookLibraryHelp": "Els àudio i llibres de text són compatibles. Reviseu la {0} guia de denominació de llibres {1}.", + "BookLibraryHelp": "L'àudio i els llibres de text són compatibles. Reviseu la {0} guia de denominació de llibres {1}.", "Backdrops": "Fons", "Backdrop": "Fons", "Artist": "Artista", @@ -802,5 +802,17 @@ "AllowOnTheFlySubtitleExtractionHelp": "Els subtítols incrustats es poden extreure de vídeos i entregar-los a clients en text senzill per tal d'evitar la transcodificació de vídeo. En alguns sistemes, això pot trigar molt i fer que la reproducció de vídeo s’aturi durant el procés d’extracció. Desactiveu-ho per tenir subtítols incrustats incrustats amb la transcodificació de vídeo quan no són compatibles amb el dispositiu client de forma nativa.", "AlbumArtist": "Album artista", "Album": "Album", - "ButtonSyncPlay": "SyncPlay" + "ButtonSyncPlay": "SyncPlay", + "CriticRating": "Ràting de la crítica", + "CopyStreamURLSuccess": "L'URL s'ha copiat correctament.", + "CopyStreamURL": "Copiar l'URL de reproducció", + "ContinueWatching": "Continuar mirant", + "ConfirmEndPlayerSession": "Vols tancar Jellyfin a {0}?", + "ConfirmDeleteItems": "L'esborrat d'aquests elements els eliminarà del sistema de fitxers i de la biblioteca multimèdia. Estàs segur que vols continuar?", + "ConfirmDeleteItem": "L'esborrat d'aquest element l'eliminarà del sistema de fitxers i de la biblioteca multimèdia. Estàs segur que vols continuar?", + "ConfigureDateAdded": "Configura com es determina la data d'afegit en el quadre de comandament dins les Preferències de la biblioteca", + "CommunityRating": "Ràting comunitari", + "ColorTransfer": "Transferència de color", + "ColorSpace": "Espai de color", + "ColorPrimaries": "Colors primaris" } From f0ca919be80e2003d3c1e917e51ce710d8e47268 Mon Sep 17 00:00:00 2001 From: Gorrunyo Date: Tue, 11 Aug 2020 05:56:13 +0000 Subject: [PATCH 45/51] Translated using Weblate (Catalan) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/ca/ --- src/strings/ca.json | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/strings/ca.json b/src/strings/ca.json index e0aa04ead1..1e566d657d 100644 --- a/src/strings/ca.json +++ b/src/strings/ca.json @@ -777,7 +777,7 @@ "Aired": "Transmès", "AirDate": "Data d'emissió", "AdditionalNotificationServices": "Examineu el catàleg de complements per instal·lar serveis de notificació addicionals.", - "AddedOnValue": "Afegit {0}", + "AddedOnValue": "Afegit {0}", "Actor": "Actor", "Absolute": "Absolut", "ClientSettings": "Configuració del client", @@ -814,5 +814,10 @@ "CommunityRating": "Ràting comunitari", "ColorTransfer": "Transferència de color", "ColorSpace": "Espai de color", - "ColorPrimaries": "Colors primaris" + "ColorPrimaries": "Colors primaris", + "DefaultMetadataLangaugeDescription": "Aquests són els teus valors per defecte i poden ser personalitats per cada biblioteca.", + "Default": "Per defecte", + "DatePlayed": "Data reproduït", + "DateAdded": "Data d'afegit", + "CustomDlnaProfilesHelp": "Crear un perfil personalitzat per a un nou dispositiu o substitueix un perfil de sistema." } From faf465603d068931b1a43fa0d4761429eb8fc209 Mon Sep 17 00:00:00 2001 From: Ted van den Brink Date: Tue, 11 Aug 2020 07:42:56 +0000 Subject: [PATCH 46/51] Translated using Weblate (Dutch) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/nl/ --- src/strings/nl.json | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/strings/nl.json b/src/strings/nl.json index 1e318f92c2..1e092d3328 100644 --- a/src/strings/nl.json +++ b/src/strings/nl.json @@ -332,7 +332,7 @@ "HeaderInstall": "Installeer", "HeaderKeepRecording": "Bewaar opname", "HeaderKeepSeries": "Series behouden", - "HeaderKodiMetadataHelp": "Om NFO-metadata in of uit te schakelen, gaat u naar de Jellyfin bibliotheekinstellingen en vervolgens naar de metadata-downloaders sectie.", + "HeaderKodiMetadataHelp": "Om NFO-metadata in of uit te schakelen, bewerk een bibliotheek en zoek in de metadata-downloaders sectie.", "HeaderLatestEpisodes": "Nieuwste Afleveringen", "HeaderLatestMedia": "Nieuwste Media", "HeaderLatestMovies": "Nieuwste Films", @@ -452,7 +452,7 @@ "HttpsRequiresCert": "Om beveiligde verbindingen in te schakelen, is een vertrouwd SSL-certificaat vereist (zoals Let's Encrypt). Geef een certificaat op of schakel beveiligde verbindingen uit.", "Identify": "Identificeer", "Images": "Afbeeldingen", - "ImportFavoriteChannelsHelp": "Bij inschakelen zullen alleen kanalen geïmporteerd worden die op de tuner als favoriet aangemerkt zijn.", + "ImportFavoriteChannelsHelp": "Alleen kanalen die als favoriet aangemerkt zijn op de tuner zullen geïmporteerd worden.", "ImportMissingEpisodesHelp": "Indien ingeschakeld, wordt informatie over ontbrekende afleveringen in uw Jellyfin de database geïmporteerd en weergegeven in de seizoenen en series. Dit kan aanzienlijk langere bibliotheekscans veroorzaken.", "InstallingPackage": "Installeren van {0} (versie {1})", "Kids": "Kinderen", @@ -480,14 +480,14 @@ "LabelAppName": "Applicatie Naam", "LabelAppNameExample": "Voorbeeld: Sickbeard, Sonarr", "LabelArtists": "Artiest:", - "LabelArtistsHelp": "Scheidt meerdere met een ;", + "LabelArtistsHelp": "Scheidt artiesten met een ;", "LabelAudioLanguagePreference": "Voorkeurs audiotaal:", "LabelAutomaticallyRefreshInternetMetadataEvery": "Vernieuw metagegevens automatisch van het internet:", "LabelBindToLocalNetworkAddress": "Binden aan het lokale netwerk adres:", "LabelBindToLocalNetworkAddressHelp": "Optioneel. Overrule het lokale IP-adres om aan de http-server te binden. Indien leeg gelaten, zal de server binden aan alle beschikbare adressen. Het veranderen van deze waarde vereist herstarten van Jellyfin Server.", "LabelBirthDate": "Geboortedatum:", "LabelBirthYear": "Geboorte jaar:", - "LabelBlastMessageInterval": "Alive bericht interval (seconden)", + "LabelBlastMessageInterval": "Alive bericht interval", "LabelBlastMessageIntervalHelp": "Bepaalt de duur in seconden tussen Blast Alive berichten.", "LabelBlockContentWithTags": "Blokkeer items met volgende tags:", "LabelBurnSubtitles": "Ondertitels inbranden:", @@ -512,7 +512,7 @@ "LabelCustomRating": "Aangepaste classificatie:", "LabelDateAdded": "Datum toegevoegd:", "LabelDateAddedBehavior": "Datum toegevoegd gedrag voor nieuwe content:", - "LabelDateAddedBehaviorHelp": "Als metadata gegevens aanwezig zijn hebben deze voorrang op deze opties.", + "LabelDateAddedBehaviorHelp": "Als metadata gegevens aanwezig is krijgt deze voorrang op deze opties.", "LabelDateTimeLocale": "Datum en tijd regio:", "LabelDay": "Dag:", "LabelDeathDate": "Overlijdens datum:", @@ -540,12 +540,12 @@ "LabelEnableAutomaticPortMapHelp": "Publieke poort automatisch doorsturen naar een lokale poort via UPnP. Dit werkt niet op alle routers en netwerk configuraties. De wijzigingen worden pas actief na een herstart van de server.", "LabelEnableBlastAliveMessages": "Alive berichten zenden", "LabelEnableBlastAliveMessagesHelp": "Zet dit aan als de server niet betrouwbaar door andere UPnP-apparaten op uw netwerk wordt gedetecteerd.", - "LabelEnableDlnaClientDiscoveryInterval": "Interval voor het zoeken naar clients (seconden)", + "LabelEnableDlnaClientDiscoveryInterval": "Interval voor het zoeken naar clients", "LabelEnableDlnaClientDiscoveryIntervalHelp": "Bepaalt de duur in seconden tussen SSDP zoekopdrachten uitgevoerd door Jellyfin.", "LabelEnableDlnaDebugLogging": "DLNA foutopsporings logboek inschakelen", "LabelEnableDlnaDebugLoggingHelp": "Genereer grote logboekbestanden en is alleen bedoeld voor het troubleshooting doeleinden.", "LabelEnableDlnaPlayTo": "DLNA \"Play To\" inschakelen", - "LabelEnableDlnaPlayToHelp": "Apparaten detecteren binnen uw netwerk en maak het mogelijk om ze op afstand te controleren.", + "LabelEnableDlnaPlayToHelp": "Apparaten detecteren binnen uw netwerk en maak het mogelijk om ze op afstand te gebruiken.", "LabelEnableDlnaServer": "DLNA server inschakelen", "LabelEnableDlnaServerHelp": "Sta UPnP apparaten op uw netwerk toe om door inhoud te bladeren en deze af te spelen.", "LabelEnableHardwareDecodingFor": "Activeer hardwaredecodering voor:", @@ -568,7 +568,7 @@ "LabelFriendlyName": "Gebruiksvriendelijke naam:", "LabelServerNameHelp": "Deze naam wordt gebruikt om de server te identificeren, standaard is deze de server zijn computer naam.", "LabelGroupMoviesIntoCollections": "Groepeer films in collecties", - "LabelGroupMoviesIntoCollectionsHelp": "Bij de weergave van film lijsten, zullen films die tot een collectie behoren worden weergegeven als een gegroepeerd object.", + "LabelGroupMoviesIntoCollectionsHelp": "Bij de weergave van film lijsten, zullen films in een collectie worden weergegeven als een gegroepeerd object.", "LabelEncoderPreset": "H264 codering preset:", "LabelHardwareAccelerationType": "Hardware acceleratie:", "LabelHardwareAccelerationTypeHelp": "Hardwarematige versnelling vereist extra configuratie.", @@ -1507,7 +1507,7 @@ "LabelRequireHttps": "HTTPS verplichten", "LabelStable": "Stabiel", "LabelChromecastVersion": "Chromecast versie", - "LabelEnableHttpsHelp": "Hiermee kan de server luisteren op de geconfigureerde HTTPS-poort. Om dit te laten werken moet ook een geldig certificaat worden geconfigureerd.", + "LabelEnableHttpsHelp": "Luisteren op de geconfigureerde HTTPS-poort. Om dit te laten werken moet ook een geldig certificaat worden ingesteld.", "LabelEnableHttps": "HTTPS inschakelen", "HeaderSyncPlayEnabled": "SyncPlay ingeschakeld", "HeaderSyncPlaySelectGroup": "Word lid van een groep", @@ -1537,5 +1537,10 @@ "LabelRepositoryUrlHelp": "De locatie van het repository manifest dat je wilt gebruiken.", "LabelRepositoryUrl": "Repository URL", "HeaderNewRepository": "Nieuwe repository", - "MessageNoRepositories": "Geen repositories." + "MessageNoRepositories": "Geen repositories.", + "LabelSubtitleVerticalPosition": "Verticale positie:", + "TabRepositories": "Repositories", + "MessageGetInstalledPluginsError": "Er is een fout opgetreden bij het ophalen van de lijst met geïnstalleerde plugins.", + "MessagePluginInstallError": "Er is een fout opgetreden tijdens het installeren van de plugin.", + "LabelUnstable": "Niet stabiel" } From 91dcdafe2302038329dcae199b4e149a54bbf2e2 Mon Sep 17 00:00:00 2001 From: Ted van den Brink Date: Tue, 11 Aug 2020 07:56:13 +0000 Subject: [PATCH 47/51] Translated using Weblate (Dutch) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/nl/ --- src/strings/nl.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings/nl.json b/src/strings/nl.json index 1e092d3328..0ea745cc34 100644 --- a/src/strings/nl.json +++ b/src/strings/nl.json @@ -575,7 +575,7 @@ "LabelHomeNetworkQuality": "Thuisnetwerk kwaliteit:", "LabelHomeScreenSectionValue": "Beginscherm sectie {0}:", "LabelHttpsPort": "Lokale HTTPS poort nummer:", - "LabelHttpsPortHelp": "Het TCP poort nummer waar Jellyfin's HTTPS server aan moet verbinden.", + "LabelHttpsPortHelp": "Het TCP poort nummer voor de HTTPS server.", "LabelIconMaxHeight": "Pictogram maximum hoogte:", "LabelIconMaxHeightHelp": "Maximum resolutie van pictogrammen weergegeven via upnp:icon.", "LabelIconMaxWidth": "Pictogram maximum breedte:", From b92c4749b30dba750dd44e61a964f2e38e9610f0 Mon Sep 17 00:00:00 2001 From: Ted van den Brink Date: Tue, 11 Aug 2020 08:12:47 +0000 Subject: [PATCH 48/51] Translated using Weblate (Dutch) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/nl/ --- src/strings/nl.json | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/strings/nl.json b/src/strings/nl.json index 0ea745cc34..5de7589647 100644 --- a/src/strings/nl.json +++ b/src/strings/nl.json @@ -602,7 +602,7 @@ "LabelLanNetworks": "LAN-netwerken:", "LabelLanguage": "Taal:", "LabelLocalHttpServerPortNumber": "Lokale HTTP poort nummer:", - "LabelLocalHttpServerPortNumberHelp": "Het TCP poort nummer waar Jellyfin's HTTP server aan moet verbinden.", + "LabelLocalHttpServerPortNumberHelp": "Het TCP poort nummer voor de HTTP server.", "LabelLockItemToPreventChanges": "Vergrendel dit item om toekomstige wijzigingen te voorkomen", "LabelLoginDisclaimer": "Aanmeld vrijwaring:", "LabelLoginDisclaimerHelp": "Een bericht dat weergeven zal worden onderaan op de login pagina.", @@ -642,7 +642,7 @@ "LabelMovieCategories": "Film categoriën:", "LabelMoviePrefix": "Film voorvoegsel:", "LabelMoviePrefixHelp": "Als een voorvoegsel wordt toegepast op filmtitels, typ deze dan eventueel hier zodat de server het goed kan verwerken.", - "LabelMovieRecordingPath": "Filmopname pad (optioneel):", + "LabelMovieRecordingPath": "Filmopname pad:", "LabelMusicStreamingTranscodingBitrate": "Muziek transcodering bitrate:", "LabelMusicStreamingTranscodingBitrateHelp": "Geef een maximum bitrate op voor het streamen van muziek.", "LabelName": "Naam:", @@ -655,7 +655,7 @@ "LabelNumber": "Nummer:", "LabelNumberOfGuideDays": "Aantal dagen van de gids om te downloaden:", "LabelNumberOfGuideDaysHelp": "Het downloaden van meer dagen van de gids gegevens biedt de mogelijkheid verder vooruit te plannen en een beter overzicht geven, maar het zal ook langer duren om te downloaden. Auto kiest op basis van het aantal kanalen.", - "LabelOptionalNetworkPath": "(Optioneel) Gedeelde netwerkmap:", + "LabelOptionalNetworkPath": "Gedeelde netwerkmap:", "LabelOptionalNetworkPathHelp": "Als deze map wordt gedeeld op uw netwerk, kunnen middels het netwerkpad Jellyfin apps op andere apparaten rechtstreeks toegang tot mediabestanden krijgen. Bijvoorbeeld {0} or {1}.", "LabelOriginalAspectRatio": "Originele aspect ratio:", "LabelOriginalTitle": "Orginele titel:", @@ -696,7 +696,7 @@ "LabelReleaseDate": "Uitgave datum:", "LabelRemoteClientBitrateLimit": "Internet streaming bitrate limiet (Mbps):", "LabelRemoteClientBitrateLimitHelp": "Een optionele bitrate per stream limiet voor alle apparaten buiten het netwerk. Dit is handig om te voorkomen dat apparaten een hogere bitrate vragen dan je internetverbinding aan kan. Dit kan een verhoogde belasting van de CPU in je server veroorzaken om videos direct te transcoderen naar een lagere bitrate.", - "LabelRuntimeMinutes": "Speelduur (minuten):", + "LabelRuntimeMinutes": "Speelduur:", "LabelSaveLocalMetadata": "Afbeeldingen opslaan in mediamappen", "LabelSaveLocalMetadataHelp": "Door afbeeldingen op te slaan in de mediamappen kunnen ze makkelijker worden aangepast.", "LabelScheduledTaskLastRan": "Laatste keer {0}, duur {1}.", @@ -708,7 +708,7 @@ "LabelSelectVersionToInstall": "Selecteer de versie om te installeren:", "LabelSendNotificationToUsers": "Stuur de melding naar:", "LabelSerialNumber": "Serienummer", - "LabelSeriesRecordingPath": "Serieopname pad (optioneel):", + "LabelSeriesRecordingPath": "Serieopname pad:", "LabelServerHost": "Server:", "LabelServerHostHelp": "192.168.1.100:8096 of https://mijnserver.nl", "LabelSimultaneousConnectionLimit": "Gelijktijdige stream limiet:", @@ -891,7 +891,7 @@ "OptionAllowLinkSharingHelp": "Alleen webpagina's met media-informatie worden gedeeld. Media-bestanden worden nooit publiekelijk gedeeld. Gedeelde items zijn beperkt in tijd en verlopen na {0} dagen.", "OptionAllowManageLiveTv": "Live TV opname beheer toestaan", "OptionAllowMediaPlayback": "Media afspelen toestaan", - "OptionAllowMediaPlaybackTranscodingHelp": "Toegang tot transcodering beperken kan afspeelfouten in Jellyfin apps door niet ondersteunde madiaformaten veroorzaken.", + "OptionAllowMediaPlaybackTranscodingHelp": "Het beperken van toegang tot transcodering kan afspeelfouten in clients veroorzaken door niet ondersteunde madiaformaten.", "OptionAllowRemoteControlOthers": "Op afstand besturen van andere gebruikers toestaan", "OptionAllowRemoteSharedDevices": "Op afstand besturen van gedeelde apparaten toestaan", "OptionAllowRemoteSharedDevicesHelp": "DLNA apparaten worden als gedeeld apparaat gezien totdat een gebruiker deze gaat gebruiken.", @@ -903,7 +903,7 @@ "OptionAscending": "Oplopend", "OptionAutomatic": "Automatisch", "OptionAutomaticallyGroupSeries": "Automatisch samenvoegen serie die zijn verspreid over meerdere mappen", - "OptionAutomaticallyGroupSeriesHelp": "Indien ingeschakeld, zal serie die zijn verspreid over meerdere mappen binnen deze bibliotheek automatisch samengevoegd tot één serie.", + "OptionAutomaticallyGroupSeriesHelp": "Serie die verspreid zijn over meerdere mappen binnen deze bibliotheek worden automatisch samengevoegd tot één serie.", "OptionBlockBooks": "Boeken", "OptionBlockChannelContent": "Internet kanaal Inhoud", "OptionBlockLiveTvChannels": "Live TV Kanalen", @@ -922,7 +922,7 @@ "OptionDatePlayed": "Datum afgespeeld", "OptionDescending": "Aflopend", "OptionDisableUser": "Deze gebruiker uitschakelen", - "OptionDisableUserHelp": "Indien uitgeschakeld zal de server geen verbindingen van deze gebruiker toestaan. Bestaande verbindingen zullen abrupt worden beëindigd.", + "OptionDisableUserHelp": "De server staat geen verbindingen van deze gebruiker toe. Bestaande verbindingen zullen abrupt worden beëindigd.", "OptionDislikes": "Niet leuk", "OptionDisplayFolderView": "Toon een mappenweergave als u gewoon Mediamappen wilt weergeven", "OptionDisplayFolderViewHelp": "Geef folders weer naast uw andere media bibliotheken. Dit kan handig zijn als u een oppervlakkig folder aanzicht wilt hebben.", @@ -1542,5 +1542,6 @@ "TabRepositories": "Repositories", "MessageGetInstalledPluginsError": "Er is een fout opgetreden bij het ophalen van de lijst met geïnstalleerde plugins.", "MessagePluginInstallError": "Er is een fout opgetreden tijdens het installeren van de plugin.", - "LabelUnstable": "Niet stabiel" + "LabelUnstable": "Niet stabiel", + "NextTrack": "Ga naar volgende" } From 7118d4456efaec83e40078ec385aa722b4911b9a Mon Sep 17 00:00:00 2001 From: nextlooper42 Date: Tue, 11 Aug 2020 09:23:17 +0000 Subject: [PATCH 49/51] Translated using Weblate (Slovak) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/sk/ --- src/strings/sk.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/strings/sk.json b/src/strings/sk.json index 44c5751175..70511a84e5 100644 --- a/src/strings/sk.json +++ b/src/strings/sk.json @@ -916,7 +916,7 @@ "ConfirmDeleteItems": "Zmazaním týchto položiek odstránite súbory zo súborového systému aj z knižnice médií. Ste si istý/á, že chcete pokračovať?", "Continuing": "Pokračujúci", "Default": "Predvolené", - "DirectStreamHelp2": "Priame streamovanie súboru používa veľmi málo procesorového výkonu bez straty kvality videa.", + "DirectStreamHelp2": "Priame streamovanie vyžaduje veľmi málo výkonu takmer bez straty kvality videa.", "DirectStreaming": "Priame streamovanie", "DisplayMissingEpisodesWithinSeasonsHelp": "Toto musí byť povolené pre TV knižnice v nastavení servera.", "DisplayModeHelp": "Vyberte štýl layoutu, ktorý chcete pre rozhranie.", @@ -1086,13 +1086,13 @@ "HeaderResponseProfile": "Profil odozvy", "HeaderRemoveMediaLocation": "Odobrať cestu medií", "HeaderRecordingPostProcessing": "Spracovanie nahratých nahrávok", - "HeaderProfileServerSettingsHelp": "Tieto hodnoty určujú, ako sa bude Jellyfin Server prezentovať v zariadeniach.", + "HeaderProfileServerSettingsHelp": "Tieto hodnoty určujú, ako sa bude server prezentovať klientom.", "HeaderPluginInstallation": "Inštalácia zásuvných modulov", "HeaderPlayback": "Prehrávanie medií", "HeaderPlayOn": "Prehrať na", "HeaderOnNow": "Práve teraz", "HeaderLiveTvTunerSetup": "Nastavenie TV tuneru pre živé vysielanie", - "HeaderKodiMetadataHelp": "Pokiaľ chcete povoliť alebo zakázať NFO metadáta, upravte knižnicu v nastavení Jellyfin knižníc v sekcii ukladania metadát.", + "HeaderKodiMetadataHelp": "Pokiaľ chcete povoliť alebo zakázať NFO metadáta, upravte knižnicu v sekcii ukladania metadát.", "HeaderKeepSeries": "Zachovať seriál", "HeaderKeepRecording": "Zachovať nahrávanie", "HeaderImageOptions": "Možnosti obrázkov", @@ -1118,7 +1118,7 @@ "HeaderBlockItemsWithNoRating": "Blokované položky so žiadnymi alebo nerozpoznanými informáciami o hodnotení:", "HeaderAppearsOn": "Objaví sa", "HeaderApp": "Appka", - "HeaderApiKeysHelp": "Externé aplikácie musia mať vlastný API kľúč, aby mohli komunikovať s Jellyfin Serverom. Kľúče sú vydávané pomocou prihlásenia sa cez Jellyfin účet alebo manuálnym priradením kľúča aplikácií.", + "HeaderApiKeysHelp": "Externé aplikácie musia mať vlastný API kľúč, aby mohli komunikovať so serverom. Kľúče sú vydávané pomocou prihlásenia cez bežný účet alebo manuálnym priradením kľúča aplikácií.", "HeaderAdditionalParts": "Dodatočné časti", "HardwareAccelerationWarning": "Povolenie hardvérovej akcelerácie môže spôsobiť nestabilitu v niektorých podmienkach. Uistite sa, že váš operačný systém a grafické ovládače sú plne aktualizované. Pokiaľ máte po zapnutí problémy s prehrávaním videa, budete musieť zmeniť nastavenie späť na Žiadne.", "EncoderPresetHelp": "Vyberte hodnotu faster pre zlepšenie výkonu alebo hodnotu slower pre zlepšenie kvality.", @@ -1142,7 +1142,7 @@ "EnableExternalVideoPlayersHelp": "Ponuka externého prehrávača sa zobrazí pri spustení prehrávania videa.", "EnableBackdropsHelp": "Zobraziť pozadia na pozadí pre niektoré stránky pri prechádzaní knižnice.", "DisplayInOtherHomeScreenSections": "Zobrazenie v sekciách domovskej obrazovky, ako sú najnovšie médiá a pokračovať v pozeraní", - "DirectStreamHelp1": "Médium je kompatibilné zo zariadením nezávisle na rozlíšení alebo type média (H.264, AC3, atď.), je však v nekompatibilnom kontajneri (mkv, avi, wmv, atď.). Video bude za behu prebalené do kompatibilného kontajnera ešte pred streamovaním do zariadenia.", + "DirectStreamHelp1": "Médium je kompatibilné zo zariadením nezávisle na rozlíšení alebo type média (H.264, AC3, atď.), je však v nekompatibilnom kontajneri (mkv, avi, wmv, atď.). Video bude za behu prebalené do kompatibilného kontajnera ešte pred odoslaním do zariadenia.", "Depressed": "Stlačený", "DefaultSubtitlesHelp": "Titulky sú načítané v závislosti od predvolených a vynútených nastavení v zabudovaných metadátach. Jazykové predvoľby sú zobrané do úvahy až vtedy, keď je k dispozícií viacero možností.", "DefaultMetadataLangaugeDescription": "Toto sú vaše predvolené hodnoty ktoré môžu byť prispôsobené na základe jednotlivých knižníc.", From 3438ddae7711df0475092b1be0163c773d9b317f Mon Sep 17 00:00:00 2001 From: nextlooper42 Date: Tue, 11 Aug 2020 09:28:04 +0000 Subject: [PATCH 50/51] Translated using Weblate (Slovak) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/sk/ --- src/strings/sk.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/strings/sk.json b/src/strings/sk.json index 70511a84e5..60c5f2c4d4 100644 --- a/src/strings/sk.json +++ b/src/strings/sk.json @@ -300,7 +300,7 @@ "Horizontal": "Horizontálne", "Identify": "Identifikovať", "Images": "Obrázky", - "ImportMissingEpisodesHelp": "Ak je možnosť povolená, informácie o chýbajúcich epizódach budú importované do Vašej Jellyfin databázy a budú zobrazené v sériách a seriáloch. Toto môže spôsobiť podstatne dlhšie skenovania knižníc.", + "ImportMissingEpisodesHelp": "Informácie o chýbajúcich epizódach budú importované do Vašej databázy a budú zobrazené v sériách a seriáloch. Toto môže spôsobiť podstatne dlhšie skenovania knižníc.", "InstallingPackage": "Inštalujem {0} (verzia{1})", "ItemCount": "{0} položiek", "Items": "Položky", @@ -1067,7 +1067,7 @@ "LabelAllowedRemoteAddressesMode": "Režim filtrácie vzdialenej IP adresy:", "LabelAlbumArtists": "Album umelca:", "InstantMix": "Okamžitý mix", - "ImportFavoriteChannelsHelp": "Pokiaľ je možnosť povolená, tak len kanály označené ako obľúbené budú importované na zariadenie tuneru.", + "ImportFavoriteChannelsHelp": "Len kanály označené ako obľúbené budú importované na zariadenie tuneru.", "HttpsRequiresCert": "Pre povolenie zabezpečeného pripojenia budete musieť dodať dôveryhodný SSL certifikát, ako napríklad Let's Encrypt. Prosím, buď dodajte certifikát alebo zakážte zabezpečené pripojenie.", "HeaderXmlDocumentAttributes": "Atribúty XML dokumentu", "HeaderXmlDocumentAttribute": "Atribúty XML dokumentu", @@ -1076,7 +1076,7 @@ "HeaderTranscodingProfileHelp": "Pridať transkódovacie profily pre určenie, ktoré formáty by mali byť použité, keď je transkódovanie vyžadované.", "HeaderSubtitleProfilesHelp": "Profily titulkov popisujú formáty titulkov, ktoré dané zariadenie podporuje.", "HeaderSeriesStatus": "Stav seriálu", - "HeaderSelectTranscodingPathHelp": "Prechádzať alebo zadať cestu, kde by ste chceli uložiť dočasné transkódované súbory. Priečinok musí mať oprávnenie na zapisovanie.", + "HeaderSelectTranscodingPathHelp": "Prechádzať alebo zadať cestu pre súbory transkódovania. Priečinok musí mať oprávnenie na zapisovanie.", "HeaderSelectTranscodingPath": "Vyberte cestu pre dočasné transkódované súbory", "HeaderSelectServerCachePathHelp": "Prechádzať alebo zadať cestu, kde by ste chceli uložiť cache súbory. Priečinok musí mať oprávnenie na zapisovanie.", "HeaderSelectServerCachePath": "Vyberte cestu pre Server Cache", From f2540fb8dbf5763f40cd208335de37452ed149ad Mon Sep 17 00:00:00 2001 From: nextlooper42 Date: Tue, 11 Aug 2020 09:29:35 +0000 Subject: [PATCH 51/51] Translated using Weblate (Slovak) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/sk/ --- src/strings/sk.json | 60 +++++++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 26 deletions(-) diff --git a/src/strings/sk.json b/src/strings/sk.json index 60c5f2c4d4..25b54db853 100644 --- a/src/strings/sk.json +++ b/src/strings/sk.json @@ -314,7 +314,7 @@ "LabelAppName": "Názov aplikácie", "LabelAppNameExample": "Príklad: Sickbeard, Sonarr", "LabelArtists": "Umelci:", - "LabelArtistsHelp": "Oddeľte pomocou ;", + "LabelArtistsHelp": "Viacej umelcov oddeľte pomocou bodkočiarky.", "LabelAudioLanguagePreference": "Uprednostňovaný jazyk zvuku:", "LabelAutomaticallyRefreshInternetMetadataEvery": "Automaticky obnoviť metadáta z internetu:", "LabelBirthDate": "Dátum narodenia:", @@ -367,9 +367,9 @@ "LabelFont": "Písmo:", "LabelForgotPasswordUsernameHelp": "Zadajte svoje používateľské meno, ak si ho pamätáte.", "LabelFormat": "Formát:", - "LabelServerNameHelp": "Tento názov bude použitý na identifikáciu servera. Ak ostane prázdny, bude použitý názov počítača.", + "LabelServerNameHelp": "Tento názov bude použitý na identifikáciu servera. Ak ostane prázdny, bude použitý názov hostiteľa serveru.", "LabelGroupMoviesIntoCollections": "Zoskupiť filmy do kolekcií", - "LabelGroupMoviesIntoCollectionsHelp": "Pri zobrazení zoznamu filmov budú filmy patriace do kolekcie zobrazené ako jedna zoskupená položka.", + "LabelGroupMoviesIntoCollectionsHelp": "Pri zobrazení zoznamu filmov budú filmy v kolekcií zobrazené ako jedna položka.", "LabelHardwareAccelerationType": "Hardvérová akcelerácia:", "LabelHardwareAccelerationTypeHelp": "Hardvérová akcelerácia vyžaduje dodatočnú konfiguráciu.", "LabelHomeScreenSectionValue": "Sekcia domácej obrazovky {0}:", @@ -401,7 +401,7 @@ "LabelMetadata": "Metadáta:", "LabelMetadataDownloadLanguage": "Preferovaný jazyk:", "LabelMetadataPath": "Umiestnenie metadát:", - "LabelMetadataSaversHelp": "Vyberte formát súboru, do ktorého chcete ukladať vaše metadáta.", + "LabelMetadataSaversHelp": "Vyberte formát súboru, ktorý chcete použiť pre ukladanie metadát.", "LabelMinResumeDurationHelp": "Najkratšia dĺžka videa v sekundách, ktorá uloží rozpozeranú polohu a dovolí sa k nej vrátiť.", "LabelMinResumePercentageHelp": "Tituly budú považované za neprehrané ak budú zastavené pred týmto časom.", "LabelModelDescription": "Popis modelu", @@ -409,7 +409,7 @@ "LabelModelNumber": "Číslo modelu", "LabelModelUrl": "Model URL", "LabelMovieCategories": "Kategórie filmov:", - "LabelMovieRecordingPath": "Umiestnenie filmových nahrávok (voliteľné):", + "LabelMovieRecordingPath": "Umiestnenie pre nahrávanie filmov:", "LabelName": "Meno:", "LabelNewName": "Nové meno:", "LabelNewPassword": "Nové heslo:", @@ -418,7 +418,7 @@ "LabelNext": "Ďalej", "LabelNotificationEnabled": "Povoliť toto hlásenie", "LabelNumber": "Číslo:", - "LabelOptionalNetworkPath": "(Voliteľné) Zdieľaný sieťový priečinok:", + "LabelOptionalNetworkPath": "Zdieľaný sieťový priečinok:", "LabelOriginalAspectRatio": "Pôvodný pomer strán:", "LabelOriginalTitle": "Pôvodný názov:", "LabelOverview": "Prehľad:", @@ -445,14 +445,14 @@ "LabelRecordingPath": "Predvolené umiestnenie nahrávok:", "LabelRecordingPathHelp": "Uveďte predvolené umiestnenie pre ukladanie nahrávok. Ak je ponechané prázdne, použije sa priečinok s programovými dátami servera.", "LabelReleaseDate": "Dátum vydania:", - "LabelRuntimeMinutes": "Dĺžka (minúty):", + "LabelRuntimeMinutes": "Dĺžka:", "LabelSaveLocalMetadata": "Uložiť obaly a metadáta do priečinka s médiami", "LabelScreensaver": "Šetrič obrazokvy:", "LabelSeasonNumber": "Číslo série:", "LabelSelectUsers": "Zvoľte užívateľov:", "LabelSelectVersionToInstall": "Vyberte verziu, ktorú chcete nainštalovať:", "LabelSerialNumber": "Sériové číslo", - "LabelSeriesRecordingPath": "Umiestnenie seriálových nahrávok (voliteľné):", + "LabelSeriesRecordingPath": "Umiestnenie pre nahrávanie seriálov:", "LabelServerHostHelp": "192.168.1.100:8096 alebo https://mojserver.sk", "LabelSkipBackLength": "Dĺžka skoku dozadu:", "LabelSkipForwardLength": "Dĺžka skoku dopredu:", @@ -609,7 +609,7 @@ "OptionDatePlayed": "Dátum prehrania", "OptionDescending": "Zostupne", "OptionDisableUser": "Zakázať tohto používateľa", - "OptionDisableUserHelp": "Ak možnosť nie je povolená, server nepovolí žiadne pripojenia od tohto používateľa. Aktívne pripojenia budú ihneď ukončené.", + "OptionDisableUserHelp": "Server nepovolí žiadne pripojenia od tohto používateľa. Aktívne pripojenia budú ihneď ukončené.", "OptionDislikes": "Nepáči sa", "OptionDownloadArtImage": "Obal", "OptionDownloadBackImage": "Späť", @@ -998,7 +998,7 @@ "Absolute": "Absolútne", "LabelDidlMode": "DIDL režim:", "LabelDateTimeLocale": "Lokálne nastavenia dátumu:", - "LabelBlastMessageInterval": "Doba zobrazenie správy (sekundy)", + "LabelBlastMessageInterval": "Doba zobrazenia správy", "LabelAlbumArtMaxWidth": "Maximálna šírka obrázku albumu:", "LabelAlbumArtMaxHeight": "Maximálna výška obrázku albumu:", "LabelAirDays": "Vysielané:", @@ -1199,7 +1199,7 @@ "SeriesCancelled": "Seriál zrušený.", "SelectAdminUsername": "Prosím, vyberte si používateľské meno pre účet administrátora.", "RefreshQueued": "Obnovenie zaradené do fronty.", - "RefreshDialogHelp": "Metadáta sa obnovujú na základe nastavení a internetových služieb, ktoré sú povolené v dashboarde Jellyfin Serveru.", + "RefreshDialogHelp": "Metadáta sa obnovujú na základe nastavení a internetových služieb, ktoré sú povolené v dashboarde.", "MessageChangeRecordingPath": "Zmenou priečinku pre nahrávanie sa existujúce nahrávky automaticky nepresunú zo starej lokácie na na novú. Budete ich musieť presunúť ručne, pokiaľ budete chcieť.", "RecordSeries": "Nahrať sériu", "Raised": "Vystupujúce", @@ -1221,18 +1221,18 @@ "OptionRandom": "Náhodne", "OptionProfileVideoAudio": "Video Zvuk", "OptionPosterCard": "Plagátová karta", - "OptionPlainVideoItemsHelp": "Pokiaľ je povolené, všetky videá sú reprezentované v DIDL ako \"object.item.videoItem\" namiesto viac špecifického typu, ako napríklad \"object.item.videoItem.movie\".", - "OptionPlainStorageFoldersHelp": "Pokiaľ je povolené, všetky priečinky sú reprezentované v DIDL ako \"object.container.storageFolder\" namiesto viac špecifického typu, ako napríklad \"object.container.person.musicArtist\".", + "OptionPlainVideoItemsHelp": "Všetky videá sú reprezentované v DIDL ako \"object.item.videoItem\" namiesto viac špecifického typu, ako napríklad \"object.item.videoItem.movie\".", + "OptionPlainStorageFoldersHelp": "Všetky priečinky sú reprezentované v DIDL ako \"object.container.storageFolder\" namiesto viac špecifického typu, ako napríklad \"object.container.person.musicArtist\".", "OptionPlainStorageFolders": "Zobraziť všetky priečinky ako jednoduché priečinky pre ukladanie", "OptionOnInterval": "V intervale", "OptionLoginAttemptsBeforeLockoutHelp": "Hodnota 0 znamená zdedenie východzej hodnoty troch pokusov pre bežného používateľa a päť pre administrátora. Nastavením na -1 sa táto funkcia zakáže.", "OptionLoginAttemptsBeforeLockout": "Určuje, koľko chybných prihlásení môže byť urobených pred uzamknutím.", - "OptionIgnoreTranscodeByteRangeRequestsHelp": "Pokiaľ je povolené, budú tieto požiadavky aj naďalej plnené, avšak hlavičky bajtových rozsahov budú ignorované.", + "OptionIgnoreTranscodeByteRangeRequestsHelp": "Tieto požiadavky budú aj naďalej plnené, avšak hlavičky bajtových rozsahov budú ignorované.", "OptionIgnoreTranscodeByteRangeRequests": "Ignorovať požiadavky na transkódovanie bajtového rozsahu", "OptionHlsSegmentedSubtitles": "HLS segmentované titulky", "OptionExternallyDownloaded": "Externé sťahovanie", "OptionEnableExternalContentInSuggestionsHelp": "Povoliť zahrnutie internetových trailerov a živých TV programov do navrhovaného obsahu.", - "OptionDownloadImagesInAdvanceHelp": "Vo východzom stave sa väčšina obrázkov sťahuje až po vyžiadaní Jellyfin aplikáciou. Povolením tejto možnosti sa budú všetky obrázky sťahovať popredu, keď sa budú importovať nové médiá. Toto môže spôsobiť výrazne dlhšie skenovanie knižnice.", + "OptionDownloadImagesInAdvanceHelp": "Vo východzom stave sa väčšina obrázkov sťahuje až po vyžiadaní klientom. Povolením tejto možnosti sa budú všetky obrázky sťahovať popredu, keď sa budú importovať nové médiá. Toto môže spôsobiť výrazne dlhšie skenovanie knižnice.", "OptionDownloadBoxImage": "Krabica", "OptionDownloadBannerImage": "Banner", "OptionDisplayFolderViewHelp": "Zobraziť priečinky popri ostatných médiách v knižnici. Toto môže byť užitočné, pokiaľ chcete vidieť jednoduché zobrazenie priečinku.", @@ -1240,10 +1240,10 @@ "OptionBlockTvShows": "Seriál", "OptionBlockLiveTvChannels": "Živé TV kanály", "OptionBanner": "Banner", - "OptionAutomaticallyGroupSeriesHelp": "Pokiaľ je povolené, tak sa série, ktoré sú rozhádzané skrz rôzne priečinky, budú automaticky v tejto knižnici zlučovať do jedného seriálu.", + "OptionAutomaticallyGroupSeriesHelp": "Seriály uložené vo viacerých priečinkoch v tejto knižnici, budú automaticky zlúčené do jedného seriálu.", "OptionAllowVideoPlaybackRemuxing": "Povoliť prehrávanie videa, ktoré vyžaduje konverziu bez opätovného enkódovania", "OptionAllowSyncTranscoding": "Povoliť sťahovanie a synchronizáciu medií, ktoré vyžadujú transkódovanie", - "OptionAllowMediaPlaybackTranscodingHelp": "Obmedzenie prístupu ku transkódovaniu môže spôsobiť zlyhania prehrávania v Jellyfin aplikáciách kvôli nepodporovaným formátom medií.", + "OptionAllowMediaPlaybackTranscodingHelp": "Obmedzenie prístupu ku transkódovaniu môže spôsobiť zlyhania prehrávania v klientoch kvôli nepodporovaným formátom medií.", "MessageNoTrailersFound": "Nainštalujte Trailer kanál pre rozšírenie vášho filmového zážitku pridaním knižnice trailerov z internetu.", "LanNetworksHelp": "Zoznam IP adries alebo IP/netmask záznamov pre všetky siete oddelené čiarkami ktoré budú považované za lokálnu sieť pri vynucovaní obmedzenia šírky pásma. Pokiaľ je toto nastavené, všetky ostatné IP adresy budú považované za vonkajšiu sieť a budú podliehať obmedzeniam šírky pásma vonkajšej siete. Pokiaľ pole ostane prázdne, podsieť serveru bude považovaná za lokálnu sieť.", "LabelUserAgent": "User agent:", @@ -1258,7 +1258,7 @@ "MusicLibraryHelp": "Pozrite si {0}príručku pomenovania hudby{1}.", "MusicAlbum": "Hudobný album", "MoreMediaInfo": "Informácie o médiu", - "MetadataSettingChangeHelp": "Zmena nastavení metadát ovplyvní nový obsah, ktorý bude pridávaný v budúcnosti. Pre obnovenie existujúceho obsahu, otvorte obrazovku s detailom a kliknite na tlačítko obnoviť alebo vykonajte hromadnú obnovu cez metadata manažér.", + "MetadataSettingChangeHelp": "Zmena nastavení metadát ovplyvní nový obsah pridávaný v budúcnosti. Pre obnovenie existujúceho obsahu, otvorte obrazovku s detailom a kliknite na tlačítko obnoviť alebo vykonajte hromadnú obnovu cez metadata manažér.", "MessageUnsetContentHelp": "Obsah bude zobrazený ako jednoduché priečinky. Pre lepšie výsledky použite manažér metadát na nastavenie typu obsahu podpriečinkov.", "MessageUnableToConnectToServer": "Nie sme schopný sa aktuálne pripojiť k vybranému serveru. Prosím, uistite sa že je spustený a skúste to znovu.", "MessageReenableUser": "Pozrite nižšie pre znovu-povolenie", @@ -1291,7 +1291,7 @@ "MediaInfoBitrate": "Dátový tok", "MediaInfoAnamorphic": "Anamorfné", "MapChannels": "Nájdi kanály", - "LabelffmpegPathHelp": "Cesta k aplikačnému súboru ffmpeg alebo k priečinku obsahujúcemu ffmpeg.", + "LabelffmpegPathHelp": "Cesta k súboru aplikácie ffmpeg alebo k priečinku obsahujúcemu ffmpeg.", "LabelXDlnaDocHelp": "Určuje obsah prvku X_DLNADOC v namespace urn:schemas-dlna-org:device-1-0.", "LabelXDlnaDoc": "X-DLNA dokumentácia:", "LabelXDlnaCapHelp": "Určuje obsah prvku X_DLNACAP v namespace urn:schemas-dlna-org:device-1-0.", @@ -1382,7 +1382,7 @@ "LabelLogs": "Logy:", "LabelLoginDisclaimer": "Vyrozumenie prihlásenia:", "LabelLockItemToPreventChanges": "Uzamknúť túto položku pre zabránenie zmien v budúcnosti", - "LabelLocalHttpServerPortNumberHelp": "Číslo TCP portu, na ktoré by sa mal naviazať Jellyfin HTTP server.", + "LabelLocalHttpServerPortNumberHelp": "Číslo portu TCP serveru HTTP.", "LabelKodiMetadataUserHelp": "Uložiť dáta o pozeraní do NFO súboru pre využitie ostatnými aplikáciami.", "LabelKodiMetadataUser": "Ukladá dáta používateľa o pozeraní do NFO súboru pre:", "LabelKodiMetadataEnablePathSubstitutionHelp": "Povoľuje nahradenie ciest k obrázkom pomocou nastavenej cesty serveru pre nahradené cesty.", @@ -1395,14 +1395,14 @@ "LabelIdentificationFieldHelp": "Podreťazec citlivý na veľkosť písmen alebo na regulárne výrazy.", "LabelIconMaxWidthHelp": "Maximálne rozlíšenie ikon pomocou prostredníctvom upnp:icon.", "LabelIconMaxHeightHelp": "Maximálne rozlíšenie ikon pomocou prostredníctvom upnp:icon.", - "LabelHttpsPortHelp": "Číslo TCP portu, na ktoré by sa mal naviazať Jellyfin HTTPS server.", + "LabelHttpsPortHelp": "Číslo portu TCP serveru HTTPS.", "LabelHomeNetworkQuality": "Kvalita na domácej sieti:", "LabelEncoderPreset": "Prednastavené H264 enkódovanie:", "LabelH264Crf": "H264 enkódovanie CRF:", "LabelFriendlyName": "Priateľský názov:", "LabelFolder": "Priečinok:", "LabelExtractChaptersDuringLibraryScanHelp": "Generovať obrázky kapitol počas toho, ako sú videá importované v prvotnom prehľadávaní knižnice. Inak sa budú extrahovať počas naplánovanej úlohy generovania obrázkov kapitol, čo dovoľuje rýchlejšie dokončenie bežného prehľadávania knižnice.", - "LabelBaseUrlHelp": "Pridá vlastný reťazec na URL adresu serveru, napr: http://priklad.sk/<vlastnyretazec>", + "LabelBaseUrlHelp": "Pridá vlastný reťazec na URL adresu serveru, napr: http://priklad.sk/<vlastny-retazec>", "LabelBaseUrl": "Východzia URL:", "LabelEveryXMinutes": "Každý:", "LabelEnableSingleImageInDidlLimitHelp": "Niektoré zariadenia nebudú zobrazovať správne pokiaľ je viacero obrázkov uložených v Didl.", @@ -1410,11 +1410,11 @@ "LabelEnableDlnaDebugLoggingHelp": "Vytvára veľké súbory s logami a mal by sa použiť len v prípade potreby odstraňovania problémov.", "LabelEnableDlnaDebugLogging": "Povoliť loggovanie DLNA debugu", "LabelEnableDlnaClientDiscoveryIntervalHelp": "Určuje dobu trvania v sekundách medzi SSDP vyhľadávaniami vykonanými Jellyfinom.", - "LabelEnableDlnaClientDiscoveryInterval": "Interval pre objavenie klienta (sekundy)", + "LabelEnableDlnaClientDiscoveryInterval": "Interval pre objavenie klienta", "LabelEnableAutomaticPortMapHelp": "Automatické namapovanie vejerného portu na lokálny port serveru cez UPnP. Toto nemusí fungovať so všetkými modelmi routerov alebo sieťových konfigurácií. Zmeny sa vykonajú až po reštarte servera.", "LabelEmbedAlbumArtDidlHelp": "Niektoré zariadenia preferujú túto metódu pre získavanie obrázku albumu. Ostatným môže zlyhať prehrávanie pokiaľ je táto možnosť povolená.", "LabelBlastMessageIntervalHelp": "Určuje dobu v sekundách medzi vysielaniami správ o serveri.", - "LabelBindToLocalNetworkAddressHelp": "Voliteľné. Prepísať lokálnu IP adresu viazanú na http server. Pokiaľ zostane prázdna, server sa naviaže na všetky dostupné adresy. Pri zmene tejto hodnoty sa vyžaduje reštart Jellyfin Servera.", + "LabelBindToLocalNetworkAddressHelp": "Prepísať lokálnu IP adresu http serveru. Pokiaľ zostane prázdna, server sa naviaže na všetky dostupné adresy. Pri zmene tejto hodnoty sa vyžaduje reštart Jellyfin Servera.", "LabelAlbumArtPN": "Obrázok albumu PN:", "LabelAlbumArtMaxWidthHelp": "Maximálne rozlíšenie obrázku albumu prostredníctvom upnp:albumArtURI.", "LabelAlbumArtMaxHeightHelp": "Maximálne rozlíšenie obrázku albumu prostredníctvom upnp:albumArtURI.", @@ -1477,7 +1477,7 @@ "TabDVR": "DVR", "LabelRequireHttpsHelp": "Pokiaľ je zaškrtnutý, server bude automaticky presmerovávať všetky HTTP požiadavky cez HTTPS. Toto nastavenie nemá žiadny efekt, pokiaľ server nepočúva na HTTPS.", "LabelRequireHttps": "Vyžadovať HTTPS", - "LabelEnableHttpsHelp": "Umožní serveru počúvať na nastavenom HTTPS porte. K správnemu fungovaniu je nutné nakonfigurovať aj platný certifikát.", + "LabelEnableHttpsHelp": "Počúvanie na nastavenom HTTPS porte. K správnemu fungovaniu je nutné nakonfigurovať aj platný certifikát.", "LabelEnableHttps": "Povoliť HTTPS", "HeaderServerAddressSettings": "Nastavenie adresy servera", "HeaderRemoteAccessSettings": "Nastavenie vzdialeného prístupu", @@ -1539,5 +1539,13 @@ "Writers": "Scenáristi", "ClearQueue": "Vymazať frontu", "StopPlayback": "Zastaviť prehrávanie", - "ViewAlbumArtist": "Zobraziť interpreta albumu" + "ViewAlbumArtist": "Zobraziť interpreta albumu", + "Preview": "Náhľad", + "SubtitleVerticalPositionHelp": "Číslo riadku, na ktorom sa zobrazí text. Kladné čísla znamenajú smer zhora dole. Záporné čísla zdola hore.", + "LabelSubtitleVerticalPosition": "Vertikálne umiestnenie:", + "PreviousTrack": "Predchádzajúca", + "MessageGetInstalledPluginsError": "Pri načítaní zoznamu nainštalovaných zásuvných modulov došlo k chybe.", + "MessagePluginInstallError": "Pri inštalácií zásuvného modulu došlo k chybe.", + "NextTrack": "Ďalšia", + "LabelUnstable": "Nestabilný" }