From 9e92bfaae78b3bd1f1dfdc9ba02ced18729bef95 Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Sun, 14 Jun 2020 23:37:48 +0300 Subject: [PATCH 001/124] Fix subtitle line spacing --- .../subtitleappearancehelper.js | 71 ++++++------------- src/plugins/htmlVideoPlayer/plugin.js | 2 +- src/plugins/htmlVideoPlayer/style.css | 6 ++ 3 files changed, 30 insertions(+), 49 deletions(-) diff --git a/src/components/subtitlesettings/subtitleappearancehelper.js b/src/components/subtitlesettings/subtitleappearancehelper.js index bec8156aca..503c81472f 100644 --- a/src/components/subtitlesettings/subtitleappearancehelper.js +++ b/src/components/subtitlesettings/subtitleappearancehelper.js @@ -1,55 +1,30 @@ define([], function () { 'use strict'; - function getTextStyles(settings, isCue) { + function getTextStyles(settings) { var list = []; - if (isCue) { - switch (settings.textSize || '') { - - case 'smaller': - list.push({ name: 'font-size', value: '.5em' }); - break; - case 'small': - list.push({ name: 'font-size', value: '.7em' }); - break; - case 'large': - list.push({ name: 'font-size', value: '1.3em' }); - break; - case 'larger': - list.push({ name: 'font-size', value: '1.72em' }); - break; - case 'extralarge': - list.push({ name: 'font-size', value: '2em' }); - break; - default: - case 'medium': - break; - } - } else { - switch (settings.textSize || '') { - - case 'smaller': - list.push({ name: 'font-size', value: '.8em' }); - break; - case 'small': - list.push({ name: 'font-size', value: 'inherit' }); - break; - case 'larger': - list.push({ name: 'font-size', value: '2em' }); - break; - case 'extralarge': - list.push({ name: 'font-size', value: '2.2em' }); - break; - case 'large': - list.push({ name: 'font-size', value: '1.72em' }); - break; - default: - case 'medium': - list.push({ name: 'font-size', value: '1.36em' }); - break; - } + switch (settings.textSize || '') { + case 'smaller': + list.push({ name: 'font-size', value: '.8em' }); + break; + case 'small': + list.push({ name: 'font-size', value: 'inherit' }); + break; + case 'larger': + list.push({ name: 'font-size', value: '2em' }); + break; + case 'extralarge': + list.push({ name: 'font-size', value: '2.2em' }); + break; + case 'large': + list.push({ name: 'font-size', value: '1.72em' }); + break; + default: + case 'medium': + list.push({ name: 'font-size', value: '1.36em' }); + break; } switch (settings.dropShadow || '') { @@ -122,10 +97,10 @@ define([], function () { return []; } - function getStyles(settings, isCue) { + function getStyles(settings) { return { - text: getTextStyles(settings, isCue), + text: getTextStyles(settings), window: getWindowStyles(settings) }; } diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index cc312bb956..9302612539 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -1178,7 +1178,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa document.getElementsByTagName('head')[0].appendChild(styleElem); } - styleElem.innerHTML = getCueCss(subtitleAppearanceHelper.getStyles(userSettings.getSubtitleAppearanceSettings(), true), '.htmlvideoplayer'); + styleElem.innerHTML = getCueCss(subtitleAppearanceHelper.getStyles(userSettings.getSubtitleAppearanceSettings()), '.htmlvideoplayer'); }); } diff --git a/src/plugins/htmlVideoPlayer/style.css b/src/plugins/htmlVideoPlayer/style.css index b83a7816f5..0f63e72d7e 100644 --- a/src/plugins/htmlVideoPlayer/style.css +++ b/src/plugins/htmlVideoPlayer/style.css @@ -33,6 +33,12 @@ video::-webkit-media-controls { text-shadow: 0.14em 0.14em 0.14em rgba(0, 0, 0, 1); -webkit-font-smoothing: antialiased; font-family: inherit; + line-height: normal; /* Restore value. See -webkit-media-text-track-container 'line-height' */ +} + +.htmlvideoplayer::-webkit-media-text-track-container { + font-size: 170% !important; /* Override element inline style */ + line-height: 50%; /* Child element cannot set line height smaller than its parent has. This allow smaller values for children */ } .htmlvideoplayer-moveupsubtitles::-webkit-media-text-track-display { From 597b4258d9eba3a663082632149511d4500264ce Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Tue, 7 Jul 2020 01:06:47 +0300 Subject: [PATCH 002/124] Add subtitle position --- .../subtitleappearancehelper.js | 39 ++++++++-- .../subtitlesettings/subtitlesettings.js | 71 ++++++++++++++++++- .../subtitlesettings.template.html | 53 ++++++++++++++ src/elements/emby-slider/emby-slider.css | 15 ++++ src/elements/emby-slider/emby-slider.js | 10 +++ src/plugins/htmlVideoPlayer/plugin.js | 39 +++++----- src/plugins/htmlVideoPlayer/style.css | 9 ++- src/scripts/settings/userSettings.js | 6 +- src/strings/en-us.json | 5 +- src/strings/ru.json | 5 +- 10 files changed, 219 insertions(+), 33 deletions(-) diff --git a/src/components/subtitlesettings/subtitleappearancehelper.js b/src/components/subtitlesettings/subtitleappearancehelper.js index 503c81472f..e611228d9a 100644 --- a/src/components/subtitlesettings/subtitleappearancehelper.js +++ b/src/components/subtitlesettings/subtitleappearancehelper.js @@ -1,7 +1,7 @@ define([], function () { 'use strict'; - function getTextStyles(settings) { + function getTextStyles(settings, preview) { var list = []; @@ -89,19 +89,44 @@ define([], function () { break; } + if (!preview) { + const pos = parseInt(settings.verticalPosition); + const lineHeight = 1.35; // FIXME: It is better to read this value from element + const line = Math.abs(pos * lineHeight); + if (pos < 0) { + list.push({ name: 'min-height', value: `${line}em` }); + list.push({ name: 'margin-top', value: '' }); + } else { + list.push({ name: 'min-height', value: '' }); + list.push({ name: 'margin-top', value: `${line}em` }); + } + } + return list; } - function getWindowStyles(settings) { + function getWindowStyles(settings, preview) { + const list = []; - return []; + if (!preview) { + const pos = parseInt(settings.verticalPosition); + if (pos < 0) { + list.push({ name: 'top', value: '' }); + list.push({ name: 'bottom', value: '0' }); + } else { + list.push({ name: 'top', value: '0' }); + list.push({ name: 'bottom', value: '' }); + } + } + + return list; } - function getStyles(settings) { + function getStyles(settings, preview) { return { - text: getTextStyles(settings), - window: getWindowStyles(settings) + text: getTextStyles(settings, preview), + window: getWindowStyles(settings, preview) }; } @@ -117,7 +142,7 @@ define([], function () { function applyStyles(elements, appearanceSettings) { - var styles = getStyles(appearanceSettings); + var styles = getStyles(appearanceSettings, !!elements.preview); if (elements.text) { applyStyleList(styles.text, elements.text); diff --git a/src/components/subtitlesettings/subtitlesettings.js b/src/components/subtitlesettings/subtitlesettings.js index d728360d05..819631184c 100644 --- a/src/components/subtitlesettings/subtitlesettings.js +++ b/src/components/subtitlesettings/subtitlesettings.js @@ -1,4 +1,4 @@ -define(['require', 'globalize', 'appSettings', 'apphost', 'focusManager', 'loading', 'connectionManager', 'subtitleAppearanceHelper', 'dom', 'events', 'listViewStyle', 'emby-select', 'emby-input', 'emby-checkbox', 'flexStyles'], function (require, globalize, appSettings, appHost, focusManager, loading, connectionManager, subtitleAppearanceHelper, dom, events) { +define(['require', 'globalize', 'appSettings', 'apphost', 'focusManager', 'loading', 'connectionManager', 'subtitleAppearanceHelper', 'dom', 'events', 'layoutManager', 'listViewStyle', 'emby-select', 'emby-input', 'emby-checkbox', 'emby-slider', 'flexStyles'], function (require, globalize, appSettings, appHost, focusManager, loading, connectionManager, subtitleAppearanceHelper, dom, events, layoutManager) { 'use strict'; function populateLanguages(select, languages) { @@ -21,6 +21,7 @@ define(['require', 'globalize', 'appSettings', 'apphost', 'focusManager', 'loadi appearanceSettings.font = context.querySelector('#selectFont').value; appearanceSettings.textBackground = context.querySelector('#inputTextBackground').value; appearanceSettings.textColor = context.querySelector('#inputTextColor').value; + appearanceSettings.verticalPosition = context.querySelector('#sliderVerticalPosition').value; return appearanceSettings; } @@ -47,6 +48,7 @@ define(['require', 'globalize', 'appSettings', 'apphost', 'focusManager', 'loadi context.querySelector('#inputTextBackground').value = appearanceSettings.textBackground || 'transparent'; context.querySelector('#inputTextColor').value = appearanceSettings.textColor || '#ffffff'; context.querySelector('#selectFont').value = appearanceSettings.font || ''; + context.querySelector('#sliderVerticalPosition').value = appearanceSettings.verticalPosition; context.querySelector('#selectSubtitleBurnIn').value = appSettings.get('subtitleburnin') || ''; @@ -134,10 +136,45 @@ define(['require', 'globalize', 'appSettings', 'apphost', 'focusManager', 'loadi var elements = { window: view.querySelector('.subtitleappearance-preview-window'), - text: view.querySelector('.subtitleappearance-preview-text') + text: view.querySelector('.subtitleappearance-preview-text'), + preview: true }; subtitleAppearanceHelper.applyStyles(elements, appearanceSettings); + + subtitleAppearanceHelper.applyStyles({ + window: view.querySelector('.subtitleappearance-fullpreview-window'), + text: view.querySelector('.subtitleappearance-fullpreview-text') + }, appearanceSettings); + } + + const subtitlePreviewDelay = 1000; + let subtitlePreviewTimer; + + function showSubtitlePreview(persistent) { + clearTimeout(subtitlePreviewTimer); + + this._fullPreview.classList.remove('subtitleappearance-fullpreview-hide'); + + if (persistent) { + this._refFullPreview++; + } + + if (this._refFullPreview === 0) { + subtitlePreviewTimer = setTimeout(hideSubtitlePreview.bind(this), subtitlePreviewDelay); + } + } + + function hideSubtitlePreview(persistent) { + clearTimeout(subtitlePreviewTimer); + + if (persistent) { + this._refFullPreview--; + } + + if (this._refFullPreview === 0) { + this._fullPreview.classList.add('subtitleappearance-fullpreview-hide'); + } } function embed(options, self) { @@ -162,6 +199,36 @@ define(['require', 'globalize', 'appSettings', 'apphost', 'focusManager', 'loadi if (appHost.supports('subtitleappearancesettings')) { options.element.querySelector('.subtitleAppearanceSection').classList.remove('hide'); + + self._fullPreview = options.element.querySelector('.subtitleappearance-fullpreview'); + self._refFullPreview = 0; + + const sliderVerticalPosition = options.element.querySelector('#sliderVerticalPosition'); + sliderVerticalPosition.addEventListener('input', onAppearanceFieldChange); + sliderVerticalPosition.addEventListener('input', () => showSubtitlePreview.call(self)); + + const eventPrefix = window.PointerEvent ? 'pointer' : 'mouse'; + sliderVerticalPosition.addEventListener(`${eventPrefix}enter`, () => showSubtitlePreview.call(self, true)); + sliderVerticalPosition.addEventListener(`${eventPrefix}leave`, () => hideSubtitlePreview.call(self, true)); + + if (layoutManager.tv) { + sliderVerticalPosition.addEventListener('focus', () => showSubtitlePreview.call(self, true)); + sliderVerticalPosition.addEventListener('blur', () => hideSubtitlePreview.call(self, true)); + + // Give CustomElements time to attach + setTimeout(() => { + sliderVerticalPosition.classList.add('focusable'); + sliderVerticalPosition.enableKeyboardDragging(); + }, 0); + } + + options.element.querySelector('.chkPreview').addEventListener('change', (e) => { + if (e.target.checked) { + showSubtitlePreview.call(self, true); + } else { + hideSubtitlePreview.call(self, true); + } + }); } self.loadData(); diff --git a/src/components/subtitlesettings/subtitlesettings.template.html b/src/components/subtitlesettings/subtitlesettings.template.html index 716296a257..2f49fa4c3e 100644 --- a/src/components/subtitlesettings/subtitlesettings.template.html +++ b/src/components/subtitlesettings/subtitlesettings.template.html @@ -38,6 +38,45 @@ ${HeaderSubtitleAppearance} + + +
+
+
+ ${HeaderSubtitleAppearance} +
+ ${TheseSettingsAffectSubtitlesOnThisDevice} +
+
+
+
@@ -89,6 +128,20 @@
+ +
+
+ +
+
${SubtitleVerticalPositionHelp}
+
+ +
+ +
'; @@ -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 011/124] 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 012/124] 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 6ef3be136a1e2f98e0d292cfdc108b94eb3eb470 Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Thu, 30 Jul 2020 23:36:52 +0300 Subject: [PATCH 013/124] Add parseInt radix --- src/components/subtitlesettings/subtitleappearancehelper.js | 4 ++-- src/plugins/htmlVideoPlayer/plugin.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/subtitlesettings/subtitleappearancehelper.js b/src/components/subtitlesettings/subtitleappearancehelper.js index 4b8b7aed2a..904c018bfc 100644 --- a/src/components/subtitlesettings/subtitleappearancehelper.js +++ b/src/components/subtitlesettings/subtitleappearancehelper.js @@ -89,7 +89,7 @@ function getTextStyles(settings, preview) { } if (!preview) { - const pos = parseInt(settings.verticalPosition); + const pos = parseInt(settings.verticalPosition, 10); const lineHeight = 1.35; // FIXME: It is better to read this value from element const line = Math.abs(pos * lineHeight); if (pos < 0) { @@ -108,7 +108,7 @@ function getWindowStyles(settings, preview) { const list = []; if (!preview) { - const pos = parseInt(settings.verticalPosition); + const pos = parseInt(settings.verticalPosition, 10); if (pos < 0) { list.push({ name: 'top', value: '' }); list.push({ name: 'bottom', value: '0' }); diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index 77e3253641..7ef35adf41 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -1200,7 +1200,7 @@ function tryRemoveElement(elem) { console.debug(`downloaded ${data.TrackEvents.length} track events`); const subtitleAppearance = userSettings.getSubtitleAppearanceSettings(); - const cueLine = parseInt(subtitleAppearance.verticalPosition); + const cueLine = parseInt(subtitleAppearance.verticalPosition, 10); // add some cues to show the text // in safari, the cues need to be added before setting the track mode to showing From 997054ab12572c52000cc1ec0186bfbf4f609679 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 31 Jul 2020 15:54:47 +0100 Subject: [PATCH 014/124] 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 015/124] 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 016/124] 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 ab52727254019450db9a9fab25bc5eb4cddc504a Mon Sep 17 00:00:00 2001 From: matjaz321 Date: Sat, 1 Aug 2020 15:32:57 +0200 Subject: [PATCH 017/124] Migrated livetvseriestimer to es6 module --- src/controllers/livetv/livetvseriestimers.js | 90 ++++++++++---------- 1 file changed, 46 insertions(+), 44 deletions(-) diff --git a/src/controllers/livetv/livetvseriestimers.js b/src/controllers/livetv/livetvseriestimers.js index c5d0da60d7..d645a66370 100644 --- a/src/controllers/livetv/livetvseriestimers.js +++ b/src/controllers/livetv/livetvseriestimers.js @@ -1,50 +1,52 @@ -define(['datetime', 'cardBuilder', 'imageLoader', 'apphost', 'loading', 'paper-icon-button-light', 'emby-button'], function (datetime, cardBuilder, imageLoader, appHost, loading) { - 'use strict'; +import cardBuilder from 'cardBuilder'; +import imageLoader from 'imageLoader'; +import loading from 'loading'; +import 'paper-icon-button-light'; +import 'emby-button'; - function renderTimers(context, timers) { - var html = ''; - html += cardBuilder.getCardsHtml({ - items: timers, - shape: 'auto', - defaultShape: 'portrait', - showTitle: true, - cardLayout: false, - preferThumb: 'auto', - coverImage: true, - overlayText: false, - showSeriesTimerTime: true, - showSeriesTimerChannel: true, - centerText: true, - overlayMoreButton: true, - lines: 3 - }); - var elem = context.querySelector('#items'); - elem.innerHTML = html; - imageLoader.lazyChildren(elem); - loading.hide(); - } +function renderTimers(context, timers) { + const html = cardBuilder.getCardsHtml({ + items: timers, + shape: 'auto', + defaultShape: 'portrait', + showTitle: true, + cardLayout: false, + preferThumb: 'auto', + coverImage: true, + overlayText: false, + showSeriesTimerTime: true, + showSeriesTimerChannel: true, + centerText: true, + overlayMoreButton: true, + lines: 3 + }); + const elem = context.querySelector('#items'); + elem.innerHTML = html; + imageLoader.lazyChildren(elem); + loading.hide(); +} - function reload(context, promise) { - loading.show(); - promise.then(function (result) { - renderTimers(context, result.Items); - }); - } +function reload(context, promise) { + loading.show(); + promise.then(function (result) { + renderTimers(context, result.Items); + }); +} - var query = { - SortBy: 'SortName', - SortOrder: 'Ascending' +const query = { + SortBy: 'SortName', + SortOrder: 'Ascending' +}; + +export default function (view, params, tabContent) { + let timersPromise; + const self = this; + + self.preRender = function () { + timersPromise = ApiClient.getLiveTvSeriesTimers(query); }; - return function (view, params, tabContent) { - var timersPromise; - var self = this; - self.preRender = function () { - timersPromise = ApiClient.getLiveTvSeriesTimers(query); - }; - - self.renderTab = function () { - reload(tabContent, timersPromise); - }; + self.renderTab = function () { + reload(tabContent, timersPromise); }; -}); +}; From 0c8e1994b79eebee5056c03bc1056b3d658c94ca Mon Sep 17 00:00:00 2001 From: matjaz321 Date: Sat, 1 Aug 2020 15:36:16 +0200 Subject: [PATCH 018/124] fixed linting errors --- src/controllers/livetv/livetvseriestimers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/livetv/livetvseriestimers.js b/src/controllers/livetv/livetvseriestimers.js index d645a66370..4f6bfaaa6a 100644 --- a/src/controllers/livetv/livetvseriestimers.js +++ b/src/controllers/livetv/livetvseriestimers.js @@ -49,4 +49,4 @@ export default function (view, params, tabContent) { self.renderTab = function () { reload(tabContent, timersPromise); }; -}; +} From b082613ed98bd550a9e6a6abdb5bf6aff687264f Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 2 Aug 2020 17:44:03 +0100 Subject: [PATCH 019/124] Migration of viewManager to ES6 module --- package.json | 1 + src/components/appRouter.js | 2 + src/components/viewManager/viewManager.js | 229 +++++++++++----------- src/scripts/libraryMenu.js | 2 + src/scripts/site.js | 4 +- 5 files changed, 123 insertions(+), 115 deletions(-) diff --git a/package.json b/package.json index 5db40a0efe..d8306aa9e2 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/viewManager/viewManager.js", "src/controllers/session/addServer/index.js", "src/controllers/session/forgotPassword/index.js", "src/controllers/session/redeemPassword/index.js", diff --git a/src/components/appRouter.js b/src/components/appRouter.js index 138d58e5c0..9ed6e39d22 100644 --- a/src/components/appRouter.js +++ b/src/components/appRouter.js @@ -1,6 +1,8 @@ define(['loading', 'globalize', 'events', 'viewManager', 'skinManager', 'backdrop', 'browser', 'page', 'appSettings', 'apphost', 'connectionManager'], function (loading, globalize, events, viewManager, skinManager, backdrop, browser, page, appSettings, appHost, connectionManager) { 'use strict'; + viewManager = viewManager.default || viewManager; + var appRouter = { showLocalLogin: function (serverId, manualLogin) { var pageName = manualLogin ? 'manuallogin' : 'login'; diff --git a/src/components/viewManager/viewManager.js b/src/components/viewManager/viewManager.js index 058ba4ebb2..695535e7b8 100644 --- a/src/components/viewManager/viewManager.js +++ b/src/components/viewManager/viewManager.js @@ -1,134 +1,137 @@ -define(['viewContainer', 'focusManager', 'queryString', 'layoutManager'], function (viewContainer, focusManager, queryString, layoutManager) { - 'use strict'; +import viewContainer from 'viewContainer'; +import focusManager from 'focusManager'; +import queryString from 'queryString'; +import layoutManager from 'layoutManager'; - var currentView; - var dispatchPageEvents; +let currentView; +let dispatchPageEvents; - viewContainer.setOnBeforeChange(function (newView, isRestored, options) { - var lastView = currentView; - if (lastView) { - var beforeHideResult = dispatchViewEvent(lastView, null, 'viewbeforehide', true); +viewContainer.setOnBeforeChange(function (newView, isRestored, options) { + const lastView = currentView; + if (lastView) { + const beforeHideResult = dispatchViewEvent(lastView, null, 'viewbeforehide', true); - if (!beforeHideResult) { - // todo: cancel - } - } - - var eventDetail = getViewEventDetail(newView, options, isRestored); - - if (!newView.initComplete) { - newView.initComplete = true; - - if (typeof options.controllerFactory === 'function') { - new options.controllerFactory(newView, eventDetail.detail.params); - } else if (options.controllerFactory && typeof options.controllerFactory.default === 'function') { - new options.controllerFactory.default(newView, eventDetail.detail.params); - } - - if (!options.controllerFactory || dispatchPageEvents) { - dispatchViewEvent(newView, eventDetail, 'viewinit'); - } - } - - dispatchViewEvent(newView, eventDetail, 'viewbeforeshow'); - }); - - function onViewChange(view, options, isRestore) { - var lastView = currentView; - if (lastView) { - dispatchViewEvent(lastView, null, 'viewhide'); - } - - currentView = view; - - var eventDetail = getViewEventDetail(view, options, isRestore); - - if (!isRestore) { - if (options.autoFocus !== false) { - focusManager.autoFocus(view); - } - } else if (!layoutManager.mobile) { - if (view.activeElement && document.body.contains(view.activeElement) && focusManager.isCurrentlyFocusable(view.activeElement)) { - focusManager.focus(view.activeElement); - } else { - focusManager.autoFocus(view); - } - } - - view.dispatchEvent(new CustomEvent('viewshow', eventDetail)); - - if (dispatchPageEvents) { - view.dispatchEvent(new CustomEvent('pageshow', eventDetail)); + if (!beforeHideResult) { + // todo: cancel } } - function getProperties(view) { - var props = view.getAttribute('data-properties'); + const eventDetail = getViewEventDetail(newView, options, isRestored); - if (props) { - return props.split(','); + if (!newView.initComplete) { + newView.initComplete = true; + + if (typeof options.controllerFactory === 'function') { + new options.controllerFactory(newView, eventDetail.detail.params); + } else if (options.controllerFactory && typeof options.controllerFactory.default === 'function') { + new options.controllerFactory.default(newView, eventDetail.detail.params); } - return []; + if (!options.controllerFactory || dispatchPageEvents) { + dispatchViewEvent(newView, eventDetail, 'viewinit'); + } } - function dispatchViewEvent(view, eventInfo, eventName, isCancellable) { - if (!eventInfo) { - eventInfo = { - detail: { - type: view.getAttribute('data-type'), - properties: getProperties(view) - }, - bubbles: true, - cancelable: isCancellable - }; - } + dispatchViewEvent(newView, eventDetail, 'viewbeforeshow'); +}); - eventInfo.cancelable = isCancellable || false; - - var eventResult = view.dispatchEvent(new CustomEvent(eventName, eventInfo)); - - if (dispatchPageEvents) { - eventInfo.cancelable = false; - view.dispatchEvent(new CustomEvent(eventName.replace('view', 'page'), eventInfo)); - } - - return eventResult; +function onViewChange(view, options, isRestore) { + const lastView = currentView; + if (lastView) { + dispatchViewEvent(lastView, null, 'viewhide'); } - function getViewEventDetail(view, options, isRestore) { - var url = options.url; - var index = url.indexOf('?'); - var params = index === -1 ? {} : queryString.parse(url.substring(index + 1)); + currentView = view; - return { + const eventDetail = getViewEventDetail(view, options, isRestore); + + if (!isRestore) { + if (options.autoFocus !== false) { + focusManager.autoFocus(view); + } + } else if (!layoutManager.mobile) { + if (view.activeElement && document.body.contains(view.activeElement) && focusManager.isCurrentlyFocusable(view.activeElement)) { + focusManager.focus(view.activeElement); + } else { + focusManager.autoFocus(view); + } + } + + view.dispatchEvent(new CustomEvent('viewshow', eventDetail)); + + if (dispatchPageEvents) { + view.dispatchEvent(new CustomEvent('pageshow', eventDetail)); + } +} + +function getProperties(view) { + const props = view.getAttribute('data-properties'); + + if (props) { + return props.split(','); + } + + return []; +} + +function dispatchViewEvent(view, eventInfo, eventName, isCancellable) { + if (!eventInfo) { + eventInfo = { detail: { type: view.getAttribute('data-type'), - properties: getProperties(view), - params: params, - isRestored: isRestore, - state: options.state, - - // The route options - options: options.options || {} + properties: getProperties(view) }, bubbles: true, - cancelable: false + cancelable: isCancellable }; } - function resetCachedViews() { - // Reset all cached views whenever the skin changes - viewContainer.reset(); + eventInfo.cancelable = isCancellable || false; + + const eventResult = view.dispatchEvent(new CustomEvent(eventName, eventInfo)); + + if (dispatchPageEvents) { + eventInfo.cancelable = false; + view.dispatchEvent(new CustomEvent(eventName.replace('view', 'page'), eventInfo)); } - document.addEventListener('skinunload', resetCachedViews); + return eventResult; +} - function ViewManager() { +function getViewEventDetail(view, options, isRestore) { + const url = options.url; + const index = url.indexOf('?'); + const params = index === -1 ? {} : queryString.parse(url.substring(index + 1)); + + return { + detail: { + type: view.getAttribute('data-type'), + properties: getProperties(view), + params: params, + isRestored: isRestore, + state: options.state, + + // The route options + options: options.options || {} + }, + bubbles: true, + cancelable: false + }; +} + +function resetCachedViews() { + // Reset all cached views whenever the skin changes + viewContainer.reset(); +} + +document.addEventListener('skinunload', resetCachedViews); + +class ViewManager { + constructor() { } - ViewManager.prototype.loadView = function (options) { - var lastView = currentView; + loadView(options) { + const lastView = currentView; // Record the element that has focus if (lastView) { @@ -142,9 +145,9 @@ define(['viewContainer', 'focusManager', 'queryString', 'layoutManager'], functi viewContainer.loadView(options).then(function (view) { onViewChange(view, options); }); - }; + } - ViewManager.prototype.tryRestoreView = function (options, onViewChanging) { + tryRestoreView(options, onViewChanging) { if (options.cancel) { return Promise.reject({ cancelled: true }); } @@ -158,15 +161,15 @@ define(['viewContainer', 'focusManager', 'queryString', 'layoutManager'], functi onViewChanging(); onViewChange(view, options, true); }); - }; + } - ViewManager.prototype.currentView = function () { + currentView() { return currentView; - }; + } - ViewManager.prototype.dispatchPageEvents = function (value) { + dispatchPageEvents(value) { dispatchPageEvents = value; - }; + } +} - return new ViewManager(); -}); +export default new ViewManager(); diff --git a/src/scripts/libraryMenu.js b/src/scripts/libraryMenu.js index 961d89dc05..d199af0ede 100644 --- a/src/scripts/libraryMenu.js +++ b/src/scripts/libraryMenu.js @@ -1,6 +1,8 @@ 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'; + viewManager = viewManager.default || viewManager; + function renderHeader() { var html = ''; html += '
'; diff --git a/src/scripts/site.js b/src/scripts/site.js index 2beb45959a..6aec5379a8 100644 --- a/src/scripts/site.js +++ b/src/scripts/site.js @@ -827,8 +827,8 @@ function initClient() { define('tvguide', [componentsPath + '/guide/guide'], returnFirstDependency); define('guide-settings-dialog', [componentsPath + '/guide/guide-settings'], returnFirstDependency); define('viewManager', [componentsPath + '/viewManager/viewManager'], function (viewManager) { - window.ViewManager = viewManager; - viewManager.dispatchPageEvents(true); + window.ViewManager = viewManager.default; + viewManager.default.dispatchPageEvents(true); return viewManager; }); define('slideshow', [componentsPath + '/slideshow/slideshow'], returnFirstDependency); From 275676f7126655981fa4600661be4519049da358 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 2 Aug 2020 17:58:21 +0100 Subject: [PATCH 020/124] remove defaults --- src/controllers/list.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/list.js b/src/controllers/list.js index c7c4b2a31d..aea791c167 100644 --- a/src/controllers/list.js +++ b/src/controllers/list.js @@ -558,7 +558,7 @@ class ItemsView { alphaPickerElement.classList.add('focuscontainer-right'); self.itemsContainer.parentNode.classList.add('padded-right-withalphapicker'); - self.alphaPicker = new AlphaPicker.default({ + self.alphaPicker = new AlphaPicker({ element: alphaPickerElement, itemsContainer: layoutManager.tv ? self.itemsContainer : null, itemClass: 'card', @@ -817,7 +817,7 @@ class ItemsView { bindAll(view.querySelectorAll('.btnShuffle'), 'click', shuffle); } - self.alphaNumericShortcuts = new AlphaNumericShortcuts.default({ + self.alphaNumericShortcuts = new AlphaNumericShortcuts({ itemsContainer: self.itemsContainer }); }); From 6b54d25b4d36b44eb357244ea8a06028d240c567 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 2 Aug 2020 18:06:40 +0100 Subject: [PATCH 021/124] Update list.js --- src/controllers/list.js | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/controllers/list.js b/src/controllers/list.js index 7dab6f5f35..b9237887d0 100644 --- a/src/controllers/list.js +++ b/src/controllers/list.js @@ -14,9 +14,6 @@ import 'emby-scroller'; /* eslint-disable indent */ - playbackManager = playbackManager.default || playbackManager; - loading = loading.default || loading; - function getInitialLiveTvQuery(instance, params) { const query = { UserId: connectionManager.getApiClient(params.serverId).getCurrentUserId(), @@ -523,7 +520,7 @@ class ItemsView { } const showYear = settings.showTitle && params.IsMovie === 'true' && params.type === 'Recordings'; - + if (showYear) { lines++; } @@ -858,6 +855,7 @@ class ItemsView { self.alphaPickerElement = null; }); } + getFilters() { const basekey = this.getSettingsKey(); return { @@ -879,6 +877,7 @@ class ItemsView { GenreIds: userSettings.getFilter(basekey + '-filter-GenreIds') }; } + getSortValues() { const basekey = this.getSettingsKey(); return { @@ -886,6 +885,7 @@ class ItemsView { sortOrder: userSettings.getFilter(basekey + '-sortorder') === 'Descending' ? 'Descending' : 'Ascending' }; } + getDefaultSortBy() { const params = this.params; const sortNameOption = this.getNameSortOption(params); @@ -896,6 +896,7 @@ class ItemsView { return 'IsFolder,' + sortNameOption.value; } + getSortMenuOptions() { const sortBy = []; const params = this.params; @@ -966,6 +967,7 @@ class ItemsView { }); return sortBy; } + getNameSortOption(params) { if (params.type === 'Episode') { return { @@ -979,6 +981,7 @@ class ItemsView { value: 'SortName' }; } + getPlayCountSortOption() { if (this.params.type === 'Programs') { return null; @@ -988,8 +991,8 @@ class ItemsView { name: globalize.translate('PlayCount'), value: 'PlayCount,SortName' }; - } + getDatePlayedSortOption() { if (this.params.type === 'Programs') { return null; @@ -1000,12 +1003,9 @@ class ItemsView { value: 'DatePlayed,SortName' }; } + getCriticRatingSortOption() { if (this.params.type === 'Programs') { - }; - - ItemsView.prototype.getCriticRatingSortOption = function () { - if () { return null; } @@ -1014,12 +1014,14 @@ class ItemsView { value: 'CriticRating,SortName' }; } + getCommunityRatingSortOption() { return { name: globalize.translate('CommunityRating'), value: 'CommunityRating,SortName' }; } + getVisibleFilters() { const filters = []; const params = this.params; @@ -1047,6 +1049,7 @@ class ItemsView { return filters; } + setFilterStatus(hasFilters) { this.hasFilters = hasFilters; const filterButtons = this.filterButtons; @@ -1074,6 +1077,7 @@ class ItemsView { } } } + getFilterMenuOptions() { const params = this.params; return { @@ -1086,6 +1090,7 @@ class ItemsView { Recursive: this.queryRecursive }; } + getVisibleViewSettings() { const item = (this.params, this.currentItem); const fields = ['showTitle']; @@ -1097,6 +1102,7 @@ class ItemsView { fields.push('viewType'); return fields; } + getViewSettings() { const basekey = this.getSettingsKey(); const params = this.params; @@ -1126,6 +1132,7 @@ class ItemsView { viewType: userSettings.get(basekey + '-viewType') || 'images' }; } + getItemTypes() { const params = this.params; @@ -1139,6 +1146,7 @@ class ItemsView { return []; } + getSettingsKey() { const values = []; values.push('items'); From 57fb1331666e1f57cfe0111cbb15a5d5a071c969 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 2 Aug 2020 18:09:17 +0100 Subject: [PATCH 022/124] fix yoda --- src/libraries/navdrawer/navdrawer.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libraries/navdrawer/navdrawer.js b/src/libraries/navdrawer/navdrawer.js index d70ef90961..41e7af3339 100644 --- a/src/libraries/navdrawer/navdrawer.js +++ b/src/libraries/navdrawer/navdrawer.js @@ -32,19 +32,19 @@ define(['browser', 'dom', 'css!./navdrawer', 'scrollStyles'], function (browser, var deltaY = endY - (menuTouchStartY || 0); setVelocity(deltaX); - if (isOpen && 1 !== dragMode && deltaX > 0) { + if (isOpen && dragMode !== 1 && deltaX > 0) { dragMode = 2; } - if (0 === dragMode && (!isOpen || Math.abs(deltaX) >= 10) && Math.abs(deltaY) < 5) { + if (dragMode === 0 && (!isOpen || Math.abs(deltaX) >= 10) && Math.abs(deltaY) < 5) { dragMode = 1; scrollContainer.addEventListener('scroll', disableEvent); self.showMask(); - } else if (0 === dragMode && Math.abs(deltaY) >= 5) { + } else if (dragMode === 0 && Math.abs(deltaY) >= 5) { dragMode = 2; } - if (1 === dragMode) { + if (dragMode === 1) { newPos = currentPos + deltaX; self.changeMenuPos(); } From a7ad147aad2dda101ec26aa7e44d8a1008448bee Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 2 Aug 2020 18:48:57 +0100 Subject: [PATCH 023/124] Migration of xmltv and livetvguideprovide to ES6 module --- package.json | 2 + src/components/tvproviders/schedulesdirect.js | 543 +++++++++--------- src/components/tvproviders/xmltv.js | 356 ++++++------ src/controllers/livetvguideprovider.js | 2 +- 4 files changed, 456 insertions(+), 447 deletions(-) diff --git a/package.json b/package.json index 22fd43308a..43d806b8de 100644 --- a/package.json +++ b/package.json @@ -166,6 +166,8 @@ "src/components/syncPlay/playbackPermissionManager.js", "src/components/syncPlay/syncPlayManager.js", "src/components/syncPlay/timeSyncManager.js", + "src/components/tvproviders/schedulesdirect.js", + "src/components/tvproviders/xmltv.js", "src/components/viewContainer.js", "src/controllers/session/addServer/index.js", "src/controllers/session/forgotPassword/index.js", diff --git a/src/components/tvproviders/schedulesdirect.js b/src/components/tvproviders/schedulesdirect.js index a0c29f48b8..9d60310e98 100644 --- a/src/components/tvproviders/schedulesdirect.js +++ b/src/components/tvproviders/schedulesdirect.js @@ -1,299 +1,304 @@ -define(['jQuery', 'loading', 'globalize', 'emby-checkbox', 'listViewStyle', 'emby-input', 'emby-select', 'emby-button', 'flexStyles'], function ($, loading, globalize) { - 'use strict'; +import $ from 'jQuery'; +import loading from 'loading'; +import globalize from 'globalize'; +import 'emby-checkbox'; +import 'emby-input'; +import 'listViewStyle'; +import 'paper-icon-button-light'; +import 'emby-select'; +import 'emby-button'; +import 'flexStyles'; - loading = loading.default || loading; +export default function (page, providerId, options) { + function reload() { + loading.show(); + ApiClient.getNamedConfiguration('livetv').then(function (config) { + var info = config.ListingProviders.filter(function (i) { + return i.Id === providerId; + })[0] || {}; + listingsId = info.ListingsId; + $('#selectListing', page).val(info.ListingsId || ''); + page.querySelector('.txtUser').value = info.Username || ''; + page.querySelector('.txtPass').value = ''; + page.querySelector('.txtZipCode').value = info.ZipCode || ''; - return function (page, providerId, options) { - function reload() { - loading.show(); - ApiClient.getNamedConfiguration('livetv').then(function (config) { - var info = config.ListingProviders.filter(function (i) { - return i.Id === providerId; - })[0] || {}; - listingsId = info.ListingsId; - $('#selectListing', page).val(info.ListingsId || ''); - page.querySelector('.txtUser').value = info.Username || ''; - page.querySelector('.txtPass').value = ''; - page.querySelector('.txtZipCode').value = info.ZipCode || ''; - - if (info.Username && info.Password) { - page.querySelector('.listingsSection').classList.remove('hide'); - } else { - page.querySelector('.listingsSection').classList.add('hide'); - } - - page.querySelector('.chkAllTuners').checked = info.EnableAllTuners; - - if (info.EnableAllTuners) { - page.querySelector('.selectTunersSection').classList.add('hide'); - } else { - page.querySelector('.selectTunersSection').classList.remove('hide'); - } - - setCountry(info); - refreshTunerDevices(page, info, config.TunerHosts); - }); - } - - function setCountry(info) { - ApiClient.getJSON(ApiClient.getUrl('LiveTv/ListingProviders/SchedulesDirect/Countries')).then(function (result) { - var i; - var length; - var countryList = []; - - for (var region in result) { - var countries = result[region]; - - if (countries.length && region !== 'ZZZ') { - for (i = 0, length = countries.length; i < length; i++) { - countryList.push({ - name: countries[i].fullName, - value: countries[i].shortName - }); - } - } - } - - countryList.sort(function (a, b) { - if (a.name > b.name) { - return 1; - } - - if (a.name < b.name) { - return -1; - } - - return 0; - }); - $('#selectCountry', page).html(countryList.map(function (c) { - return ''; - }).join('')).val(info.Country || ''); - $(page.querySelector('.txtZipCode')).trigger('change'); - }, function () { // ApiClient.getJSON() error handler - Dashboard.alert({ - message: globalize.translate('ErrorGettingTvLineups') - }); - }); - loading.hide(); - } - - function sha256(str) { - if (!self.TextEncoder) { - return Promise.resolve(''); + if (info.Username && info.Password) { + page.querySelector('.listingsSection').classList.remove('hide'); + } else { + page.querySelector('.listingsSection').classList.add('hide'); } - var buffer = new TextEncoder('utf-8').encode(str); - return crypto.subtle.digest('SHA-256', buffer).then(function (hash) { - return hex(hash); - }); - } + page.querySelector('.chkAllTuners').checked = info.EnableAllTuners; - function hex(buffer) { - var hexCodes = []; - var view = new DataView(buffer); - - for (var i = 0; i < view.byteLength; i += 4) { - var value = view.getUint32(i); - var stringValue = value.toString(16); - var paddedValue = ('00000000' + stringValue).slice(-'00000000'.length); - hexCodes.push(paddedValue); + if (info.EnableAllTuners) { + page.querySelector('.selectTunersSection').classList.add('hide'); + } else { + page.querySelector('.selectTunersSection').classList.remove('hide'); } - return hexCodes.join(''); - } + setCountry(info); + refreshTunerDevices(page, info, config.TunerHosts); + }); + } - function submitLoginForm() { - loading.show(); - sha256(page.querySelector('.txtPass').value).then(function (passwordHash) { - var info = { - Type: 'SchedulesDirect', - Username: page.querySelector('.txtUser').value, - EnableAllTuners: true, - Password: passwordHash, - Pw: page.querySelector('.txtPass').value - }; - var id = providerId; + function setCountry(info) { + ApiClient.getJSON(ApiClient.getUrl('LiveTv/ListingProviders/SchedulesDirect/Countries')).then(function (result) { + var i; + var length; + var countryList = []; - if (id) { - info.Id = id; + for (var region in result) { + var countries = result[region]; + + if (countries.length && region !== 'ZZZ') { + for (i = 0, length = countries.length; i < length; i++) { + countryList.push({ + name: countries[i].fullName, + value: countries[i].shortName + }); + } + } + } + + countryList.sort(function (a, b) { + if (a.name > b.name) { + return 1; } - ApiClient.ajax({ - type: 'POST', - url: ApiClient.getUrl('LiveTv/ListingProviders', { - ValidateLogin: true - }), - data: JSON.stringify(info), - contentType: 'application/json', - dataType: 'json' - }).then(function (result) { - Dashboard.processServerConfigurationUpdateResult(); - providerId = result.Id; - reload(); - }, function () { - Dashboard.alert({ // ApiClient.ajax() error handler - message: globalize.translate('ErrorSavingTvProvider') - }); - }); + if (a.name < b.name) { + return -1; + } + + return 0; }); + $('#selectCountry', page).html(countryList.map(function (c) { + return ''; + }).join('')).val(info.Country || ''); + $(page.querySelector('.txtZipCode')).trigger('change'); + }, function () { // ApiClient.getJSON() error handler + Dashboard.alert({ + message: globalize.translate('ErrorGettingTvLineups') + }); + }); + loading.hide(); + } + + function sha256(str) { + if (!self.TextEncoder) { + return Promise.resolve(''); } - function submitListingsForm() { - var selectedListingsId = $('#selectListing', page).val(); + var buffer = new TextEncoder('utf-8').encode(str); + return crypto.subtle.digest('SHA-256', buffer).then(function (hash) { + return hex(hash); + }); + } - if (!selectedListingsId) { - return void Dashboard.alert({ - message: globalize.translate('ErrorPleaseSelectLineup') - }); - } + function hex(buffer) { + var hexCodes = []; + var view = new DataView(buffer); - loading.show(); + for (var i = 0; i < view.byteLength; i += 4) { + var value = view.getUint32(i); + var stringValue = value.toString(16); + var paddedValue = ('00000000' + stringValue).slice(-'00000000'.length); + hexCodes.push(paddedValue); + } + + return hexCodes.join(''); + } + + function submitLoginForm() { + loading.show(); + sha256(page.querySelector('.txtPass').value).then(function (passwordHash) { + var info = { + Type: 'SchedulesDirect', + Username: page.querySelector('.txtUser').value, + EnableAllTuners: true, + Password: passwordHash, + Pw: page.querySelector('.txtPass').value + }; var id = providerId; - ApiClient.getNamedConfiguration('livetv').then(function (config) { - var info = config.ListingProviders.filter(function (i) { - return i.Id === id; - })[0]; - info.ZipCode = page.querySelector('.txtZipCode').value; - info.Country = $('#selectCountry', page).val(); - info.ListingsId = selectedListingsId; - info.EnableAllTuners = page.querySelector('.chkAllTuners').checked; - info.EnabledTuners = info.EnableAllTuners ? [] : $('.chkTuner', page).get().filter(function (i) { - return i.checked; - }).map(function (i) { - return i.getAttribute('data-id'); - }); - ApiClient.ajax({ - type: 'POST', - url: ApiClient.getUrl('LiveTv/ListingProviders', { - ValidateListings: true - }), - data: JSON.stringify(info), - contentType: 'application/json' - }).then(function (result) { - loading.hide(); - if (options.showConfirmation) { - Dashboard.processServerConfigurationUpdateResult(); - } - - Events.trigger(self, 'submitted'); - }, function () { - loading.hide(); - Dashboard.alert({ - message: globalize.translate('ErrorAddingListingsToSchedulesDirect') - }); - }); - }); - } - - function refreshListings(value) { - if (!value) { - return void $('#selectListing', page).html(''); + if (id) { + info.Id = id; } - loading.show(); ApiClient.ajax({ - type: 'GET', - url: ApiClient.getUrl('LiveTv/ListingProviders/Lineups', { - Id: providerId, - Location: value, - Country: $('#selectCountry', page).val() + type: 'POST', + url: ApiClient.getUrl('LiveTv/ListingProviders', { + ValidateLogin: true }), + data: JSON.stringify(info), + contentType: 'application/json', dataType: 'json' }).then(function (result) { - $('#selectListing', page).html(result.map(function (o) { - return ''; - })); - - if (listingsId) { - $('#selectListing', page).val(listingsId); - } - - loading.hide(); - }, function (result) { - Dashboard.alert({ - message: globalize.translate('ErrorGettingTvLineups') + Dashboard.processServerConfigurationUpdateResult(); + providerId = result.Id; + reload(); + }, function () { + Dashboard.alert({ // ApiClient.ajax() error handler + message: globalize.translate('ErrorSavingTvProvider') }); - refreshListings(''); + }); + }); + } + + function submitListingsForm() { + var selectedListingsId = $('#selectListing', page).val(); + + if (!selectedListingsId) { + return void Dashboard.alert({ + message: globalize.translate('ErrorPleaseSelectLineup') + }); + } + + loading.show(); + var id = providerId; + ApiClient.getNamedConfiguration('livetv').then(function (config) { + var info = config.ListingProviders.filter(function (i) { + return i.Id === id; + })[0]; + info.ZipCode = page.querySelector('.txtZipCode').value; + info.Country = $('#selectCountry', page).val(); + info.ListingsId = selectedListingsId; + info.EnableAllTuners = page.querySelector('.chkAllTuners').checked; + info.EnabledTuners = info.EnableAllTuners ? [] : $('.chkTuner', page).get().filter(function (i) { + return i.checked; + }).map(function (i) { + return i.getAttribute('data-id'); + }); + ApiClient.ajax({ + type: 'POST', + url: ApiClient.getUrl('LiveTv/ListingProviders', { + ValidateListings: true + }), + data: JSON.stringify(info), + contentType: 'application/json' + }).then(function (result) { loading.hide(); - }); - } - function getTunerName(providerId) { - switch (providerId = providerId.toLowerCase()) { - case 'm3u': - return 'M3U Playlist'; - case 'hdhomerun': - return 'HDHomerun'; - case 'satip': - return 'DVB'; - default: - return 'Unknown'; - } - } - - function refreshTunerDevices(page, providerInfo, devices) { - var html = ''; - - for (var i = 0, length = devices.length; i < length; i++) { - var device = devices[i]; - html += '
'; - var enabledTuners = providerInfo.EnabledTuners || []; - var isChecked = providerInfo.EnableAllTuners || enabledTuners.indexOf(device.Id) !== -1; - var checkedAttribute = isChecked ? ' checked' : ''; - html += ''; - html += '
'; - html += '
'; - html += device.FriendlyName || getTunerName(device.Type); - html += '
'; - html += '
'; - html += device.Url; - html += '
'; - html += '
'; - html += '
'; - } - - page.querySelector('.tunerList').innerHTML = html; - } - - var listingsId; - var self = this; - - self.submit = function () { - page.querySelector('.btnSubmitListingsContainer').click(); - }; - - self.init = function () { - options = options || {}; - - // Only hide the buttons if explicitly set to false; default to showing if undefined or null - // FIXME: rename this option to clarify logic - var hideCancelButton = options.showCancelButton === false; - page.querySelector('.btnCancel').classList.toggle('hide', hideCancelButton); - - var hideSubmitButton = options.showSubmitButton === false; - page.querySelector('.btnSubmitListings').classList.toggle('hide', hideSubmitButton); - - $('.formLogin', page).on('submit', function () { - submitLoginForm(); - return false; - }); - $('.formListings', page).on('submit', function () { - submitListingsForm(); - return false; - }); - $('.txtZipCode', page).on('change', function () { - refreshListings(this.value); - }); - page.querySelector('.chkAllTuners').addEventListener('change', function (e) { - if (e.target.checked) { - page.querySelector('.selectTunersSection').classList.add('hide'); - } else { - page.querySelector('.selectTunersSection').classList.remove('hide'); + if (options.showConfirmation) { + Dashboard.processServerConfigurationUpdateResult(); } + + Events.trigger(self, 'submitted'); + }, function () { + loading.hide(); + Dashboard.alert({ + message: globalize.translate('ErrorAddingListingsToSchedulesDirect') + }); }); - $('.createAccountHelp', page).html(globalize.translate('MessageCreateAccountAt', '
http://www.schedulesdirect.org')); - reload(); - }; + }); + } + + function refreshListings(value) { + if (!value) { + return void $('#selectListing', page).html(''); + } + + loading.show(); + ApiClient.ajax({ + type: 'GET', + url: ApiClient.getUrl('LiveTv/ListingProviders/Lineups', { + Id: providerId, + Location: value, + Country: $('#selectCountry', page).val() + }), + dataType: 'json' + }).then(function (result) { + $('#selectListing', page).html(result.map(function (o) { + return ''; + })); + + if (listingsId) { + $('#selectListing', page).val(listingsId); + } + + loading.hide(); + }, function (result) { + Dashboard.alert({ + message: globalize.translate('ErrorGettingTvLineups') + }); + refreshListings(''); + loading.hide(); + }); + } + + function getTunerName(providerId) { + switch (providerId = providerId.toLowerCase()) { + case 'm3u': + return 'M3U Playlist'; + case 'hdhomerun': + return 'HDHomerun'; + case 'satip': + return 'DVB'; + default: + return 'Unknown'; + } + } + + function refreshTunerDevices(page, providerInfo, devices) { + var html = ''; + + for (var i = 0, length = devices.length; i < length; i++) { + var device = devices[i]; + html += '
'; + var enabledTuners = providerInfo.EnabledTuners || []; + var isChecked = providerInfo.EnableAllTuners || enabledTuners.indexOf(device.Id) !== -1; + var checkedAttribute = isChecked ? ' checked' : ''; + html += ''; + html += '
'; + html += '
'; + html += device.FriendlyName || getTunerName(device.Type); + html += '
'; + html += '
'; + html += device.Url; + html += '
'; + html += '
'; + html += '
'; + } + + page.querySelector('.tunerList').innerHTML = html; + } + + var listingsId; + var self = this; + + self.submit = function () { + page.querySelector('.btnSubmitListingsContainer').click(); }; -}); + + self.init = function () { + options = options || {}; + + // Only hide the buttons if explicitly set to false; default to showing if undefined or null + // FIXME: rename this option to clarify logic + var hideCancelButton = options.showCancelButton === false; + page.querySelector('.btnCancel').classList.toggle('hide', hideCancelButton); + + var hideSubmitButton = options.showSubmitButton === false; + page.querySelector('.btnSubmitListings').classList.toggle('hide', hideSubmitButton); + + $('.formLogin', page).on('submit', function () { + submitLoginForm(); + return false; + }); + $('.formListings', page).on('submit', function () { + submitListingsForm(); + return false; + }); + $('.txtZipCode', page).on('change', function () { + refreshListings(this.value); + }); + page.querySelector('.chkAllTuners').addEventListener('change', function (e) { + if (e.target.checked) { + page.querySelector('.selectTunersSection').classList.add('hide'); + } else { + page.querySelector('.selectTunersSection').classList.remove('hide'); + } + }); + $('.createAccountHelp', page).html(globalize.translate('MessageCreateAccountAt', 'http://www.schedulesdirect.org')); + reload(); + }; +}; diff --git a/src/components/tvproviders/xmltv.js b/src/components/tvproviders/xmltv.js index 2203bc1fea..84e222fe09 100644 --- a/src/components/tvproviders/xmltv.js +++ b/src/components/tvproviders/xmltv.js @@ -1,191 +1,193 @@ -define(['jQuery', 'loading', 'globalize', 'emby-checkbox', 'emby-input', 'listViewStyle', 'paper-icon-button-light'], function ($, loading, globalize) { - 'use strict'; +import $ from 'jQuery'; +import loading from 'loading'; +import globalize from 'globalize'; +import 'emby-checkbox'; +import 'emby-input'; +import 'listViewStyle'; +import 'paper-icon-button-light'; - loading = loading.default || loading; +export default function (page, providerId, options) { + function getListingProvider(config, id) { + if (config && id) { + var result = config.ListingProviders.filter(function (provider) { + return provider.Id === id; + })[0]; - return function (page, providerId, options) { - function getListingProvider(config, id) { - if (config && id) { - var result = config.ListingProviders.filter(function (provider) { - return provider.Id === id; - })[0]; - - if (result) { - return Promise.resolve(result); - } - - return getListingProvider(); + if (result) { + return Promise.resolve(result); } - return ApiClient.getJSON(ApiClient.getUrl('LiveTv/ListingProviders/Default')); + return getListingProvider(); } - function reload() { - loading.show(); - ApiClient.getNamedConfiguration('livetv').then(function (config) { - getListingProvider(config, providerId).then(function (info) { - page.querySelector('.txtPath').value = info.Path || ''; - page.querySelector('.txtKids').value = (info.KidsCategories || []).join('|'); - page.querySelector('.txtNews').value = (info.NewsCategories || []).join('|'); - page.querySelector('.txtSports').value = (info.SportsCategories || []).join('|'); - page.querySelector('.txtMovies').value = (info.MovieCategories || []).join('|'); - page.querySelector('.txtMoviePrefix').value = info.MoviePrefix || ''; - page.querySelector('.txtUserAgent').value = info.UserAgent || ''; - page.querySelector('.chkAllTuners').checked = info.EnableAllTuners; + return ApiClient.getJSON(ApiClient.getUrl('LiveTv/ListingProviders/Default')); + } - if (page.querySelector('.chkAllTuners').checked) { - page.querySelector('.selectTunersSection').classList.add('hide'); - } else { - page.querySelector('.selectTunersSection').classList.remove('hide'); - } + function reload() { + loading.show(); + ApiClient.getNamedConfiguration('livetv').then(function (config) { + getListingProvider(config, providerId).then(function (info) { + page.querySelector('.txtPath').value = info.Path || ''; + page.querySelector('.txtKids').value = (info.KidsCategories || []).join('|'); + page.querySelector('.txtNews').value = (info.NewsCategories || []).join('|'); + page.querySelector('.txtSports').value = (info.SportsCategories || []).join('|'); + page.querySelector('.txtMovies').value = (info.MovieCategories || []).join('|'); + page.querySelector('.txtMoviePrefix').value = info.MoviePrefix || ''; + page.querySelector('.txtUserAgent').value = info.UserAgent || ''; + page.querySelector('.chkAllTuners').checked = info.EnableAllTuners; - refreshTunerDevices(page, info, config.TunerHosts); - loading.hide(); - }); - }); - } - - function getCategories(txtInput) { - var value = txtInput.value; - - if (value) { - return value.split('|'); - } - - return []; - } - - function submitListingsForm() { - loading.show(); - var id = providerId; - ApiClient.getNamedConfiguration('livetv').then(function (config) { - var info = config.ListingProviders.filter(function (provider) { - return provider.Id === id; - })[0] || {}; - info.Type = 'xmltv'; - info.Path = page.querySelector('.txtPath').value; - info.MoviePrefix = page.querySelector('.txtMoviePrefix').value || null; - info.UserAgent = page.querySelector('.txtUserAgent').value || null; - info.MovieCategories = getCategories(page.querySelector('.txtMovies')); - info.KidsCategories = getCategories(page.querySelector('.txtKids')); - info.NewsCategories = getCategories(page.querySelector('.txtNews')); - info.SportsCategories = getCategories(page.querySelector('.txtSports')); - info.EnableAllTuners = page.querySelector('.chkAllTuners').checked; - info.EnabledTuners = info.EnableAllTuners ? [] : $('.chkTuner', page).get().filter(function (tuner) { - return tuner.checked; - }).map(function (tuner) { - return tuner.getAttribute('data-id'); - }); - ApiClient.ajax({ - type: 'POST', - url: ApiClient.getUrl('LiveTv/ListingProviders', { - ValidateListings: true - }), - data: JSON.stringify(info), - contentType: 'application/json' - }).then(function (result) { - loading.hide(); - - if (options.showConfirmation !== false) { - Dashboard.processServerConfigurationUpdateResult(); - } - - Events.trigger(self, 'submitted'); - }, function () { - loading.hide(); - Dashboard.alert({ - message: globalize.translate('ErrorAddingXmlTvFile') - }); - }); - }); - } - - function getTunerName(providerId) { - switch (providerId = providerId.toLowerCase()) { - case 'm3u': - return 'M3U Playlist'; - case 'hdhomerun': - return 'HDHomerun'; - case 'satip': - return 'DVB'; - default: - return 'Unknown'; - } - } - - function refreshTunerDevices(page, providerInfo, devices) { - var html = ''; - - for (var i = 0, length = devices.length; i < length; i++) { - var device = devices[i]; - html += '
'; - var enabledTuners = providerInfo.EnabledTuners || []; - var isChecked = providerInfo.EnableAllTuners || enabledTuners.indexOf(device.Id) !== -1; - var checkedAttribute = isChecked ? ' checked' : ''; - html += ''; - html += '
'; - html += '
'; - html += device.FriendlyName || getTunerName(device.Type); - html += '
'; - html += '
'; - html += device.Url; - html += '
'; - html += '
'; - html += '
'; - } - - page.querySelector('.tunerList').innerHTML = html; - } - - function onSelectPathClick(e) { - var page = $(e.target).parents('.xmltvForm')[0]; - - require(['directorybrowser'], function (directoryBrowser) { - var picker = new directoryBrowser.default(); - picker.show({ - includeFiles: true, - callback: function (path) { - if (path) { - var txtPath = page.querySelector('.txtPath'); - txtPath.value = path; - txtPath.focus(); - } - picker.close(); - } - }); - }); - } - - var self = this; - - self.submit = function () { - page.querySelector('.btnSubmitListings').click(); - }; - - self.init = function () { - options = options || {}; - - // Only hide the buttons if explicitly set to false; default to showing if undefined or null - // FIXME: rename this option to clarify logic - var hideCancelButton = options.showCancelButton === false; - page.querySelector('.btnCancel').classList.toggle('hide', hideCancelButton); - - var hideSubmitButton = options.showSubmitButton === false; - page.querySelector('.btnSubmitListings').classList.toggle('hide', hideSubmitButton); - - $('form', page).on('submit', function () { - submitListingsForm(); - return false; - }); - page.querySelector('#btnSelectPath').addEventListener('click', onSelectPathClick); - page.querySelector('.chkAllTuners').addEventListener('change', function (evt) { - if (evt.target.checked) { + if (page.querySelector('.chkAllTuners').checked) { page.querySelector('.selectTunersSection').classList.add('hide'); } else { page.querySelector('.selectTunersSection').classList.remove('hide'); } + + refreshTunerDevices(page, info, config.TunerHosts); + loading.hide(); }); - reload(); - }; + }); + } + + function getCategories(txtInput) { + var value = txtInput.value; + + if (value) { + return value.split('|'); + } + + return []; + } + + function submitListingsForm() { + loading.show(); + var id = providerId; + ApiClient.getNamedConfiguration('livetv').then(function (config) { + var info = config.ListingProviders.filter(function (provider) { + return provider.Id === id; + })[0] || {}; + info.Type = 'xmltv'; + info.Path = page.querySelector('.txtPath').value; + info.MoviePrefix = page.querySelector('.txtMoviePrefix').value || null; + info.UserAgent = page.querySelector('.txtUserAgent').value || null; + info.MovieCategories = getCategories(page.querySelector('.txtMovies')); + info.KidsCategories = getCategories(page.querySelector('.txtKids')); + info.NewsCategories = getCategories(page.querySelector('.txtNews')); + info.SportsCategories = getCategories(page.querySelector('.txtSports')); + info.EnableAllTuners = page.querySelector('.chkAllTuners').checked; + info.EnabledTuners = info.EnableAllTuners ? [] : $('.chkTuner', page).get().filter(function (tuner) { + return tuner.checked; + }).map(function (tuner) { + return tuner.getAttribute('data-id'); + }); + ApiClient.ajax({ + type: 'POST', + url: ApiClient.getUrl('LiveTv/ListingProviders', { + ValidateListings: true + }), + data: JSON.stringify(info), + contentType: 'application/json' + }).then(function (result) { + loading.hide(); + + if (options.showConfirmation !== false) { + Dashboard.processServerConfigurationUpdateResult(); + } + + Events.trigger(self, 'submitted'); + }, function () { + loading.hide(); + Dashboard.alert({ + message: globalize.translate('ErrorAddingXmlTvFile') + }); + }); + }); + } + + function getTunerName(providerId) { + switch (providerId = providerId.toLowerCase()) { + case 'm3u': + return 'M3U Playlist'; + case 'hdhomerun': + return 'HDHomerun'; + case 'satip': + return 'DVB'; + default: + return 'Unknown'; + } + } + + function refreshTunerDevices(page, providerInfo, devices) { + var html = ''; + + for (var i = 0, length = devices.length; i < length; i++) { + var device = devices[i]; + html += '
'; + var enabledTuners = providerInfo.EnabledTuners || []; + var isChecked = providerInfo.EnableAllTuners || enabledTuners.indexOf(device.Id) !== -1; + var checkedAttribute = isChecked ? ' checked' : ''; + html += ''; + html += '
'; + html += '
'; + html += device.FriendlyName || getTunerName(device.Type); + html += '
'; + html += '
'; + html += device.Url; + html += '
'; + html += '
'; + html += '
'; + } + + page.querySelector('.tunerList').innerHTML = html; + } + + function onSelectPathClick(e) { + var page = $(e.target).parents('.xmltvForm')[0]; + + import('directorybrowser').then(({default: directoryBrowser}) => { + var picker = new directoryBrowser(); + picker.show({ + includeFiles: true, + callback: function (path) { + if (path) { + var txtPath = page.querySelector('.txtPath'); + txtPath.value = path; + txtPath.focus(); + } + picker.close(); + } + }); + }); + } + + var self = this; + + self.submit = function () { + page.querySelector('.btnSubmitListings').click(); }; -}); + + self.init = function () { + options = options || {}; + + // Only hide the buttons if explicitly set to false; default to showing if undefined or null + // FIXME: rename this option to clarify logic + var hideCancelButton = options.showCancelButton === false; + page.querySelector('.btnCancel').classList.toggle('hide', hideCancelButton); + + var hideSubmitButton = options.showSubmitButton === false; + page.querySelector('.btnSubmitListings').classList.toggle('hide', hideSubmitButton); + + $('form', page).on('submit', function () { + submitListingsForm(); + return false; + }); + page.querySelector('#btnSelectPath').addEventListener('click', onSelectPathClick); + page.querySelector('.chkAllTuners').addEventListener('change', function (evt) { + if (evt.target.checked) { + page.querySelector('.selectTunersSection').classList.add('hide'); + } else { + page.querySelector('.selectTunersSection').classList.remove('hide'); + } + }); + reload(); + }; +}; diff --git a/src/controllers/livetvguideprovider.js b/src/controllers/livetvguideprovider.js index 3ec04fd2af..2948fd5be2 100644 --- a/src/controllers/livetvguideprovider.js +++ b/src/controllers/livetvguideprovider.js @@ -11,7 +11,7 @@ define(['events', 'loading', 'globalize'], function (events, loading, globalize) var url = 'components/tvproviders/' + type + '.js'; require([url], function (factory) { - var instance = new factory(page, providerId, {}); + var instance = new factory.default(page, providerId, {}); events.on(instance, 'submitted', onListingsSubmitted); instance.init(); }); From 4af9b91c3b8d5cf6fabb858c481a2e070ad8269f Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 3 Aug 2020 07:40:48 +0100 Subject: [PATCH 024/124] Update var declerations --- src/components/tvproviders/schedulesdirect.js | 58 +++++++++---------- src/components/tvproviders/xmltv.js | 34 +++++------ 2 files changed, 46 insertions(+), 46 deletions(-) diff --git a/src/components/tvproviders/schedulesdirect.js b/src/components/tvproviders/schedulesdirect.js index 9d60310e98..de469e1845 100644 --- a/src/components/tvproviders/schedulesdirect.js +++ b/src/components/tvproviders/schedulesdirect.js @@ -13,7 +13,7 @@ export default function (page, providerId, options) { function reload() { loading.show(); ApiClient.getNamedConfiguration('livetv').then(function (config) { - var info = config.ListingProviders.filter(function (i) { + const info = config.ListingProviders.filter(function (i) { return i.Id === providerId; })[0] || {}; listingsId = info.ListingsId; @@ -43,12 +43,12 @@ export default function (page, providerId, options) { function setCountry(info) { ApiClient.getJSON(ApiClient.getUrl('LiveTv/ListingProviders/SchedulesDirect/Countries')).then(function (result) { - var i; - var length; - var countryList = []; + let i; + let length; + const countryList = []; - for (var region in result) { - var countries = result[region]; + for (const region in result) { + const countries = result[region]; if (countries.length && region !== 'ZZZ') { for (i = 0, length = countries.length; i < length; i++) { @@ -88,20 +88,20 @@ export default function (page, providerId, options) { return Promise.resolve(''); } - var buffer = new TextEncoder('utf-8').encode(str); + const buffer = new TextEncoder('utf-8').encode(str); return crypto.subtle.digest('SHA-256', buffer).then(function (hash) { return hex(hash); }); } function hex(buffer) { - var hexCodes = []; - var view = new DataView(buffer); + const hexCodes = []; + const view = new DataView(buffer); - for (var i = 0; i < view.byteLength; i += 4) { - var value = view.getUint32(i); - var stringValue = value.toString(16); - var paddedValue = ('00000000' + stringValue).slice(-'00000000'.length); + for (let i = 0; i < view.byteLength; i += 4) { + const value = view.getUint32(i); + const stringValue = value.toString(16); + const paddedValue = ('00000000' + stringValue).slice(-'00000000'.length); hexCodes.push(paddedValue); } @@ -111,14 +111,14 @@ export default function (page, providerId, options) { function submitLoginForm() { loading.show(); sha256(page.querySelector('.txtPass').value).then(function (passwordHash) { - var info = { + const info = { Type: 'SchedulesDirect', Username: page.querySelector('.txtUser').value, EnableAllTuners: true, Password: passwordHash, Pw: page.querySelector('.txtPass').value }; - var id = providerId; + const id = providerId; if (id) { info.Id = id; @@ -145,7 +145,7 @@ export default function (page, providerId, options) { } function submitListingsForm() { - var selectedListingsId = $('#selectListing', page).val(); + const selectedListingsId = $('#selectListing', page).val(); if (!selectedListingsId) { return void Dashboard.alert({ @@ -154,9 +154,9 @@ export default function (page, providerId, options) { } loading.show(); - var id = providerId; + const id = providerId; ApiClient.getNamedConfiguration('livetv').then(function (config) { - var info = config.ListingProviders.filter(function (i) { + const info = config.ListingProviders.filter(function (i) { return i.Id === id; })[0]; info.ZipCode = page.querySelector('.txtZipCode').value; @@ -239,14 +239,14 @@ export default function (page, providerId, options) { } function refreshTunerDevices(page, providerInfo, devices) { - var html = ''; + let html = ''; - for (var i = 0, length = devices.length; i < length; i++) { - var device = devices[i]; + for (let i = 0, length = devices.length; i < length; i++) { + const device = devices[i]; html += '
'; - var enabledTuners = providerInfo.EnabledTuners || []; - var isChecked = providerInfo.EnableAllTuners || enabledTuners.indexOf(device.Id) !== -1; - var checkedAttribute = isChecked ? ' checked' : ''; + const enabledTuners = providerInfo.EnabledTuners || []; + const isChecked = providerInfo.EnableAllTuners || enabledTuners.indexOf(device.Id) !== -1; + const checkedAttribute = isChecked ? ' checked' : ''; html += ''; html += '
'; html += '
'; @@ -262,8 +262,8 @@ export default function (page, providerId, options) { page.querySelector('.tunerList').innerHTML = html; } - var listingsId; - var self = this; + let listingsId; + const self = this; self.submit = function () { page.querySelector('.btnSubmitListingsContainer').click(); @@ -274,10 +274,10 @@ export default function (page, providerId, options) { // Only hide the buttons if explicitly set to false; default to showing if undefined or null // FIXME: rename this option to clarify logic - var hideCancelButton = options.showCancelButton === false; + const hideCancelButton = options.showCancelButton === false; page.querySelector('.btnCancel').classList.toggle('hide', hideCancelButton); - var hideSubmitButton = options.showSubmitButton === false; + const hideSubmitButton = options.showSubmitButton === false; page.querySelector('.btnSubmitListings').classList.toggle('hide', hideSubmitButton); $('.formLogin', page).on('submit', function () { @@ -301,4 +301,4 @@ export default function (page, providerId, options) { $('.createAccountHelp', page).html(globalize.translate('MessageCreateAccountAt', 'http://www.schedulesdirect.org')); reload(); }; -}; +} diff --git a/src/components/tvproviders/xmltv.js b/src/components/tvproviders/xmltv.js index 84e222fe09..a75b29eeb3 100644 --- a/src/components/tvproviders/xmltv.js +++ b/src/components/tvproviders/xmltv.js @@ -9,7 +9,7 @@ import 'paper-icon-button-light'; export default function (page, providerId, options) { function getListingProvider(config, id) { if (config && id) { - var result = config.ListingProviders.filter(function (provider) { + const result = config.ListingProviders.filter(function (provider) { return provider.Id === id; })[0]; @@ -49,7 +49,7 @@ export default function (page, providerId, options) { } function getCategories(txtInput) { - var value = txtInput.value; + const value = txtInput.value; if (value) { return value.split('|'); @@ -60,9 +60,9 @@ export default function (page, providerId, options) { function submitListingsForm() { loading.show(); - var id = providerId; + const id = providerId; ApiClient.getNamedConfiguration('livetv').then(function (config) { - var info = config.ListingProviders.filter(function (provider) { + const info = config.ListingProviders.filter(function (provider) { return provider.Id === id; })[0] || {}; info.Type = 'xmltv'; @@ -117,14 +117,14 @@ export default function (page, providerId, options) { } function refreshTunerDevices(page, providerInfo, devices) { - var html = ''; + let html = ''; - for (var i = 0, length = devices.length; i < length; i++) { - var device = devices[i]; + for (let i = 0, length = devices.length; i < length; i++) { + const device = devices[i]; html += '
'; - var enabledTuners = providerInfo.EnabledTuners || []; - var isChecked = providerInfo.EnableAllTuners || enabledTuners.indexOf(device.Id) !== -1; - var checkedAttribute = isChecked ? ' checked' : ''; + const enabledTuners = providerInfo.EnabledTuners || []; + const isChecked = providerInfo.EnableAllTuners || enabledTuners.indexOf(device.Id) !== -1; + const checkedAttribute = isChecked ? ' checked' : ''; html += ''; html += '
'; html += '
'; @@ -141,15 +141,15 @@ export default function (page, providerId, options) { } function onSelectPathClick(e) { - var page = $(e.target).parents('.xmltvForm')[0]; + const page = $(e.target).parents('.xmltvForm')[0]; import('directorybrowser').then(({default: directoryBrowser}) => { - var picker = new directoryBrowser(); + const picker = new directoryBrowser(); picker.show({ includeFiles: true, callback: function (path) { if (path) { - var txtPath = page.querySelector('.txtPath'); + const txtPath = page.querySelector('.txtPath'); txtPath.value = path; txtPath.focus(); } @@ -159,7 +159,7 @@ export default function (page, providerId, options) { }); } - var self = this; + const self = this; self.submit = function () { page.querySelector('.btnSubmitListings').click(); @@ -170,10 +170,10 @@ export default function (page, providerId, options) { // Only hide the buttons if explicitly set to false; default to showing if undefined or null // FIXME: rename this option to clarify logic - var hideCancelButton = options.showCancelButton === false; + const hideCancelButton = options.showCancelButton === false; page.querySelector('.btnCancel').classList.toggle('hide', hideCancelButton); - var hideSubmitButton = options.showSubmitButton === false; + const hideSubmitButton = options.showSubmitButton === false; page.querySelector('.btnSubmitListings').classList.toggle('hide', hideSubmitButton); $('form', page).on('submit', function () { @@ -190,4 +190,4 @@ export default function (page, providerId, options) { }); reload(); }; -}; +} From bafe857271606fd65227e5176f490673b66fabed Mon Sep 17 00:00:00 2001 From: Xantios Krugor Date: Mon, 3 Aug 2020 14:20:31 +0200 Subject: [PATCH 025/124] Move focusManager to ES6 --- package.json | 1 + src/components/focusManager.js | 7 ++++--- src/controllers/list.js | 2 +- src/libraries/scroller.js | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index f794b8b6da..eae461bb4f 100644 --- a/package.json +++ b/package.json @@ -110,6 +110,7 @@ "src/components/favoriteitems.js", "src/components/fetchhelper.js", "src/components/filterdialog/filterdialog.js", + "src/components/focusManager.js", "src/components/groupedcards.js", "src/components/homeScreenSettings/homeScreenSettings.js", "src/components/homesections/homesections.js", diff --git a/src/components/focusManager.js b/src/components/focusManager.js index 9c495bf952..1f947871de 100644 --- a/src/components/focusManager.js +++ b/src/components/focusManager.js @@ -1,4 +1,6 @@ -define(['dom', 'scrollManager'], function (dom, scrollManager) { +import dom from 'dom'; +import scrollManager from 'scrollManager'; + 'use strict'; var scopes = []; @@ -472,7 +474,7 @@ define(['dom', 'scrollManager'], function (dom, scrollManager) { } } - return { + export default { autoFocus: autoFocus, focus: focus, focusableParent: focusableParent, @@ -505,4 +507,3 @@ define(['dom', 'scrollManager'], function (dom, scrollManager) { focusLast: focusLast, moveFocus: moveFocus }; -}); diff --git a/src/controllers/list.js b/src/controllers/list.js index 68e81f05dd..a35b7a0cf9 100644 --- a/src/controllers/list.js +++ b/src/controllers/list.js @@ -771,7 +771,7 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' loading.hide(); if (refresh) { - focusManager.autoFocus(self.itemsContainer); + focusManager.default.autoFocus(self.itemsContainer); } }); diff --git a/src/libraries/scroller.js b/src/libraries/scroller.js index c0cb3e557c..d869d8c8be 100644 --- a/src/libraries/scroller.js +++ b/src/libraries/scroller.js @@ -697,7 +697,7 @@ define(['browser', 'layoutManager', 'dom', 'focusManager', 'ResizeObserver', 'sc function onFrameClick(e) { if (e.which === 1) { - var focusableParent = focusManager.focusableParent(e.target); + var focusableParent = focusManager.default.focusableParent(e.target); if (focusableParent && focusableParent !== document.activeElement) { focusableParent.focus(); } From 32e3df78010380c5c27d205a063fe647452d736e Mon Sep 17 00:00:00 2001 From: Xantios Krugor Date: Mon, 3 Aug 2020 14:20:31 +0200 Subject: [PATCH 026/124] Move focusManager to ES6 --- package.json | 1 + src/components/focusManager.js | 7 ++++--- src/controllers/list.js | 2 +- src/libraries/scroller.js | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index f794b8b6da..eae461bb4f 100644 --- a/package.json +++ b/package.json @@ -110,6 +110,7 @@ "src/components/favoriteitems.js", "src/components/fetchhelper.js", "src/components/filterdialog/filterdialog.js", + "src/components/focusManager.js", "src/components/groupedcards.js", "src/components/homeScreenSettings/homeScreenSettings.js", "src/components/homesections/homesections.js", diff --git a/src/components/focusManager.js b/src/components/focusManager.js index 9c495bf952..1f947871de 100644 --- a/src/components/focusManager.js +++ b/src/components/focusManager.js @@ -1,4 +1,6 @@ -define(['dom', 'scrollManager'], function (dom, scrollManager) { +import dom from 'dom'; +import scrollManager from 'scrollManager'; + 'use strict'; var scopes = []; @@ -472,7 +474,7 @@ define(['dom', 'scrollManager'], function (dom, scrollManager) { } } - return { + export default { autoFocus: autoFocus, focus: focus, focusableParent: focusableParent, @@ -505,4 +507,3 @@ define(['dom', 'scrollManager'], function (dom, scrollManager) { focusLast: focusLast, moveFocus: moveFocus }; -}); diff --git a/src/controllers/list.js b/src/controllers/list.js index 68e81f05dd..a35b7a0cf9 100644 --- a/src/controllers/list.js +++ b/src/controllers/list.js @@ -771,7 +771,7 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' loading.hide(); if (refresh) { - focusManager.autoFocus(self.itemsContainer); + focusManager.default.autoFocus(self.itemsContainer); } }); diff --git a/src/libraries/scroller.js b/src/libraries/scroller.js index c0cb3e557c..d869d8c8be 100644 --- a/src/libraries/scroller.js +++ b/src/libraries/scroller.js @@ -697,7 +697,7 @@ define(['browser', 'layoutManager', 'dom', 'focusManager', 'ResizeObserver', 'sc function onFrameClick(e) { if (e.which === 1) { - var focusableParent = focusManager.focusableParent(e.target); + var focusableParent = focusManager.default.focusableParent(e.target); if (focusableParent && focusableParent !== document.activeElement) { focusableParent.focus(); } From 133e1c9085649f45f275e0d5a0a77d026ec28069 Mon Sep 17 00:00:00 2001 From: Xantios Krugor Date: Mon, 3 Aug 2020 14:28:20 +0200 Subject: [PATCH 027/124] Disable linter for this file --- src/components/focusManager.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/focusManager.js b/src/components/focusManager.js index 1f947871de..74df694ff6 100644 --- a/src/components/focusManager.js +++ b/src/components/focusManager.js @@ -1,3 +1,5 @@ +/* eslint-disable indent */ + import dom from 'dom'; import scrollManager from 'scrollManager'; From df1f9470e64ce97b6d78269750b00171df07a42a Mon Sep 17 00:00:00 2001 From: Xantios Krugor Date: Mon, 3 Aug 2020 14:46:48 +0200 Subject: [PATCH 028/124] ESLint is a magnificent piece of software that everybody should love /sarcasm --- src/components/focusManager.js | 72 ++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 34 deletions(-) diff --git a/src/components/focusManager.js b/src/components/focusManager.js index 74df694ff6..b55f933db6 100644 --- a/src/components/focusManager.js +++ b/src/components/focusManager.js @@ -3,7 +3,9 @@ import dom from 'dom'; import scrollManager from 'scrollManager'; - 'use strict'; +/* eslint-disable no-unused-expressions */ +'use strict'; +/* eslint-enable no-unused-expressions */ var scopes = []; function pushScope(elem) { @@ -476,36 +478,38 @@ import scrollManager from 'scrollManager'; } } - export default { - autoFocus: autoFocus, - focus: focus, - focusableParent: focusableParent, - getFocusableElements: getFocusableElements, - moveLeft: function (sourceElement, options) { - var container = options ? options.container : null; - var focusableElements = options ? options.focusableElements : null; - nav(sourceElement, 0, container, focusableElements); - }, - moveRight: function (sourceElement, options) { - var container = options ? options.container : null; - var focusableElements = options ? options.focusableElements : null; - nav(sourceElement, 1, container, focusableElements); - }, - moveUp: function (sourceElement, options) { - var container = options ? options.container : null; - var focusableElements = options ? options.focusableElements : null; - nav(sourceElement, 2, container, focusableElements); - }, - moveDown: function (sourceElement, options) { - var container = options ? options.container : null; - var focusableElements = options ? options.focusableElements : null; - nav(sourceElement, 3, container, focusableElements); - }, - sendText: sendText, - isCurrentlyFocusable: isCurrentlyFocusable, - pushScope: pushScope, - popScope: popScope, - focusFirst: focusFirst, - focusLast: focusLast, - moveFocus: moveFocus - }; +/* eslint-enable indent */ + +export default { + autoFocus: autoFocus, + focus: focus, + focusableParent: focusableParent, + getFocusableElements: getFocusableElements, + moveLeft: function (sourceElement, options) { + var container = options ? options.container : null; + var focusableElements = options ? options.focusableElements : null; + nav(sourceElement, 0, container, focusableElements); + }, + moveRight: function (sourceElement, options) { + var container = options ? options.container : null; + var focusableElements = options ? options.focusableElements : null; + nav(sourceElement, 1, container, focusableElements); + }, + moveUp: function (sourceElement, options) { + var container = options ? options.container : null; + var focusableElements = options ? options.focusableElements : null; + nav(sourceElement, 2, container, focusableElements); + }, + moveDown: function (sourceElement, options) { + var container = options ? options.container : null; + var focusableElements = options ? options.focusableElements : null; + nav(sourceElement, 3, container, focusableElements); + }, + sendText: sendText, + isCurrentlyFocusable: isCurrentlyFocusable, + pushScope: pushScope, + popScope: popScope, + focusFirst: focusFirst, + focusLast: focusLast, + moveFocus: moveFocus +}; From 6f78cdfde2a4094d110cdeeead802ece5c086d64 Mon Sep 17 00:00:00 2001 From: Xantios Krugor Date: Mon, 3 Aug 2020 15:08:17 +0200 Subject: [PATCH 029/124] Its build as ES6 module, no need for the strict --- src/components/focusManager.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/components/focusManager.js b/src/components/focusManager.js index b55f933db6..a9ec377cce 100644 --- a/src/components/focusManager.js +++ b/src/components/focusManager.js @@ -3,10 +3,6 @@ import dom from 'dom'; import scrollManager from 'scrollManager'; -/* eslint-disable no-unused-expressions */ -'use strict'; -/* eslint-enable no-unused-expressions */ - var scopes = []; function pushScope(elem) { scopes.push(elem); From 8dced0300d02a506a6c895a3c1d0bc4ee0e6f688 Mon Sep 17 00:00:00 2001 From: matjaz321 Date: Mon, 3 Aug 2020 20:51:40 +0200 Subject: [PATCH 030/124] Added livetvseriestimers.js to package.json --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 17d175b7e7..d9b7c0970e 100644 --- a/package.json +++ b/package.json @@ -207,6 +207,7 @@ "src/controllers/searchpage.js", "src/controllers/livetvtuner.js", "src/controllers/livetvstatus.js", + "src/controllers/livetv/livetvseriestimers.js", "src/controllers/shows/episodes.js", "src/controllers/shows/tvgenres.js", "src/controllers/shows/tvlatest.js", From ffa3396a8f5eabd88b89d85f11acb4fece56a619 Mon Sep 17 00:00:00 2001 From: matjaz321 Date: Mon, 3 Aug 2020 21:17:44 +0200 Subject: [PATCH 031/124] Migrated livetvschedule.js to ES6 module --- package.json | 1 + src/controllers/livetv/livetvschedule.js | 216 ++++++++++++----------- 2 files changed, 110 insertions(+), 107 deletions(-) diff --git a/package.json b/package.json index 1d7cf770d3..7d86744574 100644 --- a/package.json +++ b/package.json @@ -222,6 +222,7 @@ "src/controllers/livetvstatus.js", "src/controllers/livetvguideprovider.js", "src/controllers/livetvsettings.js", + "src/controllers/livetv/livetvschedule.js", "src/controllers/shows/episodes.js", "src/controllers/shows/tvgenres.js", "src/controllers/shows/tvlatest.js", diff --git a/src/controllers/livetv/livetvschedule.js b/src/controllers/livetv/livetvschedule.js index 4427bef5d1..df6bcedac9 100644 --- a/src/controllers/livetv/livetvschedule.js +++ b/src/controllers/livetv/livetvschedule.js @@ -1,122 +1,124 @@ -define(['layoutManager', 'cardBuilder', 'apphost', 'imageLoader', 'loading', 'scripts/livetvcomponents', 'emby-button', 'emby-itemscontainer'], function (layoutManager, cardBuilder, appHost, imageLoader, loading) { - 'use strict'; +import layoutManager from 'layoutManager'; +import cardBuilder from 'cardBuilder'; +import imageLoader from 'imageLoader'; +import loading from 'loading'; +import 'scripts/livetvcomponents'; +import 'emby-button'; +import 'emby-itemscontainer'; - loading = loading.default || loading; +function enableScrollX() { + return !layoutManager.desktop; +} - function enableScrollX() { - return !layoutManager.desktop; +function renderRecordings(elem, recordings, cardOptions) { + if (recordings.length) { + elem.classList.remove('hide'); + } else { + elem.classList.add('hide'); } - function renderRecordings(elem, recordings, cardOptions) { - if (recordings.length) { + const recordingItems = elem.querySelector('.recordingItems'); + + if (enableScrollX()) { + recordingItems.classList.add('scrollX'); + + if (layoutManager.tv) { + recordingItems.classList.add('smoothScrollX'); + } + + recordingItems.classList.add('hiddenScrollX'); + recordingItems.classList.remove('vertical-wrap'); + } else { + recordingItems.classList.remove('scrollX'); + recordingItems.classList.remove('smoothScrollX'); + recordingItems.classList.remove('hiddenScrollX'); + recordingItems.classList.add('vertical-wrap'); + } + + recordingItems.innerHTML = cardBuilder.getCardsHtml(Object.assign({ + items: recordings, + shape: enableScrollX() ? 'autooverflow' : 'auto', + showTitle: true, + showParentTitle: true, + coverImage: true, + cardLayout: false, + centerText: true, + allowBottomPadding: !enableScrollX(), + preferThumb: 'auto' + }, cardOptions || {})); + imageLoader.lazyChildren(recordingItems); +} + +function getBackdropShape() { + return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; +} + +function renderActiveRecordings(context, promise) { + promise.then(function (result) { + renderRecordings(context.querySelector('#activeRecordings'), result.Items, { + shape: enableScrollX() ? 'autooverflow' : 'auto', + defaultShape: getBackdropShape(), + showParentTitle: false, + showParentTitleOrTitle: true, + showTitle: false, + showAirTime: true, + showAirEndTime: true, + showChannelName: true, + coverImage: true, + overlayText: false, + overlayMoreButton: true + }); + }); +} + +function renderTimers(context, timers, options) { + LiveTvHelpers.getTimersHtml(timers, options).then(function (html) { + const elem = context; + + if (html) { elem.classList.remove('hide'); } else { elem.classList.add('hide'); } - var recordingItems = elem.querySelector('.recordingItems'); + elem.querySelector('.recordingItems').innerHTML = html; + imageLoader.lazyChildren(elem); + }); +} - if (enableScrollX()) { - recordingItems.classList.add('scrollX'); +function renderUpcomingRecordings(context, promise) { + promise.then(function (result) { + renderTimers(context.querySelector('#upcomingRecordings'), result.Items); + loading.hide(); + }); +} - if (layoutManager.tv) { - recordingItems.classList.add('smoothScrollX'); - } +export default function (view, params, tabContent) { + let activeRecordingsPromise; + let upcomingRecordingsPromise; + const self = this; + tabContent.querySelector('#upcomingRecordings .recordingItems').addEventListener('timercancelled', function () { + self.preRender(); + self.renderTab(); + }); - recordingItems.classList.add('hiddenScrollX'); - recordingItems.classList.remove('vertical-wrap'); - } else { - recordingItems.classList.remove('scrollX'); - recordingItems.classList.remove('smoothScrollX'); - recordingItems.classList.remove('hiddenScrollX'); - recordingItems.classList.add('vertical-wrap'); - } - - recordingItems.innerHTML = cardBuilder.getCardsHtml(Object.assign({ - items: recordings, - shape: enableScrollX() ? 'autooverflow' : 'auto', - showTitle: true, - showParentTitle: true, - coverImage: true, - cardLayout: false, - centerText: true, - allowBottomPadding: !enableScrollX(), - preferThumb: 'auto' - }, cardOptions || {})); - imageLoader.lazyChildren(recordingItems); - } - - function getBackdropShape() { - return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; - } - - function renderActiveRecordings(context, promise) { - promise.then(function (result) { - renderRecordings(context.querySelector('#activeRecordings'), result.Items, { - shape: enableScrollX() ? 'autooverflow' : 'auto', - defaultShape: getBackdropShape(), - showParentTitle: false, - showParentTitleOrTitle: true, - showTitle: false, - showAirTime: true, - showAirEndTime: true, - showChannelName: true, - coverImage: true, - overlayText: false, - overlayMoreButton: true - }); + self.preRender = function () { + activeRecordingsPromise = ApiClient.getLiveTvRecordings({ + UserId: Dashboard.getCurrentUserId(), + IsInProgress: true, + Fields: 'CanDelete,PrimaryImageAspectRatio,BasicSyncInfo', + EnableTotalRecordCount: false, + EnableImageTypes: 'Primary,Thumb,Backdrop' }); - } - - function renderTimers(context, timers, options) { - LiveTvHelpers.getTimersHtml(timers, options).then(function (html) { - var elem = context; - - if (html) { - elem.classList.remove('hide'); - } else { - elem.classList.add('hide'); - } - - elem.querySelector('.recordingItems').innerHTML = html; - imageLoader.lazyChildren(elem); + upcomingRecordingsPromise = ApiClient.getLiveTvTimers({ + IsActive: false, + IsScheduled: true }); - } - - function renderUpcomingRecordings(context, promise) { - promise.then(function (result) { - renderTimers(context.querySelector('#upcomingRecordings'), result.Items); - loading.hide(); - }); - } - - return function (view, params, tabContent) { - var activeRecordingsPromise; - var upcomingRecordingsPromise; - var self = this; - tabContent.querySelector('#upcomingRecordings .recordingItems').addEventListener('timercancelled', function () { - self.preRender(); - self.renderTab(); - }); - - self.preRender = function () { - activeRecordingsPromise = ApiClient.getLiveTvRecordings({ - UserId: Dashboard.getCurrentUserId(), - IsInProgress: true, - Fields: 'CanDelete,PrimaryImageAspectRatio,BasicSyncInfo', - EnableTotalRecordCount: false, - EnableImageTypes: 'Primary,Thumb,Backdrop' - }); - upcomingRecordingsPromise = ApiClient.getLiveTvTimers({ - IsActive: false, - IsScheduled: true - }); - }; - - self.renderTab = function () { - loading.show(); - renderActiveRecordings(tabContent, activeRecordingsPromise); - renderUpcomingRecordings(tabContent, upcomingRecordingsPromise); - }; }; -}); + + self.renderTab = function () { + loading.show(); + renderActiveRecordings(tabContent, activeRecordingsPromise); + renderUpcomingRecordings(tabContent, upcomingRecordingsPromise); + }; +}; From 1d747c9252dc5927aa3a21e8b9d85270eaae13b6 Mon Sep 17 00:00:00 2001 From: matjaz321 Date: Mon, 3 Aug 2020 21:21:39 +0200 Subject: [PATCH 032/124] Fixed linting errors --- src/controllers/dashboard/notifications/notifications/index.js | 1 - src/controllers/livetv/livetvschedule.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/controllers/dashboard/notifications/notifications/index.js b/src/controllers/dashboard/notifications/notifications/index.js index d12a5f2e61..b97403f8eb 100644 --- a/src/controllers/dashboard/notifications/notifications/index.js +++ b/src/controllers/dashboard/notifications/notifications/index.js @@ -1,5 +1,4 @@ import loading from 'loading'; -import libraryMenu from 'libraryMenu'; import globalize from 'globalize'; import 'listViewStyle'; import 'emby-button'; diff --git a/src/controllers/livetv/livetvschedule.js b/src/controllers/livetv/livetvschedule.js index df6bcedac9..d7bfbad059 100644 --- a/src/controllers/livetv/livetvschedule.js +++ b/src/controllers/livetv/livetvschedule.js @@ -121,4 +121,4 @@ export default function (view, params, tabContent) { renderActiveRecordings(tabContent, activeRecordingsPromise); renderUpcomingRecordings(tabContent, upcomingRecordingsPromise); }; -}; +} From 2eb2ec3d8a047fe1939a184301db0f94c8f9e324 Mon Sep 17 00:00:00 2001 From: matjaz321 Date: Mon, 3 Aug 2020 21:26:13 +0200 Subject: [PATCH 033/124] Migrated livetvrecordings.js to es6 module --- package.json | 1 + src/controllers/livetv/livetvrecordings.js | 201 +++++++++++---------- 2 files changed, 103 insertions(+), 99 deletions(-) diff --git a/package.json b/package.json index 1d7cf770d3..e98cb81a5d 100644 --- a/package.json +++ b/package.json @@ -222,6 +222,7 @@ "src/controllers/livetvstatus.js", "src/controllers/livetvguideprovider.js", "src/controllers/livetvsettings.js", + "src/controllers/livetv/livetvrecordings.js", "src/controllers/shows/episodes.js", "src/controllers/shows/tvgenres.js", "src/controllers/shows/tvlatest.js", diff --git a/src/controllers/livetv/livetvrecordings.js b/src/controllers/livetv/livetvrecordings.js index b0259bc89b..cb3bfeb7a6 100644 --- a/src/controllers/livetv/livetvrecordings.js +++ b/src/controllers/livetv/livetvrecordings.js @@ -1,111 +1,114 @@ -define(['layoutManager', 'loading', 'cardBuilder', 'apphost', 'imageLoader', 'scripts/livetvcomponents', 'listViewStyle', 'emby-itemscontainer'], function (layoutManager, loading, cardBuilder, appHost, imageLoader) { - 'use strict'; +import layoutManager from 'layoutManager'; +import loading from 'loading'; +import cardBuilder from 'cardBuilder'; +import appHost from 'apphost'; +import imageLoader from 'imageLoader'; +import 'scripts/livetvcomponents'; +import 'listViewStyle'; +import 'emby-itemscontainer'; - loading = loading.default || loading; - - function renderRecordings(elem, recordings, cardOptions, scrollX) { - if (!elem) { - return; - } - - if (recordings.length) { - elem.classList.remove('hide'); - } else { - elem.classList.add('hide'); - } - - var recordingItems = elem.querySelector('.recordingItems'); - - if (scrollX) { - recordingItems.classList.add('scrollX'); - recordingItems.classList.add('hiddenScrollX'); - recordingItems.classList.remove('vertical-wrap'); - } else { - recordingItems.classList.remove('scrollX'); - recordingItems.classList.remove('hiddenScrollX'); - recordingItems.classList.add('vertical-wrap'); - } - - recordingItems.innerHTML = cardBuilder.getCardsHtml(Object.assign({ - items: recordings, - shape: scrollX ? 'autooverflow' : 'auto', - defaultShape: scrollX ? 'overflowBackdrop' : 'backdrop', - showTitle: true, - showParentTitle: true, - coverImage: true, - cardLayout: false, - centerText: true, - allowBottomPadding: !scrollX, - preferThumb: 'auto', - overlayText: false - }, cardOptions || {})); - imageLoader.lazyChildren(recordingItems); +function renderRecordings(elem, recordings, cardOptions, scrollX) { + if (!elem) { + return; } - function renderLatestRecordings(context, promise) { - promise.then(function (result) { - renderRecordings(context.querySelector('#latestRecordings'), result.Items, { - showYear: true, - lines: 2 - }, false); - loading.hide(); - }); + if (recordings.length) { + elem.classList.remove('hide'); + } else { + elem.classList.add('hide'); } - function renderRecordingFolders(context, promise) { - promise.then(function (result) { - renderRecordings(context.querySelector('#recordingFolders'), result.Items, { - showYear: false, - showParentTitle: false - }, false); - }); + const recordingItems = elem.querySelector('.recordingItems'); + + if (scrollX) { + recordingItems.classList.add('scrollX'); + recordingItems.classList.add('hiddenScrollX'); + recordingItems.classList.remove('vertical-wrap'); + } else { + recordingItems.classList.remove('scrollX'); + recordingItems.classList.remove('hiddenScrollX'); + recordingItems.classList.add('vertical-wrap'); } - function onMoreClick(e) { - var type = this.getAttribute('data-type'); - var serverId = ApiClient.serverId(); + recordingItems.innerHTML = cardBuilder.getCardsHtml(Object.assign({ + items: recordings, + shape: scrollX ? 'autooverflow' : 'auto', + defaultShape: scrollX ? 'overflowBackdrop' : 'backdrop', + showTitle: true, + showParentTitle: true, + coverImage: true, + cardLayout: false, + centerText: true, + allowBottomPadding: !scrollX, + preferThumb: 'auto', + overlayText: false + }, cardOptions || {})); + imageLoader.lazyChildren(recordingItems); +} - switch (type) { - case 'latest': - Dashboard.navigate('list.html?type=Recordings&serverId=' + serverId); - } +function renderLatestRecordings(context, promise) { + promise.then(function (result) { + renderRecordings(context.querySelector('#latestRecordings'), result.Items, { + showYear: true, + lines: 2 + }, false); + loading.hide(); + }); +} + +function renderRecordingFolders(context, promise) { + promise.then(function (result) { + renderRecordings(context.querySelector('#recordingFolders'), result.Items, { + showYear: false, + showParentTitle: false + }, false); + }); +} + +function onMoreClick(e) { + const type = this.getAttribute('data-type'); + const serverId = ApiClient.serverId(); + + switch (type) { + case 'latest': + Dashboard.navigate('list.html?type=Recordings&serverId=' + serverId); + } +} + +export default function (view, params, tabContent) { + function enableFullRender() { + return new Date().getTime() - lastFullRender > 300000; } - return function (view, params, tabContent) { - function enableFullRender() { - return new Date().getTime() - lastFullRender > 300000; + let foldersPromise; + let latestPromise; + const self = this; + let lastFullRender = 0; + const moreButtons = tabContent.querySelectorAll('.more'); + + for (let i = 0, length = moreButtons.length; i < length; i++) { + moreButtons[i].addEventListener('click', onMoreClick); + } + + self.preRender = function () { + if (enableFullRender()) { + latestPromise = ApiClient.getLiveTvRecordings({ + UserId: Dashboard.getCurrentUserId(), + Limit: 12, + Fields: 'CanDelete,PrimaryImageAspectRatio,BasicSyncInfo', + EnableTotalRecordCount: false, + EnableImageTypes: 'Primary,Thumb,Backdrop' + }); + foldersPromise = ApiClient.getRecordingFolders(Dashboard.getCurrentUserId()); } - - var foldersPromise; - var latestPromise; - var self = this; - var lastFullRender = 0; - var moreButtons = tabContent.querySelectorAll('.more'); - - for (var i = 0, length = moreButtons.length; i < length; i++) { - moreButtons[i].addEventListener('click', onMoreClick); - } - - self.preRender = function () { - if (enableFullRender()) { - latestPromise = ApiClient.getLiveTvRecordings({ - UserId: Dashboard.getCurrentUserId(), - Limit: 12, - Fields: 'CanDelete,PrimaryImageAspectRatio,BasicSyncInfo', - EnableTotalRecordCount: false, - EnableImageTypes: 'Primary,Thumb,Backdrop' - }); - foldersPromise = ApiClient.getRecordingFolders(Dashboard.getCurrentUserId()); - } - }; - - self.renderTab = function () { - if (enableFullRender()) { - loading.show(); - renderLatestRecordings(tabContent, latestPromise); - renderRecordingFolders(tabContent, foldersPromise); - lastFullRender = new Date().getTime(); - } - }; }; -}); + + self.renderTab = function () { + if (enableFullRender()) { + loading.show(); + renderLatestRecordings(tabContent, latestPromise); + renderRecordingFolders(tabContent, foldersPromise); + lastFullRender = new Date().getTime(); + } + }; +} From ba3418450ebe8f954d1ad6f2300acdb882725909 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 25 Jul 2020 11:42:21 +0100 Subject: [PATCH 034/124] Migration of movies to ES6 modules --- package.json | 6 + src/controllers/movies/moviecollections.js | 74 +++++++------ src/controllers/movies/moviegenres.js | 60 +++++----- src/controllers/movies/movies.js | 117 ++++++++++---------- src/controllers/movies/moviesrecommended.js | 111 +++++++++++-------- src/controllers/movies/movietrailers.js | 75 +++++++------ 6 files changed, 244 insertions(+), 199 deletions(-) diff --git a/package.json b/package.json index 1d7cf770d3..678794aebc 100644 --- a/package.json +++ b/package.json @@ -212,6 +212,12 @@ "src/controllers/edititemmetadata.js", "src/controllers/favorites.js", "src/controllers/hometab.js", + "src/controllers/dashboard/plugins/repositories.js", + "src/controllers/movies/moviecollections.js", + "src/controllers/movies/moviegenres.js", + "src/controllers/movies/movies.js", + "src/controllers/movies/moviesrecommended.js", + "src/controllers/movies/movietrailers.js", "src/controllers/playback/nowplaying.js", "src/controllers/playback/videoosd.js", "src/controllers/itemDetails/index.js", diff --git a/src/controllers/movies/moviecollections.js b/src/controllers/movies/moviecollections.js index 7c1a5156d9..530117295c 100644 --- a/src/controllers/movies/moviecollections.js +++ b/src/controllers/movies/moviecollections.js @@ -1,13 +1,19 @@ -define(['loading', 'events', 'libraryBrowser', 'imageLoader', 'listView', 'cardBuilder', 'userSettings', 'globalize', 'emby-itemscontainer'], function (loading, events, libraryBrowser, imageLoader, listView, cardBuilder, userSettings, globalize) { - 'use strict'; +import loading from 'loading'; +import events from 'events'; +import libraryBrowser from 'libraryBrowser'; +import imageLoader from 'imageLoader'; +import listView from 'listView'; +import cardBuilder from 'cardBuilder'; +import * as userSettings from 'userSettings'; +import globalize from 'globalize'; +import 'emby-itemscontainer'; - loading = loading.default || loading; - libraryBrowser = libraryBrowser.default || libraryBrowser; +/* eslint-disable indent */ - return function (view, params, tabContent) { + export default function (view, params, tabContent) { function getPageData(context) { - var key = getSavedQueryKey(context); - var pageData = data[key]; + const key = getSavedQueryKey(context); + let pageData = data[key]; if (!pageData) { pageData = data[key] = { @@ -48,8 +54,8 @@ define(['loading', 'events', 'libraryBrowser', 'imageLoader', 'listView', 'cardB } function onViewStyleChange() { - var viewStyle = self.getCurrentViewStyle(); - var itemsContainer = tabContent.querySelector('.itemsContainer'); + const viewStyle = self.getCurrentViewStyle(); + const itemsContainer = tabContent.querySelector('.itemsContainer'); if (viewStyle == 'List') { itemsContainer.classList.add('vertical-list'); @@ -65,7 +71,7 @@ define(['loading', 'events', 'libraryBrowser', 'imageLoader', 'listView', 'cardB function reloadItems(page) { loading.show(); isLoading = true; - var query = getQuery(page); + const query = getQuery(page); ApiClient.getItems(ApiClient.getCurrentUserId(), query).then(function (result) { function onNextPageClick() { if (isLoading) { @@ -90,8 +96,8 @@ define(['loading', 'events', 'libraryBrowser', 'imageLoader', 'listView', 'cardB } window.scrollTo(0, 0); - var html; - var pagingHtml = libraryBrowser.getQueryPagingHtml({ + let html; + const pagingHtml = libraryBrowser.getQueryPagingHtml({ startIndex: query.StartIndex, limit: query.Limit, totalRecordCount: result.TotalRecordCount, @@ -101,7 +107,7 @@ define(['loading', 'events', 'libraryBrowser', 'imageLoader', 'listView', 'cardB sortButton: false, filterButton: false }); - var viewStyle = self.getCurrentViewStyle(); + const viewStyle = self.getCurrentViewStyle(); if (viewStyle == 'Thumb') { html = cardBuilder.getCardsHtml({ items: result.Items, @@ -155,22 +161,21 @@ define(['loading', 'events', 'libraryBrowser', 'imageLoader', 'listView', 'cardB showTitle: true }); } - var i; - var length; - var elems = tabContent.querySelectorAll('.paging'); - for (i = 0, length = elems.length; i < length; i++) { - elems[i].innerHTML = pagingHtml; + let elems = tabContent.querySelectorAll('.paging'); + + for (const elem of elems) { + elem.innerHTML = pagingHtml; } elems = tabContent.querySelectorAll('.btnNextPage'); - for (i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener('click', onNextPageClick); + for (const elem of elems) { + elem.addEventListener('click', onNextPageClick); } elems = tabContent.querySelectorAll('.btnPreviousPage'); - for (i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener('click', onPreviousPageClick); + for (const elem of elems) { + elem.addEventListener('click', onPreviousPageClick); } if (!result.Items.length) { @@ -182,22 +187,22 @@ define(['loading', 'events', 'libraryBrowser', 'imageLoader', 'listView', 'cardB html += '
'; } - var itemsContainer = tabContent.querySelector('.itemsContainer'); + const itemsContainer = tabContent.querySelector('.itemsContainer'); itemsContainer.innerHTML = html; imageLoader.lazyChildren(itemsContainer); libraryBrowser.saveQueryValues(getSavedQueryKey(page), query); loading.hide(); isLoading = false; - require(['autoFocuser'], function (autoFocuser) { + import('autoFocuser').then(({default: autoFocuser}) => { autoFocuser.autoFocus(page); }); }); } - var self = this; - var data = {}; - var isLoading = false; + const self = this; + const data = {}; + let isLoading = false; self.getCurrentViewStyle = function () { return getPageData(tabContent).view; @@ -230,21 +235,21 @@ define(['loading', 'events', 'libraryBrowser', 'imageLoader', 'listView', 'cardB button: e.target }); }); - var btnSelectView = tabContent.querySelector('.btnSelectView'); + const btnSelectView = tabContent.querySelector('.btnSelectView'); btnSelectView.addEventListener('click', function (e) { libraryBrowser.showLayoutMenu(e.target, self.getCurrentViewStyle(), 'List,Poster,PosterCard,Thumb,ThumbCard'.split(',')); }); btnSelectView.addEventListener('layoutchange', function (e) { - var viewStyle = e.detail.viewStyle; + const viewStyle = e.detail.viewStyle; getPageData(tabContent).view = viewStyle; libraryBrowser.saveViewSetting(getSavedQueryKey(tabContent), viewStyle); getQuery(tabContent).StartIndex = 0; onViewStyleChange(); reloadItems(tabContent); }); - tabContent.querySelector('.btnNewCollection').addEventListener('click', function () { - require(['collectionEditor'], function (collectionEditor) { - var serverId = ApiClient.serverInfo().Id; + tabContent.querySelector('.btnNewCollection').addEventListener('click', () => { + import('collectionEditor').then(({default: collectionEditor}) => { + const serverId = ApiClient.serverInfo().Id; new collectionEditor.showEditor({ items: [], serverId: serverId @@ -261,5 +266,6 @@ define(['loading', 'events', 'libraryBrowser', 'imageLoader', 'listView', 'cardB }; self.destroy = function () {}; - }; -}); + } + +/* eslint-enable indent */ diff --git a/src/controllers/movies/moviegenres.js b/src/controllers/movies/moviegenres.js index 82ab4d4d4b..56fb88b20f 100644 --- a/src/controllers/movies/moviegenres.js +++ b/src/controllers/movies/moviegenres.js @@ -1,13 +1,20 @@ -define(['layoutManager', 'loading', 'libraryBrowser', 'cardBuilder', 'lazyLoader', 'apphost', 'globalize', 'appRouter', 'dom', 'emby-button'], function (layoutManager, loading, libraryBrowser, cardBuilder, lazyLoader, appHost, globalize, appRouter, dom) { - 'use strict'; +import layoutManager from 'layoutManager'; +import loading from 'loading'; +import libraryBrowser from 'libraryBrowser'; +import cardBuilder from 'cardBuilder'; +import lazyLoader from 'lazyLoader'; +import appHost from 'apphost'; +import globalize from 'globalize'; +import appRouter from 'appRouter'; +import dom from 'dom'; +import 'emby-button'; - loading = loading.default || loading; - libraryBrowser = libraryBrowser.default || libraryBrowser; +/* eslint-disable indent */ - return function (view, params, tabContent) { + export default function (view, params, tabContent) { function getPageData() { - var key = getSavedQueryKey(); - var pageData = data[key]; + const key = getSavedQueryKey(); + let pageData = data[key]; if (!pageData) { pageData = data[key] = { @@ -37,7 +44,7 @@ define(['layoutManager', 'loading', 'libraryBrowser', 'cardBuilder', 'lazyLoader function getPromise() { loading.show(); - var query = getQuery(); + const query = getQuery(); return ApiClient.getGenres(ApiClient.getCurrentUserId(), query); } @@ -54,17 +61,17 @@ define(['layoutManager', 'loading', 'libraryBrowser', 'cardBuilder', 'lazyLoader } function fillItemsContainer(entry) { - var elem = entry.target; - var id = elem.getAttribute('data-id'); - var viewStyle = self.getCurrentViewStyle(); - var limit = viewStyle == 'Thumb' || viewStyle == 'ThumbCard' ? 5 : 9; + const elem = entry.target; + const id = elem.getAttribute('data-id'); + const viewStyle = self.getCurrentViewStyle(); + let limit = 'Thumb' == viewStyle || 'ThumbCard' == viewStyle ? 5 : 9; if (enableScrollX()) { limit = 10; } - var enableImageTypes = viewStyle == 'Thumb' || viewStyle == 'ThumbCard' ? 'Primary,Backdrop,Thumb' : 'Primary'; - var query = { + const enableImageTypes = 'Thumb' == viewStyle || 'ThumbCard' == viewStyle ? 'Primary,Backdrop,Thumb' : 'Primary'; + const query = { SortBy: 'SortName', SortOrder: 'Ascending', IncludeItemTypes: 'Movie', @@ -129,14 +136,14 @@ define(['layoutManager', 'loading', 'libraryBrowser', 'cardBuilder', 'lazyLoader } function reloadItems(context, promise) { - var query = getQuery(); + const query = getQuery(); promise.then(function (result) { - var elem = context.querySelector('#items'); - var html = ''; - var items = result.Items; + const elem = context.querySelector('#items'); + let html = ''; + const items = result.Items; - for (var i = 0, length = items.length; i < length; i++) { - var item = items[i]; + for (let i = 0, length = items.length; i < length; i++) { + const item = items[i]; html += '
'; html += '
'; @@ -151,7 +158,7 @@ define(['layoutManager', 'loading', 'libraryBrowser', 'cardBuilder', 'lazyLoader html += ''; html += '
'; if (enableScrollX()) { - var scrollXClass = 'scrollX hiddenScrollX'; + let scrollXClass = 'scrollX hiddenScrollX'; if (layoutManager.tv) { scrollXClass += 'smoothScrollX padded-top-focusscale padded-bottom-focusscale'; @@ -187,8 +194,8 @@ define(['layoutManager', 'loading', 'libraryBrowser', 'cardBuilder', 'lazyLoader self.renderTab(); } - var self = this; - var data = {}; + const self = this; + const data = {}; self.getViewStyles = function () { return 'Poster,PosterCard,Thumb,ThumbCard'.split(','); @@ -205,7 +212,7 @@ define(['layoutManager', 'loading', 'libraryBrowser', 'cardBuilder', 'lazyLoader }; self.enableViewSelection = true; - var promise; + let promise; self.preRender = function () { promise = getPromise(); @@ -214,5 +221,6 @@ define(['layoutManager', 'loading', 'libraryBrowser', 'cardBuilder', 'lazyLoader self.renderTab = function () { reloadItems(tabContent, promise); }; - }; -}); + } + +/* eslint-enable indent */ diff --git a/src/controllers/movies/movies.js b/src/controllers/movies/movies.js index ade9dc4b89..3b68849036 100644 --- a/src/controllers/movies/movies.js +++ b/src/controllers/movies/movies.js @@ -1,12 +1,19 @@ -define(['loading', 'layoutManager', 'userSettings', 'events', 'libraryBrowser', 'alphaPicker', 'listView', 'cardBuilder', 'globalize', 'emby-itemscontainer'], function (loading, layoutManager, userSettings, events, libraryBrowser, AlphaPicker, listView, cardBuilder, globalize) { - 'use strict'; +import loading from 'loading'; +import layoutManager from 'layoutManager'; +import * as userSettings from 'userSettings'; +import events from 'events'; +import libraryBrowser from 'libraryBrowser'; +import AlphaPicker from 'alphaPicker'; +import listView from 'listView'; +import cardBuilder from 'cardBuilder'; +import globalize from 'globalize'; +import 'emby-itemscontainer'; - loading = loading.default || loading; - libraryBrowser = libraryBrowser.default || libraryBrowser; +/* eslint-disable indent */ - return function (view, params, tabContent, options) { - function onViewStyleChange() { - if (self.getCurrentViewStyle() == 'List') { + export default function (view, params, tabContent, options) { + const onViewStyleChange = () => { + if (this.getCurrentViewStyle() == 'List') { itemsContainer.classList.add('vertical-list'); itemsContainer.classList.remove('vertical-wrap'); } else { @@ -15,13 +22,13 @@ define(['loading', 'layoutManager', 'userSettings', 'events', 'libraryBrowser', } itemsContainer.innerHTML = ''; - } + }; - function updateFilterControls() { - if (self.alphaPicker) { - self.alphaPicker.value(query.NameStartsWithOrGreater); + const updateFilterControls = () => { + if (this.alphaPicker) { + this.alphaPicker.value(query.NameStartsWithOrGreater); } - } + }; function fetchData() { isLoading = true; @@ -54,7 +61,7 @@ define(['loading', 'layoutManager', 'userSettings', 'events', 'libraryBrowser', window.scrollTo(0, 0); updateFilterControls(); - var pagingHtml = libraryBrowser.getQueryPagingHtml({ + const pagingHtml = libraryBrowser.getQueryPagingHtml({ startIndex: query.StartIndex, limit: query.Limit, totalRecordCount: result.TotalRecordCount, @@ -64,35 +71,30 @@ define(['loading', 'layoutManager', 'userSettings', 'events', 'libraryBrowser', sortButton: false, filterButton: false }); - var i; - var length; - var elems = tabContent.querySelectorAll('.paging'); - for (i = 0, length = elems.length; i < length; i++) { - elems[i].innerHTML = pagingHtml; + for (const elem of tabContent.querySelectorAll('.paging')) { + elem.innerHTML = pagingHtml; } - elems = tabContent.querySelectorAll('.btnNextPage'); - for (i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener('click', onNextPageClick); + for (const elem of tabContent.querySelectorAll('.btnNextPage')) { + elem.addEventListener('click', onNextPageClick); } - elems = tabContent.querySelectorAll('.btnPreviousPage'); - for (i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener('click', onPreviousPageClick); + for (const elem of tabContent.querySelectorAll('.btnPreviousPage')) { + elem.addEventListener('click', onPreviousPageClick); } isLoading = false; loading.hide(); - require(['autoFocuser'], function (autoFocuser) { + import('autoFocuser').then(({default: autoFocuser}) => { autoFocuser.autoFocus(tabContent); }); } - function getItemsHtml(items) { - var html; - var viewStyle = self.getCurrentViewStyle(); + const getItemsHtml = (items) => { + let html; + const viewStyle = this.getCurrentViewStyle(); if (viewStyle == 'Thumb') { html = cardBuilder.getCardsHtml({ @@ -156,22 +158,22 @@ define(['loading', 'layoutManager', 'userSettings', 'events', 'libraryBrowser', } return html; - } + }; - function initPage(tabContent) { + const initPage = (tabContent) => { itemsContainer.fetchData = fetchData; itemsContainer.getItemsHtml = getItemsHtml; itemsContainer.afterRefresh = afterRefresh; - var alphaPickerElement = tabContent.querySelector('.alphaPicker'); + let alphaPickerElement = tabContent.querySelector('.alphaPicker'); if (alphaPickerElement) { alphaPickerElement.addEventListener('alphavaluechanged', function (e) { - var newValue = e.detail.value; + let newValue = e.detail.value; query.NameStartsWithOrGreater = newValue; query.StartIndex = 0; itemsContainer.refreshItems(); }); - self.alphaPicker = new AlphaPicker.default({ + this.alphaPicker = new AlphaPicker({ element: alphaPickerElement, valueChangeEvent: 'click' }); @@ -181,14 +183,14 @@ define(['loading', 'layoutManager', 'userSettings', 'events', 'libraryBrowser', itemsContainer.classList.add('padded-right-withalphapicker'); } - var btnFilter = tabContent.querySelector('.btnFilter'); + const btnFilter = tabContent.querySelector('.btnFilter'); if (btnFilter) { - btnFilter.addEventListener('click', function () { - self.showFilterMenu(); + btnFilter.addEventListener('click', () => { + this.showFilterMenu(); }); } - var btnSort = tabContent.querySelector('.btnSort'); + const btnSort = tabContent.querySelector('.btnSort'); if (btnSort) { btnSort.addEventListener('click', function (e) { @@ -231,24 +233,23 @@ define(['loading', 'layoutManager', 'userSettings', 'events', 'libraryBrowser', }); }); } - var btnSelectView = tabContent.querySelector('.btnSelectView'); + const btnSelectView = tabContent.querySelector('.btnSelectView'); btnSelectView.addEventListener('click', function (e) { - libraryBrowser.showLayoutMenu(e.target, self.getCurrentViewStyle(), 'Banner,List,Poster,PosterCard,Thumb,ThumbCard'.split(',')); + libraryBrowser.showLayoutMenu(e.target, this.getCurrentViewStyle, 'Banner,List,Poster,PosterCard,Thumb,ThumbCard'.split(',')); }); btnSelectView.addEventListener('layoutchange', function (e) { - var viewStyle = e.detail.viewStyle; + let viewStyle = e.detail.viewStyle; userSettings.set(savedViewKey, viewStyle); query.StartIndex = 0; onViewStyleChange(); itemsContainer.refreshItems(); }); - } + }; - var self = this; - var itemsContainer = tabContent.querySelector('.itemsContainer'); - var savedQueryKey = params.topParentId + '-' + options.mode; - var savedViewKey = savedQueryKey + '-view'; - var query = { + let itemsContainer = tabContent.querySelector('.itemsContainer'); + const savedQueryKey = params.topParentId + '-' + options.mode; + const savedViewKey = savedQueryKey + '-view'; + let query = { SortBy: 'SortName,ProductionYear', SortOrder: 'Ascending', IncludeItemTypes: 'Movie', @@ -264,7 +265,7 @@ define(['loading', 'layoutManager', 'userSettings', 'events', 'libraryBrowser', query['Limit'] = userSettings.libraryPageSize(); } - var isLoading = false; + let isLoading = false; if (options.mode === 'favorites') { query.IsFavorite = true; @@ -272,14 +273,14 @@ define(['loading', 'layoutManager', 'userSettings', 'events', 'libraryBrowser', query = userSettings.loadQuerySettings(savedQueryKey, query); - self.showFilterMenu = function () { - require(['components/filterdialog/filterdialog'], function ({default: filterDialogFactory}) { - var filterDialog = new filterDialogFactory({ + this.showFilterMenu = function () { + import('components/filterdialog/filterdialog').then(({default: filterDialogFactory}) => { + let filterDialog = new filterDialogFactory({ query: query, mode: 'movies', serverId: ApiClient.serverId() }); - events.on(filterDialog, 'filterchange', function () { + events.on(filterDialog, 'filterchange', () => { query.StartIndex = 0; itemsContainer.refreshItems(); }); @@ -287,22 +288,24 @@ define(['loading', 'layoutManager', 'userSettings', 'events', 'libraryBrowser', }); }; - self.getCurrentViewStyle = function () { + this.getCurrentViewStyle = function () { return userSettings.get(savedViewKey) || 'Poster'; }; - self.initTab = function () { + this.initTab = function () { initPage(tabContent); onViewStyleChange(); }; - self.renderTab = function () { + //function renderTab () { + this.renderTab = function () { itemsContainer.refreshItems(); updateFilterControls(); }; - self.destroy = function () { + this.destroy = function () { itemsContainer = null; }; - }; -}); + } + +/* eslint-enable indent */ diff --git a/src/controllers/movies/moviesrecommended.js b/src/controllers/movies/moviesrecommended.js index a633d654cd..5e376637b3 100644 --- a/src/controllers/movies/moviesrecommended.js +++ b/src/controllers/movies/moviesrecommended.js @@ -1,5 +1,20 @@ -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'; +import events from 'events'; +import layoutManager from 'layoutManager'; +import inputManager from 'inputManager'; +import * as userSettings from 'userSettings'; +import libraryMenu from 'libraryMenu'; +import mainTabsManager from 'mainTabsManager'; +import cardBuilder from 'cardBuilder'; +import dom from 'dom'; +import imageLoader from 'imageLoader'; +import playbackManager from 'playbackManager'; +import globalize from 'globalize'; +import 'emby-scroller'; +import 'emby-itemscontainer'; +import 'emby-tabs'; +import 'emby-button'; + +/* eslint-disable indent */ playbackManager = playbackManager.default || playbackManager; @@ -16,7 +31,7 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu' } function loadLatest(page, userId, parentId) { - var options = { + const options = { IncludeItemTypes: 'Movie', Limit: 18, Fields: 'PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo', @@ -26,8 +41,8 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu' EnableTotalRecordCount: false }; ApiClient.getJSON(ApiClient.getUrl('Users/' + userId + '/Items/Latest', options)).then(function (items) { - var allowBottomPadding = !enableScrollX(); - var container = page.querySelector('#recentlyAddedItems'); + const allowBottomPadding = !enableScrollX(); + const container = page.querySelector('#recentlyAddedItems'); cardBuilder.buildCards(items, { itemsContainer: container, shape: getPortraitShape(), @@ -45,8 +60,8 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu' } function loadResume(page, userId, parentId) { - var screenWidth = dom.getWindowSize().innerWidth; - var options = { + let screenWidth = dom.getWindowSize().innerWidth; + const options = { SortBy: 'DatePlayed', SortOrder: 'Descending', IncludeItemTypes: 'Movie', @@ -67,8 +82,8 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu' page.querySelector('#resumableSection').classList.add('hide'); } - var allowBottomPadding = !enableScrollX(); - var container = page.querySelector('#resumableItems'); + const allowBottomPadding = !enableScrollX(); + const container = page.querySelector('#resumableItems'); cardBuilder.buildCards(result.Items, { itemsContainer: container, preferThumb: true, @@ -88,8 +103,8 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu' } function getRecommendationHtml(recommendation) { - var html = ''; - var title = ''; + let html = ''; + let title = ''; switch (recommendation.RecommendationType) { case 'SimilarToRecentlyPlayed': @@ -113,7 +128,7 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu' html += '
'; html += '

' + title + '

'; - var allowBottomPadding = true; + const allowBottomPadding = true; if (enableScrollX()) { html += '
'; @@ -141,8 +156,8 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu' } function loadSuggestions(page, userId, parentId) { - var screenWidth = dom.getWindowSize().innerWidth; - var url = ApiClient.getUrl('Movies/Recommendations', { + let screenWidth = dom.getWindowSize().innerWidth; + let url = ApiClient.getUrl('Movies/Recommendations', { userId: userId, categoryLimit: 6, ItemLimit: screenWidth >= 1920 ? 8 : screenWidth >= 1600 ? 8 : screenWidth >= 1200 ? 6 : 5, @@ -157,9 +172,9 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu' return; } - var html = recommendations.map(getRecommendationHtml).join(''); + const html = recommendations.map(getRecommendationHtml).join(''); page.querySelector('.noItemsMessage').classList.add('hide'); - var recs = page.querySelector('.recommendations'); + let recs = page.querySelector('.recommendations'); recs.innerHTML = html; imageLoader.lazyChildren(recs); @@ -169,7 +184,7 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu' } function autoFocus(page) { - require(['autoFocuser'], function (autoFocuser) { + import('autoFocuser').then(({default: autoFocuser}) => { autoFocuser.autoFocus(page); }); } @@ -195,17 +210,16 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu' } function initSuggestedTab(page, tabContent) { - var containers = tabContent.querySelectorAll('.itemsContainer'); + const containers = tabContent.querySelectorAll('.itemsContainer'); - for (var i = 0, length = containers.length; i < length; i++) { - setScrollClasses(containers[i], enableScrollX()); + for (const container of containers) { + setScrollClasses(container, enableScrollX()); } } function loadSuggestionsTab(view, params, tabContent) { - var parentId = params.topParentId; - var userId = ApiClient.getCurrentUserId(); - console.debug('loadSuggestionsTab'); + const parentId = params.topParentId; + const userId = ApiClient.getCurrentUserId(); loadResume(tabContent, userId, parentId); loadLatest(tabContent, userId, parentId); loadSuggestions(tabContent, userId, parentId); @@ -249,13 +263,13 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu' } } - return function (view, params) { + export default function (view, params) { function onBeforeTabChange(e) { preLoadTab(view, parseInt(e.detail.selectedTabIndex)); } function onTabChange(e) { - var newIndex = parseInt(e.detail.selectedTabIndex); + const newIndex = parseInt(e.detail.selectedTabIndex); loadTab(view, newIndex); } @@ -268,45 +282,47 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu' } function getTabController(page, index, callback) { - var depends = []; + let depends = ''; switch (index) { case 0: - depends.push('controllers/movies/movies'); + depends = 'controllers/movies/movies'; break; case 1: + depends = 'controllers/movies/moviesrecommended.js'; break; case 2: - depends.push('controllers/movies/movietrailers'); + depends = 'controllers/movies/movietrailers'; break; case 3: - depends.push('controllers/movies/movies'); + depends = 'controllers/movies/movies'; break; case 4: - depends.push('controllers/movies/moviecollections'); + depends = 'controllers/movies/moviecollections'; break; case 5: - depends.push('controllers/movies/moviegenres'); + depends = 'controllers/movies/moviegenres'; break; case 6: - depends.push('scripts/searchtab'); + depends = 'scripts/searchtab'; + break; } - require(depends, function (controllerFactory) { - var tabContent; + import(depends).then(({default: controllerFactory}) => { + let tabContent; if (index === suggestionsTabIndex) { tabContent = view.querySelector(".pageTabContent[data-index='" + index + "']"); self.tabContent = tabContent; } - var controller = tabControllers[index]; + let controller = tabControllers[index]; if (!controller) { tabContent = view.querySelector(".pageTabContent[data-index='" + index + "']"); @@ -375,17 +391,17 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu' var suggestionsTabIndex = 1; self.initTab = function () { - var tabContent = view.querySelector(".pageTabContent[data-index='" + suggestionsTabIndex + "']"); + let tabContent = view.querySelector(".pageTabContent[data-index='" + suggestionsTabIndex + "']"); initSuggestedTab(view, tabContent); }; self.renderTab = function () { - var tabContent = view.querySelector(".pageTabContent[data-index='" + suggestionsTabIndex + "']"); + let tabContent = view.querySelector(".pageTabContent[data-index='" + suggestionsTabIndex + "']"); loadSuggestionsTab(view, params, tabContent); }; - var tabControllers = []; - var renderedTabs = []; + let tabControllers = []; + let renderedTabs = []; view.addEventListener('viewshow', function (e) { initTabs(); if (!view.getAttribute('data-title')) { @@ -408,12 +424,11 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu' view.addEventListener('viewbeforehide', function (e) { inputManager.off(window, onInputCommand); }); - view.addEventListener('viewdestroy', function (e) { - tabControllers.forEach(function (t) { - if (t.destroy) { - t.destroy(); - } - }); - }); - }; -}); + for (const tabController of tabControllers) { + if (tabController.destroy) { + tabController.destroy(); + } + } + } + +/* eslint-enable indent */ diff --git a/src/controllers/movies/movietrailers.js b/src/controllers/movies/movietrailers.js index 8d9fe8d090..a892769e47 100644 --- a/src/controllers/movies/movietrailers.js +++ b/src/controllers/movies/movietrailers.js @@ -1,13 +1,21 @@ -define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', 'alphaPicker', 'listView', 'cardBuilder', 'userSettings', 'globalize', 'emby-itemscontainer'], function (layoutManager, loading, events, libraryBrowser, imageLoader, AlphaPicker, listView, cardBuilder, userSettings, globalize) { - 'use strict'; +import layoutManager from 'layoutManager'; +import loading from 'loading'; +import events from 'events'; +import libraryBrowser from 'libraryBrowser'; +import imageLoader from 'imageLoader'; +import AlphaPicker from 'alphaPicker'; +import listView from 'listView'; +import cardBuilder from 'cardBuilder'; +import * as userSettings from 'userSettings'; +import globalize from 'globalize'; +import 'emby-itemscontainer'; - loading = loading.default || loading; - libraryBrowser = libraryBrowser.default || libraryBrowser; +/* eslint-disable indent */ - return function (view, params, tabContent) { + export default function (view, params, tabContent) { function getPageData(context) { - var key = getSavedQueryKey(context); - var pageData = data[key]; + const key = getSavedQueryKey(context); + let pageData = data[key]; if (!pageData) { pageData = data[key] = { @@ -49,7 +57,7 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', ' function reloadItems() { loading.show(); isLoading = true; - var query = getQuery(tabContent); + const query = getQuery(tabContent); ApiClient.getItems(ApiClient.getCurrentUserId(), query).then(function (result) { function onNextPageClick() { if (isLoading) { @@ -75,7 +83,7 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', ' window.scrollTo(0, 0); updateFilterControls(tabContent); - var pagingHtml = libraryBrowser.getQueryPagingHtml({ + const pagingHtml = libraryBrowser.getQueryPagingHtml({ startIndex: query.StartIndex, limit: query.Limit, totalRecordCount: result.TotalRecordCount, @@ -85,8 +93,8 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', ' sortButton: false, filterButton: false }); - var html; - var viewStyle = self.getCurrentViewStyle(); + let html; + const viewStyle = self.getCurrentViewStyle(); if (viewStyle == 'Thumb') { html = cardBuilder.getCardsHtml({ @@ -142,22 +150,20 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', ' }); } - var i; - var length; - var elems = tabContent.querySelectorAll('.paging'); + let elems = tabContent.querySelectorAll('.paging'); - for (i = 0, length = elems.length; i < length; i++) { - elems[i].innerHTML = pagingHtml; + for (const elem of elems) { + elem.innerHTML = pagingHtml; } elems = tabContent.querySelectorAll('.btnNextPage'); - for (i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener('click', onNextPageClick); + for (const elem of elems) { + elem.addEventListener('click', onNextPageClick); } elems = tabContent.querySelectorAll('.btnPreviousPage'); - for (i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener('click', onPreviousPageClick); + for (const elem of elems) { + elem.addEventListener('click', onPreviousPageClick); } if (!result.Items.length) { @@ -169,7 +175,7 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', ' html += '
'; } - var itemsContainer = tabContent.querySelector('.itemsContainer'); + const itemsContainer = tabContent.querySelector('.itemsContainer'); itemsContainer.innerHTML = html; imageLoader.lazyChildren(itemsContainer); libraryBrowser.saveQueryValues(getSavedQueryKey(tabContent), query); @@ -179,17 +185,17 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', ' } function updateFilterControls(tabContent) { - var query = getQuery(tabContent); + const query = getQuery(tabContent); self.alphaPicker.value(query.NameStartsWithOrGreater); } - var self = this; - var data = {}; - var isLoading = false; + const self = this; + const data = {}; + let isLoading = false; self.showFilterMenu = function () { - require(['components/filterdialog/filterdialog'], function ({default: filterDialogFactory}) { - var filterDialog = new filterDialogFactory({ + import('components/filterdialog/filterdialog').then(({default: filterDialogFactory}) => { + const filterDialog = new filterDialogFactory({ query: getQuery(tabContent), mode: 'movies', serverId: ApiClient.serverId() @@ -207,16 +213,16 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', ' }; function initPage(tabContent) { - var alphaPickerElement = tabContent.querySelector('.alphaPicker'); - var itemsContainer = tabContent.querySelector('.itemsContainer'); + const alphaPickerElement = tabContent.querySelector('.alphaPicker'); + const itemsContainer = tabContent.querySelector('.itemsContainer'); alphaPickerElement.addEventListener('alphavaluechanged', function (e) { - var newValue = e.detail.value; - var query = getQuery(tabContent); + const newValue = e.detail.value; + const query = getQuery(tabContent); query.NameStartsWithOrGreater = newValue; query.StartIndex = 0; reloadItems(); }); - self.alphaPicker = new AlphaPicker.default({ + self.alphaPicker = new AlphaPicker({ element: alphaPickerElement, valueChangeEvent: 'click' }); @@ -270,5 +276,6 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', ' }; self.destroy = function () {}; - }; -}); + } + +/* eslint-enable indent */ From f1b2a86c7d8047775bae0b9bf1bae1c60e14ee3e Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 25 Jul 2020 22:36:23 +0100 Subject: [PATCH 035/124] Apply suggestions --- src/controllers/movies/moviecollections.js | 27 +++++++++---------- src/controllers/movies/moviegenres.js | 27 +++++++++---------- src/controllers/movies/movies.js | 1 - src/controllers/movies/movietrailers.js | 31 +++++++++++----------- 4 files changed, 41 insertions(+), 45 deletions(-) diff --git a/src/controllers/movies/moviecollections.js b/src/controllers/movies/moviecollections.js index 530117295c..ffedb10eef 100644 --- a/src/controllers/movies/moviecollections.js +++ b/src/controllers/movies/moviecollections.js @@ -53,8 +53,8 @@ import 'emby-itemscontainer'; return context.savedQueryKey; } - function onViewStyleChange() { - const viewStyle = self.getCurrentViewStyle(); + const onViewStyleChange = () => { + const viewStyle = this.getCurrentViewStyle(); const itemsContainer = tabContent.querySelector('.itemsContainer'); if (viewStyle == 'List') { @@ -66,13 +66,13 @@ import 'emby-itemscontainer'; } itemsContainer.innerHTML = ''; - } + }; - function reloadItems(page) { + const reloadItems = (page) => { loading.show(); isLoading = true; const query = getQuery(page); - ApiClient.getItems(ApiClient.getCurrentUserId(), query).then(function (result) { + ApiClient.getItems(ApiClient.getCurrentUserId(), query).then((result) => { function onNextPageClick() { if (isLoading) { return; @@ -107,7 +107,7 @@ import 'emby-itemscontainer'; sortButton: false, filterButton: false }); - const viewStyle = self.getCurrentViewStyle(); + const viewStyle = this.getCurrentViewStyle(); if (viewStyle == 'Thumb') { html = cardBuilder.getCardsHtml({ items: result.Items, @@ -198,17 +198,16 @@ import 'emby-itemscontainer'; autoFocuser.autoFocus(page); }); }); - } + }; - const self = this; const data = {}; let isLoading = false; - self.getCurrentViewStyle = function () { + this.getCurrentViewStyle = function () { return getPageData(tabContent).view; }; - function initPage(tabContent) { + const initPage = (tabContent) => { tabContent.querySelector('.btnSort').addEventListener('click', function (e) { libraryBrowser.showSortMenu({ items: [{ @@ -237,7 +236,7 @@ import 'emby-itemscontainer'; }); const btnSelectView = tabContent.querySelector('.btnSelectView'); btnSelectView.addEventListener('click', function (e) { - libraryBrowser.showLayoutMenu(e.target, self.getCurrentViewStyle(), 'List,Poster,PosterCard,Thumb,ThumbCard'.split(',')); + libraryBrowser.showLayoutMenu(e.target, this.getCurrentViewStyle(), 'List,Poster,PosterCard,Thumb,ThumbCard'.split(',')); }); btnSelectView.addEventListener('layoutchange', function (e) { const viewStyle = e.detail.viewStyle; @@ -256,16 +255,16 @@ import 'emby-itemscontainer'; }); }); }); - } + }; initPage(tabContent); onViewStyleChange(); - self.renderTab = function () { + this.renderTab = function () { reloadItems(tabContent); }; - self.destroy = function () {}; + this.destroy = function () {}; } /* eslint-enable indent */ diff --git a/src/controllers/movies/moviegenres.js b/src/controllers/movies/moviegenres.js index 56fb88b20f..f426ac024d 100644 --- a/src/controllers/movies/moviegenres.js +++ b/src/controllers/movies/moviegenres.js @@ -60,10 +60,10 @@ import 'emby-button'; return enableScrollX() ? 'overflowPortrait' : 'portrait'; } - function fillItemsContainer(entry) { + const fillItemsContainer = (entry) => { const elem = entry.target; const id = elem.getAttribute('data-id'); - const viewStyle = self.getCurrentViewStyle(); + const viewStyle = this.getCurrentViewStyle(); let limit = 'Thumb' == viewStyle || 'ThumbCard' == viewStyle ? 5 : 9; if (enableScrollX()) { @@ -133,7 +133,7 @@ import 'emby-button'; tabContent.querySelector('.btnMoreFromGenre' + id + ' .material-icons').classList.remove('hide'); } }); - } + }; function reloadItems(context, promise) { const query = getQuery(); @@ -189,36 +189,35 @@ import 'emby-button'; }); } - function fullyReload() { - self.preRender(); - self.renderTab(); - } + const fullyReload = () => { + this.preRender(); + this.renderTab(); + }; - const self = this; const data = {}; - self.getViewStyles = function () { + this.getViewStyles = function () { return 'Poster,PosterCard,Thumb,ThumbCard'.split(','); }; - self.getCurrentViewStyle = function () { + this.getCurrentViewStyle = function () { return getPageData().view; }; - self.setCurrentViewStyle = function (viewStyle) { + this.setCurrentViewStyle = function (viewStyle) { getPageData().view = viewStyle; libraryBrowser.saveViewSetting(getSavedQueryKey(), viewStyle); fullyReload(); }; - self.enableViewSelection = true; + this.enableViewSelection = true; let promise; - self.preRender = function () { + this.preRender = function () { promise = getPromise(); }; - self.renderTab = function () { + this.renderTab = function () { reloadItems(tabContent, promise); }; } diff --git a/src/controllers/movies/movies.js b/src/controllers/movies/movies.js index 3b68849036..302e8b717e 100644 --- a/src/controllers/movies/movies.js +++ b/src/controllers/movies/movies.js @@ -297,7 +297,6 @@ import 'emby-itemscontainer'; onViewStyleChange(); }; - //function renderTab () { this.renderTab = function () { itemsContainer.refreshItems(); updateFilterControls(); diff --git a/src/controllers/movies/movietrailers.js b/src/controllers/movies/movietrailers.js index a892769e47..1cab0f1999 100644 --- a/src/controllers/movies/movietrailers.js +++ b/src/controllers/movies/movietrailers.js @@ -54,11 +54,11 @@ import 'emby-itemscontainer'; return context.savedQueryKey; } - function reloadItems() { + const reloadItems = () => { loading.show(); isLoading = true; const query = getQuery(tabContent); - ApiClient.getItems(ApiClient.getCurrentUserId(), query).then(function (result) { + ApiClient.getItems(ApiClient.getCurrentUserId(), query).then((result) => { function onNextPageClick() { if (isLoading) { return; @@ -94,7 +94,7 @@ import 'emby-itemscontainer'; filterButton: false }); let html; - const viewStyle = self.getCurrentViewStyle(); + const viewStyle = this.getCurrentViewStyle(); if (viewStyle == 'Thumb') { html = cardBuilder.getCardsHtml({ @@ -182,18 +182,17 @@ import 'emby-itemscontainer'; loading.hide(); isLoading = false; }); - } + }; - function updateFilterControls(tabContent) { + const updateFilterControls = (tabContent) => { const query = getQuery(tabContent); - self.alphaPicker.value(query.NameStartsWithOrGreater); - } + this.alphaPicker.value(query.NameStartsWithOrGreater); + }; - const self = this; const data = {}; let isLoading = false; - self.showFilterMenu = function () { + this.showFilterMenu = function () { import('components/filterdialog/filterdialog').then(({default: filterDialogFactory}) => { const filterDialog = new filterDialogFactory({ query: getQuery(tabContent), @@ -208,11 +207,11 @@ import 'emby-itemscontainer'; }); }; - self.getCurrentViewStyle = function () { + this.getCurrentViewStyle = function () { return getPageData(tabContent).view; }; - function initPage(tabContent) { + const initPage = (tabContent) => { const alphaPickerElement = tabContent.querySelector('.alphaPicker'); const itemsContainer = tabContent.querySelector('.itemsContainer'); alphaPickerElement.addEventListener('alphavaluechanged', function (e) { @@ -222,7 +221,7 @@ import 'emby-itemscontainer'; query.StartIndex = 0; reloadItems(); }); - self.alphaPicker = new AlphaPicker({ + this.alphaPicker = new AlphaPicker({ element: alphaPickerElement, valueChangeEvent: 'click' }); @@ -232,7 +231,7 @@ import 'emby-itemscontainer'; itemsContainer.classList.add('padded-right-withalphapicker'); tabContent.querySelector('.btnFilter').addEventListener('click', function () { - self.showFilterMenu(); + this.showFilterMenu(); }); tabContent.querySelector('.btnSort').addEventListener('click', function (e) { libraryBrowser.showSortMenu({ @@ -266,16 +265,16 @@ import 'emby-itemscontainer'; button: e.target }); }); - } + }; initPage(tabContent); - self.renderTab = function () { + this.renderTab = function () { reloadItems(); updateFilterControls(tabContent); }; - self.destroy = function () {}; + this.destroy = function () {}; } /* eslint-enable indent */ From a09ceeefa35e908ac33d29a1cead39fc25b8cb07 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 25 Jul 2020 23:03:15 +0100 Subject: [PATCH 036/124] removal of self --- src/controllers/movies/moviesrecommended.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/controllers/movies/moviesrecommended.js b/src/controllers/movies/moviesrecommended.js index 5e376637b3..dda75a2320 100644 --- a/src/controllers/movies/moviesrecommended.js +++ b/src/controllers/movies/moviesrecommended.js @@ -281,7 +281,7 @@ import 'emby-button'; mainTabsManager.setTabs(view, currentTabIndex, getTabs, getTabContainers, onBeforeTabChange, onTabChange); } - function getTabController(page, index, callback) { + const getTabController = (page, index, callback) => { let depends = ''; switch (index) { @@ -319,7 +319,7 @@ import 'emby-button'; if (index === suggestionsTabIndex) { tabContent = view.querySelector(".pageTabContent[data-index='" + index + "']"); - self.tabContent = tabContent; + this.tabContent = tabContent; } let controller = tabControllers[index]; @@ -328,7 +328,7 @@ import 'emby-button'; tabContent = view.querySelector(".pageTabContent[data-index='" + index + "']"); if (index === suggestionsTabIndex) { - controller = self; + controller = this; } else if (index === 6) { controller = new controllerFactory(view, tabContent, { collectionType: 'movies', @@ -351,7 +351,7 @@ import 'emby-button'; callback(controller); }); - } + }; function preLoadTab(page, index) { getTabController(page, index, function (controller) { @@ -363,12 +363,14 @@ import 'emby-button'; function loadTab(page, index) { currentTabIndex = index; - getTabController(page, index, function (controller) { + getTabController(page, index, ((controller) => { + initialTabIndex = null; + if (renderedTabs.indexOf(index) == -1) { renderedTabs.push(index); controller.renderTab(); } - }); + })); } function onPlaybackStop(e, state) { @@ -390,12 +392,12 @@ import 'emby-button'; var currentTabIndex = parseInt(params.tab || getDefaultTabIndex(params.topParentId)); var suggestionsTabIndex = 1; - self.initTab = function () { + this.initTab = function () { let tabContent = view.querySelector(".pageTabContent[data-index='" + suggestionsTabIndex + "']"); initSuggestedTab(view, tabContent); }; - self.renderTab = function () { + this.renderTab = function () { let tabContent = view.querySelector(".pageTabContent[data-index='" + suggestionsTabIndex + "']"); loadSuggestionsTab(view, params, tabContent); }; From f7c3445e1c2c14ebe806e052328ab55432ff15ba Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 28 Jul 2020 21:14:40 +0100 Subject: [PATCH 037/124] remove smells --- src/controllers/movies/moviecollections.js | 1 - src/controllers/movies/moviegenres.js | 2 -- src/controllers/movies/movies.js | 1 - src/controllers/movies/moviesrecommended.js | 2 +- src/controllers/movies/movietrailers.js | 1 - 5 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/controllers/movies/moviecollections.js b/src/controllers/movies/moviecollections.js index ffedb10eef..b99c918a13 100644 --- a/src/controllers/movies/moviecollections.js +++ b/src/controllers/movies/moviecollections.js @@ -1,5 +1,4 @@ import loading from 'loading'; -import events from 'events'; import libraryBrowser from 'libraryBrowser'; import imageLoader from 'imageLoader'; import listView from 'listView'; diff --git a/src/controllers/movies/moviegenres.js b/src/controllers/movies/moviegenres.js index f426ac024d..3ca39601fc 100644 --- a/src/controllers/movies/moviegenres.js +++ b/src/controllers/movies/moviegenres.js @@ -3,10 +3,8 @@ import loading from 'loading'; import libraryBrowser from 'libraryBrowser'; import cardBuilder from 'cardBuilder'; import lazyLoader from 'lazyLoader'; -import appHost from 'apphost'; import globalize from 'globalize'; import appRouter from 'appRouter'; -import dom from 'dom'; import 'emby-button'; /* eslint-disable indent */ diff --git a/src/controllers/movies/movies.js b/src/controllers/movies/movies.js index 302e8b717e..91b428ec68 100644 --- a/src/controllers/movies/movies.js +++ b/src/controllers/movies/movies.js @@ -1,5 +1,4 @@ import loading from 'loading'; -import layoutManager from 'layoutManager'; import * as userSettings from 'userSettings'; import events from 'events'; import libraryBrowser from 'libraryBrowser'; diff --git a/src/controllers/movies/moviesrecommended.js b/src/controllers/movies/moviesrecommended.js index dda75a2320..63de12dd35 100644 --- a/src/controllers/movies/moviesrecommended.js +++ b/src/controllers/movies/moviesrecommended.js @@ -423,7 +423,7 @@ import 'emby-button'; events.on(playbackManager, 'playbackstop', onPlaybackStop); inputManager.on(window, onInputCommand); }); - view.addEventListener('viewbeforehide', function (e) { + view.addEventListener('viewbeforehide', function () { inputManager.off(window, onInputCommand); }); for (const tabController of tabControllers) { diff --git a/src/controllers/movies/movietrailers.js b/src/controllers/movies/movietrailers.js index 1cab0f1999..5f1aa1fe62 100644 --- a/src/controllers/movies/movietrailers.js +++ b/src/controllers/movies/movietrailers.js @@ -1,4 +1,3 @@ -import layoutManager from 'layoutManager'; import loading from 'loading'; import events from 'events'; import libraryBrowser from 'libraryBrowser'; From c13742ff544008829398378192143b8726f98085 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 29 Jul 2020 19:23:45 +0100 Subject: [PATCH 038/124] fix lint --- src/controllers/movies/moviesrecommended.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/controllers/movies/moviesrecommended.js b/src/controllers/movies/moviesrecommended.js index 63de12dd35..04f1d26ebf 100644 --- a/src/controllers/movies/moviesrecommended.js +++ b/src/controllers/movies/moviesrecommended.js @@ -388,9 +388,8 @@ import 'emby-button'; } } - var self = this; - var currentTabIndex = parseInt(params.tab || getDefaultTabIndex(params.topParentId)); - var suggestionsTabIndex = 1; + let currentTabIndex = parseInt(params.tab || getDefaultTabIndex(params.topParentId)); + const suggestionsTabIndex = 1; this.initTab = function () { let tabContent = view.querySelector(".pageTabContent[data-index='" + suggestionsTabIndex + "']"); From 9cafbb3eeb41a60db9719299bd02098c6cfc7190 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 30 Jul 2020 13:32:29 +0100 Subject: [PATCH 039/124] remove duplicates, defaults and fix --- package.json | 6 ++++++ src/controllers/movies/moviesrecommended.js | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 678794aebc..ad4c8e1109 100644 --- a/package.json +++ b/package.json @@ -191,6 +191,12 @@ "src/controllers/music/musicplaylists.js", "src/controllers/music/musicrecommended.js", "src/controllers/music/songs.js", + "src/controllers/dashboard/plugins/repositories.js", + "src/controllers/movies/moviecollections.js", + "src/controllers/movies/moviegenres.js", + "src/controllers/movies/movies.js", + "src/controllers/movies/moviesrecommended.js", + "src/controllers/movies/movietrailers.js", "src/controllers/dashboard/mediaLibrary.js", "src/controllers/dashboard/metadataImages.js", "src/controllers/dashboard/metadatanfo.js", diff --git a/src/controllers/movies/moviesrecommended.js b/src/controllers/movies/moviesrecommended.js index 04f1d26ebf..ad8579e292 100644 --- a/src/controllers/movies/moviesrecommended.js +++ b/src/controllers/movies/moviesrecommended.js @@ -3,7 +3,7 @@ import layoutManager from 'layoutManager'; import inputManager from 'inputManager'; import * as userSettings from 'userSettings'; import libraryMenu from 'libraryMenu'; -import mainTabsManager from 'mainTabsManager'; +import * as mainTabsManager from 'mainTabsManager'; import cardBuilder from 'cardBuilder'; import dom from 'dom'; import imageLoader from 'imageLoader'; From 9bdc1d8bd9d5b3180fe13d8431c92e9cdda502b1 Mon Sep 17 00:00:00 2001 From: matjaz321 Date: Mon, 3 Aug 2020 21:34:14 +0200 Subject: [PATCH 040/124] Migrated livetvchannels to es6 module --- package.json | 1 + src/controllers/livetv/livetvchannels.js | 241 ++++++++++++----------- 2 files changed, 122 insertions(+), 120 deletions(-) diff --git a/package.json b/package.json index 1d7cf770d3..6540af9f43 100644 --- a/package.json +++ b/package.json @@ -222,6 +222,7 @@ "src/controllers/livetvstatus.js", "src/controllers/livetvguideprovider.js", "src/controllers/livetvsettings.js", + "src/controllers/livetv/livetvchannels.js", "src/controllers/shows/episodes.js", "src/controllers/shows/tvgenres.js", "src/controllers/shows/tvlatest.js", diff --git a/src/controllers/livetv/livetvchannels.js b/src/controllers/livetv/livetvchannels.js index f5a0abeb38..8af6411296 100644 --- a/src/controllers/livetv/livetvchannels.js +++ b/src/controllers/livetv/livetvchannels.js @@ -1,133 +1,134 @@ -define(['cardBuilder', 'imageLoader', 'libraryBrowser', 'loading', 'events', 'userSettings', 'emby-itemscontainer'], function (cardBuilder, imageLoader, libraryBrowser, loading, events, userSettings) { - 'use strict'; +import cardBuilder from 'cardBuilder'; +import imageLoader from 'imageLoader'; +import libraryBrowser from 'libraryBrowser'; +import loading from 'loading'; +import events from 'events'; +import userSettings from 'userSettings'; +import 'emby-itemscontainer'; - loading = loading.default || loading; - libraryBrowser = libraryBrowser.default || libraryBrowser; +export default function (view, params, tabContent) { + function getPageData() { + if (!pageData) { + pageData = { + query: { + StartIndex: 0, + Fields: 'PrimaryImageAspectRatio' + } + }; + } - return function (view, params, tabContent) { - function getPageData() { - if (!pageData) { - pageData = { - query: { - StartIndex: 0, - Fields: 'PrimaryImageAspectRatio' - } - }; + if (userSettings.libraryPageSize() > 0) { + pageData.query['Limit'] = userSettings.libraryPageSize(); + } + + return pageData; + } + + function getQuery() { + return getPageData().query; + } + + function getChannelsHtml(channels) { + return cardBuilder.getCardsHtml({ + items: channels, + shape: 'square', + showTitle: true, + lazy: true, + cardLayout: true, + showDetailsMenu: true, + showCurrentProgram: true, + showCurrentProgramTime: true + }); + } + + function renderChannels(context, result) { + function onNextPageClick() { + if (isLoading) { + return; } if (userSettings.libraryPageSize() > 0) { - pageData.query['Limit'] = userSettings.libraryPageSize(); + query.StartIndex += query.Limit; + } + reloadItems(context); + } + + function onPreviousPageClick() { + if (isLoading) { + return; } - return pageData; - } - - function getQuery() { - return getPageData().query; - } - - function getChannelsHtml(channels) { - return cardBuilder.getCardsHtml({ - items: channels, - shape: 'square', - showTitle: true, - lazy: true, - cardLayout: true, - showDetailsMenu: true, - showCurrentProgram: true, - showCurrentProgramTime: true - }); - } - - function renderChannels(context, result) { - function onNextPageClick() { - if (isLoading) { - return; - } - - if (userSettings.libraryPageSize() > 0) { - query.StartIndex += query.Limit; - } - reloadItems(context); - } - - function onPreviousPageClick() { - if (isLoading) { - return; - } - - if (userSettings.libraryPageSize() > 0) { - query.StartIndex = Math.max(0, query.StartIndex - query.Limit); - } - reloadItems(context); - } - - var query = getQuery(); - context.querySelector('.paging').innerHTML = libraryBrowser.getQueryPagingHtml({ - startIndex: query.StartIndex, - limit: query.Limit, - totalRecordCount: result.TotalRecordCount, - showLimit: false, - updatePageSizeSetting: false, - filterButton: false - }); - var html = getChannelsHtml(result.Items); - var elem = context.querySelector('#items'); - elem.innerHTML = html; - imageLoader.lazyChildren(elem); - var i; - var length; - var elems; - - for (elems = context.querySelectorAll('.btnNextPage'), i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener('click', onNextPageClick); - } - - for (elems = context.querySelectorAll('.btnPreviousPage'), i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener('click', onPreviousPageClick); + if (userSettings.libraryPageSize() > 0) { + query.StartIndex = Math.max(0, query.StartIndex - query.Limit); } + reloadItems(context); } - function showFilterMenu(context) { - require(['components/filterdialog/filterdialog'], function ({default: filterDialogFactory}) { - var filterDialog = new filterDialogFactory({ - query: getQuery(), - mode: 'livetvchannels', - serverId: ApiClient.serverId() - }); - events.on(filterDialog, 'filterchange', function () { - reloadItems(context); - }); - filterDialog.show(); - }); - } - - function reloadItems(context, save) { - loading.show(); - isLoading = true; - var query = getQuery(); - var apiClient = ApiClient; - query.UserId = apiClient.getCurrentUserId(); - apiClient.getLiveTvChannels(query).then(function (result) { - renderChannels(context, result); - loading.hide(); - isLoading = false; - - require(['autoFocuser'], function (autoFocuser) { - autoFocuser.autoFocus(view); - }); - }); - } - - var pageData; - var self = this; - var isLoading = false; - tabContent.querySelector('.btnFilter').addEventListener('click', function () { - showFilterMenu(tabContent); + const query = getQuery(); + context.querySelector('.paging').innerHTML = libraryBrowser.getQueryPagingHtml({ + startIndex: query.StartIndex, + limit: query.Limit, + totalRecordCount: result.TotalRecordCount, + showLimit: false, + updatePageSizeSetting: false, + filterButton: false }); + const html = getChannelsHtml(result.Items); + const elem = context.querySelector('#items'); + elem.innerHTML = html; + imageLoader.lazyChildren(elem); + let i; + let length; + let elems; - self.renderTab = function () { - reloadItems(tabContent); - }; + for (elems = context.querySelectorAll('.btnNextPage'), i = 0, length = elems.length; i < length; i++) { + elems[i].addEventListener('click', onNextPageClick); + } + + for (elems = context.querySelectorAll('.btnPreviousPage'), i = 0, length = elems.length; i < length; i++) { + elems[i].addEventListener('click', onPreviousPageClick); + } + } + + function showFilterMenu(context) { + import(['components/filterdialog/filterdialog']).then(({default: filterDialogFactory}) => { + const filterDialog = new filterDialogFactory({ + query: getQuery(), + mode: 'livetvchannels', + serverId: ApiClient.serverId() + }); + events.on(filterDialog, 'filterchange', function () { + reloadItems(context); + }); + filterDialog.show(); + }); + } + + function reloadItems(context, save) { + loading.show(); + isLoading = true; + const query = getQuery(); + const apiClient = ApiClient; + query.UserId = apiClient.getCurrentUserId(); + apiClient.getLiveTvChannels(query).then(function (result) { + renderChannels(context, result); + loading.hide(); + isLoading = false; + + require(['autoFocuser'], function (autoFocuser) { + autoFocuser.autoFocus(view); + }); + }); + } + + let pageData; + const self = this; + let isLoading = false; + tabContent.querySelector('.btnFilter').addEventListener('click', function () { + showFilterMenu(tabContent); + }); + + self.renderTab = function () { + reloadItems(tabContent); }; -}); +} From ddf364adb2e08443d79092810d0b3096243c1645 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 3 Aug 2020 20:46:39 +0100 Subject: [PATCH 041/124] Lint --- src/controllers/movies/moviegenres.js | 4 ++-- src/controllers/movies/moviesrecommended.js | 4 ---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/controllers/movies/moviegenres.js b/src/controllers/movies/moviegenres.js index 3ca39601fc..ca02ede36d 100644 --- a/src/controllers/movies/moviegenres.js +++ b/src/controllers/movies/moviegenres.js @@ -62,13 +62,13 @@ import 'emby-button'; const elem = entry.target; const id = elem.getAttribute('data-id'); const viewStyle = this.getCurrentViewStyle(); - let limit = 'Thumb' == viewStyle || 'ThumbCard' == viewStyle ? 5 : 9; + let limit = viewStyle == 'Thumb' || viewStyle == 'ThumbCard' ? 5 : 9; if (enableScrollX()) { limit = 10; } - const enableImageTypes = 'Thumb' == viewStyle || 'ThumbCard' == viewStyle ? 'Primary,Backdrop,Thumb' : 'Primary'; + const enableImageTypes = viewStyle == 'Thumb' || viewStyle == 'ThumbCard' ? 'Primary,Backdrop,Thumb' : 'Primary'; const query = { SortBy: 'SortName', SortOrder: 'Ascending', diff --git a/src/controllers/movies/moviesrecommended.js b/src/controllers/movies/moviesrecommended.js index ad8579e292..5cba66d2f8 100644 --- a/src/controllers/movies/moviesrecommended.js +++ b/src/controllers/movies/moviesrecommended.js @@ -16,8 +16,6 @@ import 'emby-button'; /* eslint-disable indent */ - playbackManager = playbackManager.default || playbackManager; - function enableScrollX() { return !layoutManager.desktop; } @@ -364,8 +362,6 @@ import 'emby-button'; function loadTab(page, index) { currentTabIndex = index; getTabController(page, index, ((controller) => { - initialTabIndex = null; - if (renderedTabs.indexOf(index) == -1) { renderedTabs.push(index); controller.renderTab(); From 4b84e6c0aa97d53be1996941591c762126251116 Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Tue, 4 Aug 2020 00:51:19 +0300 Subject: [PATCH 042/124] Fix old Edge loading --- src/scripts/site.js | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/src/scripts/site.js b/src/scripts/site.js index 604f9c7bab..ab8e17971d 100644 --- a/src/scripts/site.js +++ b/src/scripts/site.js @@ -696,20 +696,12 @@ function initClient() { onError: onRequireJsError }); - require(['fetch']); - require(['polyfill']); - require(['fast-text-encoding']); - require(['intersection-observer']); - require(['classlist-polyfill']); - - // Expose jQuery globally - require(['jQuery'], function(jQuery) { - window.$ = jQuery; - window.jQuery = jQuery; - }); - - require(['css!assets/css/site']); - require(['jellyfin-noto']); + require(['fetch']) + .then(() => require(['jQuery', 'polyfill', 'fast-text-encoding', 'intersection-observer', 'classlist-polyfill', 'css!assets/css/site', 'jellyfin-noto'], (jQuery) => { + // Expose jQuery globally + window.$ = jQuery; + window.jQuery = jQuery; + })); // define styles // TODO determine which of these files can be moved to the components themselves From 07a54e93039593b9f66769951f6aa34d044528e8 Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Tue, 4 Aug 2020 09:39:34 +0300 Subject: [PATCH 043/124] Wait for initialization to complete --- src/scripts/site.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/scripts/site.js b/src/scripts/site.js index ab8e17971d..cd85c35e83 100644 --- a/src/scripts/site.js +++ b/src/scripts/site.js @@ -615,6 +615,7 @@ function initClient() { } var localApiClient; + let promise; (function () { var urlArgs = 'v=' + (window.dashboardVersion || new Date().getDate()); @@ -696,7 +697,7 @@ function initClient() { onError: onRequireJsError }); - require(['fetch']) + promise = require(['fetch']) .then(() => require(['jQuery', 'polyfill', 'fast-text-encoding', 'intersection-observer', 'classlist-polyfill', 'css!assets/css/site', 'jellyfin-noto'], (jQuery) => { // Expose jQuery globally window.$ = jQuery; @@ -1107,7 +1108,7 @@ function initClient() { }); })(); - return onWebComponentsReady(); + promise.then(onWebComponentsReady); } initClient(); From 3a35a9c5f2ee664fd9409de1d47acdaac54be1d2 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 4 Aug 2020 11:55:00 +0100 Subject: [PATCH 044/124] 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 70204584274b831e6ac9c5187c258cf355c1272a Mon Sep 17 00:00:00 2001 From: Matjaz Zavski Date: Tue, 4 Aug 2020 12:59:21 +0200 Subject: [PATCH 045/124] Update src/controllers/livetv/livetvchannels.js Co-authored-by: Cameron --- src/controllers/livetv/livetvchannels.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/livetv/livetvchannels.js b/src/controllers/livetv/livetvchannels.js index 8af6411296..14d7bcfdf4 100644 --- a/src/controllers/livetv/livetvchannels.js +++ b/src/controllers/livetv/livetvchannels.js @@ -3,7 +3,7 @@ import imageLoader from 'imageLoader'; import libraryBrowser from 'libraryBrowser'; import loading from 'loading'; import events from 'events'; -import userSettings from 'userSettings'; +import * as userSettings from 'userSettings'; import 'emby-itemscontainer'; export default function (view, params, tabContent) { From e3fe8a1964acfd7acdb154ffbe4b10d80ccf4f3c Mon Sep 17 00:00:00 2001 From: Matjaz Zavski Date: Tue, 4 Aug 2020 12:59:28 +0200 Subject: [PATCH 046/124] Update src/controllers/livetv/livetvchannels.js Co-authored-by: Cameron --- src/controllers/livetv/livetvchannels.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/livetv/livetvchannels.js b/src/controllers/livetv/livetvchannels.js index 14d7bcfdf4..7cdddda716 100644 --- a/src/controllers/livetv/livetvchannels.js +++ b/src/controllers/livetv/livetvchannels.js @@ -115,7 +115,7 @@ export default function (view, params, tabContent) { loading.hide(); isLoading = false; - require(['autoFocuser'], function (autoFocuser) { + import('autoFocuser').then(({default: autoFocuser}) => { autoFocuser.autoFocus(view); }); }); From 39a003742716d7dcfe7e27e232fea91743f722d3 Mon Sep 17 00:00:00 2001 From: Matjaz Zavski Date: Tue, 4 Aug 2020 14:12:43 +0200 Subject: [PATCH 047/124] Update src/controllers/livetv/livetvchannels.js Co-authored-by: Cameron --- src/controllers/livetv/livetvchannels.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/livetv/livetvchannels.js b/src/controllers/livetv/livetvchannels.js index 7cdddda716..d34fe58132 100644 --- a/src/controllers/livetv/livetvchannels.js +++ b/src/controllers/livetv/livetvchannels.js @@ -115,7 +115,7 @@ export default function (view, params, tabContent) { loading.hide(); isLoading = false; - import('autoFocuser').then(({default: autoFocuser}) => { + import('autoFocuser').then(({default: autoFocuser}) => { autoFocuser.autoFocus(view); }); }); From 73bb781c6a2a4028dc1bd92c497dbe0ffc0ccb8c Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 4 Aug 2020 13:47:40 +0100 Subject: [PATCH 048/124] Migration of layoutManager and itemsrefresher to ES6 module --- package.json | 2 + src/components/itemsrefresher.js | 324 +++++++++++++++---------------- src/components/layoutManager.js | 51 +++-- 3 files changed, 189 insertions(+), 188 deletions(-) diff --git a/package.json b/package.json index 5ad12d2011..97784fa2e2 100644 --- a/package.json +++ b/package.json @@ -124,6 +124,8 @@ "src/components/itemHelper.js", "src/components/itemidentifier/itemidentifier.js", "src/components/itemMediaInfo/itemMediaInfo.js", + "src/components/itemsrefresher.js", + "src/components/layoutManager.js", "src/components/lazyLoader/lazyLoaderIntersectionObserver.js", "src/components/libraryoptionseditor/libraryoptionseditor.js", "src/components/listview/listview.js", diff --git a/src/components/itemsrefresher.js b/src/components/itemsrefresher.js index 74b08db07f..3883e6e490 100644 --- a/src/components/itemsrefresher.js +++ b/src/components/itemsrefresher.js @@ -1,130 +1,130 @@ -define(['playbackManager', 'serverNotifications', 'events'], function (playbackManager, serverNotifications, events) { - 'use strict'; +import playbackManager from 'playbackManager'; +import serverNotifications from 'serverNotifications'; +import events from 'events'; - playbackManager = playbackManager.default || playbackManager; +function onUserDataChanged(e, apiClient, userData) { + const instance = this; - function onUserDataChanged(e, apiClient, userData) { - var instance = this; - - var eventsToMonitor = getEventsToMonitor(instance); - - // TODO: Check user data change reason? - if (eventsToMonitor.indexOf('markfavorite') !== -1) { - instance.notifyRefreshNeeded(); - } else if (eventsToMonitor.indexOf('markplayed') !== -1) { - instance.notifyRefreshNeeded(); - } - } - - function getEventsToMonitor(instance) { - var options = instance.options; - var monitor = options ? options.monitorEvents : null; - if (monitor) { - return monitor.split(','); - } - - return []; - } - - function onTimerCreated(e, apiClient, data) { - var instance = this; - - if (getEventsToMonitor(instance).indexOf('timers') !== -1) { - instance.notifyRefreshNeeded(); - return; - } - } - - function onSeriesTimerCreated(e, apiClient, data) { - var instance = this; - if (getEventsToMonitor(instance).indexOf('seriestimers') !== -1) { - instance.notifyRefreshNeeded(); - return; - } - } - - function onTimerCancelled(e, apiClient, data) { - var instance = this; - - if (getEventsToMonitor(instance).indexOf('timers') !== -1) { - instance.notifyRefreshNeeded(); - return; - } - } - - function onSeriesTimerCancelled(e, apiClient, data) { - var instance = this; - if (getEventsToMonitor(instance).indexOf('seriestimers') !== -1) { - instance.notifyRefreshNeeded(); - return; - } - } - - function onLibraryChanged(e, apiClient, data) { - var instance = this; - var eventsToMonitor = getEventsToMonitor(instance); - if (eventsToMonitor.indexOf('seriestimers') !== -1 || eventsToMonitor.indexOf('timers') !== -1) { - // yes this is an assumption - return; - } - - var itemsAdded = data.ItemsAdded || []; - var itemsRemoved = data.ItemsRemoved || []; - if (!itemsAdded.length && !itemsRemoved.length) { - return; - } - - var options = instance.options || {}; - var parentId = options.parentId; - if (parentId) { - var foldersAddedTo = data.FoldersAddedTo || []; - var foldersRemovedFrom = data.FoldersRemovedFrom || []; - var collectionFolders = data.CollectionFolders || []; - - if (foldersAddedTo.indexOf(parentId) === -1 && foldersRemovedFrom.indexOf(parentId) === -1 && collectionFolders.indexOf(parentId) === -1) { - return; - } - } + const eventsToMonitor = getEventsToMonitor(instance); + // TODO: Check user data change reason? + if (eventsToMonitor.indexOf('markfavorite') !== -1) { + instance.notifyRefreshNeeded(); + } else if (eventsToMonitor.indexOf('markplayed') !== -1) { instance.notifyRefreshNeeded(); } +} - function onPlaybackStopped(e, stopInfo) { - var instance = this; +function getEventsToMonitor(instance) { + const options = instance.options; + const monitor = options ? options.monitorEvents : null; + if (monitor) { + return monitor.split(','); + } - var state = stopInfo.state; + return []; +} - var eventsToMonitor = getEventsToMonitor(instance); - if (state.NowPlayingItem && state.NowPlayingItem.MediaType === 'Video') { - if (eventsToMonitor.indexOf('videoplayback') !== -1) { - instance.notifyRefreshNeeded(true); - return; - } - } else if (state.NowPlayingItem && state.NowPlayingItem.MediaType === 'Audio') { - if (eventsToMonitor.indexOf('audioplayback') !== -1) { - instance.notifyRefreshNeeded(true); - return; - } +function onTimerCreated(e, apiClient, data) { + const instance = this; + + if (getEventsToMonitor(instance).indexOf('timers') !== -1) { + instance.notifyRefreshNeeded(); + return; + } +} + +function onSeriesTimerCreated(e, apiClient, data) { + const instance = this; + if (getEventsToMonitor(instance).indexOf('seriestimers') !== -1) { + instance.notifyRefreshNeeded(); + return; + } +} + +function onTimerCancelled(e, apiClient, data) { + const instance = this; + + if (getEventsToMonitor(instance).indexOf('timers') !== -1) { + instance.notifyRefreshNeeded(); + return; + } +} + +function onSeriesTimerCancelled(e, apiClient, data) { + const instance = this; + if (getEventsToMonitor(instance).indexOf('seriestimers') !== -1) { + instance.notifyRefreshNeeded(); + return; + } +} + +function onLibraryChanged(e, apiClient, data) { + const instance = this; + const eventsToMonitor = getEventsToMonitor(instance); + if (eventsToMonitor.indexOf('seriestimers') !== -1 || eventsToMonitor.indexOf('timers') !== -1) { + // yes this is an assumption + return; + } + + const itemsAdded = data.ItemsAdded || []; + const itemsRemoved = data.ItemsRemoved || []; + if (!itemsAdded.length && !itemsRemoved.length) { + return; + } + + const options = instance.options || {}; + const parentId = options.parentId; + if (parentId) { + const foldersAddedTo = data.FoldersAddedTo || []; + const foldersRemovedFrom = data.FoldersRemovedFrom || []; + const collectionFolders = data.CollectionFolders || []; + + if (foldersAddedTo.indexOf(parentId) === -1 && foldersRemovedFrom.indexOf(parentId) === -1 && collectionFolders.indexOf(parentId) === -1) { + return; } } - function addNotificationEvent(instance, name, handler, owner) { - var localHandler = handler.bind(instance); + instance.notifyRefreshNeeded(); +} + +function onPlaybackStopped(e, stopInfo) { + const instance = this; + + const state = stopInfo.state; + + const eventsToMonitor = getEventsToMonitor(instance); + if (state.NowPlayingItem && state.NowPlayingItem.MediaType === 'Video') { + if (eventsToMonitor.indexOf('videoplayback') !== -1) { + instance.notifyRefreshNeeded(true); + return; + } + } else if (state.NowPlayingItem && state.NowPlayingItem.MediaType === 'Audio') { + if (eventsToMonitor.indexOf('audioplayback') !== -1) { + instance.notifyRefreshNeeded(true); + return; + } + } +} + +function addNotificationEvent(instance, name, handler, owner) { + const localHandler = handler.bind(instance); + owner = owner || serverNotifications; + events.on(owner, name, localHandler); + instance['event_' + name] = localHandler; +} + +function removeNotificationEvent(instance, name, owner) { + const handler = instance['event_' + name]; + if (handler) { owner = owner || serverNotifications; - events.on(owner, name, localHandler); - instance['event_' + name] = localHandler; + events.off(owner, name, handler); + instance['event_' + name] = null; } +} - function removeNotificationEvent(instance, name, owner) { - var handler = instance['event_' + name]; - if (handler) { - owner = owner || serverNotifications; - events.off(owner, name, handler); - instance['event_' + name] = null; - } - } - - function ItemsRefresher(options) { +class ItemsRefresher { + constructor(options) { this.options = options || {}; addNotificationEvent(this, 'UserDataChanged', onUserDataChanged); @@ -136,18 +136,18 @@ define(['playbackManager', 'serverNotifications', 'events'], function (playbackM addNotificationEvent(this, 'playbackstop', onPlaybackStopped, playbackManager); } - ItemsRefresher.prototype.pause = function () { + pause() { clearRefreshInterval(this, true); this.paused = true; - }; + } - ItemsRefresher.prototype.resume = function (options) { + resume(options) { this.paused = false; - var refreshIntervalEndTime = this.refreshIntervalEndTime; + const refreshIntervalEndTime = this.refreshIntervalEndTime; if (refreshIntervalEndTime) { - var remainingMs = refreshIntervalEndTime - new Date().getTime(); + const remainingMs = refreshIntervalEndTime - new Date().getTime(); if (remainingMs > 0 && !this.needsRefresh) { resetRefreshInterval(this, remainingMs); } else { @@ -161,9 +161,9 @@ define(['playbackManager', 'serverNotifications', 'events'], function (playbackM } return Promise.resolve(); - }; + } - ItemsRefresher.prototype.refreshItems = function () { + refreshItems() { if (!this.fetchData) { return Promise.resolve(); } @@ -176,15 +176,15 @@ define(['playbackManager', 'serverNotifications', 'events'], function (playbackM this.needsRefresh = false; return this.fetchData().then(onDataFetched.bind(this)); - }; + } - ItemsRefresher.prototype.notifyRefreshNeeded = function (isInForeground) { + notifyRefreshNeeded(isInForeground) { if (this.paused) { this.needsRefresh = true; return; } - var timeout = this.refreshTimeout; + const timeout = this.refreshTimeout; if (timeout) { clearTimeout(timeout); } @@ -194,44 +194,9 @@ define(['playbackManager', 'serverNotifications', 'events'], function (playbackM } else { this.refreshTimeout = setTimeout(this.refreshItems.bind(this), 10000); } - }; - - function clearRefreshInterval(instance, isPausing) { - if (instance.refreshInterval) { - clearInterval(instance.refreshInterval); - instance.refreshInterval = null; - - if (!isPausing) { - instance.refreshIntervalEndTime = null; - } - } } - function resetRefreshInterval(instance, intervalMs) { - clearRefreshInterval(instance); - - if (!intervalMs) { - var options = instance.options; - if (options) { - intervalMs = options.refreshIntervalMs; - } - } - - if (intervalMs) { - instance.refreshInterval = setInterval(instance.notifyRefreshNeeded.bind(instance), intervalMs); - instance.refreshIntervalEndTime = new Date().getTime() + intervalMs; - } - } - - function onDataFetched(result) { - resetRefreshInterval(this); - - if (this.afterRefresh) { - this.afterRefresh(result); - } - } - - ItemsRefresher.prototype.destroy = function () { + destroy() { clearRefreshInterval(this); removeNotificationEvent(this, 'UserDataChanged'); @@ -244,7 +209,42 @@ define(['playbackManager', 'serverNotifications', 'events'], function (playbackM this.fetchData = null; this.options = null; - }; + } +} - return ItemsRefresher; -}); +function clearRefreshInterval(instance, isPausing) { + if (instance.refreshInterval) { + clearInterval(instance.refreshInterval); + instance.refreshInterval = null; + + if (!isPausing) { + instance.refreshIntervalEndTime = null; + } + } +} + +function resetRefreshInterval(instance, intervalMs) { + clearRefreshInterval(instance); + + if (!intervalMs) { + const options = instance.options; + if (options) { + intervalMs = options.refreshIntervalMs; + } + } + + if (intervalMs) { + instance.refreshInterval = setInterval(instance.notifyRefreshNeeded.bind(instance), intervalMs); + instance.refreshIntervalEndTime = new Date().getTime() + intervalMs; + } +} + +function onDataFetched(result) { + resetRefreshInterval(this); + + if (this.afterRefresh) { + this.afterRefresh(result); + } +} + +export default ItemsRefresher; diff --git a/src/components/layoutManager.js b/src/components/layoutManager.js index 85d78f8ff4..d104632573 100644 --- a/src/components/layoutManager.js +++ b/src/components/layoutManager.js @@ -1,23 +1,22 @@ -define(['browser', 'appSettings', 'events'], function (browser, appSettings, events) { - 'use strict'; +import browser from 'browser'; +import appSettings from 'appSettings'; +import events from 'events'; - browser = browser.default || browser; +function setLayout(instance, layout, selectedLayout) { + if (layout === selectedLayout) { + instance[layout] = true; + document.documentElement.classList.add('layout-' + layout); + } else { + instance[layout] = false; + document.documentElement.classList.remove('layout-' + layout); + } +} - function setLayout(instance, layout, selectedLayout) { - if (layout === selectedLayout) { - instance[layout] = true; - document.documentElement.classList.add('layout-' + layout); - } else { - instance[layout] = false; - document.documentElement.classList.remove('layout-' + layout); - } +class LayoutManager { + constructor() { } - function LayoutManager() { - - } - - LayoutManager.prototype.setLayout = function (layout, save) { + setLayout(layout, save) { if (!layout || layout === 'auto') { this.autoLayout(); @@ -35,13 +34,13 @@ define(['browser', 'appSettings', 'events'], function (browser, appSettings, eve } events.trigger(this, 'modechange'); - }; + } - LayoutManager.prototype.getSavedLayout = function (layout) { + getSavedLayout(layout) { return appSettings.get('layout'); - }; + } - LayoutManager.prototype.autoLayout = function () { + autoLayout() { // Take a guess at initial layout. The consuming app can override if (browser.mobile) { this.setLayout('mobile', false); @@ -50,16 +49,16 @@ define(['browser', 'appSettings', 'events'], function (browser, appSettings, eve } else { this.setLayout(this.defaultLayout || 'tv', false); } - }; + } - LayoutManager.prototype.init = function () { - var saved = this.getSavedLayout(); + init() { + const saved = this.getSavedLayout(); if (saved) { this.setLayout(saved, false); } else { this.autoLayout(); } - }; + } +} - return new LayoutManager(); -}); +export default new LayoutManager(); From aaad59cca6044f90b02ce53118cd575e42fef604 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 4 Aug 2020 13:47:47 +0100 Subject: [PATCH 049/124] addition of defaults --- src/components/filtermenu/filtermenu.js | 2 ++ src/components/guide/guide-settings.js | 2 ++ src/components/guide/guide.js | 1 + src/components/recordingcreator/recordingcreator.js | 2 ++ src/components/recordingcreator/recordingeditor.js | 1 + src/components/recordingcreator/seriesrecordingeditor.js | 1 + src/components/remotecontrol/remotecontrol.js | 1 + src/components/slideshow/slideshow.js | 1 + src/components/sortmenu/sortmenu.js | 2 ++ src/components/subtitleeditor/subtitleeditor.js | 1 + src/components/subtitlesync/subtitlesync.js | 1 + src/components/tabbedview/tabbedview.js | 2 ++ src/components/tunerPicker.js | 1 + src/components/upnextdialog/upnextdialog.js | 1 + src/components/viewManager/viewManager.js | 2 ++ src/components/viewSettings/viewSettings.js | 2 ++ src/controllers/list.js | 1 + src/controllers/livetv/livetvrecordings.js | 1 + src/controllers/livetv/livetvschedule.js | 1 + src/controllers/livetv/livetvsuggested.js | 1 + src/controllers/movies/moviegenres.js | 1 + src/controllers/movies/movies.js | 1 + src/controllers/movies/moviesrecommended.js | 1 + src/controllers/movies/movietrailers.js | 1 + src/libraries/scroller.js | 1 + src/scripts/libraryMenu.js | 1 + src/scripts/livetvcomponents.js | 2 ++ src/scripts/site.js | 1 + 28 files changed, 36 insertions(+) diff --git a/src/components/filtermenu/filtermenu.js b/src/components/filtermenu/filtermenu.js index 20399fb52d..03a1706fb5 100644 --- a/src/components/filtermenu/filtermenu.js +++ b/src/components/filtermenu/filtermenu.js @@ -1,6 +1,8 @@ define(['require', 'dom', 'focusManager', 'dialogHelper', 'loading', 'apphost', 'inputManager', 'layoutManager', 'connectionManager', 'appRouter', 'globalize', 'userSettings', 'emby-checkbox', 'emby-input', 'paper-icon-button-light', 'emby-select', 'material-icons', 'css!./../formdialog', 'emby-button', 'flexStyles'], function (require, dom, focusManager, dialogHelper, loading, appHost, inputManager, layoutManager, connectionManager, appRouter, globalize, userSettings) { 'use strict'; + layoutManager = layoutManager.default || layoutManager; + function onSubmit(e) { e.preventDefault(); return false; diff --git a/src/components/guide/guide-settings.js b/src/components/guide/guide-settings.js index c3ba49f283..437dd5fcec 100644 --- a/src/components/guide/guide-settings.js +++ b/src/components/guide/guide-settings.js @@ -1,6 +1,8 @@ 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'; + layoutManager = layoutManager.default || layoutManager; + function saveCategories(context, options) { var categories = []; diff --git a/src/components/guide/guide.js b/src/components/guide/guide.js index 61caa9188f..6057375ec2 100644 --- a/src/components/guide/guide.js +++ b/src/components/guide/guide.js @@ -4,6 +4,7 @@ define(['require', 'inputManager', 'browser', 'globalize', 'connectionManager', playbackManager = playbackManager.default || playbackManager; browser = browser.default || browser; loading = loading.default || loading; + layoutManager = layoutManager.default || layoutManager; function showViewSettings(instance) { require(['guide-settings-dialog'], function (guideSettingsDialog) { diff --git a/src/components/recordingcreator/recordingcreator.js b/src/components/recordingcreator/recordingcreator.js index ca5c475829..fb452fa8a4 100644 --- a/src/components/recordingcreator/recordingcreator.js +++ b/src/components/recordingcreator/recordingcreator.js @@ -1,6 +1,8 @@ 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'; + layoutManager = layoutManager.default || layoutManager; + var currentDialog; var closeAction; var currentRecordingFields; diff --git a/src/components/recordingcreator/recordingeditor.js b/src/components/recordingcreator/recordingeditor.js index 2086129a9e..2ac8c826e1 100644 --- a/src/components/recordingcreator/recordingeditor.js +++ b/src/components/recordingcreator/recordingeditor.js @@ -2,6 +2,7 @@ define(['dialogHelper', 'globalize', 'layoutManager', 'mediaInfo', 'apphost', 'c 'use strict'; loading = loading.default || loading; + layoutManager = layoutManager.default || layoutManager; var currentDialog; var recordingDeleted = false; diff --git a/src/components/recordingcreator/seriesrecordingeditor.js b/src/components/recordingcreator/seriesrecordingeditor.js index b115e273e6..b5d8eee82b 100644 --- a/src/components/recordingcreator/seriesrecordingeditor.js +++ b/src/components/recordingcreator/seriesrecordingeditor.js @@ -2,6 +2,7 @@ define(['dialogHelper', 'globalize', 'layoutManager', 'mediaInfo', 'apphost', 'c 'use strict'; loading = loading.default || loading; + layoutManager = layoutManager.default || layoutManager; var currentDialog; var recordingUpdated = false; diff --git a/src/components/remotecontrol/remotecontrol.js b/src/components/remotecontrol/remotecontrol.js index b5ac4c9a8b..c6c2a123b4 100644 --- a/src/components/remotecontrol/remotecontrol.js +++ b/src/components/remotecontrol/remotecontrol.js @@ -2,6 +2,7 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL 'use strict'; playbackManager = playbackManager.default || playbackManager; + layoutManager = layoutManager.default || layoutManager; var showMuteButton = true; var showVolumeSlider = true; diff --git a/src/components/slideshow/slideshow.js b/src/components/slideshow/slideshow.js index 60c458e234..e29a3c8fb9 100644 --- a/src/components/slideshow/slideshow.js +++ b/src/components/slideshow/slideshow.js @@ -6,6 +6,7 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f 'use strict'; browser = browser.default || browser; + layoutManager = layoutManager.default || layoutManager; /** * Name of transition event. diff --git a/src/components/sortmenu/sortmenu.js b/src/components/sortmenu/sortmenu.js index f62e5bb3a4..56f33001e7 100644 --- a/src/components/sortmenu/sortmenu.js +++ b/src/components/sortmenu/sortmenu.js @@ -1,6 +1,8 @@ define(['require', 'dom', 'focusManager', 'dialogHelper', 'loading', 'layoutManager', 'connectionManager', 'globalize', 'userSettings', 'emby-select', 'paper-icon-button-light', 'material-icons', 'css!./../formdialog', 'emby-button', 'flexStyles'], function (require, dom, focusManager, dialogHelper, loading, layoutManager, connectionManager, globalize, userSettings) { 'use strict'; + layoutManager = layoutManager.default || layoutManager; + function onSubmit(e) { e.preventDefault(); return false; diff --git a/src/components/subtitleeditor/subtitleeditor.js b/src/components/subtitleeditor/subtitleeditor.js index 7df24b5da8..093bbe7a4d 100644 --- a/src/components/subtitleeditor/subtitleeditor.js +++ b/src/components/subtitleeditor/subtitleeditor.js @@ -2,6 +2,7 @@ define(['dialogHelper', 'require', 'layoutManager', 'globalize', 'userSettings', 'use strict'; loading = loading.default || loading; + layoutManager = layoutManager.default || layoutManager; var currentItem; var hasChanges; diff --git a/src/components/subtitlesync/subtitlesync.js b/src/components/subtitlesync/subtitlesync.js index 203d88535f..4433c8d958 100644 --- a/src/components/subtitlesync/subtitlesync.js +++ b/src/components/subtitlesync/subtitlesync.js @@ -2,6 +2,7 @@ define(['playbackManager', 'layoutManager', 'text!./subtitlesync.template.html', 'use strict'; playbackManager = playbackManager.default || playbackManager; + layoutManager = layoutManager.default || layoutManager; var player; var subtitleSyncSlider; diff --git a/src/components/tabbedview/tabbedview.js b/src/components/tabbedview/tabbedview.js index 8bd3afd372..c8c0f232be 100644 --- a/src/components/tabbedview/tabbedview.js +++ b/src/components/tabbedview/tabbedview.js @@ -1,6 +1,8 @@ define(['backdrop', 'mainTabsManager', 'layoutManager', 'emby-tabs'], function (backdrop, mainTabsManager, layoutManager) { 'use strict'; + layoutManager = layoutManager.default || layoutManager; + function onViewDestroy(e) { var tabControllers = this.tabControllers; diff --git a/src/components/tunerPicker.js b/src/components/tunerPicker.js index 5bc9386053..6391a84e3a 100644 --- a/src/components/tunerPicker.js +++ b/src/components/tunerPicker.js @@ -3,6 +3,7 @@ define(['dialogHelper', 'dom', 'layoutManager', 'connectionManager', 'globalize' browser = browser.default || browser; loading = loading.default || loading; + layoutManager = layoutManager.default || layoutManager; var enableFocusTransform = !browser.slow && !browser.edge; diff --git a/src/components/upnextdialog/upnextdialog.js b/src/components/upnextdialog/upnextdialog.js index 5eb2a2ffcc..fdf7c056f5 100644 --- a/src/components/upnextdialog/upnextdialog.js +++ b/src/components/upnextdialog/upnextdialog.js @@ -2,6 +2,7 @@ define(['dom', 'playbackManager', 'connectionManager', 'events', 'mediaInfo', 'l 'use strict'; playbackManager = playbackManager.default || playbackManager; + layoutManager = layoutManager.default || layoutManager; var transitionEndEventName = dom.whichTransitionEvent(); diff --git a/src/components/viewManager/viewManager.js b/src/components/viewManager/viewManager.js index 058ba4ebb2..8c9840fec3 100644 --- a/src/components/viewManager/viewManager.js +++ b/src/components/viewManager/viewManager.js @@ -1,6 +1,8 @@ define(['viewContainer', 'focusManager', 'queryString', 'layoutManager'], function (viewContainer, focusManager, queryString, layoutManager) { 'use strict'; + layoutManager = layoutManager.default || layoutManager; + var currentView; var dispatchPageEvents; diff --git a/src/components/viewSettings/viewSettings.js b/src/components/viewSettings/viewSettings.js index 67abc25a9c..9356f991fc 100644 --- a/src/components/viewSettings/viewSettings.js +++ b/src/components/viewSettings/viewSettings.js @@ -1,6 +1,8 @@ define(['require', 'dialogHelper', 'loading', 'apphost', 'layoutManager', 'connectionManager', 'appRouter', 'globalize', 'userSettings', 'emby-checkbox', 'emby-input', 'paper-icon-button-light', 'emby-select', 'material-icons', 'css!./../formdialog', 'emby-button', 'flexStyles'], function (require, dialogHelper, loading, appHost, layoutManager, connectionManager, appRouter, globalize, userSettings) { 'use strict'; + layoutManager = layoutManager.default || layoutManager; + function onSubmit(e) { e.preventDefault(); return false; diff --git a/src/controllers/list.js b/src/controllers/list.js index 68e81f05dd..3cfa7ce281 100644 --- a/src/controllers/list.js +++ b/src/controllers/list.js @@ -3,6 +3,7 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' playbackManager = playbackManager.default || playbackManager; loading = loading.default || loading; + layoutManager = layoutManager.default || layoutManager; function getInitialLiveTvQuery(instance, params) { var query = { diff --git a/src/controllers/livetv/livetvrecordings.js b/src/controllers/livetv/livetvrecordings.js index b0259bc89b..ab92770336 100644 --- a/src/controllers/livetv/livetvrecordings.js +++ b/src/controllers/livetv/livetvrecordings.js @@ -2,6 +2,7 @@ define(['layoutManager', 'loading', 'cardBuilder', 'apphost', 'imageLoader', 'sc 'use strict'; loading = loading.default || loading; + layoutManager = layoutManager.default || layoutManager; function renderRecordings(elem, recordings, cardOptions, scrollX) { if (!elem) { diff --git a/src/controllers/livetv/livetvschedule.js b/src/controllers/livetv/livetvschedule.js index 4427bef5d1..a7c3f56279 100644 --- a/src/controllers/livetv/livetvschedule.js +++ b/src/controllers/livetv/livetvschedule.js @@ -2,6 +2,7 @@ define(['layoutManager', 'cardBuilder', 'apphost', 'imageLoader', 'loading', 'sc 'use strict'; loading = loading.default || loading; + layoutManager = layoutManager.default || layoutManager; function enableScrollX() { return !layoutManager.desktop; diff --git a/src/controllers/livetv/livetvsuggested.js b/src/controllers/livetv/livetvsuggested.js index 79d9aac6e9..b3adc4fa85 100644 --- a/src/controllers/livetv/livetvsuggested.js +++ b/src/controllers/livetv/livetvsuggested.js @@ -2,6 +2,7 @@ define(['layoutManager', 'userSettings', 'inputManager', 'loading', 'globalize', 'use strict'; loading = loading.default || loading; + layoutManager = layoutManager.default || layoutManager; function enableScrollX() { return !layoutManager.desktop; diff --git a/src/controllers/movies/moviegenres.js b/src/controllers/movies/moviegenres.js index 82ab4d4d4b..716b9b94cd 100644 --- a/src/controllers/movies/moviegenres.js +++ b/src/controllers/movies/moviegenres.js @@ -3,6 +3,7 @@ define(['layoutManager', 'loading', 'libraryBrowser', 'cardBuilder', 'lazyLoader loading = loading.default || loading; libraryBrowser = libraryBrowser.default || libraryBrowser; + layoutManager = layoutManager.default || layoutManager; return function (view, params, tabContent) { function getPageData() { diff --git a/src/controllers/movies/movies.js b/src/controllers/movies/movies.js index ade9dc4b89..48f9fa8f5d 100644 --- a/src/controllers/movies/movies.js +++ b/src/controllers/movies/movies.js @@ -3,6 +3,7 @@ define(['loading', 'layoutManager', 'userSettings', 'events', 'libraryBrowser', loading = loading.default || loading; libraryBrowser = libraryBrowser.default || libraryBrowser; + layoutManager = layoutManager.default || layoutManager; return function (view, params, tabContent, options) { function onViewStyleChange() { diff --git a/src/controllers/movies/moviesrecommended.js b/src/controllers/movies/moviesrecommended.js index a633d654cd..1d50f85919 100644 --- a/src/controllers/movies/moviesrecommended.js +++ b/src/controllers/movies/moviesrecommended.js @@ -2,6 +2,7 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu' 'use strict'; playbackManager = playbackManager.default || playbackManager; + layoutManager = layoutManager.default || layoutManager; function enableScrollX() { return !layoutManager.desktop; diff --git a/src/controllers/movies/movietrailers.js b/src/controllers/movies/movietrailers.js index 8d9fe8d090..7dc3b577da 100644 --- a/src/controllers/movies/movietrailers.js +++ b/src/controllers/movies/movietrailers.js @@ -3,6 +3,7 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', ' loading = loading.default || loading; libraryBrowser = libraryBrowser.default || libraryBrowser; + layoutManager = layoutManager.default || layoutManager; return function (view, params, tabContent) { function getPageData(context) { diff --git a/src/libraries/scroller.js b/src/libraries/scroller.js index c0cb3e557c..9b441da012 100644 --- a/src/libraries/scroller.js +++ b/src/libraries/scroller.js @@ -2,6 +2,7 @@ define(['browser', 'layoutManager', 'dom', 'focusManager', 'ResizeObserver', 'sc 'use strict'; browser = browser.default || browser; + layoutManager = layoutManager.default || layoutManager; /** * Return type of the value. diff --git a/src/scripts/libraryMenu.js b/src/scripts/libraryMenu.js index bbe01276ba..773804f84b 100644 --- a/src/scripts/libraryMenu.js +++ b/src/scripts/libraryMenu.js @@ -3,6 +3,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' playbackManager = playbackManager.default || playbackManager; browser = browser.default || browser; + layoutManager = layoutManager.default || layoutManager; function renderHeader() { var html = ''; diff --git a/src/scripts/livetvcomponents.js b/src/scripts/livetvcomponents.js index fd1b48d0eb..2f4e71642c 100644 --- a/src/scripts/livetvcomponents.js +++ b/src/scripts/livetvcomponents.js @@ -1,6 +1,8 @@ define(['layoutManager', 'datetime', 'cardBuilder', 'apphost'], function (layoutManager, datetime, cardBuilder, appHost) { 'use strict'; + layoutManager = layoutManager.default || layoutManager; + function enableScrollX() { return !layoutManager.desktop; } diff --git a/src/scripts/site.js b/src/scripts/site.js index 87402635f5..ab44d03834 100644 --- a/src/scripts/site.js +++ b/src/scripts/site.js @@ -350,6 +350,7 @@ function initClient() { } function getLayoutManager(layoutManager, appHost) { + layoutManager = layoutManager.default || layoutManager; if (appHost.getDefaultLayout) { layoutManager.defaultLayout = appHost.getDefaultLayout(); } From ce8484e8f0c37678b0cf1dbd1d84c8e1dc85fd2b Mon Sep 17 00:00:00 2001 From: SaddFox Date: Tue, 4 Aug 2020 12:55:59 +0000 Subject: [PATCH 050/124] Translated using Weblate (Slovenian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/sl/ --- src/strings/sl-si.json | 52 +++++++++++++++++++++++++++++++----------- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/src/strings/sl-si.json b/src/strings/sl-si.json index 2f08ad8b40..423ee7797c 100644 --- a/src/strings/sl-si.json +++ b/src/strings/sl-si.json @@ -448,7 +448,7 @@ "FastForward": "Hitro naprej", "FFmpegSavePathNotFound": "Nismo mogli locirati FFmpeg na navedeni poti. FFprobe je prav tako zahtevan in mora biti v isti mapi. Ti komponenti sta običajno združeni skupaj v istem prenosu. Preverite pot in poskusite znova.", "Extras": "Dodatki", - "ExtractChapterImagesHelp": "Izločanje slik poglavij omogoča odjemalcem prikaz grafičnih menijev za izbor scene. Ta proces je lahko počasen, procesorsko zahteven in lahko potrebuje več gigabajtov prostora na disku. Teče ob odkritju novih videov in kot načrtovana naloga ponoči. Urnik izvajanja lahko nastavite v nastavitvah načrtovanih nalog. Zaganjanje tega procesa med urami visoke obremenitve ni priporočeno.", + "ExtractChapterImagesHelp": "Ekstrakcija slik poglavij omogoča odjemalcem prikaz grafičnih menijev za izbor scene. Ta proces je lahko počasen, procesorsko zahteven in lahko potrebuje več gigabajtov prostora na disku. Teče ob odkritju novih videov in kot načrtovana naloga ponoči. Urnik izvajanja lahko nastavite v nastavitvah načrtovanih nalog. Zaganjanje tega procesa med urami visoke obremenitve ni priporočeno.", "ExtraLarge": "Zelo veliko", "ExitFullscreen": "Izhod in celozaslonskega načina", "ErrorSavingTvProvider": "Prišlo je do težave pri shranjevanju TV ponudnika. Preverite ali je dostopen in poskusite znova.", @@ -723,16 +723,16 @@ "LabelMonitorUsers": "Spremljaj aktivnost iz:", "LabelMovieCategories": "Kategorije filmov:", "LabelMoviePrefix": "Predpona filma:", - "LabelMovieRecordingPath": "Pot za snemanje filmov (neobvezno):", + "LabelMovieRecordingPath": "Pot za snemanje filmov:", "LabelMusicStreamingTranscodingBitrate": "Bitna hitrost pretvarjanja glasbe:", - "LabelMusicStreamingTranscodingBitrateHelp": "Določi največjo bitno hitrost pretakanja glasbe.", + "LabelMusicStreamingTranscodingBitrateHelp": "Določite največjo bitno hitrost pretakanja glasbe.", "LabelName": "Ime:", "LabelFriendlyName": "Uporabniku prijazno ime:", "LabelKodiMetadataEnablePathSubstitutionHelp": "Omogoči zamenjavo poti za poti slik glede na nastavitve zamenjave poti strežnika.", "LabelKodiMetadataSaveImagePaths": "Shrani poti slik znotraj nfo datotek", "LabelMetadataDownloadersHelp": "Omogoči in uredi želene vire metapodatkov po prioriteti. Viri z nižjo prioriteto bodo uporabljeni zgolj za dopolnjevanje manjkajočih informacij.", "LabelBaseUrlHelp": "Dodjte podnaslov po meri na konec URL-ja strežnika. Na primer: http://example.com/<baseurl>", - "LabelExtractChaptersDuringLibraryScanHelp": "Ustvari slike poglavij med uvozom videov pri preiskovanju knjižnjice. Sicer bodo ustvarjene med načrtovanim opravilom, kar omogoča hitrejše preiskovanje knjižnjice.", + "LabelExtractChaptersDuringLibraryScanHelp": "Ustvari slike poglavij med uvozom videov pri preiskovanju knjižnjice. Sicer bodo ustvarjene med načrtovanim opravilom ekstrakcije slik, kar omogoča hitrejše preiskovanje knjižnjice.", "LabelForgotPasswordUsernameHelp": "Vpišite svoje uporabniško ime, v kolikor se ga spomnite.", "LabelInNetworkSignInWithEasyPasswordHelp": "Uporabi enostavno PIN kodo za prijavo v naprave znotraj lokalnega omrežja. Vaše geslo bo potrebno zgolj za prijave zunaj domačega omrežja. Če pustite prazno, za prijavo v domačem omrežju omrežju ne boste potrebovali gesla.", "LabelMaxStreamingBitrate": "Največja kvaliteta pretakanja:", @@ -820,7 +820,7 @@ "OptionReportByteRangeSeekingWhenTranscoding": "Sporoči, da strežnik podpira iskanje po bajtih pri prekodiranju", "Quality": "Kvaliteta", "PlaceFavoriteChannelsAtBeginning": "Postavi priljubljene kanale na začetek", - "LabelOptionalNetworkPath": "(Neobvezno) Omrežna mapa:", + "LabelOptionalNetworkPath": "Omrežna mapa v skupni rabi:", "LabelOptionalNetworkPathHelp": "V primeru, da je mapa deljena v vašem omrežju, lahko Jellyfin deli omrežno pot z ostalimi napravami in jim omogoči neposreden dostop do vsebin. Na primer {0} ali {1}.", "LabelRemoteClientBitrateLimitHelp": "Neobvezna omejitev bitne hitrosti na posamezno predvajanje za vse naprave izven domačega omrežja. S tem lahko preprečite, da bi naprave zahtevale višjo bitno hitrost predvajanja, kot jo lahko prenese vaše omrežje. To lahko poveča obremenitev CPU-ja, saj bo morda potrebno sprotno prekodiranje za zmanjšanje bitne hitrosti.", "LanNetworksHelp": "Z vejico ločen seznam IP naslovov ali IP/maska omrežji, ki bodo upoštevana kot lokalna pri uveljavljanju omejitev pasovne širine. Če nastavite, se bodo vsi ostali naslovi upoštevali kot zunanji in bodo predmet omejitve pasovne širine. Če pustite prazno, bo kot lokalno omrežje upoštevano zgolj omrežje strežnika.", @@ -848,14 +848,14 @@ "OptionWeekly": "Tedensko", "OriginalAirDateValue": "Prvotni datum predvajanja: {0}", "Overview": "Pregled", - "PackageInstallCancelled": "{0} namestitev preklicana.", - "PackageInstallCompleted": "{0} namestitev uspešna.", - "PackageInstallFailed": "{0} namestitev neuspešna.", + "PackageInstallCancelled": "{0} (različica {1}) namestitev preklicana.", + "PackageInstallCompleted": "{0} (različica {1}) namestitev uspešna.", + "PackageInstallFailed": "{0} (različica {1}) namestitev neuspešna.", "PasswordMatchError": "Geslo in potrditev gesla se moreta ujemati.", "PasswordResetComplete": "Geslo je bilo ponastavljeno.", "PasswordResetConfirmation": "Ali ste prepričani, da želite ponastaviti geslo?", "HeaderResetPassword": "Ponastavi geslo", - "PasswordResetProviderHelp": "Izberite ponudnika ponastavitve gesla, ki bo uporabljen, ko bo ta uporabnik zahteval ponastavitev gesla", + "PasswordResetProviderHelp": "Izberite ponudnika ponastavitve gesla, ki bo uporabljen, ko bo ta uporabnik zahteval ponastavitev gesla.", "PasswordSaved": "Geslo shranjeno.", "PerfectMatch": "Popolno ujemanje", "PictureInPicture": "Slika v sliki", @@ -1047,7 +1047,7 @@ "OptionDvd": "DVD", "OptionDownloadMenuImage": "Meni", "OptionDownloadLogoImage": "Logotip", - "OptionDownloadImagesInAdvanceHelp": "Privzeto se večina slik prenese šele, ko jih zahtevajo aplikacije. Omogočite to možnost za prenos slik vnaprej, pri uvozu predstavnosti. To lahko občutno podaljša preiskovanje knjižnice.", + "OptionDownloadImagesInAdvanceHelp": "Privzeto se večina slik prenese šele, ko jih zahtevajo odjemalci. Omogočite to možnost za prenos slik vnaprej, pri uvozu predstavnosti. To lahko občutno podaljša preiskovanje knjižnic.", "OptionDownloadImagesInAdvance": "Prenesi slike vnaprej", "OptionAllowSyncTranscoding": "Dovoli prenašanje in sinhronizacijo predstavnosti ki zahteva pretvarjanje", "OptionAllowRemoteControlOthers": "Dovoli daljinsko upravljanje drugih uporabnikov", @@ -1077,14 +1077,14 @@ "LabelServerName": "Ime strežnika:", "LabelServerHostHelp": "192.168.1.100:8096 ali https://myserver.com", "LabelServerHost": "Naslov:", - "LabelSeriesRecordingPath": "Pot za snemanje serij (neobvezno):", + "LabelSeriesRecordingPath": "Pot za snemanje serij:", "LabelSerialNumber": "Serijska številka", "LabelSendNotificationToUsers": "Pošlji obvestilo na:", "LabelSeasonNumber": "Številka sezone:", "LabelScreensaver": "Ohranjevalnik zaslona:", "LabelSaveLocalMetadataHelp": "Shranjevanje slik v mape predstavnosti omogoča lažji dostop in urejanje slik.", "LabelSaveLocalMetadata": "Shrani slike v mape predstavnosti", - "LabelRuntimeMinutes": "Čas trajanja (minute):", + "LabelRuntimeMinutes": "Čas trajanja:", "LabelRemoteClientBitrateLimit": "Omejitev bitne hitrosti pretakanja preko interneta (Mbps):", "LabelRecordingPathHelp": "Določite privzeto lokacijo za shranjevanje posnetkov. Če pustite prazno, bo uporabljena mapa namestitve strežnika.", "LabelRecordingPath": "Privzeta pot posnetkov:", @@ -1403,5 +1403,31 @@ "MessageLeaveEmptyToInherit": "Pustite prazno za podedovanje nastavitev od nadrejenega elementa ali globalne privzete vrednosti.", "LabelXDlnaDocHelp": "Določa vsebino X_DLNADOC elementa v urn:schemas-dlna-org:device-1-0 namespace.", "LabelXDlnaCapHelp": "Določa vsebino X_DLNACAP elementa v urn:schemas-dlna-org:device-1-0 namespace.", - "OptionDislikes": "Ni mi všeč" + "OptionDislikes": "Ni mi všeč", + "ClearQueue": "Počisti čakalno vrsto", + "StopPlayback": "Ustavi predvajanje", + "ButtonCast": "Zasedba", + "EnableBlurHashHelp": "Med nalaganjem bodo slike nadomeščene z edinstvenimi nadomestnimi sličicami.", + "EnableBlurHash": "Omogoči zamegljene začasne sličice za slike", + "UnsupportedPlayback": "Jellyfin ne more dešifrirati vsebin zaščitenih z DRM, vendar bo strežnik kljub temu poskušal predvajati vse vsebine. Zaradi šifriranja ali drugih nepodprtih funkcij, na primer interaktivnih naslovov, bodo nekatere vsebine prikazane popolnoma črne.", + "OnApplicationStartup": "Ob zagonu aplikacije", + "EveryXHours": "Vsakih {0} ur", + "EveryHour": "Vsako uro", + "EveryXMinutes": "Vsakih {0} minut", + "OnWakeFromSleep": "Ob prebujenju iz spanja", + "WeeklyAt": "{0}s ob {1}", + "DailyAt": "Vsak dan ob {0}", + "OptionPlainVideoItemsHelp": "Vsi videi so predstavljeni v DIDL kot \"object.item.videoItem\" namesto bolj specifičen tip, kot na primer \"object.item.videoItem.movie\".", + "OptionPlainVideoItems": "Prikaži vse videe kot preproste video vsebine", + "OptionPlainStorageFoldersHelp": "Vse mape so predstavljene v DIDL kot \"object.container.storageFolder\" namesto bolj specifičen tip, kot na primer \"object.container.person.musicArtist\".", + "OptionPlainStorageFolders": "Prikaži vse mape kot enostavne mape shrambe", + "OptionHlsSegmentedSubtitles": "HLS ločeni podnapisi", + "OptionExtractChapterImage": "Omogoči ekstrakcijo slik poglavij", + "OptionEstimateContentLength": "Oceni dolžino vsebine pri prekodiranju", + "OptionEquals": "Je enako", + "OptionEnded": "Zaključeno", + "OptionEnableM2tsModeHelp": "Omogoči m2ts način pri kodiranju v mpegts.", + "OptionEnableM2tsMode": "Omogoči M2ts način", + "OptionDisplayFolderViewHelp": "Prikaže mape poleg ostalih knjižnic predstavnosti. Uporabno za preprost ogled map.", + "OptionDisplayFolderView": "Prikaži pogled mape za prikaz navadnih map predstavnosti" } From 4b1e94867ced840b2df1934924fe36bf4c92c248 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2020 16:47:34 +0000 Subject: [PATCH 051/124] Bump whatwg-fetch from 3.2.0 to 3.3.1 Bumps [whatwg-fetch](https://github.com/github/fetch) from 3.2.0 to 3.3.1. - [Release notes](https://github.com/github/fetch/releases) - [Commits](https://github.com/github/fetch/compare/v3.2.0...v3.3.1) 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 1d7cf770d3..7365d4f0e4 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "sortablejs": "^1.10.2", "swiper": "^5.4.5", "webcomponents.js": "^0.7.24", - "whatwg-fetch": "^3.2.0" + "whatwg-fetch": "^3.3.1" }, "babel": { "presets": [ diff --git a/yarn.lock b/yarn.lock index 17c2b176ef..400a7c2bee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11988,10 +11988,10 @@ webworkify@^1.5.0: resolved "https://registry.yarnpkg.com/webworkify/-/webworkify-1.5.0.tgz#734ad87a774de6ebdd546e1d3e027da5b8f4a42c" integrity sha512-AMcUeyXAhbACL8S2hqqdqOLqvJ8ylmIbNwUIqQujRSouf4+eUFaXbG6F1Rbu+srlJMmxQWsiU7mOJi0nMBfM1g== -whatwg-fetch@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.2.0.tgz#8e134f701f0a4ab5fda82626f113e2b647fd16dc" - integrity sha512-SdGPoQMMnzVYThUbSrEvqTlkvC1Ux27NehaJ/GUHBfNrh5Mjg+1/uRyFMwVnxO2MrikMWvWAqUGgQOfVU4hT7w== +whatwg-fetch@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.3.1.tgz#6c1acf37dec176b0fd6bc9a74b616bec2f612935" + integrity sha512-faXTmGDcLuEPBpJwb5LQfyxvubKiE+RlbmmweFGKjvIPFj4uHTTfdtTIkdTRhC6OSH9S9eyYbx8kZ0UEaQqYTA== which-module@^1.0.0: version "1.0.0" From a930482ca11df112e7eac1b1a57d36a588331195 Mon Sep 17 00:00:00 2001 From: matjaz321 Date: Tue, 4 Aug 2020 19:00:19 +0200 Subject: [PATCH 052/124] Fixed linting errors --- src/controllers/dashboard/notifications/notifications/index.js | 1 - src/controllers/livetv/livetvrecordings.js | 2 -- 2 files changed, 3 deletions(-) diff --git a/src/controllers/dashboard/notifications/notifications/index.js b/src/controllers/dashboard/notifications/notifications/index.js index d12a5f2e61..b97403f8eb 100644 --- a/src/controllers/dashboard/notifications/notifications/index.js +++ b/src/controllers/dashboard/notifications/notifications/index.js @@ -1,5 +1,4 @@ import loading from 'loading'; -import libraryMenu from 'libraryMenu'; import globalize from 'globalize'; import 'listViewStyle'; import 'emby-button'; diff --git a/src/controllers/livetv/livetvrecordings.js b/src/controllers/livetv/livetvrecordings.js index cb3bfeb7a6..ec2f57e14f 100644 --- a/src/controllers/livetv/livetvrecordings.js +++ b/src/controllers/livetv/livetvrecordings.js @@ -1,7 +1,5 @@ -import layoutManager from 'layoutManager'; import loading from 'loading'; import cardBuilder from 'cardBuilder'; -import appHost from 'apphost'; import imageLoader from 'imageLoader'; import 'scripts/livetvcomponents'; import 'listViewStyle'; From 8c58472dcf1b2586f71eb3e2d247d1955b3a721b Mon Sep 17 00:00:00 2001 From: matjaz321 Date: Tue, 4 Aug 2020 19:15:15 +0200 Subject: [PATCH 053/124] Migrated castSenderApi.js to es6 module --- package.json | 1 + src/components/castSenderApi.js | 50 ++++++++++++-------------- src/plugins/chromecastPlayer/plugin.js | 2 +- 3 files changed, 24 insertions(+), 29 deletions(-) diff --git a/package.json b/package.json index 1d7cf770d3..bf4d9ccf0a 100644 --- a/package.json +++ b/package.json @@ -169,6 +169,7 @@ "src/components/toast/toast.js", "src/components/upnextdialog/upnextdialog.js", "src/components/viewContainer.js", + "src/components/castSenderApi.js", "src/controllers/session/addServer/index.js", "src/controllers/session/forgotPassword/index.js", "src/controllers/session/redeemPassword/index.js", diff --git a/src/components/castSenderApi.js b/src/components/castSenderApi.js index b541c1f87a..c7fd5df217 100644 --- a/src/components/castSenderApi.js +++ b/src/components/castSenderApi.js @@ -1,34 +1,28 @@ -define([], function() { - 'use strict'; - - if (window.appMode === 'cordova' || window.appMode === 'android') { - return { - load: function () { - window.chrome = window.chrome || {}; +class CastSenderApi { + load() { + if (window.appMode === 'cordova' || window.appMode === 'android') { + window.chrome = window.chrome || {}; + return Promise.resolve(); + } else { + let ccLoaded = false; + if (ccLoaded) { return Promise.resolve(); } - }; - } else { - var ccLoaded = false; - return { - load: function () { - if (ccLoaded) { - return Promise.resolve(); - } - return new Promise(function (resolve, reject) { - var fileref = document.createElement('script'); - fileref.setAttribute('type', 'text/javascript'); + return new Promise(function (resolve) { + const fileref = document.createElement('script'); + fileref.setAttribute('type', 'text/javascript'); - fileref.onload = function () { - ccLoaded = true; - resolve(); - }; + fileref.onload = function () { + ccLoaded = true; + resolve(); + }; - fileref.setAttribute('src', 'https://www.gstatic.com/cv/js/sender/v1/cast_sender.js'); - document.querySelector('head').appendChild(fileref); - }); - } - }; + fileref.setAttribute('src', 'https://www.gstatic.com/cv/js/sender/v1/cast_sender.js'); + document.querySelector('head').appendChild(fileref); + }); + } } -}); +} + +export default CastSenderApi; diff --git a/src/plugins/chromecastPlayer/plugin.js b/src/plugins/chromecastPlayer/plugin.js index 2741a6f0e3..3d49f6fff6 100644 --- a/src/plugins/chromecastPlayer/plugin.js +++ b/src/plugins/chromecastPlayer/plugin.js @@ -577,7 +577,7 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' this.isLocalPlayer = false; this.lastPlayerData = {}; - castSenderApiLoader.load().then(initializeChromecast.bind(this)); + new castSenderApiLoader().load().then(initializeChromecast.bind(this)); } ChromecastPlayer.prototype.tryPair = function (target) { From 8e17d603a87dc7581334b362c6c5c54048e542e2 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 4 Aug 2020 21:43:24 +0100 Subject: [PATCH 054/124] Update list.js --- src/controllers/list.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/controllers/list.js b/src/controllers/list.js index b9237887d0..7d35bcf5cb 100644 --- a/src/controllers/list.js +++ b/src/controllers/list.js @@ -722,11 +722,9 @@ class ItemsView { this.itemsContainer.setAttribute('data-refreshinterval', '300000'); } - let i; - let length; const btnViewSettings = view.querySelectorAll('.btnViewSettings'); - for (i = 0, length = btnViewSettings.length; i < length; i++) { + for (let i = 0, length = btnViewSettings.length; i < length; i++) { btnViewSettings[i].addEventListener('click', showViewSettingsMenu.bind(this)); } @@ -734,7 +732,7 @@ class ItemsView { this.filterButtons = filterButtons; const hasVisibleFilters = this.getVisibleFilters().length; - for (i = 0, length = filterButtons.length; i < length; i++) { + for (let i = 0, length = filterButtons.length; i < length; i++) { const btnFilter = filterButtons[i]; btnFilter.addEventListener('click', showFilterMenu.bind(this)); From 0963fb37486372f48aaf16fbba08218d25d2c40a Mon Sep 17 00:00:00 2001 From: millallo Date: Tue, 4 Aug 2020 21:02:48 +0000 Subject: [PATCH 055/124] Translated using Weblate (Italian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/it/ --- src/strings/it.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/strings/it.json b/src/strings/it.json index 9035251c12..15e32d84bf 100644 --- a/src/strings/it.json +++ b/src/strings/it.json @@ -157,8 +157,8 @@ "DetectingDevices": "Rilevamento dispositivi", "DeviceAccessHelp": "Si applica solo ai dispositivi che possono essere identificati univocamente e non impedirà l'accesso dal browser. Filtrare l'accesso ai dispositivi dell'utente impedirà di usare nuovi dispositivi fino a quando non saranno stati approvati qui.", "DirectPlaying": "Riproduzione Diretta", - "DirectStreamHelp1": "Il file multimediale è compatibile con il dispositivo per quanto riguarda la risoluzione e il tipo di supporto (H. 264, AC3, ecc), ma è in un contenitore file incompatibile (mkv, avi, wmv, ecc). Il video sarà ri-confezionato al volo prima di streammarlo sul dispositivo.", - "DirectStreamHelp2": "Lo Streaming in Diretta di un file utilizza poco il processore senza alcuna perdita di qualità video.", + "DirectStreamHelp1": "Il file multimediale è compatibile con il dispositivo per quanto riguarda la risoluzione e il tipo di supporto (H. 264, AC3, ecc), ma è in un contenitore file incompatibile (mkv, avi, wmv, ecc). Il video sarà quindi reimpacchettato al volo prima dell'invio al dispositivo.", + "DirectStreamHelp2": "Lo streaming in diretta utilizza poco processore e con poca perdita di qualità video.", "DirectStreaming": "Streaming Diretto", "Director": "Regista", "Directors": "Registi", @@ -265,7 +265,7 @@ "HeaderAllowMediaDeletionFrom": "Abilita Eliminazione Media Da", "HeaderApiKey": "Chiave API", "HeaderApiKeys": "Chiavi API", - "HeaderApiKeysHelp": "Le Applicazioni esterne devono avere una chiave API per comunicare con il Server Jellyfin. Le chiavi sono emesse accedendo con un account Jellyfin, o fornendo manualmente una chiave all'applicazione.", + "HeaderApiKeysHelp": "Le Applicazioni esterne devono avere una chiave API per comunicare con il server. Le chiavi sono emesse accedendo con un qualsiasi account, o fornendo manualmente una chiave all'applicazione.", "HeaderAudioBooks": "Audiolibri", "HeaderAudioSettings": "Impostazioni audio", "HeaderBlockItemsWithNoRating": "Blocca elementi sconosciuti o senza informazioni:", @@ -1281,7 +1281,7 @@ "HeaderCastCrew": "Cast", "HeaderMedia": "Media", "HeaderPassword": "Password", - "AuthProviderHelp": "Selezionare un Provider di Autenticazione da utilizzare per autenticare la password dell'utente.", + "AuthProviderHelp": "Selezionare un provider di autenticazione da utilizzare per autenticare la password dell'utente.", "HeaderFavoriteMovies": "Film Preferiti", "HeaderFavoriteShows": "Serie TV Preferite", "HeaderFavoriteEpisodes": "Episodi Preferiti", From 0e388d4d6deab055926ee7093ec051694e985e9c Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 5 Aug 2020 02:51:36 +0000 Subject: [PATCH 056/124] Bump @babel/core from 7.11.0 to 7.11.1 Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.11.0 to 7.11.1. - [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.1/packages/babel-core) Signed-off-by: dependabot-preview[bot] --- package.json | 2 +- yarn.lock | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 7365d4f0e4..a014dc1b76 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "repository": "https://github.com/jellyfin/jellyfin-web", "license": "GPL-2.0-or-later", "devDependencies": { - "@babel/core": "^7.11.0", + "@babel/core": "^7.11.1", "@babel/eslint-parser": "^7.11.0", "@babel/eslint-plugin": "^7.11.0", "@babel/plugin-proposal-class-properties": "^7.10.1", diff --git a/yarn.lock b/yarn.lock index 400a7c2bee..5c8a7f7e14 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18,16 +18,16 @@ invariant "^2.2.4" semver "^5.5.0" -"@babel/core@>=7.2.2", "@babel/core@>=7.9.0", "@babel/core@^7.11.0": - version "7.11.0" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.11.0.tgz#73b9c33f1658506887f767c26dae07798b30df76" - integrity sha512-mkLq8nwaXmDtFmRkQ8ED/eA2CnVw4zr7dCztKalZXBvdK5EeNUAesrrwUqjQEzFgomJssayzB0aqlOsP1vGLqg== +"@babel/core@>=7.2.2", "@babel/core@>=7.9.0", "@babel/core@^7.11.1": + version "7.11.1" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.11.1.tgz#2c55b604e73a40dc21b0e52650b11c65cf276643" + integrity sha512-XqF7F6FWQdKGGWAzGELL+aCO1p+lRY5Tj5/tbT3St1G8NaH70jhhDIKknIZaDans0OQBG5wRAldROLHSt44BgQ== dependencies: "@babel/code-frame" "^7.10.4" "@babel/generator" "^7.11.0" "@babel/helper-module-transforms" "^7.11.0" "@babel/helpers" "^7.10.4" - "@babel/parser" "^7.11.0" + "@babel/parser" "^7.11.1" "@babel/template" "^7.10.4" "@babel/traverse" "^7.11.0" "@babel/types" "^7.11.0" @@ -281,10 +281,10 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.10.4", "@babel/parser@^7.11.0": - version "7.11.0" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.11.0.tgz#a9d7e11aead25d3b422d17b2c6502c8dddef6a5d" - integrity sha512-qvRvi4oI8xii8NllyEc4MDJjuZiNaRzyb7Y7lup1NqJV8TZHF4O27CcP+72WPn/k1zkgJ6WJfnIbk4jTsVAZHw== +"@babel/parser@^7.10.4", "@babel/parser@^7.11.0", "@babel/parser@^7.11.1": + version "7.11.1" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.11.1.tgz#d91a387990b21e5d20047b336bb19b0553f02ff5" + integrity sha512-u9QMIRdKVF7hfEkb3nu2LgZDIzCQPv+yHD9Eg6ruoJLjkrQ9fFz4IBSlF/9XwoNri9+2F1IY+dYuOfZrXq8t3w== "@babel/plugin-proposal-async-generator-functions@^7.10.4": version "7.10.4" From 88bbada5064c4caa5576e33c180827fd725da44d Mon Sep 17 00:00:00 2001 From: Daisuke Inoue Date: Wed, 5 Aug 2020 05:59:37 +0000 Subject: [PATCH 057/124] Translated using Weblate (Japanese) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/ja/ --- src/strings/ja.json | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/strings/ja.json b/src/strings/ja.json index 4e0a0a4fed..a6738d9517 100644 --- a/src/strings/ja.json +++ b/src/strings/ja.json @@ -169,7 +169,7 @@ "DetectingDevices": "検出デバイス", "DeviceAccessHelp": "これは、識別できるデバイスにのみ適用され、ブラウザへのアクセスを妨げることはありません。 ユーザーのデバイスアクセスをフィルタリングすると、ここで承認されるまで新しいデバイスを使用できなくなります。", "DirectPlaying": "ダイレクト再生", - "DirectStreamHelp1": "メディアの種類 (H.264, AC3, etc.)がお使いのデバイスに対応している場合Jellyfinサーバーからメディアファイルの直接再生が可能です。互換性のないファイルコンテナ(.mkv, .avi, .wmv, etc.)はリパックを行い再生されます。", + "DirectStreamHelp1": "メディアは、解像度と種類 (H.264, AC3など)がお使いのデバイスに対応していますが、ファイルコンテナ(.mkv, .avi, .wmvなど)が対応していません。ビデオはデバイスに送信される前に、再パッケージされます。", "DirectStreamHelp2": "ファイルのダイレクトストリーミングは、ビデオ品質を損なうことなく、Jellyfin Serverにもほとんど負荷がありません。", "DirectStreaming": "ダイレクトストリーミング", "Director": "ディレクター", @@ -291,7 +291,7 @@ "HeaderAllowMediaDeletionFrom": "メディアの削除を許可", "HeaderApiKey": "API キー", "HeaderApiKeys": "API キー", - "HeaderApiKeysHelp": "Jellyfin サーバーと通信するには、外部アプリケーション用ににAPIキーが必要です。 キーはJellyfinアカウントでログインするか、手動でアプリケーションにキーを付与することによって発行されます。", + "HeaderApiKeysHelp": "外部アプリケーションが Jellyfin サーバーと通信するには、API キーが必要です。キーは 通常のユーザーアカウントでログインするか、手動でアプリケーションにキーを付与することで発行します。", "HeaderApp": "アプリ", "HeaderAppearsOn": "表示", "HeaderAudioBooks": "オーディオブック", @@ -534,7 +534,7 @@ "LabelEncoderPreset": "H264エンコーディングプリセット:", "LabelHardwareAccelerationType": "ハードウェアアクセラレーション:", "LabelH264Crf": "H264エンコーディングCRF:", - "LabelHttpsPortHelp": "JellyfinのHTTPSサーバーがバインドするTCPポート番号。", + "LabelHttpsPortHelp": "HTTPS サーバーのTCPポート番号。", "LabelKodiMetadataDateFormat": "リリース日時フォーマット:", "LabelLogs": "ログ:", "LabelMessageText": "メッセージテキスト:", @@ -1136,7 +1136,7 @@ "LabelCustomCertificatePathHelp": "カスタムドメインでTLSサポートを有効にするための証明書と秘密鍵を含むPKCS #12ファイルのパス。", "LabelCachePathHelp": "画像などのサーバーキャッシュファイルの場所を指定します。空欄にしておくと、サーバーのデフォルトを使います。", "LabelBlastMessageIntervalHelp": "ブラスト アライブ メッセージ間の時間を秒単位で指定します。", - "LabelBindToLocalNetworkAddressHelp": "追加の設定。http サーバをバインドするローカル IP アドレスを上書きします。空のままにしておくと、サーバは利用可能なすべてのアドレスにバインドします。この値を変更するには、Jellyfin Server を再起動する必要があります。", + "LabelBindToLocalNetworkAddressHelp": "HTTP サーバー用のローカル IP アドレスを上書きします。空のままにしておくと、サーバーは利用可能なすべてのアドレスにバインドします。この値の変更を反映するには、Jellyfin サーバーの再起動が必要です。", "LabelAlbumArtMaxWidthHelp": "upnp:albumArtURI で公開するアルバムアートの最大解像度。", "LabelAlbumArtMaxHeightHelp": "upnp:albumArtURI で公開するアルバムアートの最大解像度。", "LabelAlbumArtHelp": "upnp:albumArtURI の dlna:profileID 属性で、アルバムアートに使われるPN。デバイスによっては、画像のサイズと無関係に特定の値を要求するものもあります。", @@ -1161,14 +1161,14 @@ "LabelEnableDlnaPlayTo": "DLNA 再生を有効にする", "LabelEnableDlnaDebugLoggingHelp": "巨大なログファイルを作成します。トラブルシューティングでの必要な際にだけ使用してください。", "LabelEnableDlnaClientDiscoveryIntervalHelp": "Jellyfin が実行する SSDP 検索の間隔を決めます(秒単位)。", - "LabelGroupMoviesIntoCollectionsHelp": "ムービーリストを表示する際、コレクションに属するムービーを1つのグループとして表示します。", - "LabelServerNameHelp": "この名前はサーバーを識別するために使用します。デフォルトではサーバーのコンピュータ名です。", + "LabelGroupMoviesIntoCollectionsHelp": "ムービー リストを表示する際、コレクションに属するムービーは1つのグループとして表示します。", + "LabelServerNameHelp": "この名前はサーバーを識別するために使用します。デフォルトではサーバーのホスト名です。", "LabelExtractChaptersDuringLibraryScanHelp": "ライブラリー スキャン中に動画を取り込んだときに、チャプター画像を生成します。もしくは、スケジュールタスクの中でチャプター画像を抽出することで、通常のライブラリー スキャンをより速く完了させることができます。", "LabelExtractChaptersDuringLibraryScan": "ライブラリーをスキャンしながら、チャプター画像を生成する", - "LabelBaseUrlHelp": "サーバーの URL にカスタム サブディレクトリを加えます。例 : http://example.com/<baseurl>", + "LabelBaseUrlHelp": "サーバーの URL に、カスタム サブディレクトリを加えます。例 : http://example.com/<baseurl>", "LabelEnableSingleImageInDidlLimitHelp": "Didl 内に複数の画像が埋め込まれている場合、一部のデバイスでは正しくレンダリングされません。", - "LabelEnableRealtimeMonitorHelp": "ファイルへの変更は、サポートされているファイルシステム上ですぐに処理されます。", - "LabelEnableHttpsHelp": "構成された HTTPS ポートからサーバーがリッスンするのを有効にします。この機能を有効にするには、適切な証明書を設定する必要があります。", + "LabelEnableRealtimeMonitorHelp": "サポートしているファイルシステムでは、ファイルの変更をすぐに処理します。", + "LabelEnableHttpsHelp": "設定した HTTPS ポートをリッスンします。この機能を有効にするには、適切な証明書の設定が必要です。", "LabelImageFetchersHelp": "画像検索サイトの優先度を設定します。", "LabelPostProcessorArgumentsHelp": "{path}を録画ファイルのパスとして使用します。", "LabelPostProcessorArguments": "後処理のコマンドライン引数:", @@ -1214,5 +1214,7 @@ "LabelKodiMetadataDateFormatHelp": "NFOファイル内の全データが,このフォーマットによって解析されます。", "LabelKeepUpTo": "最新:", "LabelInNetworkSignInWithEasyPasswordHelp": "ローカルネットワーク内では簡単なPINコードを利用してサインインするようにします。ローカル以外からのアクセスのときのみ通常のパスワードが必要になります。PINコードを空欄にした場合,ローカルネットワーク内からのアクセスではパスワードが不要になります。", - "LabelInNetworkSignInWithEasyPassword": "簡単なPINコードを利用してネットワーク内からサインインする機能の有効化" + "LabelInNetworkSignInWithEasyPassword": "簡単なPINコードを利用してネットワーク内からサインインする機能の有効化", + "LabelIconMaxWidthHelp": "upnp:icon として表示されるアイコンの最大解像度(幅)。", + "LabelIconMaxHeightHelp": "upnp:icon として表示されるアイコンの最大解像度(高さ)。" } From 91f6532e35f347e134b534f93e89d0c68e7866fa Mon Sep 17 00:00:00 2001 From: Matjaz Zavski Date: Wed, 5 Aug 2020 09:17:17 +0200 Subject: [PATCH 058/124] Update src/controllers/livetv/livetvchannels.js Co-authored-by: Julien Machiels --- src/controllers/livetv/livetvchannels.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/livetv/livetvchannels.js b/src/controllers/livetv/livetvchannels.js index d34fe58132..278200c634 100644 --- a/src/controllers/livetv/livetvchannels.js +++ b/src/controllers/livetv/livetvchannels.js @@ -91,8 +91,8 @@ export default function (view, params, tabContent) { } function showFilterMenu(context) { - import(['components/filterdialog/filterdialog']).then(({default: filterDialogFactory}) => { - const filterDialog = new filterDialogFactory({ + import(['components/filterdialog/filterdialog']).then(({default: FilterDialog}) => { + const filterDialog = new FilterDialog({ query: getQuery(), mode: 'livetvchannels', serverId: ApiClient.serverId() From 5bb37263fc8a76e9d7382bd4e8782a42fb04872f Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 5 Aug 2020 08:48:21 +0000 Subject: [PATCH 059/124] Bump gulp-terser from 1.3.0 to 1.3.2 Bumps [gulp-terser](https://github.com/duan602728596/gulp-terser) from 1.3.0 to 1.3.2. - [Release notes](https://github.com/duan602728596/gulp-terser/releases) - [Commits](https://github.com/duan602728596/gulp-terser/compare/v1.3.0...v1.3.2) Signed-off-by: dependabot-preview[bot] --- package.json | 2 +- yarn.lock | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index a014dc1b76..504f4038d9 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "gulp-postcss": "^8.0.0", "gulp-sass": "^4.0.2", "gulp-sourcemaps": "^2.6.5", - "gulp-terser": "^1.3.0", + "gulp-terser": "^1.3.2", "html-webpack-plugin": "^4.3.0", "lazypipe": "^1.0.2", "node-sass": "^4.13.1", diff --git a/yarn.lock b/yarn.lock index 5c8a7f7e14..3ec7f21487 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5262,11 +5262,12 @@ gulp-sourcemaps@^2.6.5: strip-bom-string "1.X" through2 "2.X" -gulp-terser@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/gulp-terser/-/gulp-terser-1.3.0.tgz#6423fdb7dd15cc376e28063b5271271a928084bd" - integrity sha512-EvizE1LJLfOh3/EmpJoq9iqYziObOkTzFgN4KvxfB0ICp3+W5H+MOO9B7Xq5Iuu9N+RKByNJLmqR+Ph13U1vtQ== +gulp-terser@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/gulp-terser/-/gulp-terser-1.3.2.tgz#c91a71c31de0c40a94330bfd43b20c2a40df9a6b" + integrity sha512-hLx9Ww9PX304R3E7pMyL+jFftH47QXQCgKE6IZajbz7OoqAPMdr+sTxBpaujgIVkdbnJwJ7AFhfrcyy/cjBgZQ== dependencies: + is-promise "^4.0.0" plugin-error "^1.0.1" terser ">=4" through2 "^4.0.2" @@ -6166,6 +6167,11 @@ is-promise@^2.1: resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== +is-promise@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-4.0.0.tgz#42ff9f84206c1991d26debf520dd5c01042dd2f3" + integrity sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ== + is-regex@^1.0.5, is-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.0.tgz#ece38e389e490df0dc21caea2bd596f987f767ff" From 2a822704d674bc32574505784819306ec36cd883 Mon Sep 17 00:00:00 2001 From: Xantios Krugor Date: Wed, 5 Aug 2020 12:19:51 +0200 Subject: [PATCH 060/124] Use focusManager.default in non ES6 modules --- src/components/filtermenu/filtermenu.js | 3 +++ src/components/guide/guide.js | 1 + src/components/slideshow/slideshow.js | 1 + src/components/sortmenu/sortmenu.js | 2 ++ src/components/subtitleeditor/subtitleeditor.js | 1 + src/components/tunerPicker.js | 1 + src/components/viewManager/viewManager.js | 2 ++ src/controllers/hometab.js | 1 + src/controllers/list.js | 1 + src/libraries/scroller.js | 3 ++- src/scripts/scrollHelper.js | 2 ++ src/scripts/serverNotifications.js | 1 + 12 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/components/filtermenu/filtermenu.js b/src/components/filtermenu/filtermenu.js index 20399fb52d..bd9bcb3c56 100644 --- a/src/components/filtermenu/filtermenu.js +++ b/src/components/filtermenu/filtermenu.js @@ -1,4 +1,7 @@ define(['require', 'dom', 'focusManager', 'dialogHelper', 'loading', 'apphost', 'inputManager', 'layoutManager', 'connectionManager', 'appRouter', 'globalize', 'userSettings', 'emby-checkbox', 'emby-input', 'paper-icon-button-light', 'emby-select', 'material-icons', 'css!./../formdialog', 'emby-button', 'flexStyles'], function (require, dom, focusManager, dialogHelper, loading, appHost, inputManager, layoutManager, connectionManager, appRouter, globalize, userSettings) { + + focusManager = focusManager.default || focusManager; + 'use strict'; function onSubmit(e) { diff --git a/src/components/guide/guide.js b/src/components/guide/guide.js index 61caa9188f..71d63f82c0 100644 --- a/src/components/guide/guide.js +++ b/src/components/guide/guide.js @@ -4,6 +4,7 @@ define(['require', 'inputManager', 'browser', 'globalize', 'connectionManager', playbackManager = playbackManager.default || playbackManager; browser = browser.default || browser; loading = loading.default || loading; + focusManager = focusManager.default || focusManager; function showViewSettings(instance) { require(['guide-settings-dialog'], function (guideSettingsDialog) { diff --git a/src/components/slideshow/slideshow.js b/src/components/slideshow/slideshow.js index 60c458e234..38728ec6c6 100644 --- a/src/components/slideshow/slideshow.js +++ b/src/components/slideshow/slideshow.js @@ -6,6 +6,7 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f 'use strict'; browser = browser.default || browser; + focusManager = focusManager.default || focusManager; /** * Name of transition event. diff --git a/src/components/sortmenu/sortmenu.js b/src/components/sortmenu/sortmenu.js index f62e5bb3a4..52a7b95c40 100644 --- a/src/components/sortmenu/sortmenu.js +++ b/src/components/sortmenu/sortmenu.js @@ -1,6 +1,8 @@ define(['require', 'dom', 'focusManager', 'dialogHelper', 'loading', 'layoutManager', 'connectionManager', 'globalize', 'userSettings', 'emby-select', 'paper-icon-button-light', 'material-icons', 'css!./../formdialog', 'emby-button', 'flexStyles'], function (require, dom, focusManager, dialogHelper, loading, layoutManager, connectionManager, globalize, userSettings) { 'use strict'; + focusManager = focusManager.default || focusManager; + function onSubmit(e) { e.preventDefault(); return false; diff --git a/src/components/subtitleeditor/subtitleeditor.js b/src/components/subtitleeditor/subtitleeditor.js index 7df24b5da8..c42658b2d5 100644 --- a/src/components/subtitleeditor/subtitleeditor.js +++ b/src/components/subtitleeditor/subtitleeditor.js @@ -2,6 +2,7 @@ define(['dialogHelper', 'require', 'layoutManager', 'globalize', 'userSettings', 'use strict'; loading = loading.default || loading; + focusManager = focusManager.default || focusManager; var currentItem; var hasChanges; diff --git a/src/components/tunerPicker.js b/src/components/tunerPicker.js index 5bc9386053..4e78030a2a 100644 --- a/src/components/tunerPicker.js +++ b/src/components/tunerPicker.js @@ -3,6 +3,7 @@ define(['dialogHelper', 'dom', 'layoutManager', 'connectionManager', 'globalize' browser = browser.default || browser; loading = loading.default || loading; + focusManager = focusManager.default || focusManager; var enableFocusTransform = !browser.slow && !browser.edge; diff --git a/src/components/viewManager/viewManager.js b/src/components/viewManager/viewManager.js index 058ba4ebb2..55425994d2 100644 --- a/src/components/viewManager/viewManager.js +++ b/src/components/viewManager/viewManager.js @@ -1,6 +1,8 @@ define(['viewContainer', 'focusManager', 'queryString', 'layoutManager'], function (viewContainer, focusManager, queryString, layoutManager) { 'use strict'; + focusManager = focusManager.default || focusManager; + var currentView; var dispatchPageEvents; diff --git a/src/controllers/hometab.js b/src/controllers/hometab.js index 97034d4505..9610efbd39 100644 --- a/src/controllers/hometab.js +++ b/src/controllers/hometab.js @@ -2,6 +2,7 @@ define(['userSettings', 'loading', 'connectionManager', 'apphost', 'layoutManage 'use strict'; loading = loading.default || loading; + focusManager = focusManager.default || focusManager; function HomeTab(view, params) { this.view = view; diff --git a/src/controllers/list.js b/src/controllers/list.js index a35b7a0cf9..77830badaf 100644 --- a/src/controllers/list.js +++ b/src/controllers/list.js @@ -3,6 +3,7 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' playbackManager = playbackManager.default || playbackManager; loading = loading.default || loading; + focusManager = focusManager.default || focusManager; function getInitialLiveTvQuery(instance, params) { var query = { diff --git a/src/libraries/scroller.js b/src/libraries/scroller.js index d869d8c8be..cc75dcdeef 100644 --- a/src/libraries/scroller.js +++ b/src/libraries/scroller.js @@ -2,6 +2,7 @@ define(['browser', 'layoutManager', 'dom', 'focusManager', 'ResizeObserver', 'sc 'use strict'; browser = browser.default || browser; + focusManager = focusManager.default || focusManager; /** * Return type of the value. @@ -697,7 +698,7 @@ define(['browser', 'layoutManager', 'dom', 'focusManager', 'ResizeObserver', 'sc function onFrameClick(e) { if (e.which === 1) { - var focusableParent = focusManager.default.focusableParent(e.target); + var focusableParent = focusManager.focusableParent(e.target); if (focusableParent && focusableParent !== document.activeElement) { focusableParent.focus(); } diff --git a/src/scripts/scrollHelper.js b/src/scripts/scrollHelper.js index 82413fe9cf..8b596571b4 100644 --- a/src/scripts/scrollHelper.js +++ b/src/scripts/scrollHelper.js @@ -1,6 +1,8 @@ define(['focusManager', 'dom', 'scrollStyles'], function (focusManager, dom) { 'use strict'; + focusManager = focusManager.default || focusManager; + function getBoundingClientRect(elem) { // Support: BlackBerry 5, iOS 3 (original iPhone) // If we don't have gBCR, just use 0,0 rather than error diff --git a/src/scripts/serverNotifications.js b/src/scripts/serverNotifications.js index e5fb0bcd61..87db52402d 100644 --- a/src/scripts/serverNotifications.js +++ b/src/scripts/serverNotifications.js @@ -2,6 +2,7 @@ define(['connectionManager', 'playbackManager', 'syncPlayManager', 'events', 'in 'use strict'; playbackManager = playbackManager.default || playbackManager; + focusManager = focusManager.default || focusManager; var serverNotifications = {}; From 8a16793b971e960e9e0208450c7e00ff43f8e71c Mon Sep 17 00:00:00 2001 From: Xantios Krugor Date: Wed, 5 Aug 2020 12:24:26 +0200 Subject: [PATCH 061/124] Note to self: lint first, commit later --- src/components/filtermenu/filtermenu.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/filtermenu/filtermenu.js b/src/components/filtermenu/filtermenu.js index bd9bcb3c56..b02b5fb9f8 100644 --- a/src/components/filtermenu/filtermenu.js +++ b/src/components/filtermenu/filtermenu.js @@ -1,8 +1,6 @@ define(['require', 'dom', 'focusManager', 'dialogHelper', 'loading', 'apphost', 'inputManager', 'layoutManager', 'connectionManager', 'appRouter', 'globalize', 'userSettings', 'emby-checkbox', 'emby-input', 'paper-icon-button-light', 'emby-select', 'material-icons', 'css!./../formdialog', 'emby-button', 'flexStyles'], function (require, dom, focusManager, dialogHelper, loading, appHost, inputManager, layoutManager, connectionManager, appRouter, globalize, userSettings) { - - focusManager = focusManager.default || focusManager; - 'use strict'; + focusManager = focusManager.default || focusManager; function onSubmit(e) { e.preventDefault(); From 53d654261344b9a614e29fac700cfb76795c3d62 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 5 Aug 2020 11:48:31 +0100 Subject: [PATCH 062/124] Migration of recordinghelper and seriesrecordingeditor to ES6 modules --- package.json | 2 + .../recordingcreator/recordingbutton.js | 2 + .../recordingcreator/recordingeditor.js | 2 + .../recordingcreator/recordingfields.js | 1 + .../recordingcreator/recordinghelper.js | 337 +++++++------- .../recordingcreator/seriesrecordingeditor.js | 414 +++++++++--------- 6 files changed, 390 insertions(+), 368 deletions(-) diff --git a/package.json b/package.json index 8408217bf3..5277ea3a9e 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/seriesrecordingeditor.js", + "src/components/recordingcreator/recordinghelper.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..1207208e90 100644 --- a/src/components/recordingcreator/recordingbutton.js +++ b/src/components/recordingcreator/recordingbutton.js @@ -1,6 +1,8 @@ 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'; + recordingHelper = recordingHelper.default || recordingHelper; + function onRecordingButtonClick(e) { var item = this.item; diff --git a/src/components/recordingcreator/recordingeditor.js b/src/components/recordingcreator/recordingeditor.js index 2086129a9e..2e54b3601c 100644 --- a/src/components/recordingcreator/recordingeditor.js +++ b/src/components/recordingcreator/recordingeditor.js @@ -12,6 +12,8 @@ define(['dialogHelper', 'globalize', 'layoutManager', 'mediaInfo', 'apphost', 'c function deleteTimer(apiClient, timerId) { return new Promise(function (resolve, reject) { require(['recordingHelper'], function (recordingHelper) { + recordingHelper = recordingHelper.default || recordingHelper; + recordingHelper.cancelTimerWithConfirmation(timerId, apiClient.serverId()).then(resolve, reject); }); }); diff --git a/src/components/recordingcreator/recordingfields.js b/src/components/recordingcreator/recordingfields.js index e3739f1cfe..741570581e 100644 --- a/src/components/recordingcreator/recordingfields.js +++ b/src/components/recordingcreator/recordingfields.js @@ -1,6 +1,7 @@ define(['globalize', 'connectionManager', 'serverNotifications', 'require', 'loading', 'apphost', 'dom', 'recordingHelper', 'events', 'paper-icon-button-light', 'emby-button', 'css!./recordingfields', 'flexStyles'], function (globalize, connectionManager, serverNotifications, require, loading, appHost, dom, recordingHelper, events) { 'use strict'; + recordingHelper = recordingHelper.default || recordingHelper; loading = loading.default || loading; function loadData(parent, program, apiClient) { diff --git a/src/components/recordingcreator/recordinghelper.js b/src/components/recordingcreator/recordinghelper.js index 5d72394282..13359de2bc 100644 --- a/src/components/recordingcreator/recordinghelper.js +++ b/src/components/recordingcreator/recordinghelper.js @@ -1,194 +1,195 @@ -define(['globalize', 'loading', 'connectionManager'], function (globalize, loading, connectionManager) { - 'use strict'; +import globalize from 'globalize'; +import loading from 'loading'; +import connectionManager from 'connectionManager'; - loading = loading.default || loading; +/*eslint prefer-const: "error"*/ - function changeRecordingToSeries(apiClient, timerId, programId, confirmTimerCancellation) { - loading.show(); +function changeRecordingToSeries(apiClient, timerId, programId, confirmTimerCancellation) { + loading.show(); - return apiClient.getItem(apiClient.getCurrentUserId(), programId).then(function (item) { - if (item.IsSeries) { - // create series - return apiClient.getNewLiveTvTimerDefaults({ programId: programId }).then(function (timerDefaults) { - return apiClient.createLiveTvSeriesTimer(timerDefaults).then(function () { - loading.hide(); - sendToast(globalize.translate('SeriesRecordingScheduled')); + return apiClient.getItem(apiClient.getCurrentUserId(), programId).then(function (item) { + if (item.IsSeries) { + // create series + return apiClient.getNewLiveTvTimerDefaults({ programId: programId }).then(function (timerDefaults) { + return apiClient.createLiveTvSeriesTimer(timerDefaults).then(function () { + loading.hide(); + sendToast(globalize.translate('SeriesRecordingScheduled')); + }); + }); + } else { + // cancel + if (confirmTimerCancellation) { + return cancelTimerWithConfirmation(timerId, apiClient.serverId()); + } + + return cancelTimer(apiClient.serverId(), timerId, true); + } + }); +} + +function cancelTimerWithConfirmation(timerId, serverId) { + return new Promise(function (resolve, reject) { + import('confirm').then(({ default: confirm }) => { + confirm.default({ + + text: globalize.translate('MessageConfirmRecordingCancellation'), + primary: 'delete', + confirmText: globalize.translate('HeaderCancelRecording'), + cancelText: globalize.translate('HeaderKeepRecording') + + }).then(function () { + loading.show(); + + const apiClient = connectionManager.getApiClient(serverId); + cancelTimer(apiClient, timerId, true).then(resolve, reject); + }, reject); + }); + }); +} + +function cancelSeriesTimerWithConfirmation(timerId, serverId) { + return new Promise(function (resolve, reject) { + import('confirm').then(({ default: confirm }) => { + confirm.default({ + + text: globalize.translate('MessageConfirmRecordingCancellation'), + primary: 'delete', + confirmText: globalize.translate('HeaderCancelSeries'), + cancelText: globalize.translate('HeaderKeepSeries') + + }).then(function () { + loading.show(); + + const apiClient = connectionManager.getApiClient(serverId); + apiClient.cancelLiveTvSeriesTimer(timerId).then(function () { + import('toast').then(({default: toast}) => { + toast(globalize.translate('SeriesCancelled')); }); + + loading.hide(); + resolve(); + }, reject); + }, reject); + }); + }); +} + +function cancelTimer(apiClient, timerId, hideLoading) { + loading.show(); + return apiClient.cancelLiveTvTimer(timerId).then(function () { + if (hideLoading !== false) { + loading.hide(); + sendToast(globalize.translate('RecordingCancelled')); + } + }); +} + +function createRecording(apiClient, programId, isSeries) { + loading.show(); + return apiClient.getNewLiveTvTimerDefaults({ programId: programId }).then(function (item) { + const promise = isSeries ? + apiClient.createLiveTvSeriesTimer(item) : + apiClient.createLiveTvTimer(item); + + return promise.then(function () { + loading.hide(); + sendToast(globalize.translate('RecordingScheduled')); + }); + }); +} + +function sendToast(msg) { + import('toast').then(({ default: toast }) => { + toast(msg); + }); +} + +function showMultiCancellationPrompt(serverId, programId, timerId, timerStatus, seriesTimerId) { + return new Promise(function (resolve, reject) { + import('dialog').then(({ default: dialog }) => { + const items = []; + + items.push({ + name: globalize.translate('HeaderKeepRecording'), + id: 'cancel', + type: 'submit' + }); + + if (timerStatus === 'InProgress') { + items.push({ + name: globalize.translate('HeaderStopRecording'), + id: 'canceltimer', + type: 'cancel' }); } else { - // cancel - if (confirmTimerCancellation) { - return cancelTimerWithConfirmation(timerId, apiClient.serverId()); - } - - return cancelTimer(apiClient.serverId(), timerId, true); + items.push({ + name: globalize.translate('HeaderCancelRecording'), + id: 'canceltimer', + type: 'cancel' + }); } - }); - } - function cancelTimerWithConfirmation(timerId, serverId) { - return new Promise(function (resolve, reject) { - require(['confirm'], function (confirm) { - confirm.default({ - - text: globalize.translate('MessageConfirmRecordingCancellation'), - primary: 'delete', - confirmText: globalize.translate('HeaderCancelRecording'), - cancelText: globalize.translate('HeaderKeepRecording') - - }).then(function () { - loading.show(); - - var apiClient = connectionManager.getApiClient(serverId); - cancelTimer(apiClient, timerId, true).then(resolve, reject); - }, reject); + items.push({ + name: globalize.translate('HeaderCancelSeries'), + id: 'cancelseriestimer', + type: 'cancel' }); - }); - } - function cancelSeriesTimerWithConfirmation(timerId, serverId) { - return new Promise(function (resolve, reject) { - require(['confirm'], function (confirm) { - confirm.default({ + dialog({ - text: globalize.translate('MessageConfirmRecordingCancellation'), - primary: 'delete', - confirmText: globalize.translate('HeaderCancelSeries'), - cancelText: globalize.translate('HeaderKeepSeries') + text: globalize.translate('MessageConfirmRecordingCancellation'), + buttons: items - }).then(function () { + }).then(function (result) { + const apiClient = connectionManager.getApiClient(serverId); + + if (result === 'canceltimer') { loading.show(); - var apiClient = connectionManager.getApiClient(serverId); - apiClient.cancelLiveTvSeriesTimer(timerId).then(function () { - require(['toast'], function (toast) { + cancelTimer(apiClient, timerId, true).then(resolve, reject); + } else if (result === 'cancelseriestimer') { + loading.show(); + + apiClient.cancelLiveTvSeriesTimer(seriesTimerId).then(function () { + import('toast').then(({ default: toast }) => { toast(globalize.translate('SeriesCancelled')); }); loading.hide(); resolve(); }, reject); - }, reject); - }); - }); - } - - function cancelTimer(apiClient, timerId, hideLoading) { - loading.show(); - return apiClient.cancelLiveTvTimer(timerId).then(function () { - if (hideLoading !== false) { - loading.hide(); - sendToast(globalize.translate('RecordingCancelled')); - } - }); - } - - function createRecording(apiClient, programId, isSeries) { - loading.show(); - return apiClient.getNewLiveTvTimerDefaults({ programId: programId }).then(function (item) { - var promise = isSeries ? - apiClient.createLiveTvSeriesTimer(item) : - apiClient.createLiveTvTimer(item); - - return promise.then(function () { - loading.hide(); - sendToast(globalize.translate('RecordingScheduled')); - }); - }); - } - - function sendToast(msg) { - require(['toast'], function (toast) { - toast(msg); - }); - } - - function showMultiCancellationPrompt(serverId, programId, timerId, timerStatus, seriesTimerId) { - return new Promise(function (resolve, reject) { - require(['dialog'], function (dialog) { - var items = []; - - items.push({ - name: globalize.translate('HeaderKeepRecording'), - id: 'cancel', - type: 'submit' - }); - - if (timerStatus === 'InProgress') { - items.push({ - name: globalize.translate('HeaderStopRecording'), - id: 'canceltimer', - type: 'cancel' - }); } else { - items.push({ - name: globalize.translate('HeaderCancelRecording'), - id: 'canceltimer', - type: 'cancel' - }); + resolve(); } - - items.push({ - name: globalize.translate('HeaderCancelSeries'), - id: 'cancelseriestimer', - type: 'cancel' - }); - - dialog({ - - text: globalize.translate('MessageConfirmRecordingCancellation'), - buttons: items - - }).then(function (result) { - var apiClient = connectionManager.getApiClient(serverId); - - if (result === 'canceltimer') { - loading.show(); - - cancelTimer(apiClient, timerId, true).then(resolve, reject); - } else if (result === 'cancelseriestimer') { - loading.show(); - - apiClient.cancelLiveTvSeriesTimer(seriesTimerId).then(function () { - require(['toast'], function (toast) { - toast(globalize.translate('SeriesCancelled')); - }); - - loading.hide(); - resolve(); - }, reject); - } else { - resolve(); - } - }, reject); - }); + }, reject); }); - } + }); +} - function toggleRecording(serverId, programId, timerId, timerStatus, seriesTimerId) { - var apiClient = connectionManager.getApiClient(serverId); - var hasTimer = timerId && timerStatus !== 'Cancelled'; - if (seriesTimerId && hasTimer) { - // cancel - return showMultiCancellationPrompt(serverId, programId, timerId, timerStatus, seriesTimerId); - } else if (hasTimer && programId) { - // change to series recording, if possible - // otherwise cancel individual recording - return changeRecordingToSeries(apiClient, timerId, programId, true); - } else if (programId) { - // schedule recording - return createRecording(apiClient, programId); - } else { - return Promise.reject(); - } +function toggleRecording(serverId, programId, timerId, timerStatus, seriesTimerId) { + const apiClient = connectionManager.getApiClient(serverId); + const hasTimer = timerId && timerStatus !== 'Cancelled'; + if (seriesTimerId && hasTimer) { + // cancel + return showMultiCancellationPrompt(serverId, programId, timerId, timerStatus, seriesTimerId); + } else if (hasTimer && programId) { + // change to series recording, if possible + // otherwise cancel individual recording + return changeRecordingToSeries(apiClient, timerId, programId, true); + } else if (programId) { + // schedule recording + return createRecording(apiClient, programId); + } else { + return Promise.reject(); } +} + +export default { + cancelTimer: cancelTimer, + createRecording: createRecording, + changeRecordingToSeries: changeRecordingToSeries, + toggleRecording: toggleRecording, + cancelTimerWithConfirmation: cancelTimerWithConfirmation, + cancelSeriesTimerWithConfirmation: cancelSeriesTimerWithConfirmation +}; - return { - cancelTimer: cancelTimer, - createRecording: createRecording, - changeRecordingToSeries: changeRecordingToSeries, - toggleRecording: toggleRecording, - cancelTimerWithConfirmation: cancelTimerWithConfirmation, - cancelSeriesTimerWithConfirmation: cancelSeriesTimerWithConfirmation - }; -}); diff --git a/src/components/recordingcreator/seriesrecordingeditor.js b/src/components/recordingcreator/seriesrecordingeditor.js index b115e273e6..48fbbcf22c 100644 --- a/src/components/recordingcreator/seriesrecordingeditor.js +++ b/src/components/recordingcreator/seriesrecordingeditor.js @@ -1,143 +1,200 @@ -define(['dialogHelper', 'globalize', 'layoutManager', 'mediaInfo', 'apphost', 'connectionManager', 'require', 'loading', 'scrollHelper', 'imageLoader', 'datetime', 'scrollStyles', 'emby-button', 'emby-checkbox', 'emby-input', 'emby-select', 'paper-icon-button-light', 'css!./../formdialog', 'css!./recordingcreator', 'material-icons', 'flexStyles'], function (dialogHelper, globalize, layoutManager, mediaInfo, appHost, connectionManager, require, loading, scrollHelper, imageLoader, datetime) { - 'use strict'; +import dialogHelper from 'dialogHelper'; +import globalize from 'globalize'; +import layoutManager from 'layoutManager'; +import connectionManager from 'connectionManager'; +import loading from 'loading'; +import scrollHelper from 'scrollHelper'; +import datetime from 'datetime'; +import 'scrollStyles'; +import 'emby-button'; +import 'emby-checkbox'; +import 'emby-input'; +import 'emby-select'; +import 'paper-icon-button-light'; +import 'css!./../formdialog'; +import 'css!./recordingcreator'; +import 'material-icons'; +import 'flexStyles'; - loading = loading.default || loading; +/*eslint prefer-const: "error"*/ - var currentDialog; - var recordingUpdated = false; - var recordingDeleted = false; - var currentItemId; - var currentServerId; +let currentDialog; +let recordingUpdated = false; +let recordingDeleted = false; +let currentItemId; +let currentServerId; - function deleteTimer(apiClient, timerId) { - return new Promise(function (resolve, reject) { - require(['recordingHelper'], function (recordingHelper) { - recordingHelper.cancelSeriesTimerWithConfirmation(timerId, apiClient.serverId()).then(resolve, reject); - }); +function deleteTimer(apiClient, timerId) { + return new Promise(function (resolve, reject) { + import('recordingHelper').then(({ default: recordingHelper }) => { + recordingHelper.cancelSeriesTimerWithConfirmation(timerId, apiClient.serverId()).then(resolve, reject); }); + }); +} + +function renderTimer(context, item) { + context.querySelector('#txtPrePaddingMinutes').value = item.PrePaddingSeconds / 60; + context.querySelector('#txtPostPaddingMinutes').value = item.PostPaddingSeconds / 60; + + context.querySelector('.selectChannels').value = item.RecordAnyChannel ? 'all' : 'one'; + context.querySelector('.selectAirTime').value = item.RecordAnyTime ? 'any' : 'original'; + + context.querySelector('.selectShowType').value = item.RecordNewOnly ? 'new' : 'all'; + context.querySelector('.chkSkipEpisodesInLibrary').checked = item.SkipEpisodesInLibrary; + context.querySelector('.selectKeepUpTo').value = item.KeepUpTo || 0; + + if (item.ChannelName || item.ChannelNumber) { + context.querySelector('.optionChannelOnly').innerHTML = globalize.translate('ChannelNameOnly', item.ChannelName || item.ChannelNumber); + } else { + context.querySelector('.optionChannelOnly').innerHTML = globalize.translate('OneChannel'); } - function renderTimer(context, item, apiClient) { - context.querySelector('#txtPrePaddingMinutes').value = item.PrePaddingSeconds / 60; - context.querySelector('#txtPostPaddingMinutes').value = item.PostPaddingSeconds / 60; + context.querySelector('.optionAroundTime').innerHTML = globalize.translate('AroundTime', datetime.getDisplayTime(datetime.parseISO8601Date(item.StartDate))); - context.querySelector('.selectChannels').value = item.RecordAnyChannel ? 'all' : 'one'; - context.querySelector('.selectAirTime').value = item.RecordAnyTime ? 'any' : 'original'; + loading.hide(); +} - context.querySelector('.selectShowType').value = item.RecordNewOnly ? 'new' : 'all'; - context.querySelector('.chkSkipEpisodesInLibrary').checked = item.SkipEpisodesInLibrary; - context.querySelector('.selectKeepUpTo').value = item.KeepUpTo || 0; +function closeDialog(isDeleted) { + recordingUpdated = true; + recordingDeleted = isDeleted; - if (item.ChannelName || item.ChannelNumber) { - context.querySelector('.optionChannelOnly').innerHTML = globalize.translate('ChannelNameOnly', item.ChannelName || item.ChannelNumber); - } else { - context.querySelector('.optionChannelOnly').innerHTML = globalize.translate('OneChannel'); - } + dialogHelper.close(currentDialog); +} - context.querySelector('.optionAroundTime').innerHTML = globalize.translate('AroundTime', datetime.getDisplayTime(datetime.parseISO8601Date(item.StartDate))); +function onSubmit(e) { + const form = this; + const apiClient = connectionManager.getApiClient(currentServerId); + + apiClient.getLiveTvSeriesTimer(currentItemId).then(function (item) { + item.PrePaddingSeconds = form.querySelector('#txtPrePaddingMinutes').value * 60; + item.PostPaddingSeconds = form.querySelector('#txtPostPaddingMinutes').value * 60; + item.RecordAnyChannel = form.querySelector('.selectChannels').value === 'all'; + item.RecordAnyTime = form.querySelector('.selectAirTime').value === 'any'; + item.RecordNewOnly = form.querySelector('.selectShowType').value === 'new'; + item.SkipEpisodesInLibrary = form.querySelector('.chkSkipEpisodesInLibrary').checked; + item.KeepUpTo = form.querySelector('.selectKeepUpTo').value; + + apiClient.updateLiveTvSeriesTimer(item); + }); + + e.preventDefault(); + + // Disable default form submission + return false; +} + +function init(context) { + fillKeepUpTo(context); + + context.querySelector('.btnCancel').addEventListener('click', function () { + closeDialog(false); + }); + + context.querySelector('.btnCancelRecording').addEventListener('click', function () { + const apiClient = connectionManager.getApiClient(currentServerId); + deleteTimer(apiClient, currentItemId).then(function () { + closeDialog(true); + }); + }); + + context.querySelector('form').addEventListener('submit', onSubmit); +} + +function reload(context, id) { + const apiClient = connectionManager.getApiClient(currentServerId); + + loading.show(); + if (typeof id === 'string') { + currentItemId = id; + + apiClient.getLiveTvSeriesTimer(id).then(function (result) { + renderTimer(context, result); + loading.hide(); + }); + } else if (id) { + currentItemId = id.Id; + + renderTimer(context, id); loading.hide(); } +} - function closeDialog(isDeleted) { - recordingUpdated = true; - recordingDeleted = isDeleted; +function fillKeepUpTo(context) { + let html = ''; - dialogHelper.close(currentDialog); - } + for (let i = 0; i <= 50; i++) { + let text; - function onSubmit(e) { - var form = this; - - var apiClient = connectionManager.getApiClient(currentServerId); - - apiClient.getLiveTvSeriesTimer(currentItemId).then(function (item) { - item.PrePaddingSeconds = form.querySelector('#txtPrePaddingMinutes').value * 60; - item.PostPaddingSeconds = form.querySelector('#txtPostPaddingMinutes').value * 60; - item.RecordAnyChannel = form.querySelector('.selectChannels').value === 'all'; - item.RecordAnyTime = form.querySelector('.selectAirTime').value === 'any'; - item.RecordNewOnly = form.querySelector('.selectShowType').value === 'new'; - item.SkipEpisodesInLibrary = form.querySelector('.chkSkipEpisodesInLibrary').checked; - item.KeepUpTo = form.querySelector('.selectKeepUpTo').value; - - apiClient.updateLiveTvSeriesTimer(item); - }); - - e.preventDefault(); - - // Disable default form submission - return false; - } - - function init(context) { - fillKeepUpTo(context); - - context.querySelector('.btnCancel').addEventListener('click', function () { - closeDialog(false); - }); - - context.querySelector('.btnCancelRecording').addEventListener('click', function () { - var apiClient = connectionManager.getApiClient(currentServerId); - deleteTimer(apiClient, currentItemId).then(function () { - closeDialog(true); - }); - }); - - context.querySelector('form').addEventListener('submit', onSubmit); - } - - function reload(context, id) { - var apiClient = connectionManager.getApiClient(currentServerId); - - loading.show(); - if (typeof id === 'string') { - currentItemId = id; - - apiClient.getLiveTvSeriesTimer(id).then(function (result) { - renderTimer(context, result, apiClient); - loading.hide(); - }); - } else if (id) { - currentItemId = id.Id; - - renderTimer(context, id, apiClient); - loading.hide(); - } - } - - function fillKeepUpTo(context) { - var html = ''; - - for (var i = 0; i <= 50; i++) { - var text; - - if (i === 0) { - text = globalize.translate('AsManyAsPossible'); - } else if (i === 1) { - text = globalize.translate('ValueOneEpisode'); - } else { - text = globalize.translate('ValueEpisodeCount', i); - } - - html += ''; + if (i === 0) { + text = globalize.translate('AsManyAsPossible'); + } else if (i === 1) { + text = globalize.translate('ValueOneEpisode'); + } else { + text = globalize.translate('ValueEpisodeCount', i); } - context.querySelector('.selectKeepUpTo').innerHTML = html; + html += ''; } - function onFieldChange(e) { - this.querySelector('.btnSubmit').click(); - } + context.querySelector('.selectKeepUpTo').innerHTML = html; +} - function embed(itemId, serverId, options) { +function onFieldChange() { + this.querySelector('.btnSubmit').click(); +} + +function embed(itemId, serverId, options) { + recordingUpdated = false; + recordingDeleted = false; + currentServerId = serverId; + loading.show(); + options = options || {}; + + import('text!./seriesrecordingeditor.template.html').then(({ default: template }) => { + const dialogOptions = { + removeOnClose: true, + scrollY: false + }; + + if (layoutManager.tv) { + dialogOptions.size = 'fullscreen'; + } else { + dialogOptions.size = 'small'; + } + + const dlg = options.context; + + dlg.classList.add('hide'); + dlg.innerHTML = globalize.translateHtml(template, 'core'); + + dlg.querySelector('.formDialogHeader').classList.add('hide'); + dlg.querySelector('.formDialogFooter').classList.add('hide'); + dlg.querySelector('.formDialogContent').className = ''; + dlg.querySelector('.dialogContentInner').className = ''; + dlg.classList.remove('hide'); + + dlg.removeEventListener('change', onFieldChange); + dlg.addEventListener('change', onFieldChange); + + currentDialog = dlg; + + init(dlg); + + reload(dlg, itemId); + }); +} + +function showEditor(itemId, serverId, options) { + return new Promise(function (resolve, reject) { recordingUpdated = false; recordingDeleted = false; currentServerId = serverId; loading.show(); options = options || {}; - require(['text!./seriesrecordingeditor.template.html'], function (template) { - var dialogOptions = { + import('text!./seriesrecordingeditor.template.html').then(({ default: template }) => { + const dialogOptions = { removeOnClose: true, scrollY: false }; @@ -148,101 +205,58 @@ define(['dialogHelper', 'globalize', 'layoutManager', 'mediaInfo', 'apphost', 'c dialogOptions.size = 'small'; } - var dlg = options.context; + const dlg = dialogHelper.createDialog(dialogOptions); - dlg.classList.add('hide'); - dlg.innerHTML = globalize.translateHtml(template, 'core'); + dlg.classList.add('formDialog'); + dlg.classList.add('recordingDialog'); - dlg.querySelector('.formDialogHeader').classList.add('hide'); - dlg.querySelector('.formDialogFooter').classList.add('hide'); - dlg.querySelector('.formDialogContent').className = ''; - dlg.querySelector('.dialogContentInner').className = ''; - dlg.classList.remove('hide'); + if (!layoutManager.tv) { + dlg.style['min-width'] = '20%'; + } - dlg.removeEventListener('change', onFieldChange); - dlg.addEventListener('change', onFieldChange); + let html = ''; + + html += globalize.translateHtml(template, 'core'); + + dlg.innerHTML = html; + + if (options.enableCancel === false) { + dlg.querySelector('.formDialogFooter').classList.add('hide'); + } currentDialog = dlg; + dlg.addEventListener('closing', function () { + if (!recordingDeleted) { + this.querySelector('.btnSubmit').click(); + } + }); + + dlg.addEventListener('close', function () { + if (recordingUpdated) { + resolve({ + updated: true, + deleted: recordingDeleted + }); + } else { + reject(); + } + }); + + if (layoutManager.tv) { + scrollHelper.centerFocus.on(dlg.querySelector('.formDialogContent'), false); + } + init(dlg); reload(dlg, itemId); + + dialogHelper.open(dlg); }); - } + }); +} - function showEditor(itemId, serverId, options) { - return new Promise(function (resolve, reject) { - recordingUpdated = false; - recordingDeleted = false; - currentServerId = serverId; - loading.show(); - options = options || {}; - - require(['text!./seriesrecordingeditor.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'); - - if (!layoutManager.tv) { - dlg.style['min-width'] = '20%'; - } - - var html = ''; - - html += globalize.translateHtml(template, 'core'); - - dlg.innerHTML = html; - - if (options.enableCancel === false) { - dlg.querySelector('.formDialogFooter').classList.add('hide'); - } - - currentDialog = dlg; - - dlg.addEventListener('closing', function () { - if (!recordingDeleted) { - this.querySelector('.btnSubmit').click(); - } - }); - - dlg.addEventListener('close', function () { - if (recordingUpdated) { - resolve({ - updated: true, - deleted: recordingDeleted - }); - } else { - reject(); - } - }); - - if (layoutManager.tv) { - scrollHelper.centerFocus.on(dlg.querySelector('.formDialogContent'), false); - } - - init(dlg); - - reload(dlg, itemId); - - dialogHelper.open(dlg); - }); - }); - } - - return { - show: showEditor, - embed: embed - }; -}); +export default { + show: showEditor, + embed: embed +}; From 839dcbf8618c655ee4175eecc6b1fa92e64eb486 Mon Sep 17 00:00:00 2001 From: Xantios Krugor Date: Wed, 5 Aug 2020 14:38:23 +0200 Subject: [PATCH 063/124] Update src/controllers/list.js Co-authored-by: Cameron --- src/controllers/list.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/list.js b/src/controllers/list.js index 77830badaf..afa0a60fa8 100644 --- a/src/controllers/list.js +++ b/src/controllers/list.js @@ -772,7 +772,7 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' loading.hide(); if (refresh) { - focusManager.default.autoFocus(self.itemsContainer); + focusManager.autoFocus(self.itemsContainer); } }); From e53eacc949dace9961d841375c28fdcbeef04d5f Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Wed, 5 Aug 2020 11:53:31 -0400 Subject: [PATCH 064/124] Add start script as alias for serve --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 504f4038d9..e6697051d2 100644 --- a/package.json +++ b/package.json @@ -318,6 +318,7 @@ "Firefox ESR" ], "scripts": { + "start": "yarn serve", "serve": "gulp serve --development", "prepare": "gulp --production", "build:development": "gulp --development", From 83300e6bf6bc45b15817e348793b0555b9fb30f5 Mon Sep 17 00:00:00 2001 From: sharkykh Date: Wed, 5 Aug 2020 18:12:31 +0000 Subject: [PATCH 065/124] Translated using Weblate (Hebrew) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/he/ --- src/strings/he.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/strings/he.json b/src/strings/he.json index 3db7c86f26..ae274c26c8 100644 --- a/src/strings/he.json +++ b/src/strings/he.json @@ -760,5 +760,9 @@ "ButtonSyncPlay": "SyncPlay", "ButtonPlayer": "נגן", "StopPlayback": "הפסק הפעלה", - "ClearQueue": "נקה תור" + "ClearQueue": "נקה תור", + "DashboardServerName": "שרת: {0}", + "DashboardVersionNumber": "גירסה: {0}", + "DashboardArchitecture": "ארכיטקטורה: {0}", + "DashboardOperatingSystem": "מערכת הפעלה: {0}" } From f81126844d2411f48090d06ed75a5183e13ffd27 Mon Sep 17 00:00:00 2001 From: Accendit Date: Wed, 5 Aug 2020 17:07:43 +0000 Subject: [PATCH 066/124] 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 39b2ea1b5e..860783b083 100644 --- a/src/strings/nl.json +++ b/src/strings/nl.json @@ -1441,7 +1441,7 @@ "Season": "Seizoen", "PreferEmbeddedEpisodeInfosOverFileNames": "Verkies ingeladen afleveringsinformatie boven bestandsnaam", "PreferEmbeddedEpisodeInfosOverFileNamesHelp": "Dit gebruikt de afleveringsinformatie van de ingeladen metadata als deze aanwezig is.", - "PlaybackErrorNoCompatibleStream": "Deze machine is niet leesbaar met de media en de server verstuurd geen leesbare media formaten.", + "PlaybackErrorNoCompatibleStream": "Dit apparaat ondersteund de afgespeelde media niet en de server verstuurd geen ondersteund formaat.", "Person": "Persoon", "OptionForceRemoteSourceTranscoding": "Forceer het transcoderen van op afstand bediende media bronnen (zoals LiveTV)", "NoCreatedLibraries": "Het lijkt erop dat er geen bibliotheek is gecreëerd. {0}Wilt u er nu een aanmaken?{1}", From 60be236f1c04c93e2ed0a5b93e38a43786ba568f Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 5 Aug 2020 19:24:22 +0100 Subject: [PATCH 067/124] Remove empy Constructor --- src/components/layoutManager.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/components/layoutManager.js b/src/components/layoutManager.js index d104632573..88a7265f8b 100644 --- a/src/components/layoutManager.js +++ b/src/components/layoutManager.js @@ -13,9 +13,6 @@ function setLayout(instance, layout, selectedLayout) { } class LayoutManager { - constructor() { - } - setLayout(layout, save) { if (!layout || layout === 'auto') { this.autoLayout(); From 2e47b89ada6e6b65089b74b471460fe276b71eb5 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 5 Aug 2020 19:59:25 +0100 Subject: [PATCH 068/124] remove constructor --- src/components/viewManager/viewManager.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/components/viewManager/viewManager.js b/src/components/viewManager/viewManager.js index 695535e7b8..00c3018590 100644 --- a/src/components/viewManager/viewManager.js +++ b/src/components/viewManager/viewManager.js @@ -127,9 +127,6 @@ function resetCachedViews() { document.addEventListener('skinunload', resetCachedViews); class ViewManager { - constructor() { - } - loadView(options) { const lastView = currentView; From 6e5dc7f7d11672ad70567cc3953220f6cd242611 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 5 Aug 2020 20:08:42 +0100 Subject: [PATCH 069/124] lint --- src/controllers/list.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/controllers/list.js b/src/controllers/list.js index 7d35bcf5cb..979bb76a20 100644 --- a/src/controllers/list.js +++ b/src/controllers/list.js @@ -744,7 +744,8 @@ class ItemsView { } const sortButtons = view.querySelectorAll('.btnSort'); - + let i; + let length; for (this.sortButtons = sortButtons, i = 0, length = sortButtons.length; i < length; i++) { const sortButton = sortButtons[i]; sortButton.addEventListener('click', showSortMenu.bind(this)); From c88345239525e012f48f6f4f0aa4832d842ab486 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 5 Aug 2020 20:22:48 +0100 Subject: [PATCH 070/124] add default --- src/plugins/chromecastPlayer/plugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/chromecastPlayer/plugin.js b/src/plugins/chromecastPlayer/plugin.js index 3d49f6fff6..22b49d6faf 100644 --- a/src/plugins/chromecastPlayer/plugin.js +++ b/src/plugins/chromecastPlayer/plugin.js @@ -577,7 +577,7 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' this.isLocalPlayer = false; this.lastPlayerData = {}; - new castSenderApiLoader().load().then(initializeChromecast.bind(this)); + new castSenderApiLoader.default().load().then(initializeChromecast.bind(this)); } ChromecastPlayer.prototype.tryPair = function (target) { From 6af88a366230a958340193b72bcf7320df7e575e Mon Sep 17 00:00:00 2001 From: sharkykh Date: Wed, 5 Aug 2020 18:28:45 +0000 Subject: [PATCH 071/124] Translated using Weblate (Hebrew) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/he/ --- src/strings/he.json | 84 +++++++++++++++++++++++++-------------------- 1 file changed, 46 insertions(+), 38 deletions(-) diff --git a/src/strings/he.json b/src/strings/he.json index ae274c26c8..a063f59b76 100644 --- a/src/strings/he.json +++ b/src/strings/he.json @@ -10,7 +10,7 @@ "AllEpisodes": "כל הפרקים", "AllLibraries": "כל הספריות", "Anytime": "בכל עת", - "AroundTime": "בסביבות {0}", + "AroundTime": "בסביבות", "AsManyAsPossible": "כמה שיותר", "AttributeNew": "חדש", "Backdrops": "תמונות רקע", @@ -32,13 +32,13 @@ "ButtonRefreshGuideData": "רענן את מדריך השידור", "ButtonRemove": "הסר", "ButtonResetPassword": "איפוס סיסמא", - "ButtonRestart": "איתחול", + "ButtonRestart": "הפעל מחדש", "ButtonSave": "שמור", "ButtonSearch": "חיפוש", "ButtonSelectDirectory": "בחר תיקיות", "ButtonShutdown": "כבה", "ButtonSignIn": "היכנס", - "ButtonSignOut": "Sign out", + "ButtonSignOut": "התנתק", "ButtonSort": "מיין", "CancelRecording": "ביטול הקלטה", "CancelSeries": "בטל סדרה", @@ -114,11 +114,11 @@ "HeaderLatestRecordings": "הקלטות אחרונות", "HeaderLiveTV": "שידורים חיים", "HeaderMediaFolders": "ספריות מדיה", - "HeaderMetadataSettings": "הגדרות מטא נתונים", + "HeaderMetadataSettings": "הגדרות מטא-דאטה", "HeaderMovies": "סרטים", "HeaderMusicVideos": "קליפים", - "HeaderMyMedia": "הספרייה שלי", - "HeaderNextUp": "הבא", + "HeaderMyMedia": "המדיה שלי", + "HeaderNextUp": "הבא בתור", "HeaderPaths": "נתיבים", "HeaderPlayAll": "נגן הכל", "HeaderPleaseSignIn": "אנא היכנס", @@ -138,7 +138,7 @@ "Help": "עזרה", "Identify": "לזהות", "Images": "תמונות", - "InstallingPackage": "מתקין {0}", + "InstallingPackage": "מתקין {0} (גירסה {1})", "InstantMix": "מיקס מיידי", "ItemCount": "פריטים {0}", "Kids": "ילדים", @@ -151,11 +151,11 @@ "LabelAlbum": "אלבום:", "LabelAlbumArtists": "אלבום אומנים:", "LabelArtists": "אומנים:", - "LabelArtistsHelp": "הפרד מרובים באמצעות;", + "LabelArtistsHelp": "הפרד אמנים מרובים באמצעות נקודה-פסיק (;).", "LabelAudioLanguagePreference": "שפת קול מועדפת:", "LabelBirthDate": "תאריך לידה:", "LabelBirthYear": "שנת לידה:", - "LabelBlastMessageInterval": "אינטרוול הודעות דחיפה (בשניות)", + "LabelBlastMessageInterval": "תדירות הודעות דחיפה", "LabelBlastMessageIntervalHelp": "מגדיר את משך הזמן בשניות בין הודעות דחיפה של השרת.", "LabelCachePath": "נתיב cache:", "LabelChannels": "ערוצים:", @@ -165,7 +165,7 @@ "LabelCountry": "מדינה:", "LabelCriticRating": "דירוג ביקורת:", "LabelCurrentPassword": "סיסמא נוכחית:", - "LabelCustomCss": "CSS מותאם אישית", + "LabelCustomCss": "CSS מותאם אישית:", "LabelCustomRating": "דירוג מותאם אישית:", "LabelDateAdded": "תאריך הוסף:", "LabelDay": "יום:", @@ -175,15 +175,15 @@ "LabelDiscNumber": "מספר דיסק:", "LabelDisplayMissingEpisodesWithinSeasons": "הצג פרקים חסרים בתוך העונות", "LabelDisplayOrder": "סדר תצוגה:", - "LabelDownMixAudioScaleHelp": "הגבר אודיו כאשר הוא ממוזג. הגדר ל-1 לשמור על ערך הווליום המקורי", + "LabelDownMixAudioScaleHelp": "הגבר את עוצמת השמע כאשר הוא ממוזג. ערך השווה ל-1 יישמר את העוצמה המקורית.", "LabelDynamicExternalId": "{0} תעודת זהות:", "LabelEnableBlastAliveMessages": "הודעות דחיפה", "LabelEnableBlastAliveMessagesHelp": "אפשר זאת אם השרת לא מזוהה כאמין על ידי מכשירי UPnP אחרים ברשת שלך.", - "LabelEnableDlnaClientDiscoveryInterval": "זמן גילוי קליינטים (בשניות)", + "LabelEnableDlnaClientDiscoveryInterval": "זמן גילוי קליינטים", "LabelEnableDlnaDebugLogging": "אפשר ניהול רישום באגים בDLNA", "LabelEnableDlnaDebugLoggingHelp": "אפשרות זו תיצור קבצי לוג גדולים יותר ועליך להשתמש בה רק על מנת לפתור תקלות.", "LabelEnableDlnaPlayTo": "מאפשר ניגון DLNA ל", - "LabelEnableDlnaServer": "אפשר שרת Dina", + "LabelEnableDlnaServer": "אפשר שרת DLNA", "LabelEnableRealtimeMonitor": "אפשר מעקב בזמן אמת", "LabelEnableRealtimeMonitorHelp": "שינויים יעשו באופן מיידית על מערכות קבצים נתמכות.", "LabelEndDate": "תאריך סיום:", @@ -195,21 +195,21 @@ "LabelServerNameHelp": "השם יתן לזיהוי השרת. אם מושאר ריק, שם השרת יהיה שם המחשב.", "LabelKeepUpTo": "שמור עד ל:", "LabelLanguage": "שפה:", - "LabelLocalHttpServerPortNumber": "מספר פורט HTTP מקומי", + "LabelLocalHttpServerPortNumber": "מספר פורט HTTP מקומי:", "LabelLockItemToPreventChanges": "נעל פריט זה כדי למנוע שינויים עתידיים", "LabelMaxBackdropsPerItem": "מספר תמונות רקע מקסימאלי לפריט:", "LabelMaxParentalRating": "דירוג הורים מקסימאלי:", - "LabelMaxResumePercentage": "אחוזי המשכה מקסימאלים", - "LabelMaxResumePercentageHelp": "קובץ מוגדר כנוגן במלואו אם נעצר אחרי הזמן הזה", + "LabelMaxResumePercentage": "אחוזי המשכה מקסימאלים:", + "LabelMaxResumePercentageHelp": "קובץ מוגדר כנוגן במלואו אם נעצר אחרי הזמן הזה.", "LabelMaxScreenshotsPerItem": "מספר תמונות מסך מקסימאלי לפריט:", "LabelMessageTitle": "כותרת הודעה:", "LabelMetadataDownloadLanguage": "שפת הורדה מועדפת:", "LabelMetadataPath": "נתיב Metadata:", "LabelMinBackdropDownloadWidth": "רוחב תמונת רקע מינימאלי להורדה:", - "LabelMinResumeDuration": "משך המשכה מינימאלי (בשניות):", + "LabelMinResumeDuration": "משך המשכה מינימאלי:", "LabelMinResumeDurationHelp": "קובץ קצר מזה לא יהיה ניתן להמשך ניגון מנקודת העצירה", "LabelMinResumePercentage": "אחוזי המשכה מינימאלים:", - "LabelMinResumePercentageHelp": "כותרים יוצגו כלא נוגנו אם נצרו לפני הזמן הזה", + "LabelMinResumePercentageHelp": "כותרים יוצגו כלא נוגנו אם נצרו לפני הזמן הזה.", "LabelMinScreenshotDownloadWidth": "רחוב תמונת מסך מינימאלית להורדה:", "LabelMonitorUsers": "עקוב אחר פעילות מ:", "LabelName": "שם:", @@ -237,12 +237,12 @@ "LabelProfileAudioCodecs": "מקודדי שמע:", "LabelProfileCodecs": "מקודדים:", "LabelProfileVideoCodecs": "‮מקודדי וידאו:", - "LabelPublicHttpPort": "מספר פורט HTTP פומבי", + "LabelPublicHttpPort": "מספר פורט HTTP פומבי:", "LabelReadHowYouCanContribute": "למד איך אתה יכול לתרום.", "LabelRecord": "הקלטה:", "LabelRefreshMode": "מצב רענון:", "LabelReleaseDate": "תאריך הוצאה:", - "LabelRuntimeMinutes": "זמן ריצה (דקות):", + "LabelRuntimeMinutes": "זמן ריצה:", "LabelSaveLocalMetadata": "שמור תמונות רקע בתוך ספריות המדיה", "LabelSaveLocalMetadataHelp": "שמירת תמונות רקע בתוך ספריות המדיה תשים אותם במקום שבו יהיה קל לערוך אותם.", "LabelSeasonNumber": "מספר עונה:", @@ -264,7 +264,7 @@ "LabelUserLibrary": "ספריית משתמש:", "LabelYear": "שנה:", "LabelYoureDone": "סיימת!", - "LibraryAccessHelp": "בחר את ספריות המדיה אשר ישותפו עם המשתמש. מנהלים יוכלו לערות את כל התיקיות באמצעות עורך המידע.", + "LibraryAccessHelp": "בחר את הספריות אשר ישותפו עם המשתמש. מנהלים יוכלו לערות את כל התיקיות באמצעות עורך המידע.", "Like": "אוהב", "Live": "שידור חי", "LiveBroadcasts": "שידורים חיים", @@ -319,7 +319,7 @@ "OptionDatePlayed": "תאריך ניגון", "OptionDescending": "סדר יורד", "OptionDisableUser": "בטל משתמש זה", - "OptionDisableUserHelp": "אם מבוטל, השרת שלא יאפשר חיבורים ממשתמש זה. חיבורים פעילים יבוטלו מייד.", + "OptionDisableUserHelp": "השרת לא יאפשר חיבורים ממשתמש זה. חיבורים פעילים יבוטלו מייד.", "OptionDislikes": "לא אוהב", "OptionDownloadArtImage": "עטיפה", "OptionDownloadBackImage": "גב", @@ -344,7 +344,7 @@ "OptionMissingEpisode": "פרקים חסרים", "OptionMonday": "שני", "OptionNameSort": "שם", - "OptionNew": "חדש...", + "OptionNew": "חדש…", "OptionOnAppStartup": "בהפעלת התוכנה", "OptionOnInterval": "כל פרק זמן", "OptionParentalRating": "דירוג בקרת הורים", @@ -363,7 +363,7 @@ "OptionThursday": "חמישי", "OptionTrackName": "שם השיר", "OptionTuesday": "שלישי", - "OptionTvdbRating": "דירוג Tvdb", + "OptionTvdbRating": "דירוג TVDB", "OptionUnairedEpisode": "פרקים שלא שודרו", "OptionUnplayed": "לא נוגן", "OptionWakeFromSleep": "הער ממצב שינה", @@ -371,8 +371,8 @@ "OptionWeekly": "שבועי", "OriginalAirDateValue": "תאריך אוויר מקורי: {0}", "Overview": "סקירה כללית", - "PackageInstallCancelled": "{0} ההתקנה בוטלה.", - "PackageInstallFailed": "ההתקנה {0} נכשלה.", + "PackageInstallCancelled": "ההתקנה של {0} (גירסה {1}) בוטלה.", + "PackageInstallFailed": "ההתקנה של {0} (גירסה {1}) נכשלה.", "ParentalRating": "דירוג ההורים", "PasswordMatchError": "הסיסמא ואימות הסיסמא צריכות להיות זהות.", "PasswordResetComplete": "הסיסמא אופסה.", @@ -395,7 +395,7 @@ "RecordingCancelled": "הקלטה בוטלה.", "RecordingScheduled": "ההקלטה מתוזמנת.", "Refresh": "רענון", - "RefreshDialogHelp": "המטא נתונים מתרעננים על סמך הגדרות ושירותי אינטרנט שמופעלים בלוח המחוונים של מרכז אמבי.", + "RefreshDialogHelp": "המטא-דאטה מתרעננת על סמך הגדרות ושירותי אינטרנט שמופעלים בלוח הבקרה.", "RefreshQueued": "רענן תור.", "ReleaseDate": "תאריך שיחרור", "RemoveFromCollection": "הסר מאוספים", @@ -514,8 +514,8 @@ "AllLanguages": "כל השפות", "Alerts": "התראות", "Box": "מארז", - "BirthPlaceValue": "מיקום לידה: {0}", - "BirthDateValue": "תאריך לידה: {0}", + "BirthPlaceValue": "מקום לידה: {0}", + "BirthDateValue": "נולד: {0}", "Backdrop": "רקע", "AuthProviderHelp": "בחר ספק אימות שישמש לאימות הסיסמה של משתמש זה.", "Audio": "שמע", @@ -530,10 +530,10 @@ "Yesterday": "אתמול", "HeaderAlbumArtists": "אמני האלבום", "Favorites": "מועדפים", - "HeaderFavoriteAlbums": "אלבומים שאהבתי", + "HeaderFavoriteAlbums": "אלבומים מועדפים", "HeaderFavoriteArtists": "אמנים מועדפים", "Folders": "תיקיות", - "HeaderFavoriteShows": "סדרות מועדפות", + "HeaderFavoriteShows": "תוכניות מועדפות", "HeaderFavoriteEpisodes": "פרקים מועדפים", "HeaderFavoriteSongs": "שירים מועדפים", "Collections": "אוספים", @@ -541,7 +541,7 @@ "HeaderContinueWatching": "המשך לצפות", "AllowOnTheFlySubtitleExtraction": "אפשר חילוץ כתוביות בזמן אמת", "AllowHWTranscodingHelp": "אפשר למלקט לקודד הזרמות בזמן אמת. זה עשוי לעזור בהפחתת הקידוד שנעשה ע\"י השרת.", - "AllComplexFormats": "כל הפורמטים המורכבים (ASS, SSA, VOBSUB, PGS, SUB/IDX)", + "AllComplexFormats": "כל הפורמטים המורכבים (ASS, SSA, VOBSUB, PGS, SUB/IDX, …)", "Songs": "שירים", "Shows": "סדרות", "DownloadsValue": "{0} הורדות", @@ -601,7 +601,7 @@ "HeaderRestart": "הפעלה מחדש", "HeaderProfileInformation": "מידע פרופיל", "HeaderProfile": "פרופיל", - "HeaderPreferredMetadataLanguage": "שפת מטא-נתונים מועדפת", + "HeaderPreferredMetadataLanguage": "שפת מטא-דאטה מועדפת", "HeaderPluginInstallation": "התקנת תוסף", "HeaderPlayOn": "נגן על", "HeaderPinCodeReset": "איפוס קוד סיכה", @@ -677,7 +677,7 @@ "EnableExternalVideoPlayers": "נגני וידאו חיצוניים", "EnableCinemaMode": "מצב קולנוע", "EnableBackdrops": "תמונות רקע", - "EditMetadata": "ערוך מטא-נתונים", + "EditMetadata": "ערוך מטא-דאטה", "DrmChannelsNotImported": "‫ערוצים בעלי ניהול זכויות דיגיטלי (DRM) לא ייובאו.", "Down": "למטה", "Display": "תצוגה", @@ -726,7 +726,7 @@ "ButtonAddScheduledTaskTrigger": "הוסף טריגר", "Browse": "עיין", "BoxRear": "מארז (מאחור)", - "BookLibraryHelp": "ניתן להוסיף ספרים מוקלטים וספרים כתובים. עיינו {0}במדריך מתן שמות לספרים{1}.", + "BookLibraryHelp": "ניתן להוסיף ספרים מוקלטים וספרים כתובים. עיינו {0} במדריך מתן שמות לספרים {1}.", "Desktop": "שולחן עבודה", "MessageDeleteTaskTrigger": "האם אתה בטוח שברצונך למחוק את מפעיל המשימה הזה?", "LastSeen": "נראה לאחרונה ב-{0}", @@ -738,11 +738,11 @@ "DeleteDeviceConfirmation": "האם אתה בטוח שברצונך למחוק את המכשיר? הוא יופיע שוב בפעם הבאה שמשתמש ייכנס באמצעותו.", "ColorSpace": "מרחב צבע", "CinemaModeConfigurationHelp": "מצב קולנוע מביא את חוויית הקולנוע היישר אל הסלון עם האפשרות להפעיל טריילרים וקדימונים מותאמים אישית לפני הסרט.", - "ChannelAccessHelp": "בחר את הערוצים לשיתוף עם משתמש זה. מנהלים יוכלו לערוך את כל הערוצים בעזרת \"מנהל המטא-דאטה\".", + "ChannelAccessHelp": "בחר את הערוצים לשיתוף עם משתמש זה. מנהלים יוכלו לערוך את כל הערוצים בעזרת מנהל המטא-דאטה.", "ButtonResetEasyPassword": "אתחל קוד פין פשוט", "ButtonOff": "כיבוי", "ButtonLibraryAccess": "הרשאות גישה לספרייה", - "BurnSubtitlesHelp": "מחליט אם על השרת לצרוב כתוביות בזמן קידוד וידאו. הימנעות מכך תשפר מאוד את הביצועים. בחר \"אוטומטי\" לצריבת כתוביות על בסיס פורמט תמונה (VOBSUB, PGS, SUB, IDX) וכתוביות ASS או SSA מסויימות.", + "BurnSubtitlesHelp": "מחליט אם על השרת לצרוב כתוביות בזמן קידוד וידאו. הימנעות מכך תשפר מאוד את הביצועים. בחר \"אוטומטי\" לצריבת כתוביות על בסיס פורמט תמונה (VOBSUB, PGS, SUB, IDX, …) וכתוביות ASS או SSA מסויימות.", "Artist": "אמן", "AllowedRemoteAddressesHelp": "רשימת IP \\ מיסוך רשת המופרדת בפסיקים עבור רשתות שיורשו להתחבר מרחוק. במידה ותישאר ריקה, כל הכתובות יורשו להתחבר.", "Album": "אלבום", @@ -764,5 +764,13 @@ "DashboardServerName": "שרת: {0}", "DashboardVersionNumber": "גירסה: {0}", "DashboardArchitecture": "ארכיטקטורה: {0}", - "DashboardOperatingSystem": "מערכת הפעלה: {0}" + "DashboardOperatingSystem": "מערכת הפעלה: {0}", + "HeaderMyMediaSmall": "המדיה שלי (קטן)", + "HeaderMusicQuality": "איכות מוזיקה", + "HeaderMediaInfo": "מידע על המדיה", + "HeaderMoreLikeThis": "עוד כמו זה", + "HeaderMedia": "מדיה", + "LabelMetadata": "מטא-דאטה:", + "HeaderSelectMetadataPath": "בחר נתיב מטא-דאטה", + "NextUp": "הבא בתור" } From 73e8b5039527bd0c7b23571bed3dff73c9c2a640 Mon Sep 17 00:00:00 2001 From: sharkykh Date: Wed, 5 Aug 2020 19:38:19 +0000 Subject: [PATCH 072/124] Translated using Weblate (Hebrew) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/he/ --- src/strings/he.json | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/strings/he.json b/src/strings/he.json index a063f59b76..9155e310a3 100644 --- a/src/strings/he.json +++ b/src/strings/he.json @@ -772,5 +772,23 @@ "HeaderMedia": "מדיה", "LabelMetadata": "מטא-דאטה:", "HeaderSelectMetadataPath": "בחר נתיב מטא-דאטה", - "NextUp": "הבא בתור" + "NextUp": "הבא בתור", + "LabelBaseUrl": "כתובת בסיס:", + "LabelEnableHttpsHelp": "האזן על גבי פורט ה-HTTPS המוגדר. חובה לספק תעודה תקינה על מנת שהגדרה זו תכנס לתוקף.", + "LabelEnableHttps": "הפעל HTTPS", + "LabelEnableHardwareDecodingFor": "הפעל פענוח חומרה עבור:", + "LabelEnableDlnaServerHelp": "אפשר למכשירי UPnP ברשת שלך לעיין בתוכן ולנגן אותו.", + "LabelEnableAutomaticPortMap": "הפעל מיפוי פורט אוטומטי", + "LabelDropImageHere": "גרור תמונה לכאן, או לחץ כדי לעיין.", + "LabelDownloadLanguages": "הורד שפות:", + "LabelDownMixAudioScale": "הגברת עוצמת שמע כאשר הוא ממוזג:", + "LabelDisplaySpecialsWithinSeasons": "הצג פרקים מיוחדים בתוך העונות שבמהלכן הם שודרו", + "LabelDisplayName": "שם תצוגה:", + "LabelDisplayMode": "מצב תצוגה:", + "LabelDisplayLanguageHelp": "תרגום Jellyfin הוא פרויקט מתמשך.", + "LabelDisplayLanguage": "שפת תצוגה:", + "LabelDidlMode": "מצב DIDL:", + "LabelDeviceDescription": "תיאור מכשיר", + "LabelDefaultScreen": "מסך ברירת-מחדל:", + "LabelCustomDeviceDisplayName": "שם תצוגה:" } From c73a2d83761e480c7b1dce88ddb717147458d9a4 Mon Sep 17 00:00:00 2001 From: sharkykh Date: Wed, 5 Aug 2020 19:51:52 +0000 Subject: [PATCH 073/124] Translated using Weblate (Hebrew) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/he/ --- src/strings/he.json | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/strings/he.json b/src/strings/he.json index 9155e310a3..c61bfe9e1a 100644 --- a/src/strings/he.json +++ b/src/strings/he.json @@ -790,5 +790,15 @@ "LabelDidlMode": "מצב DIDL:", "LabelDeviceDescription": "תיאור מכשיר", "LabelDefaultScreen": "מסך ברירת-מחדל:", - "LabelCustomDeviceDisplayName": "שם תצוגה:" + "LabelCustomDeviceDisplayName": "שם תצוגה:", + "LabelImageType": "סוג תמונה:", + "LabelHttpsPortHelp": "מספר פורט ה-TCP עבור שרת ה-HTTPS.", + "LabelHttpsPort": "מספר פורט HTTPS מקומי:", + "LabelGroupMoviesIntoCollections": "אגד סרטים לתוך אוספים", + "LabelFriendlyName": "שם ידידותי:", + "LabelFormat": "תבנית:", + "LabelForgotPasswordUsernameHelp": "הכנס/י את שם המשתמש שלך, אם את/ה זוכר/ת אותו.", + "LabelFont": "גופן:", + "LabelFolder": "תיקייה:", + "LabelFileOrUrl": "קובץ או כתובת אינטרנט:" } From 98481fc4502abe3fe5864a8dfc07f93108840b80 Mon Sep 17 00:00:00 2001 From: WWWesten Date: Wed, 5 Aug 2020 21:26:05 +0000 Subject: [PATCH 074/124] Translated using Weblate (Russian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/ru/ --- src/strings/ru.json | 75 +++++++++++++++++++++++---------------------- 1 file changed, 39 insertions(+), 36 deletions(-) diff --git a/src/strings/ru.json b/src/strings/ru.json index 9089e4bea7..24d5cdd90f 100644 --- a/src/strings/ru.json +++ b/src/strings/ru.json @@ -175,8 +175,8 @@ "DetectingDevices": "Обнаруживются устройства", "DeviceAccessHelp": "Это относится только к устройствам, которые могут быть однозначно распознаны и не препятствует доступу через браузер. Фильтрация доступа пользовательского устройства запретит использование новых устройств до тех пор, пока они не будут одобрены.", "DirectPlaying": "Воспроизводится напрямую", - "DirectStreamHelp1": "Медиаданные совместимы с устройством в отношении разрешения и типа медиаданных (H.264, AC3, и т.д.), но в несовместимом файловом контейнере (mkv, avi, wmv и т.д.). Видео будет повторно упаковано динамически перед его трансляцией на устройство.", - "DirectStreamHelp2": "При прямой трансляции файла расходуется очень мало вычислительной мощности без потери качества видео.", + "DirectStreamHelp1": "Медиаданные совместимы с устройством в отношении разрешения и типа медиаданных (H.264, AC3, и т.д.), но в несовместимом файловом контейнере (mkv, avi, wmv и т.д.). Видео будет повторно упаковано динамически перед его отправлением на устройство.", + "DirectStreamHelp2": "Прямая трансляции расходует очень мало вычислительной мощности с минимальной потерей качества видео.", "DirectStreaming": "Транслируется напрямую", "Director": "Режиссёр", "Directors": "Режиссёры", @@ -287,7 +287,7 @@ "HeaderAllowMediaDeletionFrom": "Разрешить удаление медиаданных из", "HeaderApiKey": "API-ключ", "HeaderApiKeys": "API-ключи", - "HeaderApiKeysHelp": "Внешним приложениям требуется API-ключ для того, чтобы подключиться к Jellyfin Server. Ключи выдаются при входе с учётной записью Jellyfin или ключ предоставляется приложению вручную.", + "HeaderApiKeysHelp": "Внешним приложениям требуется API-ключ для того, чтобы подключиться к серверу. Ключи выдаются при входе с учётной записью обычного пользователя или ключ предоставляется приложению вручную.", "HeaderApp": "Приложение", "HeaderAppearsOn": "Фигурирует в", "HeaderAudioBooks": "Аудиокниги", @@ -359,7 +359,7 @@ "HeaderItems": "Элементы", "HeaderKeepRecording": "Хранение записи", "HeaderKeepSeries": "Хранение сериала", - "HeaderKodiMetadataHelp": "Для включения или отключения NFO-метаданных, начните править медиатеку в области настройки медиатек и найдите раздел хранителей метаданных.", + "HeaderKodiMetadataHelp": "Для включения или отключения NFO-метаданных, правьте медиатеку и найдите раздел хранителей метаданных.", "HeaderLatestEpisodes": "Новейшие эпизоды", "HeaderLatestMedia": "Новейшие медиаданные", "HeaderLatestMovies": "Новейшие фильмы", @@ -408,7 +408,7 @@ "HeaderPreferredMetadataLanguage": "Выбор языка метаданных", "HeaderProfile": "Профиль", "HeaderProfileInformation": "О профиле", - "HeaderProfileServerSettingsHelp": "Данные значения управляют тем, как Jellyfin Server будет представляться устройству.", + "HeaderProfileServerSettingsHelp": "Данные значения управляют тем, как сервер будет представлять себя клиентам.", "HeaderRecentlyPlayed": "Воспроизведённые недавно", "HeaderRecordingOptions": "Опции записи", "HeaderRecordingPostProcessing": "Постобработка записи", @@ -426,13 +426,13 @@ "HeaderSecondsValue": "{0} с", "HeaderSelectCertificatePath": "Выбор пути к сертификату", "HeaderSelectMetadataPath": "Выбор пути для метаданных", - "HeaderSelectMetadataPathHelp": "Найдите или введите путь, в пределах которого хотите хранить метаданные. Папка должна быть доступна для записи.", + "HeaderSelectMetadataPathHelp": "Найдите или введите путь, который хотите использовать для метаданныхе. Папка должна быть доступна для записи.", "HeaderSelectPath": "Выбор пути", "HeaderSelectServer": "Выбор сервера", "HeaderSelectServerCachePath": "Выбор пути для серверного кэша", "HeaderSelectServerCachePathHelp": "Найдите или введите путь, чтобы использовать для файлов серверного кэша. Папка должна быть доступна для записи.", "HeaderSelectTranscodingPath": "Выбор пути для временных файлов перекодировки", - "HeaderSelectTranscodingPathHelp": "Найдите или введите путь, чтобы использовать для временных файлов перекодировки. Папка должна быть доступна для записи.", + "HeaderSelectTranscodingPathHelp": "Найдите или введите путь, чтобы использовать для файлов перекодировки. Папка должна быть доступна для записи.", "HeaderSendMessage": "Передача сообщения", "HeaderSeries": "Сериалы", "HeaderSeriesOptions": "Опции сериала", @@ -485,8 +485,8 @@ "HttpsRequiresCert": "Чтобы включить HTTPS для внешних подключений, вам нужно будет предоставить доверенный SSL-cертификат, например, Let's Encrypt. Предоставьте сертификат или отключите защищенные соединения.", "Identify": "Распознать", "Images": "Изображения", - "ImportFavoriteChannelsHelp": "При включении, будут импортированы только каналы, которые обозначены как избранное на тюнерном устройстве.", - "ImportMissingEpisodesHelp": "При включении, информация об отсутствующих эпизодах будет импортирована в вашу базу данных Jellyfin и отображаться в пределах сезонов и сериалов. Это может увеличить время сканирования медиатеки.", + "ImportFavoriteChannelsHelp": "Будут импортированы только каналы, которые обозначены как избранное на тюнерном устройстве.", + "ImportMissingEpisodesHelp": "Информация об отсутствующих эпизодах будет импортирована в вашу базу данных и отображена в пределах сезонов и сериалов. Это может заметно увеличить время сканирования медиатеки.", "InstallingPackage": "Устанавливается {0} (версия {1})", "InstantMix": "Автомикс", "ItemCount": "{0} элемент(а/ов)", @@ -517,15 +517,15 @@ "LabelAppName": "Название приложения", "LabelAppNameExample": "Пример: Sickbeard, Sonarr", "LabelArtists": "Исполнители:", - "LabelArtistsHelp": "Для разделения используйте точку с запятой ;", + "LabelArtistsHelp": "Для разделения исполнителей используйте точку с запятой ;", "LabelAudio": "Аудио", "LabelAudioLanguagePreference": "Выбор языка аудио:", "LabelAutomaticallyRefreshInternetMetadataEvery": "Автоматически обновлять метаданные из Интернета:", "LabelBindToLocalNetworkAddress": "Привязка к адресу в локальной сети:", - "LabelBindToLocalNetworkAddressHelp": "Необязательно. Переопределяется локальный IP-адрес для привязки HTTP-сервера. Если поле пусто, то привязка сервера будет ко всем доступным адресам. При изменении данного значения потребуется перезапуск Jellyfin Server.", + "LabelBindToLocalNetworkAddressHelp": "Переопределяется локальный IP-адрес для HTTP-сервера. Если поле пусто, то привязка сервера будет ко всем доступным адресам. При изменении данного значения потребуется перезапуск Jellyfin Server.", "LabelBirthDate": "Дата рождения:", "LabelBirthYear": "Год рождения:", - "LabelBlastMessageInterval": "Интервал сообщений проверки активности, с", + "LabelBlastMessageInterval": "Интервал сообщений проверки активности", "LabelBlastMessageIntervalHelp": "Определяет длительность в секундах между бомбардированием сообщениями проверки активности.", "LabelBlockContentWithTags": "Блокирование элементов с тегами:", "LabelBurnSubtitles": "Внедрение субтитров:", @@ -545,7 +545,7 @@ "LabelCustomCertificatePath": "Путь к пользовательскому SSL-сертификату:", "LabelCustomCertificatePathHelp": "Путь к файлу PKCS #12, содержащему сертификат и \tзакрытый ключ для включения поддержки TLS на произвольном домене.", "LabelCustomCss": "Настраиваемые CSS:", - "LabelCustomCssHelp": "Применяйте свою собственную настраиваемую стилизацию к веб-интерфейсу.", + "LabelCustomCssHelp": "Применяйте свои собственные настраиваемые стили в веб-интерфейсе.", "LabelCustomDeviceDisplayName": "Отображаемое название:", "LabelCustomDeviceDisplayNameHelp": "Приведите произвольное имя для отображения или не заполняйте, чтобы использовать имя, выданное устройством.", "LabelCustomRating": "Произвольная возрастная категория:", @@ -585,7 +585,7 @@ "LabelEnableDlnaDebugLogging": "Включить журналирование отладки DLNA", "LabelEnableDlnaDebugLoggingHelp": "Создаются большие файлы Журнала, рекомендуется использовать только для поиска неполадок.", "LabelEnableDlnaPlayTo": "Включить DLNA-функцию Воспроизвести На", - "LabelEnableDlnaPlayToHelp": "Обнаруживаются устройства внутри своей сети, а также предоставляется возможность удалённо управлять ими.", + "LabelEnableDlnaPlayToHelp": "Обнаруживаются устройства внутри своей сети, а также предоставляется возможность управлять ими удалённо.", "LabelEnableDlnaServer": "Включить DLNA-сервер", "LabelEnableDlnaServerHelp": "Для UPnP-устройств домашней сети возможна навигация по содержанию и его воспроизведение.", "LabelEnableHardwareDecodingFor": "Включить аппаратное декодирование для:", @@ -606,9 +606,9 @@ "LabelForgotPasswordUsernameHelp": "Введите имя пользователя, если помните его.", "LabelFormat": "Формат:", "LabelFriendlyName": "Понятное имя:", - "LabelServerNameHelp": "Это имя используется для распознавания сервера и будет по умолчанию именем компьютера.", + "LabelServerNameHelp": "Это имя используется для распознавания сервера и будет по умолчанию именем хоста сервера.", "LabelGroupMoviesIntoCollections": "Группировать фильмы внутрь коллекций", - "LabelGroupMoviesIntoCollectionsHelp": "При отображении списка фильмов, элементы, принадлежащие к одной коллекции будут отображаться как единый сгруппированный элемент.", + "LabelGroupMoviesIntoCollectionsHelp": "При отображении списков фильмов, фильмы из коллекции будут отображаться как единый группированный элемент.", "LabelH264Crf": "Значение CRF H264-кодирования:", "LabelEncoderPreset": "Предустановка H264-кодирования:", "LabelHardwareAccelerationType": "Аппаратное ускорение:", @@ -616,7 +616,7 @@ "LabelHomeNetworkQuality": "Качество в домашней сети:", "LabelHomeScreenSectionValue": "Главная страница - раздел {0}:", "LabelHttpsPort": "Номер локального HTTPS-порта:", - "LabelHttpsPortHelp": "TCP-порт, ко которому следует создать привязку HTTPS-сервера Jellyfin.", + "LabelHttpsPortHelp": "Номер TCP-порта для HTTPS-сервера.", "LabelIconMaxHeight": "Макс. высота значка:", "LabelIconMaxHeightHelp": "Максимальное разрешение значков представляемых с помощью upnp:icon.", "LabelIconMaxWidth": "Макс. ширина значка:", @@ -644,7 +644,7 @@ "LabelLanguage": "Язык:", "LabelLineup": "Список сопоставления:", "LabelLocalHttpServerPortNumber": "Номер локального HTTP-порта:", - "LabelLocalHttpServerPortNumberHelp": "TCP-порт, ко которому следует создать привязку HTTP-сервера Jellyfin.", + "LabelLocalHttpServerPortNumberHelp": "Номер TCP-порта для HTTP-сервера.", "LabelLockItemToPreventChanges": "Зафиксировать данный элемент, чтобы запретить будущие правки", "LabelLoginDisclaimer": "Предупреждение при входе:", "LabelLoginDisclaimerHelp": "Сообщение будет отображаться в нижней части страницы входа в систему.", @@ -670,7 +670,7 @@ "LabelMetadataReaders": "Считыватели метаданных:", "LabelMetadataReadersHelp": "Ранжируйте предпочитаемые локальные источники метаданных в порядке приоритета. Будет считан первый же найденный файл.", "LabelMetadataSavers": "Хранители метаданных:", - "LabelMetadataSaversHelp": "Выберите форматы файлов, куда будут сохраняться метаданные.", + "LabelMetadataSaversHelp": "Выберите форматы файлов, которые будут использоваться при сохранении метаданных.", "LabelMethod": "Метод:", "LabelMinBackdropDownloadWidth": "Минимальная ширина загружаемого фона:", "LabelMinResumeDuration": "Минимальная длительность для возобновления:", @@ -686,7 +686,7 @@ "LabelMovieCategories": "Фильмовые категории:", "LabelMoviePrefix": "Префикс фильма:", "LabelMoviePrefixHelp": "При применении к названиям фильмов префикса, введите его здесь, чтобы он правильно обрабатывался на сервере.", - "LabelMovieRecordingPath": "Путь к записываемым фильмам (необязательно):", + "LabelMovieRecordingPath": "Путь к записываемым фильмам:", "LabelMusicStreamingTranscodingBitrate": "Битрейт перекодировки музыки:", "LabelMusicStreamingTranscodingBitrateHelp": "Укажите максимальный битрейт при трансляции музыки.", "LabelName": "Имя:", @@ -699,7 +699,7 @@ "LabelNumber": "Номер:", "LabelNumberOfGuideDays": "Число дней для загрузки данных телегида:", "LabelNumberOfGuideDaysHelp": "Больше дней загрузки данных телегида обеспечивает возможность заблаговременно назначать расписание и просматривать больше перечней, однако это займёт больше времени для загрузки. При значении «Авто» выбор определяется числом каналов.", - "LabelOptionalNetworkPath": "(Необязательно) Общедоступная сетевая папка:", + "LabelOptionalNetworkPath": "Общедоступная сетевая папка:", "LabelOptionalNetworkPathHelp": "Если данная папка является общей в сети, указание пути к сетевой папке может позволить Jellyfin-приложениям на других устройствах иметь прямой доступ к медиафайлам. Например, {0} или {1}.", "LabelOriginalAspectRatio": "Исходное соотношение сторон:", "LabelOriginalTitle": "Оригинальное название:", @@ -744,7 +744,7 @@ "LabelReleaseDate": "Дата выпуска:", "LabelRemoteClientBitrateLimit": "Ограничение битрейта интернет-трансляции, Мбит/с:", "LabelRemoteClientBitrateLimitHelp": "Необязательное ограничение битрейта для каждого из сетевых устройств. Может потребоваться, чтобы не допускать использования устройствами большего битрейта, чем способно пропустить интернет-соединение. Может привести к росту загрузки процессора на вашем сервере, так как потребуется динамическое перекодирование видео для снижения битрейта.", - "LabelRuntimeMinutes": "Длительность, мин:", + "LabelRuntimeMinutes": "Длительность:", "LabelSaveLocalMetadata": "Сохранять иллюстрации внутри медиапапок", "LabelSaveLocalMetadataHelp": "При сохранении иллюстраций внутри медиапапок, те помещаются в месте, где их можно легко править.", "LabelScheduledTaskLastRan": "Последний запуск был {0}, занял {1}.", @@ -756,7 +756,7 @@ "LabelSelectVersionToInstall": "Выбрать версию для установки:", "LabelSendNotificationToUsers": "Передача уведомления для:", "LabelSerialNumber": "Серийный номер", - "LabelSeriesRecordingPath": "Путь к записываемым сериалам (необязательно):", + "LabelSeriesRecordingPath": "Путь к записываемым сериалам:", "LabelServerHost": "Узел:", "LabelServerHostHelp": "192.168.1.100:8096 или https://myserver.com", "LabelSimultaneousConnectionLimit": "Лимит одновременных потоков:", @@ -917,7 +917,7 @@ "MessageYouHaveVersionInstalled": "В настоящее время установлена версия {0}.", "Metadata": "Метаданные", "MetadataManager": "Дисп. метаданных", - "MetadataSettingChangeHelp": "Изменение параметров метаданных повлияет на новое содержание, которое будет добавляться в дальнейшем. Чтобы обновить существующие содержание, откройте экран с подробностями и нажмите кнопку Обновить, или выполните массовое обновление, с помощью Диспетчера метаданных.", + "MetadataSettingChangeHelp": "Изменение параметров метаданных повлияет на новое содержание, добавляемое в будущем.. Чтобы обновить существующие содержание, откройте экран с подробностями и нажмите кнопку Обновить, или выполните массовое обновление, с помощью Диспетчера метаданных.", "MinutesAfter": "минут(у/ы) после", "MinutesBefore": "минут(у/ы) до", "Mobile": "Мобильный", @@ -966,7 +966,7 @@ "OptionAllowLinkSharingHelp": "Общедоступны только веб-страницы содержащие сведения о медиаданных. Медиафайлы никогда не предоставляются для общего просмотра. Совместно используемые ресурсы ограничены во времени, а срок действия истекает через {0} дн(я/ей).", "OptionAllowManageLiveTv": "Разрешить управление эфирными записями", "OptionAllowMediaPlayback": "Разрешить воспроизведение медиаданных", - "OptionAllowMediaPlaybackTranscodingHelp": "Ограничение доступа к перекодировке может привести к сбоям воспроизведения в Jellyfin-приложениях из-за неподдерживаемых форматов носителей.", + "OptionAllowMediaPlaybackTranscodingHelp": "Ограничение доступа к перекодировке может привести к сбоям воспроизведения в клиентах из-за неподдерживаемых форматов носителей.", "OptionAllowRemoteControlOthers": "Разрешить удалённое управление другими пользователями", "OptionAllowRemoteSharedDevices": "Разрешить удалённое управление используемыми совместно устройствами", "OptionAllowRemoteSharedDevicesHelp": "DLNA-устройства считаются используемыми совместно, пока какой-либо пользователь не начнёт управлять ими.", @@ -979,7 +979,7 @@ "OptionAuto": "Авто", "OptionAutomatic": "Авто", "OptionAutomaticallyGroupSeries": "Автоматически сливать вместе сериалы, которые разбросаны по нескольким папкам", - "OptionAutomaticallyGroupSeriesHelp": "При включении, сериалы, которые разбросаны по нескольким папкам данной медиатеки, будут автоматически слиты в единый сериал.", + "OptionAutomaticallyGroupSeriesHelp": "Части сериала, которые разбросаны по нескольким папкам данной медиатеки, будут автоматически слиты в единый сериал.", "OptionBlockBooks": "Книги", "OptionBlockChannelContent": "Содержание интернет-канала", "OptionBlockLiveTvChannels": "Эфирные каналы", @@ -999,7 +999,7 @@ "OptionDatePlayed": "Дата воспроизведения", "OptionDescending": "По убыванию", "OptionDisableUser": "Заблокировать пользователя", - "OptionDisableUserHelp": "При блокировании, этому пользователю не разрешаются любые подключения к серверу. Имеющиеся соединения будут разорваны.", + "OptionDisableUserHelp": "Этому пользователю не разрешаются любые подключения к серверу. Имеющиеся соединения будут разорваны.", "OptionDislikes": "Не нравящиеся", "OptionDisplayFolderView": "Отображать аспект Папки для просмотра обычных медиапапок", "OptionDisplayFolderViewHelp": "Отображение аспекта \"Папки\" рядом с другими вашими медиатеками. Это может быть полезно, если вы хотите вид обычных папок.", @@ -1009,7 +1009,7 @@ "OptionDownloadBoxImage": "DVD-бокс", "OptionDownloadDiscImage": "Диск", "OptionDownloadImagesInAdvance": "Загружать изображения заблаговременно", - "OptionDownloadImagesInAdvanceHelp": "По умолчанию, большинство изображений загружаются только при запросе от Jellyfin-приложения. Включите данную опцию, чтобы загружать все изображения заблаговременно, при импорте новых медиаданных. Это может привести к существенно длительным сканированиям медиатеки.", + "OptionDownloadImagesInAdvanceHelp": "По умолчанию, большинство изображений загружаются только при запросе от клиента. Включите данную опцию, чтобы загружать все изображения заблаговременно, при импорте новых медиаданных. Это может привести к существенно длительным сканированиям медиатеки.", "OptionDownloadLogoImage": "Логотип", "OptionDownloadMenuImage": "Меню", "OptionDownloadPrimaryImage": "Основной", @@ -1042,7 +1042,7 @@ "OptionHlsSegmentedSubtitles": "Сегмент. субтитры HLS", "OptionHomeVideos": "Фотографии", "OptionIgnoreTranscodeByteRangeRequests": "Игнорировать запросы диапазона байтов перекодировки", - "OptionIgnoreTranscodeByteRangeRequestsHelp": "При включении, эти запросы будут учтены, но заголовок диапазона байтов будет проигнорирован.", + "OptionIgnoreTranscodeByteRangeRequestsHelp": "Эти запросы будут учтены, но заголовок диапазона байтов будет проигнорирован.", "OptionImdbRating": "Оценка IMDb", "OptionLikes": "Нравящиеся", "OptionMax": "Макс.", @@ -1055,9 +1055,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": "Дата премьеры", @@ -1148,7 +1148,7 @@ "RecordingScheduled": "Запись назначена.", "Recordings": "Записи", "Refresh": "Обновить", - "RefreshDialogHelp": "Обновление метаданных определяются параметрами и интернет-услугами, которые включены в Панели Jellyfin Server.", + "RefreshDialogHelp": "Обновление метаданных определяются параметрами и интернет-услугами, которые включены в Панели.", "RefreshMetadata": "Обновить метаданные", "RefreshQueued": "Обновление в очереди.", "ReleaseDate": "Дата выпуска", @@ -1395,11 +1395,11 @@ "OptionPosterCard": "Постер-карта", "OptionThumb": "Эскиз", "OptionThumbCard": "Эскиз-карта", - "PasswordResetProviderHelp": "Выберите поставщика сброса пароля, который будет использоваться, когда этот пользователь запрашивает сброс пароля", + "PasswordResetProviderHelp": "Выберите поставщика сброса пароля, который использовуется при запросе пользователем сброса пароля", "PlaybackData": "Данные воспроизведения", "SubtitleOffset": "Сдвиг субтитров", "TabNetworking": "Работа в сети", - "LabelBaseUrlHelp": "Добавляется пользовательский подкаталог к URL сервера. Например: http://example.com/<baseurl>", + "LabelBaseUrlHelp": "Добавляет пользовательский подкаталог к URL сервера. Например: http://example.com/<baseurl>", "LabelPlayer": "Проигрыватель:", "MoreMediaInfo": "О медиаданных", "LabelVideoCodec": "Видео кодек:", @@ -1472,7 +1472,7 @@ "UnsupportedPlayback": "Jellyfin не может расшифровать содержимое, защищенное DRM, но в любом случае будет предпринята попытка расшифровки всего содержимого, включая защищенные заголовки. Некоторые файлы могут выглядеть полностью черными из-за шифрования или других неподдерживаемых функций, таких как интерактивные заголовки.", "HeaderFavoritePlaylists": "Избранные плей-листы", "LabelRequireHttpsHelp": "Если этот флажок установлен, сервер будет автоматически перенаправлять все запросы через HTTP на HTTPS. Это не имеет никакого эффекта, если сервер не слушает HTTPS.", - "LabelEnableHttpsHelp": "Позволяет серверу слушать HTTPS-порт. Для работы необходим действующий сертификат.", + "LabelEnableHttpsHelp": "Прослушивается указанный HTTPS-порт. Чтобы это вступило в силу, также необходимо предоставить действительный сертификат.", "ApiKeysCaption": "Список действующих текущих API-ключей", "TabDVR": "DVR", "SaveChanges": "Сохранить изменения", @@ -1539,5 +1539,8 @@ "Writers": "Сценаристы", "ViewAlbumArtist": "Посмотреть альбом исполнителя", "ClearQueue": "Очистить очередь", - "ButtonPlayer": "Проигрыватель" + "ButtonPlayer": "Проигрыватель", + "PreviousTrack": "Перейти к предыдущему", + "NextTrack": "Перейти к следующему", + "LabelUnstable": "Нестабильная" } From 9d822d911af4fa09ef67a41bd20048d194ffeb13 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 6 Aug 2020 08:43:52 +0100 Subject: [PATCH 075/124] Migration of sortMenu to ES6 module --- package.json | 1 + src/components/sortmenu/sortmenu.js | 87 +++++++++++++++-------------- src/controllers/list.js | 2 +- 3 files changed, 48 insertions(+), 42 deletions(-) diff --git a/package.json b/package.json index 1d7cf770d3..33c4670d5d 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/components/sortmenu/sortmenu.js", "src/plugins/htmlVideoPlayer/plugin.js", "src/components/search/searchfields.js", "src/components/search/searchresults.js", diff --git a/src/components/sortmenu/sortmenu.js b/src/components/sortmenu/sortmenu.js index f62e5bb3a4..d38d98c090 100644 --- a/src/components/sortmenu/sortmenu.js +++ b/src/components/sortmenu/sortmenu.js @@ -1,46 +1,51 @@ -define(['require', 'dom', 'focusManager', 'dialogHelper', 'loading', 'layoutManager', 'connectionManager', 'globalize', 'userSettings', 'emby-select', 'paper-icon-button-light', 'material-icons', 'css!./../formdialog', 'emby-button', 'flexStyles'], function (require, dom, focusManager, dialogHelper, loading, layoutManager, connectionManager, globalize, userSettings) { - 'use strict'; +import dialogHelper from 'dialogHelper'; +import layoutManager from 'layoutManager'; +import globalize from 'globalize'; +import * as userSettings from 'userSettings'; +import 'emby-select'; +import 'paper-icon-button-light'; +import 'material-icons'; +import 'css!./../formdialog'; +import 'emby-button'; +import 'flexStyles'; - function onSubmit(e) { - e.preventDefault(); - return false; - } +function onSubmit(e) { + e.preventDefault(); + return false; +} - function initEditor(context, settings) { - context.querySelector('form').addEventListener('submit', onSubmit); +function initEditor(context, settings) { + context.querySelector('form').addEventListener('submit', onSubmit); - context.querySelector('.selectSortOrder').value = settings.sortOrder; - context.querySelector('.selectSortBy').value = settings.sortBy; - } + context.querySelector('.selectSortOrder').value = settings.sortOrder; + context.querySelector('.selectSortBy').value = settings.sortBy; +} - function centerFocus(elem, horiz, on) { - require(['scrollHelper'], function (scrollHelper) { - var fn = on ? 'on' : 'off'; - scrollHelper.centerFocus[fn](elem, horiz); - }); - } +function centerFocus(elem, horiz, on) { + import('scrollHelper').then(({default: scrollHelper}) => { + const fn = on ? 'on' : 'off'; + scrollHelper.centerFocus[fn](elem, horiz); + }); +} - function fillSortBy(context, options) { - var selectSortBy = context.querySelector('.selectSortBy'); +function fillSortBy(context, options) { + const selectSortBy = context.querySelector('.selectSortBy'); - selectSortBy.innerHTML = options.map(function (o) { - return ''; - }).join(''); - } + selectSortBy.innerHTML = options.map(function (o) { + return ''; + }).join(''); +} - function saveValues(context, settings, settingsKey) { - userSettings.setFilter(settingsKey + '-sortorder', context.querySelector('.selectSortOrder').value); - userSettings.setFilter(settingsKey + '-sortby', context.querySelector('.selectSortBy').value); - } +function saveValues(context, settingsKey) { + userSettings.setFilter(settingsKey + '-sortorder', context.querySelector('.selectSortOrder').value); + userSettings.setFilter(settingsKey + '-sortby', context.querySelector('.selectSortBy').value); +} - function SortMenu() { - - } - - SortMenu.prototype.show = function (options) { +class SortMenu { + show(options) { return new Promise(function (resolve, reject) { - require(['text!./sortmenu.template.html'], function (template) { - var dialogOptions = { + import('text!./sortmenu.template.html').then(({default: template}) => { + const dialogOptions = { removeOnClose: true, scrollY: false }; @@ -51,11 +56,11 @@ define(['require', 'dom', 'focusManager', 'dialogHelper', 'loading', 'layoutMana dialogOptions.size = 'small'; } - var dlg = dialogHelper.createDialog(dialogOptions); + const dlg = dialogHelper.createDialog(dialogOptions); dlg.classList.add('formDialog'); - var html = ''; + let html = ''; html += '
'; html += ''; @@ -78,7 +83,7 @@ define(['require', 'dom', 'focusManager', 'dialogHelper', 'loading', 'layoutMana centerFocus(dlg.querySelector('.formDialogContent'), false, true); } - var submitted; + let submitted; dlg.querySelector('form').addEventListener('change', function () { submitted = true; @@ -90,7 +95,7 @@ define(['require', 'dom', 'focusManager', 'dialogHelper', 'loading', 'layoutMana } if (submitted) { - saveValues(dlg, options.settings, options.settingsKey); + saveValues(dlg, options.settingsKey); resolve(); return; } @@ -99,7 +104,7 @@ define(['require', 'dom', 'focusManager', 'dialogHelper', 'loading', 'layoutMana }); }); }); - }; + } +} - return SortMenu; -}); +export default SortMenu; diff --git a/src/controllers/list.js b/src/controllers/list.js index 68e81f05dd..904ef5aa66 100644 --- a/src/controllers/list.js +++ b/src/controllers/list.js @@ -371,7 +371,7 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' var instance = this; require(['sortMenu'], function (SortMenu) { - new SortMenu().show({ + new SortMenu.default().show({ settingsKey: instance.getSettingsKey(), settings: instance.getSortValues(), onChange: instance.itemsContainer.refreshItems.bind(instance.itemsContainer), From f3c00499824d6664bc26e705817c1a655aaf82b3 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 6 Aug 2020 08:44:04 +0100 Subject: [PATCH 076/124] Migration of slideshow to ES6 module --- package.json | 1 + src/components/slideshow/slideshow.js | 1290 +++++++++++++------------ 2 files changed, 649 insertions(+), 642 deletions(-) diff --git a/package.json b/package.json index 33c4670d5d..fa20c96339 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/components/slideshow/slideshow.js", "src/components/sortmenu/sortmenu.js", "src/plugins/htmlVideoPlayer/plugin.js", "src/components/search/searchfields.js", diff --git a/src/components/slideshow/slideshow.js b/src/components/slideshow/slideshow.js index 60c458e234..82f541a116 100644 --- a/src/components/slideshow/slideshow.js +++ b/src/components/slideshow/slideshow.js @@ -2,666 +2,672 @@ * Image viewer component * @module components/slideshow/slideshow */ -define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'focusManager', 'browser', 'apphost', 'dom', 'css!./style', 'material-icons', 'paper-icon-button-light'], function (dialogHelper, inputManager, connectionManager, layoutManager, focusManager, browser, appHost, dom) { - 'use strict'; +import dialogHelper from 'dialogHelper'; +import inputManager from 'inputManager'; +import connectionManager from 'connectionManager'; +import layoutManager from 'layoutManager'; +import focusManager from 'focusManager'; +import browser from 'browser'; +import appHost from 'apphost'; +import dom from 'dom'; +import 'css!./style'; +import 'material-icons'; +import 'paper-icon-button-light'; - browser = browser.default || browser; +/** + * Name of transition event. + */ +const transitionEndEventName = dom.whichTransitionEvent(); - /** - * Name of transition event. - */ - const transitionEndEventName = dom.whichTransitionEvent(); +/** + * Flag to use fake image to fix blurry zoomed image. + * At least WebKit doesn't restore quality for zoomed images. + */ +const useFakeZoomImage = browser.safari; - /** - * Flag to use fake image to fix blurry zoomed image. - * At least WebKit doesn't restore quality for zoomed images. - */ - const useFakeZoomImage = browser.safari; +/** + * Retrieves an item's image URL from the API. + * @param {object|string} item - Item used to generate the image URL. + * @param {object} options - Options of the image. + * @param {object} apiClient - API client instance used to retrieve the image. + * @returns {null|string} URL of the item's image. + */ +function getImageUrl(item, options, apiClient) { + options = options || {}; + options.type = options.type || 'Primary'; - /** - * Retrieves an item's image URL from the API. - * @param {object|string} item - Item used to generate the image URL. - * @param {object} options - Options of the image. - * @param {object} apiClient - API client instance used to retrieve the image. - * @returns {null|string} URL of the item's image. - */ - function getImageUrl(item, options, apiClient) { - options = options || {}; - options.type = options.type || 'Primary'; + if (typeof (item) === 'string') { + return apiClient.getScaledImageUrl(item, options); + } - if (typeof (item) === 'string') { - return apiClient.getScaledImageUrl(item, options); + if (item.ImageTags && item.ImageTags[options.type]) { + options.tag = item.ImageTags[options.type]; + return apiClient.getScaledImageUrl(item.Id, options); + } + + if (options.type === 'Primary') { + if (item.AlbumId && item.AlbumPrimaryImageTag) { + options.tag = item.AlbumPrimaryImageTag; + return apiClient.getScaledImageUrl(item.AlbumId, options); } + } - if (item.ImageTags && item.ImageTags[options.type]) { - options.tag = item.ImageTags[options.type]; - return apiClient.getScaledImageUrl(item.Id, options); + return null; +} + +/** + * Retrieves a backdrop's image URL from the API. + * @param {object} item - Item used to generate the image URL. + * @param {object} options - Options of the image. + * @param {object} apiClient - API client instance used to retrieve the image. + * @returns {null|string} URL of the item's backdrop. + */ +function getBackdropImageUrl(item, options, apiClient) { + options = options || {}; + options.type = options.type || 'Backdrop'; + + // If not resizing, get the original image + if (!options.maxWidth && !options.width && !options.maxHeight && !options.height) { + options.quality = 100; + } + + if (item.BackdropImageTags && item.BackdropImageTags.length) { + options.tag = item.BackdropImageTags[0]; + return apiClient.getScaledImageUrl(item.Id, options); + } + + return null; +} + +/** + * Dispatches a request for an item's image to its respective handler. + * @param {object} item - Item used to generate the image URL. + * @returns {string} URL of the item's image. + */ +function getImgUrl(item, user) { + const apiClient = connectionManager.getApiClient(item.ServerId); + const imageOptions = {}; + + if (item.BackdropImageTags && item.BackdropImageTags.length) { + return getBackdropImageUrl(item, imageOptions, apiClient); + } else { + if (item.MediaType === 'Photo' && user && user.Policy.EnableContentDownloading) { + return apiClient.getItemDownloadUrl(item.Id); } + imageOptions.type = 'Primary'; + return getImageUrl(item, imageOptions, apiClient); + } +} - if (options.type === 'Primary') { - if (item.AlbumId && item.AlbumPrimaryImageTag) { - options.tag = item.AlbumPrimaryImageTag; - return apiClient.getScaledImageUrl(item.AlbumId, options); +/** + * Generates a button using the specified icon, classes and properties. + * @param {string} icon - Name of the material icon on the button + * @param {string} cssClass - CSS classes to assign to the button + * @param {boolean} canFocus - Flag to set the tabindex attribute on the button to -1. + * @param {boolean} autoFocus - Flag to set the autofocus attribute on the button. + * @returns {string} The HTML markup of the button. + */ +function getIcon(icon, cssClass, canFocus, autoFocus) { + const tabIndex = canFocus ? '' : ' tabindex="-1"'; + autoFocus = autoFocus ? ' autofocus' : ''; + return ''; +} + +/** + * Sets the viewport meta tag to enable or disable scaling by the user. + * @param {boolean} scalable - Flag to set the scalability of the viewport. + */ +function setUserScalable(scalable) { + try { + appHost.setUserScalable(scalable); + } catch (err) { + console.error('error in appHost.setUserScalable: ' + err); + } +} + +export default function (options) { + const self = this; + /** Initialized instance of Swiper. */ + let swiperInstance; + /** Initialized instance of the dialog containing the Swiper instance. */ + let dialog; + /** Options of the slideshow components */ + let currentOptions; + /** ID of the timeout used to hide the OSD. */ + let hideTimeout; + /** Last coordinates of the mouse pointer. */ + let lastMouseMoveData; + + /** + * Creates the HTML markup for the dialog and the OSD. + * @param {Object} options - Options used to create the dialog and slideshow. + */ + function createElements(options) { + currentOptions = options; + + dialog = dialogHelper.createDialog({ + exitAnimationDuration: options.interactive ? 400 : 800, + size: 'fullscreen', + autoFocus: false, + scrollY: false, + exitAnimation: 'fadeout', + removeOnClose: true + }); + + dialog.classList.add('slideshowDialog'); + + let html = ''; + + html += '
'; + + if (options.interactive && !layoutManager.tv) { + const actionButtonsOnTop = layoutManager.mobile; + + html += getIcon('keyboard_arrow_left', 'btnSlideshowPrevious slideshowButton hide-mouse-idle-tv', false); + html += getIcon('keyboard_arrow_right', 'btnSlideshowNext slideshowButton hide-mouse-idle-tv', false); + + html += '
'; + if (actionButtonsOnTop) { + if (appHost.supports('filedownload') && options.user && options.user.Policy.EnableContentDownloading) { + html += getIcon('file_download', 'btnDownload slideshowButton', true); + } + if (appHost.supports('sharing')) { + html += getIcon('share', 'btnShare slideshowButton', true); + } } - } + html += getIcon('close', 'slideshowButton btnSlideshowExit hide-mouse-idle-tv', false); + html += '
'; - return null; - } + if (!actionButtonsOnTop) { + html += '
'; - /** - * Retrieves a backdrop's image URL from the API. - * @param {object} item - Item used to generate the image URL. - * @param {object} options - Options of the image. - * @param {object} apiClient - API client instance used to retrieve the image. - * @returns {null|string} URL of the item's backdrop. - */ - function getBackdropImageUrl(item, options, apiClient) { - options = options || {}; - options.type = options.type || 'Backdrop'; + html += getIcon('play_arrow', 'btnSlideshowPause slideshowButton', true, true); + if (appHost.supports('filedownload') && options.user && options.user.Policy.EnableContentDownloading) { + html += getIcon('file_download', 'btnDownload slideshowButton', true); + } + if (appHost.supports('sharing')) { + html += getIcon('share', 'btnShare slideshowButton', true); + } - // If not resizing, get the original image - if (!options.maxWidth && !options.width && !options.maxHeight && !options.height) { - options.quality = 100; - } - - if (item.BackdropImageTags && item.BackdropImageTags.length) { - options.tag = item.BackdropImageTags[0]; - return apiClient.getScaledImageUrl(item.Id, options); - } - - return null; - } - - /** - * Dispatches a request for an item's image to its respective handler. - * @param {object} item - Item used to generate the image URL. - * @returns {string} URL of the item's image. - */ - function getImgUrl(item, user) { - var apiClient = connectionManager.getApiClient(item.ServerId); - var imageOptions = {}; - - if (item.BackdropImageTags && item.BackdropImageTags.length) { - return getBackdropImageUrl(item, imageOptions, apiClient); + html += '
'; + } } else { - if (item.MediaType === 'Photo' && user && user.Policy.EnableContentDownloading) { - return apiClient.getItemDownloadUrl(item.Id); - } - imageOptions.type = 'Primary'; - return getImageUrl(item, imageOptions, apiClient); - } - } - - /** - * Generates a button using the specified icon, classes and properties. - * @param {string} icon - Name of the material icon on the button - * @param {string} cssClass - CSS classes to assign to the button - * @param {boolean} canFocus - Flag to set the tabindex attribute on the button to -1. - * @param {boolean} autoFocus - Flag to set the autofocus attribute on the button. - * @returns {string} The HTML markup of the button. - */ - function getIcon(icon, cssClass, canFocus, autoFocus) { - var tabIndex = canFocus ? '' : ' tabindex="-1"'; - autoFocus = autoFocus ? ' autofocus' : ''; - return ''; - } - - /** - * Sets the viewport meta tag to enable or disable scaling by the user. - * @param {boolean} scalable - Flag to set the scalability of the viewport. - */ - function setUserScalable(scalable) { - try { - appHost.setUserScalable(scalable); - } catch (err) { - console.error('error in appHost.setUserScalable: ' + err); - } - } - - return function (options) { - var self = this; - /** Initialized instance of Swiper. */ - var swiperInstance; - /** Initialized instance of the dialog containing the Swiper instance. */ - var dialog; - /** Options of the slideshow components */ - var currentOptions; - /** ID of the timeout used to hide the OSD. */ - var hideTimeout; - /** Last coordinates of the mouse pointer. */ - var lastMouseMoveData; - - /** - * Creates the HTML markup for the dialog and the OSD. - * @param {Object} options - Options used to create the dialog and slideshow. - */ - function createElements(options) { - currentOptions = options; - - dialog = dialogHelper.createDialog({ - exitAnimationDuration: options.interactive ? 400 : 800, - size: 'fullscreen', - autoFocus: false, - scrollY: false, - exitAnimation: 'fadeout', - removeOnClose: true - }); - - dialog.classList.add('slideshowDialog'); - - var html = ''; - - html += '
'; - - if (options.interactive && !layoutManager.tv) { - var actionButtonsOnTop = layoutManager.mobile; - - html += getIcon('keyboard_arrow_left', 'btnSlideshowPrevious slideshowButton hide-mouse-idle-tv', false); - html += getIcon('keyboard_arrow_right', 'btnSlideshowNext slideshowButton hide-mouse-idle-tv', false); - - html += '
'; - if (actionButtonsOnTop) { - if (appHost.supports('filedownload') && options.user && options.user.Policy.EnableContentDownloading) { - html += getIcon('file_download', 'btnDownload slideshowButton', true); - } - if (appHost.supports('sharing')) { - html += getIcon('share', 'btnShare slideshowButton', true); - } - } - html += getIcon('close', 'slideshowButton btnSlideshowExit hide-mouse-idle-tv', false); - html += '
'; - - if (!actionButtonsOnTop) { - html += '
'; - - html += getIcon('play_arrow', 'btnSlideshowPause slideshowButton', true, true); - if (appHost.supports('filedownload') && options.user && options.user.Policy.EnableContentDownloading) { - html += getIcon('file_download', 'btnDownload slideshowButton', true); - } - if (appHost.supports('sharing')) { - html += getIcon('share', 'btnShare slideshowButton', true); - } - - html += '
'; - } - } else { - html += '

'; - } - - dialog.innerHTML = html; - - if (options.interactive && !layoutManager.tv) { - dialog.querySelector('.btnSlideshowExit').addEventListener('click', function (e) { - dialogHelper.close(dialog); - }); - - var btnPause = dialog.querySelector('.btnSlideshowPause'); - if (btnPause) { - btnPause.addEventListener('click', playPause); - } - - var btnDownload = dialog.querySelector('.btnDownload'); - if (btnDownload) { - btnDownload.addEventListener('click', download); - } - - var btnShare = dialog.querySelector('.btnShare'); - if (btnShare) { - btnShare.addEventListener('click', share); - } - } - - setUserScalable(true); - - dialogHelper.open(dialog).then(function () { - setUserScalable(false); - }); - - inputManager.on(window, onInputCommand); - /* eslint-disable-next-line compat/compat */ - document.addEventListener((window.PointerEvent ? 'pointermove' : 'mousemove'), onPointerMove); - - dialog.addEventListener('close', onDialogClosed); - - loadSwiper(dialog, options); + html += '

'; } - /** - * Handles OSD changes when the autoplay is started. - */ - function onAutoplayStart() { - var btnSlideshowPause = dialog.querySelector('.btnSlideshowPause .material-icons'); - if (btnSlideshowPause) { - btnSlideshowPause.classList.replace('play_arrow', 'pause'); - } - } + dialog.innerHTML = html; - /** - * Handles OSD changes when the autoplay is stopped. - */ - function onAutoplayStop() { - var btnSlideshowPause = dialog.querySelector('.btnSlideshowPause .material-icons'); - if (btnSlideshowPause) { - btnSlideshowPause.classList.replace('pause', 'play_arrow'); - } - } - - /** - * Handles zoom changes. - */ - function onZoomChange(scale, imageEl, slideEl) { - const zoomImage = slideEl.querySelector('.swiper-zoom-fakeimg'); - - if (zoomImage) { - zoomImage.style.width = zoomImage.style.height = scale * 100 + '%'; - - if (scale > 1) { - if (zoomImage.classList.contains('swiper-zoom-fakeimg-hidden')) { - // Await for Swiper style changes - setTimeout(() => { - const callback = () => { - imageEl.removeEventListener(transitionEndEventName, callback); - zoomImage.classList.remove('swiper-zoom-fakeimg-hidden'); - }; - - // Swiper set 'transition-duration: 300ms' for auto zoom - // and 'transition-duration: 0s' for touch zoom - const transitionDuration = parseFloat(imageEl.style.transitionDuration.replace(/[a-z]/i, '')); - - if (transitionDuration > 0) { - imageEl.addEventListener(transitionEndEventName, callback); - } else { - callback(); - } - }, 0); - } - } else { - zoomImage.classList.add('swiper-zoom-fakeimg-hidden'); - } - } - } - - /** - * Initializes the Swiper instance and binds the relevant events. - * @param {HTMLElement} dialog - Element containing the dialog. - * @param {Object} options - Options used to initialize the Swiper instance. - */ - function loadSwiper(dialog, options) { - var slides; - if (currentOptions.slides) { - slides = currentOptions.slides; - } else { - slides = currentOptions.items; - } - - require(['swiper'], function (Swiper) { - swiperInstance = new Swiper(dialog.querySelector('.slideshowSwiperContainer'), { - direction: 'horizontal', - // Loop is disabled due to the virtual slides option not supporting it. - loop: false, - zoom: { - minRatio: 1, - toggle: true - }, - autoplay: !options.interactive, - keyboard: { - enabled: true - }, - preloadImages: true, - slidesPerView: 1, - slidesPerColumn: 1, - initialSlide: options.startIndex || 0, - speed: 240, - navigation: { - nextEl: '.btnSlideshowNext', - prevEl: '.btnSlideshowPrevious' - }, - // Virtual slides reduce memory consumption for large libraries while allowing preloading of images; - virtual: { - slides: slides, - cache: true, - renderSlide: getSwiperSlideHtml, - addSlidesBefore: 1, - addSlidesAfter: 1 - } - }); - - swiperInstance.on('autoplayStart', onAutoplayStart); - swiperInstance.on('autoplayStop', onAutoplayStop); - - if (useFakeZoomImage) { - swiperInstance.on('zoomChange', onZoomChange); - } - }); - } - - /** - * Renders the HTML markup of a slide for an item or a slide. - * @param {Object} item - The item used to render the slide. - * @param {number} index - The index of the item in the Swiper instance. - * @returns {string} The HTML markup of the slide. - */ - function getSwiperSlideHtml(item, index) { - if (currentOptions.slides) { - return getSwiperSlideHtmlFromSlide(item); - } else { - return getSwiperSlideHtmlFromItem(item); - } - } - - /** - * Renders the HTML markup of a slide for an item. - * @param {Object} item - Item used to generate the slide. - * @returns {string} The HTML markup of the slide. - */ - function getSwiperSlideHtmlFromItem(item) { - return getSwiperSlideHtmlFromSlide({ - originalImage: getImgUrl(item, currentOptions.user), - Id: item.Id, - ServerId: item.ServerId - }); - } - - /** - * Renders the HTML markup of a slide for a slide object. - * @param {Object} item - Slide object used to generate the slide. - * @returns {string} The HTML markup of the slide. - */ - function getSwiperSlideHtmlFromSlide(item) { - var html = ''; - html += '
'; - html += '
'; - if (useFakeZoomImage) { - html += `
`; - } - html += ''; - html += '
'; - if (item.title || item.subtitle) { - html += '
'; - html += '
'; - if (item.title) { - html += '

'; - html += item.title; - html += '

'; - } - if (item.description) { - html += '
'; - html += item.description; - html += '
'; - } - html += '
'; - html += '
'; - } - html += '
'; - - return html; - } - - /** - * Fetches the information of the currently displayed slide. - * @returns {null|{itemId: string, shareUrl: string, serverId: string, url: string}} Object containing the information of the currently displayed slide. - */ - function getCurrentImageInfo() { - if (swiperInstance) { - var slide = document.querySelector('.swiper-slide-active'); - - if (slide) { - return { - url: slide.getAttribute('data-original'), - shareUrl: slide.getAttribute('data-original'), - itemId: slide.getAttribute('data-itemid'), - serverId: slide.getAttribute('data-serverid') - }; - } - return null; - } else { - return null; - } - } - - /** - * Starts a download for the currently displayed slide. - */ - function download() { - var imageInfo = getCurrentImageInfo(); - - require(['fileDownloader'], function (fileDownloader) { - fileDownloader.download([imageInfo]); - }); - } - - /** - * Shares the currently displayed slide using the browser's built-in sharing feature. - */ - function share() { - var imageInfo = getCurrentImageInfo(); - - navigator.share({ - url: imageInfo.shareUrl - }); - } - - /** - * Starts the autoplay feature of the Swiper instance. - */ - function play() { - if (swiperInstance.autoplay) { - swiperInstance.autoplay.start(); - } - } - - /** - * Pauses the autoplay feature of the Swiper instance; - */ - function pause() { - if (swiperInstance.autoplay) { - swiperInstance.autoplay.stop(); - } - } - - /** - * Toggles the autoplay feature of the Swiper instance. - */ - function playPause() { - var paused = !dialog.querySelector('.btnSlideshowPause .material-icons').classList.contains('pause'); - if (paused) { - play(); - } else { - pause(); - } - } - - /** - * Closes the dialog and destroys the Swiper instance. - */ - function onDialogClosed() { - var swiper = swiperInstance; - if (swiper) { - swiper.destroy(true, true); - swiperInstance = null; - } - - inputManager.off(window, onInputCommand); - /* eslint-disable-next-line compat/compat */ - document.removeEventListener((window.PointerEvent ? 'pointermove' : 'mousemove'), onPointerMove); - // Shows page scrollbar - document.body.classList.remove('hide-scroll'); - document.body.classList.add('force-scroll'); - } - - /** - * Shows the OSD. - */ - function showOsd() { - var bottom = dialog.querySelector('.slideshowBottomBar'); - if (bottom) { - slideUpToShow(bottom); - startHideTimer(); - } - } - - /** - * Hides the OSD. - */ - function hideOsd() { - var bottom = dialog.querySelector('.slideshowBottomBar'); - if (bottom) { - slideDownToHide(bottom); - } - } - - /** - * Starts the timer used to automatically hide the OSD. - */ - function startHideTimer() { - stopHideTimer(); - hideTimeout = setTimeout(hideOsd, 3000); - } - - /** - * Stops the timer used to automatically hide the OSD. - */ - function stopHideTimer() { - if (hideTimeout) { - clearTimeout(hideTimeout); - hideTimeout = null; - } - } - - /** - * Shows the OSD by sliding it into view. - * @param {HTMLElement} element - Element containing the OSD. - */ - function slideUpToShow(element) { - if (!element.classList.contains('hide')) { - return; - } - - element.classList.remove('hide'); - - var onFinish = function () { - focusManager.focus(element.querySelector('.btnSlideshowPause')); - }; - - if (!element.animate) { - onFinish(); - return; - } - - requestAnimationFrame(function () { - var keyframes = [ - { transform: 'translate3d(0,' + element.offsetHeight + 'px,0)', opacity: '.3', offset: 0 }, - { transform: 'translate3d(0,0,0)', opacity: '1', offset: 1 } - ]; - var timing = { duration: 300, iterations: 1, easing: 'ease-out' }; - element.animate(keyframes, timing).onfinish = onFinish; - }); - } - - /** - * Hides the OSD by sliding it out of view. - * @param {HTMLElement} element - Element containing the OSD. - */ - function slideDownToHide(element) { - if (element.classList.contains('hide')) { - return; - } - - var onFinish = function () { - element.classList.add('hide'); - }; - - if (!element.animate) { - onFinish(); - return; - } - - requestAnimationFrame(function () { - var keyframes = [ - { transform: 'translate3d(0,0,0)', opacity: '1', offset: 0 }, - { transform: 'translate3d(0,' + element.offsetHeight + 'px,0)', opacity: '.3', offset: 1 } - ]; - var timing = { duration: 300, iterations: 1, easing: 'ease-out' }; - element.animate(keyframes, timing).onfinish = onFinish; - }); - } - - /** - * Shows the OSD when moving the mouse pointer or touching the screen. - * @param {Event} event - Pointer movement event. - */ - function onPointerMove(event) { - var pointerType = event.pointerType || (layoutManager.mobile ? 'touch' : 'mouse'); - - if (pointerType === 'mouse') { - var eventX = event.screenX || 0; - var eventY = event.screenY || 0; - - var obj = lastMouseMoveData; - if (!obj) { - lastMouseMoveData = { - x: eventX, - y: eventY - }; - return; - } - - // if coord are same, it didn't move - if (Math.abs(eventX - obj.x) < 10 && Math.abs(eventY - obj.y) < 10) { - return; - } - - obj.x = eventX; - obj.y = eventY; - - showOsd(); - } - } - - /** - * Dispatches keyboard inputs to their proper handlers. - * @param {Event} event - Keyboard input event. - */ - function onInputCommand(event) { - switch (event.detail.command) { - case 'up': - case 'down': - case 'select': - case 'menu': - case 'info': - showOsd(); - break; - case 'play': - play(); - break; - case 'pause': - pause(); - break; - case 'playpause': - playPause(); - break; - default: - break; - } - } - - /** - * Shows the slideshow component. - */ - self.show = function () { - createElements(options); - // Hides page scrollbar - document.body.classList.remove('force-scroll'); - document.body.classList.add('hide-scroll'); - }; - - /** - * Hides the slideshow element. - */ - self.hide = function () { - if (dialog) { + if (options.interactive && !layoutManager.tv) { + dialog.querySelector('.btnSlideshowExit').addEventListener('click', function (e) { dialogHelper.close(dialog); + }); + + const btnPause = dialog.querySelector('.btnSlideshowPause'); + if (btnPause) { + btnPause.addEventListener('click', playPause); } + + const btnDownload = dialog.querySelector('.btnDownload'); + if (btnDownload) { + btnDownload.addEventListener('click', download); + } + + const btnShare = dialog.querySelector('.btnShare'); + if (btnShare) { + btnShare.addEventListener('click', share); + } + } + + setUserScalable(true); + + dialogHelper.open(dialog).then(function () { + setUserScalable(false); + }); + + inputManager.on(window, onInputCommand); + /* eslint-disable-next-line compat/compat */ + document.addEventListener((window.PointerEvent ? 'pointermove' : 'mousemove'), onPointerMove); + + dialog.addEventListener('close', onDialogClosed); + + loadSwiper(dialog, options); + } + + /** + * Handles OSD changes when the autoplay is started. + */ + function onAutoplayStart() { + const btnSlideshowPause = dialog.querySelector('.btnSlideshowPause .material-icons'); + if (btnSlideshowPause) { + btnSlideshowPause.classList.replace('play_arrow', 'pause'); + } + } + + /** + * Handles OSD changes when the autoplay is stopped. + */ + function onAutoplayStop() { + const btnSlideshowPause = dialog.querySelector('.btnSlideshowPause .material-icons'); + if (btnSlideshowPause) { + btnSlideshowPause.classList.replace('pause', 'play_arrow'); + } + } + + /** + * Handles zoom changes. + */ + function onZoomChange(scale, imageEl, slideEl) { + const zoomImage = slideEl.querySelector('.swiper-zoom-fakeimg'); + + if (zoomImage) { + zoomImage.style.width = zoomImage.style.height = scale * 100 + '%'; + + if (scale > 1) { + if (zoomImage.classList.contains('swiper-zoom-fakeimg-hidden')) { + // Await for Swiper style changes + setTimeout(() => { + const callback = () => { + imageEl.removeEventListener(transitionEndEventName, callback); + zoomImage.classList.remove('swiper-zoom-fakeimg-hidden'); + }; + + // Swiper set 'transition-duration: 300ms' for auto zoom + // and 'transition-duration: 0s' for touch zoom + const transitionDuration = parseFloat(imageEl.style.transitionDuration.replace(/[a-z]/i, '')); + + if (transitionDuration > 0) { + imageEl.addEventListener(transitionEndEventName, callback); + } else { + callback(); + } + }, 0); + } + } else { + zoomImage.classList.add('swiper-zoom-fakeimg-hidden'); + } + } + } + + /** + * Initializes the Swiper instance and binds the relevant events. + * @param {HTMLElement} dialog - Element containing the dialog. + * @param {Object} options - Options used to initialize the Swiper instance. + */ + function loadSwiper(dialog, options) { + let slides; + if (currentOptions.slides) { + slides = currentOptions.slides; + } else { + slides = currentOptions.items; + } + + import('swiper').then(({default: Swiper}) => { + swiperInstance = new Swiper(dialog.querySelector('.slideshowSwiperContainer'), { + direction: 'horizontal', + // Loop is disabled due to the virtual slides option not supporting it. + loop: false, + zoom: { + minRatio: 1, + toggle: true + }, + autoplay: !options.interactive, + keyboard: { + enabled: true + }, + preloadImages: true, + slidesPerView: 1, + slidesPerColumn: 1, + initialSlide: options.startIndex || 0, + speed: 240, + navigation: { + nextEl: '.btnSlideshowNext', + prevEl: '.btnSlideshowPrevious' + }, + // Virtual slides reduce memory consumption for large libraries while allowing preloading of images; + virtual: { + slides: slides, + cache: true, + renderSlide: getSwiperSlideHtml, + addSlidesBefore: 1, + addSlidesAfter: 1 + } + }); + + swiperInstance.on('autoplayStart', onAutoplayStart); + swiperInstance.on('autoplayStop', onAutoplayStop); + + if (useFakeZoomImage) { + swiperInstance.on('zoomChange', onZoomChange); + } + }); + } + + /** + * Renders the HTML markup of a slide for an item or a slide. + * @param {Object} item - The item used to render the slide. + * @param {number} index - The index of the item in the Swiper instance. + * @returns {string} The HTML markup of the slide. + */ + function getSwiperSlideHtml(item, index) { + if (currentOptions.slides) { + return getSwiperSlideHtmlFromSlide(item); + } else { + return getSwiperSlideHtmlFromItem(item); + } + } + + /** + * Renders the HTML markup of a slide for an item. + * @param {Object} item - Item used to generate the slide. + * @returns {string} The HTML markup of the slide. + */ + function getSwiperSlideHtmlFromItem(item) { + return getSwiperSlideHtmlFromSlide({ + originalImage: getImgUrl(item, currentOptions.user), + Id: item.Id, + ServerId: item.ServerId + }); + } + + /** + * Renders the HTML markup of a slide for a slide object. + * @param {Object} item - Slide object used to generate the slide. + * @returns {string} The HTML markup of the slide. + */ + function getSwiperSlideHtmlFromSlide(item) { + let html = ''; + html += '
'; + html += '
'; + if (useFakeZoomImage) { + html += `
`; + } + html += ''; + html += '
'; + if (item.title || item.subtitle) { + html += '
'; + html += '
'; + if (item.title) { + html += '

'; + html += item.title; + html += '

'; + } + if (item.description) { + html += '
'; + html += item.description; + html += '
'; + } + html += '
'; + html += '
'; + } + html += '
'; + + return html; + } + + /** + * Fetches the information of the currently displayed slide. + * @returns {null|{itemId: string, shareUrl: string, serverId: string, url: string}} Object containing the information of the currently displayed slide. + */ + function getCurrentImageInfo() { + if (swiperInstance) { + const slide = document.querySelector('.swiper-slide-active'); + + if (slide) { + return { + url: slide.getAttribute('data-original'), + shareUrl: slide.getAttribute('data-original'), + itemId: slide.getAttribute('data-itemid'), + serverId: slide.getAttribute('data-serverid') + }; + } + return null; + } else { + return null; + } + } + + /** + * Starts a download for the currently displayed slide. + */ + function download() { + const imageInfo = getCurrentImageInfo(); + + import('fileDownloader').then(({default: fileDownloader}) => { + fileDownloader.download([imageInfo]); + }); + } + + /** + * Shares the currently displayed slide using the browser's built-in sharing feature. + */ + function share() { + const imageInfo = getCurrentImageInfo(); + + navigator.share({ + url: imageInfo.shareUrl + }); + } + + /** + * Starts the autoplay feature of the Swiper instance. + */ + function play() { + if (swiperInstance.autoplay) { + swiperInstance.autoplay.start(); + } + } + + /** + * Pauses the autoplay feature of the Swiper instance; + */ + function pause() { + if (swiperInstance.autoplay) { + swiperInstance.autoplay.stop(); + } + } + + /** + * Toggles the autoplay feature of the Swiper instance. + */ + function playPause() { + const paused = !dialog.querySelector('.btnSlideshowPause .material-icons').classList.contains('pause'); + if (paused) { + play(); + } else { + pause(); + } + } + + /** + * Closes the dialog and destroys the Swiper instance. + */ + function onDialogClosed() { + const swiper = swiperInstance; + if (swiper) { + swiper.destroy(true, true); + swiperInstance = null; + } + + inputManager.off(window, onInputCommand); + /* eslint-disable-next-line compat/compat */ + document.removeEventListener((window.PointerEvent ? 'pointermove' : 'mousemove'), onPointerMove); + // Shows page scrollbar + document.body.classList.remove('hide-scroll'); + document.body.classList.add('force-scroll'); + } + + /** + * Shows the OSD. + */ + function showOsd() { + const bottom = dialog.querySelector('.slideshowBottomBar'); + if (bottom) { + slideUpToShow(bottom); + startHideTimer(); + } + } + + /** + * Hides the OSD. + */ + function hideOsd() { + const bottom = dialog.querySelector('.slideshowBottomBar'); + if (bottom) { + slideDownToHide(bottom); + } + } + + /** + * Starts the timer used to automatically hide the OSD. + */ + function startHideTimer() { + stopHideTimer(); + hideTimeout = setTimeout(hideOsd, 3000); + } + + /** + * Stops the timer used to automatically hide the OSD. + */ + function stopHideTimer() { + if (hideTimeout) { + clearTimeout(hideTimeout); + hideTimeout = null; + } + } + + /** + * Shows the OSD by sliding it into view. + * @param {HTMLElement} element - Element containing the OSD. + */ + function slideUpToShow(element) { + if (!element.classList.contains('hide')) { + return; + } + + element.classList.remove('hide'); + + const onFinish = function () { + focusManager.focus(element.querySelector('.btnSlideshowPause')); }; + + if (!element.animate) { + onFinish(); + return; + } + + requestAnimationFrame(function () { + const keyframes = [ + { transform: 'translate3d(0,' + element.offsetHeight + 'px,0)', opacity: '.3', offset: 0 }, + { transform: 'translate3d(0,0,0)', opacity: '1', offset: 1 } + ]; + const timing = { duration: 300, iterations: 1, easing: 'ease-out' }; + element.animate(keyframes, timing).onfinish = onFinish; + }); + } + + /** + * Hides the OSD by sliding it out of view. + * @param {HTMLElement} element - Element containing the OSD. + */ + function slideDownToHide(element) { + if (element.classList.contains('hide')) { + return; + } + + const onFinish = function () { + element.classList.add('hide'); + }; + + if (!element.animate) { + onFinish(); + return; + } + + requestAnimationFrame(function () { + const keyframes = [ + { transform: 'translate3d(0,0,0)', opacity: '1', offset: 0 }, + { transform: 'translate3d(0,' + element.offsetHeight + 'px,0)', opacity: '.3', offset: 1 } + ]; + const timing = { duration: 300, iterations: 1, easing: 'ease-out' }; + element.animate(keyframes, timing).onfinish = onFinish; + }); + } + + /** + * Shows the OSD when moving the mouse pointer or touching the screen. + * @param {Event} event - Pointer movement event. + */ + function onPointerMove(event) { + const pointerType = event.pointerType || (layoutManager.mobile ? 'touch' : 'mouse'); + + if (pointerType === 'mouse') { + const eventX = event.screenX || 0; + const eventY = event.screenY || 0; + + const obj = lastMouseMoveData; + if (!obj) { + lastMouseMoveData = { + x: eventX, + y: eventY + }; + return; + } + + // if coord are same, it didn't move + if (Math.abs(eventX - obj.x) < 10 && Math.abs(eventY - obj.y) < 10) { + return; + } + + obj.x = eventX; + obj.y = eventY; + + showOsd(); + } + } + + /** + * Dispatches keyboard inputs to their proper handlers. + * @param {Event} event - Keyboard input event. + */ + function onInputCommand(event) { + switch (event.detail.command) { + case 'up': + case 'down': + case 'select': + case 'menu': + case 'info': + showOsd(); + break; + case 'play': + play(); + break; + case 'pause': + pause(); + break; + case 'playpause': + playPause(); + break; + default: + break; + } + } + + /** + * Shows the slideshow component. + */ + self.show = function () { + createElements(options); + // Hides page scrollbar + document.body.classList.remove('force-scroll'); + document.body.classList.add('hide-scroll'); }; -}); + + /** + * Hides the slideshow element. + */ + self.hide = function () { + if (dialog) { + dialogHelper.close(dialog); + } + }; +} From b7d29eae1b971eae3be7483109da3297f1c7df3f Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 6 Aug 2020 09:02:45 +0100 Subject: [PATCH 077/124] Migration of subtitleeditor to ES6 module --- package.json | 1 + .../subtitleeditor/subtitleeditor.js | 725 +++++++++--------- 2 files changed, 369 insertions(+), 357 deletions(-) diff --git a/package.json b/package.json index 1d7cf770d3..a7dcc43114 100644 --- a/package.json +++ b/package.json @@ -160,6 +160,7 @@ "src/components/search/searchresults.js", "src/components/settingshelper.js", "src/components/shortcuts.js", + "src/components/subtitleeditor/subtitleeditor.js", "src/components/subtitlesettings/subtitleappearancehelper.js", "src/components/subtitlesettings/subtitlesettings.js", "src/components/syncPlay/groupSelectionMenu.js", diff --git a/src/components/subtitleeditor/subtitleeditor.js b/src/components/subtitleeditor/subtitleeditor.js index 7df24b5da8..dfd7f9446c 100644 --- a/src/components/subtitleeditor/subtitleeditor.js +++ b/src/components/subtitleeditor/subtitleeditor.js @@ -1,426 +1,437 @@ -define(['dialogHelper', 'require', 'layoutManager', 'globalize', 'userSettings', 'connectionManager', 'loading', 'focusManager', 'dom', 'apphost', 'emby-select', 'listViewStyle', 'paper-icon-button-light', 'css!./../formdialog', 'material-icons', 'css!./subtitleeditor', 'emby-button', 'flexStyles'], function (dialogHelper, require, layoutManager, globalize, userSettings, connectionManager, loading, focusManager, dom, appHost) { - 'use strict'; +import dialogHelper from 'dialogHelper'; +import layoutManager from 'layoutManager'; +import globalize from 'globalize'; +import * as userSettings from 'userSettings'; +import connectionManager from 'connectionManager'; +import loading from 'loading'; +import focusManager from 'focusManager'; +import dom from 'dom'; +import 'emby-select'; +import 'listViewStyle'; +import 'paper-icon-button-light'; +import 'css!./../formdialog'; +import 'material-icons'; +import 'css!./subtitleeditor'; +import 'emby-button'; +import 'flexStyles'; - loading = loading.default || loading; +let currentItem; +let hasChanges; - var currentItem; - var hasChanges; +function downloadRemoteSubtitles(context, id) { + let url = 'Items/' + currentItem.Id + '/RemoteSearch/Subtitles/' + id; - function downloadRemoteSubtitles(context, id) { - var url = 'Items/' + currentItem.Id + '/RemoteSearch/Subtitles/' + id; + let apiClient = connectionManager.getApiClient(currentItem.ServerId); + apiClient.ajax({ - var apiClient = connectionManager.getApiClient(currentItem.ServerId); - apiClient.ajax({ + type: 'POST', + url: apiClient.getUrl(url) - type: 'POST', - url: apiClient.getUrl(url) + }).then(function () { + hasChanges = true; + + import('toast').then(({default: toast}) => { + toast(globalize.translate('MessageDownloadQueued')); + }); + + focusManager.autoFocus(context); + }); +} + +function deleteLocalSubtitle(context, index) { + let msg = globalize.translate('MessageAreYouSureDeleteSubtitles'); + + import('confirm').then(({default: confirm}) => { + confirm({ + + title: globalize.translate('ConfirmDeletion'), + text: msg, + confirmText: globalize.translate('Delete'), + primary: 'delete' }).then(function () { - hasChanges = true; + loading.show(); - require(['toast'], function (toast) { - toast(globalize.translate('MessageDownloadQueued')); - }); + let itemId = currentItem.Id; + let url = 'Videos/' + itemId + '/Subtitles/' + index; - focusManager.autoFocus(context); - }); - } + let apiClient = connectionManager.getApiClient(currentItem.ServerId); - function deleteLocalSubtitle(context, index) { - var msg = globalize.translate('MessageAreYouSureDeleteSubtitles'); + apiClient.ajax({ - require(['confirm'], function (confirm) { - confirm.default({ - - title: globalize.translate('ConfirmDeletion'), - text: msg, - confirmText: globalize.translate('Delete'), - primary: 'delete' + type: 'DELETE', + url: apiClient.getUrl(url) }).then(function () { - loading.show(); - - var itemId = currentItem.Id; - var url = 'Videos/' + itemId + '/Subtitles/' + index; - - var apiClient = connectionManager.getApiClient(currentItem.ServerId); - - apiClient.ajax({ - - type: 'DELETE', - url: apiClient.getUrl(url) - - }).then(function () { - hasChanges = true; - reload(context, apiClient, itemId); - }); + hasChanges = true; + reload(context, apiClient, itemId); }); }); - } + }); +} - function fillSubtitleList(context, item) { - var streams = item.MediaStreams || []; +function fillSubtitleList(context, item) { + let streams = item.MediaStreams || []; - var subs = streams.filter(function (s) { - return s.Type === 'Subtitle'; - }); + let subs = streams.filter(function (s) { + return s.Type === 'Subtitle'; + }); - var html = ''; + let html = ''; - if (subs.length) { - html += '

' + globalize.translate('MySubtitles') + '

'; + if (subs.length) { + html += '

' + globalize.translate('MySubtitles') + '

'; - html += '
'; + html += '
'; - html += subs.map(function (s) { - var itemHtml = ''; + html += subs.map(function (s) { + let itemHtml = ''; - var tagName = layoutManager.tv ? 'button' : 'div'; - var className = layoutManager.tv && s.Path ? 'listItem listItem-border btnDelete' : 'listItem listItem-border'; + let tagName = layoutManager.tv ? 'button' : 'div'; + let className = layoutManager.tv && s.Path ? 'listItem listItem-border btnDelete' : 'listItem listItem-border'; - if (layoutManager.tv) { - className += ' listItem-focusscale listItem-button'; - } - - className += ' listItem-noborder'; - - itemHtml += '<' + tagName + ' class="' + className + '" data-index="' + s.Index + '">'; - - itemHtml += ''; - - itemHtml += '
'; - - itemHtml += '
'; - itemHtml += s.DisplayTitle || ''; - itemHtml += '
'; - - if (s.Path) { - itemHtml += '
' + (s.Path) + '
'; - } - - itemHtml += ''; - itemHtml += '
'; - - if (!layoutManager.tv) { - if (s.Path) { - itemHtml += ''; - } - } - - itemHtml += ''; - - return itemHtml; - }).join(''); - - html += '
'; - } - - var elem = context.querySelector('.subtitleList'); - - if (subs.length) { - elem.classList.remove('hide'); - } else { - elem.classList.add('hide'); - } - elem.innerHTML = html; - } - - function fillLanguages(context, apiClient, languages) { - var selectLanguage = context.querySelector('#selectLanguage'); - - selectLanguage.innerHTML = languages.map(function (l) { - return ''; - }); - - var lastLanguage = userSettings.get('subtitleeditor-language'); - if (lastLanguage) { - selectLanguage.value = lastLanguage; - } else { - apiClient.getCurrentUser().then(function (user) { - var lang = user.Configuration.SubtitleLanguagePreference; - - if (lang) { - selectLanguage.value = lang; - } - }); - } - } - - function renderSearchResults(context, results) { - var lastProvider = ''; - var html = ''; - - if (!results.length) { - context.querySelector('.noSearchResults').classList.remove('hide'); - context.querySelector('.subtitleResults').innerHTML = ''; - loading.hide(); - return; - } - - context.querySelector('.noSearchResults').classList.add('hide'); - - for (var i = 0, length = results.length; i < length; i++) { - var result = results[i]; - - var provider = result.ProviderName; - - if (provider !== lastProvider) { - if (i > 0) { - html += '
'; - } - html += '

' + provider + '

'; - html += '
'; - lastProvider = provider; - } - - var tagName = layoutManager.tv ? 'button' : 'div'; - var className = layoutManager.tv ? 'listItem listItem-border btnOptions' : 'listItem listItem-border'; if (layoutManager.tv) { className += ' listItem-focusscale listItem-button'; } - html += '<' + tagName + ' class="' + className + '" data-subid="' + result.Id + '">'; + className += ' listItem-noborder'; - html += ''; + itemHtml += '<' + tagName + ' class="' + className + '" data-index="' + s.Index + '">'; - var bodyClass = result.Comment || result.IsHashMatch ? 'three-line' : 'two-line'; + itemHtml += ''; - html += '
'; + itemHtml += '
'; - html += '
' + (result.Name) + '
'; - html += '
'; + itemHtml += '
'; + itemHtml += s.DisplayTitle || ''; + itemHtml += '
'; - if (result.Format) { - html += '' + globalize.translate('FormatValue', result.Format) + ''; + if (s.Path) { + itemHtml += '
' + (s.Path) + '
'; } - if (result.DownloadCount != null) { - html += '' + globalize.translate('DownloadsValue', result.DownloadCount) + ''; - } - html += '
'; - - if (result.Comment) { - html += '
' + (result.Comment) + '
'; - } - - if (result.IsHashMatch) { - html += '
' + globalize.translate('PerfectMatch') + '
'; - } - - html += '
'; + itemHtml += ''; + itemHtml += '
'; if (!layoutManager.tv) { - html += ''; + if (s.Path) { + itemHtml += ''; + } } - html += ''; + itemHtml += ''; + + return itemHtml; + }).join(''); + + html += '
'; + } + + let elem = context.querySelector('.subtitleList'); + + if (subs.length) { + elem.classList.remove('hide'); + } else { + elem.classList.add('hide'); + } + elem.innerHTML = html; +} + +function fillLanguages(context, apiClient, languages) { + let selectLanguage = context.querySelector('#selectLanguage'); + + selectLanguage.innerHTML = languages.map(function (l) { + return ''; + }); + + let lastLanguage = userSettings.get('subtitleeditor-language'); + if (lastLanguage) { + selectLanguage.value = lastLanguage; + } else { + apiClient.getCurrentUser().then(function (user) { + let lang = user.Configuration.SubtitleLanguagePreference; + + if (lang) { + selectLanguage.value = lang; + } + }); + } +} + +function renderSearchResults(context, results) { + let lastProvider = ''; + let html = ''; + + if (!results.length) { + context.querySelector('.noSearchResults').classList.remove('hide'); + context.querySelector('.subtitleResults').innerHTML = ''; + loading.hide(); + return; + } + + context.querySelector('.noSearchResults').classList.add('hide'); + + for (let i = 0, length = results.length; i < length; i++) { + let result = results[i]; + + let provider = result.ProviderName; + + if (provider !== lastProvider) { + if (i > 0) { + html += '
'; + } + html += '

' + provider + '

'; + html += '
'; + lastProvider = provider; } - if (results.length) { - html += '
'; + let tagName = layoutManager.tv ? 'button' : 'div'; + let className = layoutManager.tv ? 'listItem listItem-border btnOptions' : 'listItem listItem-border'; + if (layoutManager.tv) { + className += ' listItem-focusscale listItem-button'; } - var elem = context.querySelector('.subtitleResults'); - elem.innerHTML = html; + html += '<' + tagName + ' class="' + className + '" data-subid="' + result.Id + '">'; + + html += ''; + + let bodyClass = result.Comment || result.IsHashMatch ? 'three-line' : 'two-line'; + + html += '
'; + + html += '
' + (result.Name) + '
'; + html += '
'; + + if (result.Format) { + html += '' + globalize.translate('FormatValue', result.Format) + ''; + } + + if (result.DownloadCount != null) { + html += '' + globalize.translate('DownloadsValue', result.DownloadCount) + ''; + } + html += '
'; + + if (result.Comment) { + html += '
' + (result.Comment) + '
'; + } + + if (result.IsHashMatch) { + html += '
' + globalize.translate('PerfectMatch') + '
'; + } + + html += '
'; + + if (!layoutManager.tv) { + html += ''; + } + + html += ''; + } + + if (results.length) { + html += '
'; + } + + let elem = context.querySelector('.subtitleResults'); + elem.innerHTML = html; + + loading.hide(); +} + +function searchForSubtitles(context, language) { + userSettings.set('subtitleeditor-language', language); + + loading.show(); + + let apiClient = connectionManager.getApiClient(currentItem.ServerId); + let url = apiClient.getUrl('Items/' + currentItem.Id + '/RemoteSearch/Subtitles/' + language); + + apiClient.getJSON(url).then(function (results) { + renderSearchResults(context, results); + }); +} + +function reload(context, apiClient, itemId) { + context.querySelector('.noSearchResults').classList.add('hide'); + + function onGetItem(item) { + currentItem = item; + + fillSubtitleList(context, item); + let file = item.Path || ''; + let index = Math.max(file.lastIndexOf('/'), file.lastIndexOf('\\')); + if (index > -1) { + file = file.substring(index + 1); + } + + if (file) { + context.querySelector('.pathValue').innerHTML = file; + context.querySelector('.originalFile').classList.remove('hide'); + } else { + context.querySelector('.pathValue').innerHTML = ''; + context.querySelector('.originalFile').classList.add('hide'); + } loading.hide(); } - function searchForSubtitles(context, language) { - userSettings.set('subtitleeditor-language', language); + if (typeof itemId === 'string') { + apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(onGetItem); + } else { + onGetItem(itemId); + } +} - loading.show(); +function onSearchSubmit(e) { + let form = this; - var apiClient = connectionManager.getApiClient(currentItem.ServerId); - var url = apiClient.getUrl('Items/' + currentItem.Id + '/RemoteSearch/Subtitles/' + language); + let lang = form.querySelector('#selectLanguage', form).value; - apiClient.getJSON(url).then(function (results) { - renderSearchResults(context, results); - }); + searchForSubtitles(dom.parentWithClass(form, 'formDialogContent'), lang); + + e.preventDefault(); + return false; +} + +function onSubtitleListClick(e) { + let btnDelete = dom.parentWithClass(e.target, 'btnDelete'); + if (btnDelete) { + let index = btnDelete.getAttribute('data-index'); + let context = dom.parentWithClass(btnDelete, 'subtitleEditorDialog'); + deleteLocalSubtitle(context, index); + } +} + +function onSubtitleResultsClick(e) { + let subtitleId; + let context; + + let btnOptions = dom.parentWithClass(e.target, 'btnOptions'); + if (btnOptions) { + subtitleId = btnOptions.getAttribute('data-subid'); + context = dom.parentWithClass(btnOptions, 'subtitleEditorDialog'); + showDownloadOptions(btnOptions, context, subtitleId); } - function reload(context, apiClient, itemId) { - context.querySelector('.noSearchResults').classList.add('hide'); + let btnDownload = dom.parentWithClass(e.target, 'btnDownload'); + if (btnDownload) { + subtitleId = btnDownload.getAttribute('data-subid'); + context = dom.parentWithClass(btnDownload, 'subtitleEditorDialog'); + downloadRemoteSubtitles(context, subtitleId); + } +} - function onGetItem(item) { - currentItem = item; +function showDownloadOptions(button, context, subtitleId) { + let items = []; - fillSubtitleList(context, item); - var file = item.Path || ''; - var index = Math.max(file.lastIndexOf('/'), file.lastIndexOf('\\')); - if (index > -1) { - file = file.substring(index + 1); + items.push({ + name: globalize.translate('Download'), + id: 'download' + }); + + import('actionsheet').then(({default: actionsheet}) => { + actionsheet.show({ + items: items, + positionTo: button + + }).then(function (id) { + switch (id) { + case 'download': + downloadRemoteSubtitles(context, subtitleId); + break; + default: + break; } + }); + }); +} - if (file) { - context.querySelector('.pathValue').innerHTML = file; - context.querySelector('.originalFile').classList.remove('hide'); - } else { - context.querySelector('.pathValue').innerHTML = ''; - context.querySelector('.originalFile').classList.add('hide'); - } +function centerFocus(elem, horiz, on) { + import('scrollHelper').then(({default: scrollHelper}) => { + let fn = on ? 'on' : 'off'; + scrollHelper.centerFocus[fn](elem, horiz); + }); +} - loading.hide(); - } +function showEditorInternal(itemId, serverId, template) { + hasChanges = false; - if (typeof itemId === 'string') { - apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(onGetItem); + let apiClient = connectionManager.getApiClient(serverId); + return apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(function (item) { + let dialogOptions = { + removeOnClose: true, + scrollY: false + }; + + if (layoutManager.tv) { + dialogOptions.size = 'fullscreen'; } else { - onGetItem(itemId); - } - } - - function onSearchSubmit(e) { - var form = this; - - var lang = form.querySelector('#selectLanguage', form).value; - - searchForSubtitles(dom.parentWithClass(form, 'formDialogContent'), lang); - - e.preventDefault(); - return false; - } - - function onSubtitleListClick(e) { - var btnDelete = dom.parentWithClass(e.target, 'btnDelete'); - if (btnDelete) { - var index = btnDelete.getAttribute('data-index'); - var context = dom.parentWithClass(btnDelete, 'subtitleEditorDialog'); - deleteLocalSubtitle(context, index); - } - } - - function onSubtitleResultsClick(e) { - var subtitleId; - var context; - - var btnOptions = dom.parentWithClass(e.target, 'btnOptions'); - if (btnOptions) { - subtitleId = btnOptions.getAttribute('data-subid'); - context = dom.parentWithClass(btnOptions, 'subtitleEditorDialog'); - showDownloadOptions(btnOptions, context, subtitleId); + dialogOptions.size = 'small'; } - var btnDownload = dom.parentWithClass(e.target, 'btnDownload'); - if (btnDownload) { - subtitleId = btnDownload.getAttribute('data-subid'); - context = dom.parentWithClass(btnDownload, 'subtitleEditorDialog'); - downloadRemoteSubtitles(context, subtitleId); + let dlg = dialogHelper.createDialog(dialogOptions); + + dlg.classList.add('formDialog'); + dlg.classList.add('subtitleEditorDialog'); + + dlg.innerHTML = globalize.translateHtml(template, 'core'); + + dlg.querySelector('.originalSubtitleFileLabel').innerHTML = globalize.translate('File'); + + dlg.querySelector('.subtitleSearchForm').addEventListener('submit', onSearchSubmit); + + let btnSubmit = dlg.querySelector('.btnSubmit'); + + if (layoutManager.tv) { + centerFocus(dlg.querySelector('.formDialogContent'), false, true); + dlg.querySelector('.btnSearchSubtitles').classList.add('hide'); + } else { + btnSubmit.classList.add('hide'); } - } - function showDownloadOptions(button, context, subtitleId) { - var items = []; + let editorContent = dlg.querySelector('.formDialogContent'); - items.push({ - name: globalize.translate('Download'), - id: 'download' + dlg.querySelector('.subtitleList').addEventListener('click', onSubtitleListClick); + dlg.querySelector('.subtitleResults').addEventListener('click', onSubtitleResultsClick); + + apiClient.getCultures().then(function (languages) { + fillLanguages(editorContent, apiClient, languages); }); - require(['actionsheet'], function (actionsheet) { - actionsheet.show({ - items: items, - positionTo: button - - }).then(function (id) { - switch (id) { - case 'download': - downloadRemoteSubtitles(context, subtitleId); - break; - default: - break; - } - }); + dlg.querySelector('.btnCancel').addEventListener('click', function () { + dialogHelper.close(dlg); }); - } - - function centerFocus(elem, horiz, on) { - require(['scrollHelper'], function (scrollHelper) { - var fn = on ? 'on' : 'off'; - scrollHelper.centerFocus[fn](elem, horiz); - }); - } - - function showEditorInternal(itemId, serverId, template) { - hasChanges = false; - - var apiClient = connectionManager.getApiClient(serverId); - return apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(function (item) { - 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('subtitleEditorDialog'); - - dlg.innerHTML = globalize.translateHtml(template, 'core'); - - dlg.querySelector('.originalSubtitleFileLabel').innerHTML = globalize.translate('File'); - - dlg.querySelector('.subtitleSearchForm').addEventListener('submit', onSearchSubmit); - - var btnSubmit = dlg.querySelector('.btnSubmit'); - - if (layoutManager.tv) { - centerFocus(dlg.querySelector('.formDialogContent'), false, true); - dlg.querySelector('.btnSearchSubtitles').classList.add('hide'); - } else { - btnSubmit.classList.add('hide'); - } - - var editorContent = dlg.querySelector('.formDialogContent'); - - dlg.querySelector('.subtitleList').addEventListener('click', onSubtitleListClick); - dlg.querySelector('.subtitleResults').addEventListener('click', onSubtitleResultsClick); - - apiClient.getCultures().then(function (languages) { - fillLanguages(editorContent, apiClient, languages); - }); - - dlg.querySelector('.btnCancel').addEventListener('click', function () { - dialogHelper.close(dlg); - }); - - return new Promise(function (resolve, reject) { - dlg.addEventListener('close', function () { - if (layoutManager.tv) { - centerFocus(dlg.querySelector('.formDialogContent'), false, false); - } - - if (hasChanges) { - resolve(); - } else { - reject(); - } - }); - - dialogHelper.open(dlg); - - reload(editorContent, apiClient, item); - }); - }); - } - - function showEditor(itemId, serverId) { - loading.show(); return new Promise(function (resolve, reject) { - require(['text!./subtitleeditor.template.html'], function (template) { - showEditorInternal(itemId, serverId, template).then(resolve, reject); - }); - }); - } + dlg.addEventListener('close', function () { + if (layoutManager.tv) { + centerFocus(dlg.querySelector('.formDialogContent'), false, false); + } - return { - show: showEditor - }; -}); + if (hasChanges) { + resolve(); + } else { + reject(); + } + }); + + dialogHelper.open(dlg); + + reload(editorContent, apiClient, item); + }); + }); +} + +function showEditor(itemId, serverId) { + loading.show(); + + return new Promise(function (resolve, reject) { + import('text!./subtitleeditor.template.html').then(({default: template}) => { + showEditorInternal(itemId, serverId, template).then(resolve, reject); + }); + }); +} + +export default { + show: showEditor +}; From cdb07e94436052d3d3fcc7b7f61f9160a935a93d Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 6 Aug 2020 09:03:30 +0100 Subject: [PATCH 078/124] Migration of subtitlesync to ES6 module --- package.json | 1 + src/components/subtitlesync/subtitlesync.js | 219 ++++++++++---------- 2 files changed, 111 insertions(+), 109 deletions(-) diff --git a/package.json b/package.json index a7dcc43114..6409981f9c 100644 --- a/package.json +++ b/package.json @@ -161,6 +161,7 @@ "src/components/settingshelper.js", "src/components/shortcuts.js", "src/components/subtitleeditor/subtitleeditor.js", + "src/components/subtitlesync/subtitlesync.js", "src/components/subtitlesettings/subtitleappearancehelper.js", "src/components/subtitlesettings/subtitlesettings.js", "src/components/syncPlay/groupSelectionMenu.js", diff --git a/src/components/subtitlesync/subtitlesync.js b/src/components/subtitlesync/subtitlesync.js index 203d88535f..efb2087a1b 100644 --- a/src/components/subtitlesync/subtitlesync.js +++ b/src/components/subtitlesync/subtitlesync.js @@ -1,147 +1,148 @@ -define(['playbackManager', 'layoutManager', 'text!./subtitlesync.template.html', 'css!./subtitlesync'], function (playbackManager, layoutManager, template, css) { - 'use strict'; +import playbackManager from 'playbackManager'; +import layoutManager from 'layoutManager'; +import template from 'text!./subtitlesync.template.html'; +import 'css!./subtitlesync'; - playbackManager = playbackManager.default || playbackManager; +let player; +let subtitleSyncSlider; +let subtitleSyncTextField; +let subtitleSyncCloseButton; +let subtitleSyncContainer; - var player; - var subtitleSyncSlider; - var subtitleSyncTextField; - var subtitleSyncCloseButton; - var subtitleSyncContainer; +function init(instance) { + const parent = document.createElement('div'); + document.body.appendChild(parent); + parent.innerHTML = template; - function init(instance) { - var parent = document.createElement('div'); - document.body.appendChild(parent); - parent.innerHTML = template; + subtitleSyncSlider = parent.querySelector('.subtitleSyncSlider'); + subtitleSyncTextField = parent.querySelector('.subtitleSyncTextField'); + subtitleSyncCloseButton = parent.querySelector('.subtitleSync-closeButton'); + subtitleSyncContainer = parent.querySelector('.subtitleSyncContainer'); - subtitleSyncSlider = parent.querySelector('.subtitleSyncSlider'); - subtitleSyncTextField = parent.querySelector('.subtitleSyncTextField'); - subtitleSyncCloseButton = parent.querySelector('.subtitleSync-closeButton'); - subtitleSyncContainer = parent.querySelector('.subtitleSyncContainer'); + if (layoutManager.tv) { + subtitleSyncSlider.classList.add('focusable'); + // HACK: Delay to give time for registered element attach (Firefox) + setTimeout(function () { + subtitleSyncSlider.enableKeyboardDragging(); + }, 0); + } - if (layoutManager.tv) { - subtitleSyncSlider.classList.add('focusable'); - // HACK: Delay to give time for registered element attach (Firefox) - setTimeout(function () { - subtitleSyncSlider.enableKeyboardDragging(); - }, 0); - } + subtitleSyncContainer.classList.add('hide'); - subtitleSyncContainer.classList.add('hide'); + subtitleSyncTextField.updateOffset = function (offset) { + this.textContent = offset + 's'; + }; - subtitleSyncTextField.updateOffset = function(offset) { - this.textContent = offset + 's'; - }; + subtitleSyncTextField.addEventListener('click', function () { + // keep focus to prevent fade with osd + this.hasFocus = true; + }); - subtitleSyncTextField.addEventListener('click', function () { + subtitleSyncTextField.addEventListener('keydown', function (event) { + if (event.key === 'Enter') { + // if input key is enter search for float pattern + let inputOffset = /[-+]?\d+\.?\d*/g.exec(this.textContent); + if (inputOffset) { + inputOffset = inputOffset[0]; + + // replace current text by considered offset + this.textContent = inputOffset + 's'; + + inputOffset = parseFloat(inputOffset); + // set new offset + playbackManager.setSubtitleOffset(inputOffset, player); + // synchronize with slider value + subtitleSyncSlider.updateOffset( + getPercentageFromOffset(inputOffset)); + } else { + this.textContent = (playbackManager.getPlayerSubtitleOffset(player) || 0) + 's'; + } + this.hasFocus = false; + event.preventDefault(); + } else { // keep focus to prevent fade with osd this.hasFocus = true; - }); - - subtitleSyncTextField.addEventListener('keydown', function(event) { - if (event.key === 'Enter') { - // if input key is enter search for float pattern - var inputOffset = /[-+]?\d+\.?\d*/g.exec(this.textContent); - if (inputOffset) { - inputOffset = inputOffset[0]; - - // replace current text by considered offset - this.textContent = inputOffset + 's'; - - inputOffset = parseFloat(inputOffset); - // set new offset - playbackManager.setSubtitleOffset(inputOffset, player); - // synchronize with slider value - subtitleSyncSlider.updateOffset( - getPercentageFromOffset(inputOffset)); - } else { - this.textContent = (playbackManager.getPlayerSubtitleOffset(player) || 0) + 's'; - } - this.hasFocus = false; + if (event.key.match(/[+-\d.s]/) === null) { event.preventDefault(); - } else { - // keep focus to prevent fade with osd - this.hasFocus = true; - if (event.key.match(/[+-\d.s]/) === null) { - event.preventDefault(); - } } + } - // FIXME: TV layout will require special handling for navigation keys. But now field is not focusable - event.stopPropagation(); - }); + // FIXME: TV layout will require special handling for navigation keys. But now field is not focusable + event.stopPropagation(); + }); - subtitleSyncTextField.blur = function() { - // prevent textfield to blur while element has focus - if (!this.hasFocus && this.prototype) { - this.prototype.blur(); - } - }; + subtitleSyncTextField.blur = function () { + // prevent textfield to blur while element has focus + if (!this.hasFocus && this.prototype) { + this.prototype.blur(); + } + }; - subtitleSyncSlider.updateOffset = function(percent) { - // default value is 0s = 50% - this.value = percent === undefined ? 50 : percent; - }; + subtitleSyncSlider.updateOffset = function (percent) { + // default value is 0s = 50% + this.value = percent === undefined ? 50 : percent; + }; - subtitleSyncSlider.addEventListener('change', function () { - // set new offset - playbackManager.setSubtitleOffset(getOffsetFromPercentage(this.value), player); - // synchronize with textField value - subtitleSyncTextField.updateOffset( - getOffsetFromPercentage(this.value)); - }); + subtitleSyncSlider.addEventListener('change', function () { + // set new offset + playbackManager.setSubtitleOffset(getOffsetFromPercentage(this.value), player); + // synchronize with textField value + subtitleSyncTextField.updateOffset( + getOffsetFromPercentage(this.value)); + }); - subtitleSyncSlider.getBubbleHtml = function (value) { - var newOffset = getOffsetFromPercentage(value); - return '

' + + subtitleSyncSlider.getBubbleHtml = function (value) { + const newOffset = getOffsetFromPercentage(value); + return '

' + (newOffset > 0 ? '+' : '') + parseFloat(newOffset) + 's' + '

'; - }; + }; - subtitleSyncCloseButton.addEventListener('click', function() { - playbackManager.disableShowingSubtitleOffset(player); - SubtitleSync.prototype.toggle('forceToHide'); - }); + subtitleSyncCloseButton.addEventListener('click', function () { + playbackManager.disableShowingSubtitleOffset(player); + SubtitleSync.prototype.toggle('forceToHide'); + }); - instance.element = parent; - } + instance.element = parent; +} - function getOffsetFromPercentage(value) { - // convert percent to fraction - var offset = (value - 50) / 50; - // multiply by offset min/max range value (-x to +x) : - offset *= 30; - return offset.toFixed(1); - } +function getOffsetFromPercentage(value) { + // convert percent to fraction + let offset = (value - 50) / 50; + // multiply by offset min/max range value (-x to +x) : + offset *= 30; + return offset.toFixed(1); +} - function getPercentageFromOffset(value) { - // divide by offset min/max range value (-x to +x) : - var percentValue = value / 30; - // convert fraction to percent - percentValue *= 50; - percentValue += 50; - return Math.min(100, Math.max(0, percentValue.toFixed())); - } +function getPercentageFromOffset(value) { + // divide by offset min/max range value (-x to +x) : + let percentValue = value / 30; + // convert fraction to percent + percentValue *= 50; + percentValue += 50; + return Math.min(100, Math.max(0, percentValue.toFixed())); +} - function SubtitleSync(currentPlayer) { +class SubtitleSync { + constructor(currentPlayer) { player = currentPlayer; init(this); } - SubtitleSync.prototype.destroy = function() { + destroy() { SubtitleSync.prototype.toggle('forceToHide'); if (player) { playbackManager.disableShowingSubtitleOffset(player); playbackManager.setSubtitleOffset(0, player); } - var elem = this.element; + const elem = this.element; if (elem) { elem.parentNode.removeChild(elem); this.element = null; } - }; + } - SubtitleSync.prototype.toggle = function(action) { + toggle(action) { if (player && playbackManager.supportSubtitleOffset(player)) { /* eslint-disable no-fallthrough */ switch (action) { @@ -170,7 +171,7 @@ define(['playbackManager', 'layoutManager', 'text!./subtitlesync.template.html', } /* eslint-enable no-fallthrough */ } - }; + } +} - return SubtitleSync; -}); +export default SubtitleSync; From 3680f478457f10acf2ee94d8c23c60b395b9a86e Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 6 Aug 2020 09:46:23 +0100 Subject: [PATCH 079/124] Remove duplicates from package.json --- package.json | 7 ------- 1 file changed, 7 deletions(-) diff --git a/package.json b/package.json index ad4c8e1109..2ac1702d50 100644 --- a/package.json +++ b/package.json @@ -191,12 +191,6 @@ "src/controllers/music/musicplaylists.js", "src/controllers/music/musicrecommended.js", "src/controllers/music/songs.js", - "src/controllers/dashboard/plugins/repositories.js", - "src/controllers/movies/moviecollections.js", - "src/controllers/movies/moviegenres.js", - "src/controllers/movies/movies.js", - "src/controllers/movies/moviesrecommended.js", - "src/controllers/movies/movietrailers.js", "src/controllers/dashboard/mediaLibrary.js", "src/controllers/dashboard/metadataImages.js", "src/controllers/dashboard/metadatanfo.js", @@ -218,7 +212,6 @@ "src/controllers/edititemmetadata.js", "src/controllers/favorites.js", "src/controllers/hometab.js", - "src/controllers/dashboard/plugins/repositories.js", "src/controllers/movies/moviecollections.js", "src/controllers/movies/moviegenres.js", "src/controllers/movies/movies.js", From d61fa6bdf654dbe68ae1b14d11341e09a0cab65e Mon Sep 17 00:00:00 2001 From: sharkykh Date: Thu, 6 Aug 2020 12:20:42 +0000 Subject: [PATCH 080/124] Translated using Weblate (Hebrew) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/he/ --- src/strings/he.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/strings/he.json b/src/strings/he.json index c61bfe9e1a..7c27d407c3 100644 --- a/src/strings/he.json +++ b/src/strings/he.json @@ -800,5 +800,6 @@ "LabelForgotPasswordUsernameHelp": "הכנס/י את שם המשתמש שלך, אם את/ה זוכר/ת אותו.", "LabelFont": "גופן:", "LabelFolder": "תיקייה:", - "LabelFileOrUrl": "קובץ או כתובת אינטרנט:" + "LabelFileOrUrl": "קובץ או כתובת אינטרנט:", + "Season": "עונה" } From 3204eefc5c8f9cf0dbe2bace960f8f6ef50a57cd Mon Sep 17 00:00:00 2001 From: sharkykh Date: Thu, 6 Aug 2020 12:26:20 +0000 Subject: [PATCH 081/124] Translated using Weblate (Hebrew) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/he/ --- src/strings/he.json | 45 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/src/strings/he.json b/src/strings/he.json index 7c27d407c3..838c1c2ac8 100644 --- a/src/strings/he.json +++ b/src/strings/he.json @@ -157,7 +157,7 @@ "LabelBirthYear": "שנת לידה:", "LabelBlastMessageInterval": "תדירות הודעות דחיפה", "LabelBlastMessageIntervalHelp": "מגדיר את משך הזמן בשניות בין הודעות דחיפה של השרת.", - "LabelCachePath": "נתיב cache:", + "LabelCachePath": "נתיב מטמון:", "LabelChannels": "ערוצים:", "LabelCollection": "אוספים:", "LabelCommunityRating": "דירוג הקהילה:", @@ -204,7 +204,7 @@ "LabelMaxScreenshotsPerItem": "מספר תמונות מסך מקסימאלי לפריט:", "LabelMessageTitle": "כותרת הודעה:", "LabelMetadataDownloadLanguage": "שפת הורדה מועדפת:", - "LabelMetadataPath": "נתיב Metadata:", + "LabelMetadataPath": "נתיב מטא-דאטה:", "LabelMinBackdropDownloadWidth": "רוחב תמונת רקע מינימאלי להורדה:", "LabelMinResumeDuration": "משך המשכה מינימאלי:", "LabelMinResumeDurationHelp": "קובץ קצר מזה לא יהיה ניתן להמשך ניגון מנקודת העצירה", @@ -302,7 +302,7 @@ "OptionAlbumArtist": "אמן אלבום", "OptionAllUsers": "כל המשתמשים", "OptionAllowLinkSharing": "אפשר שיתוף ברשתות חברתיות", - "OptionAllowMediaPlayback": "הרשה נגינת מדיה", + "OptionAllowMediaPlayback": "אפשר ניגון מדיה", "OptionAllowUserToManageServer": "אפשר למשתמש זה לנהל את השרת", "OptionArtist": "אמן", "OptionAscending": "סדר עולה", @@ -419,7 +419,7 @@ "SeriesSettings": "הגדרות סדרה", "SeriesYearToPresent": "{0} - היום", "ServerNameIsRestarting": "שרת Jellyfin - {0} מופעל מחדש.", - "ServerNameIsShuttingDown": "שרת Jellyfin - {0} נכבה.", + "ServerNameIsShuttingDown": "שרת Jellyfin - {0} בתהליך כיבוי.", "ServerUpdateNeeded": "שרת אמבי זה צריך להיות מעודכן. כדי להוריד את הגרסה העדכנית ביותר, בקר בכתובת {0}", "Settings": "הגדרות", "SettingsSaved": "ההגדרות נשמרו.", @@ -801,5 +801,40 @@ "LabelFont": "גופן:", "LabelFolder": "תיקייה:", "LabelFileOrUrl": "קובץ או כתובת אינטרנט:", - "Season": "עונה" + "Season": "עונה", + "OptionEnableAccessFromAllDevices": "אפשר גישה מכל המכשירים", + "Primary": "ראשי", + "Menu": "תפריט", + "LiveTV": "שידורים חיים", + "ManageLibrary": "נהל ספרייה", + "Logo": "לוגו", + "OptionDateAddedImportTime": "השתמש בתאריך הסריקה לתוך הספרייה", + "OptionDateAddedFileTime": "השתמש בתאריך יצירת הקובץ", + "OptionBlockTrailers": "קדימונים", + "OptionBlockMusic": "מוזיקה", + "OptionBlockLiveTvChannels": "ערוצי שידורים חיים", + "OptionBlockBooks": "ספרים", + "OptionAllowRemoteSharedDevices": "אפשר שליטה מרחוק על מכשירים משותפים", + "OptionAllowRemoteControlOthers": "אפשר שליטה מרחוק על משתמשים אחרים", + "SelectAdminUsername": "נא לבחור שם משתמש עבור חשבון המנהל.", + "OptionHideUserFromLoginHelp": "שימושי עבור חשבונות פרטיים או חשבונות מנהל מוסתרים. המשתמש יצטרך להזין את שם המשתמש והסיסמה ידנית על מנת להתחבר.", + "MessagePlayAccessRestricted": "התוכן הזה לא ניתן לניגון כרגע. למידע נוסף, נא ליצור קשר עם מנהל המערכת שלך.", + "MessageContactAdminToResetPassword": "נא ליצור קשר עם מנהל המערכת שלך על מנת לאפס את הסיסמה שלך.", + "HeaderAdmin": "מנהל", + "TabDisplay": "תצוגה", + "HeaderDisplay": "תצוגה", + "Suggestions": "המלצות", + "MessageSyncPlayNoGroupsAvailable": "אין קבוצות זמינות. התחל לנגן משהו קודם.", + "OptionHomeVideos": "תמונות", + "Home": "בית", + "LabelServerName": "שם השרת:", + "TabPlugins": "תוספים", + "MessageNoPluginsInstalled": "אין לך תוספים מותקנים.", + "MessageNoAvailablePlugins": "אין תוספים זמינים.", + "TabLogs": "יומני רישום", + "LabelLogs": "יומני רישום:", + "TabNetworking": "תקשורת", + "TabDVR": "ממיר-מקליט", + "HeaderDVR": "ממיר-מקליט", + "LabelScheduledTaskLastRan": "רץ לאחרונה {0}, במשך {1}." } From cc02cb01cf6df9fedcf381c0b03c2c3f6f275334 Mon Sep 17 00:00:00 2001 From: sharkykh Date: Thu, 6 Aug 2020 12:52:34 +0000 Subject: [PATCH 082/124] Translated using Weblate (Hebrew) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/he/ --- src/strings/he.json | 47 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/src/strings/he.json b/src/strings/he.json index 838c1c2ac8..58eb6ee1b4 100644 --- a/src/strings/he.json +++ b/src/strings/he.json @@ -314,7 +314,7 @@ "OptionContinuing": "ממשיך", "OptionCriticRating": "ציון מבקרים", "OptionCustomUsers": "מותאם אישית", - "OptionDaily": "יומי", + "OptionDaily": "כל יום", "OptionDateAdded": "תאריך הוספה", "OptionDatePlayed": "תאריך ניגון", "OptionDescending": "סדר יורד", @@ -337,7 +337,7 @@ "OptionHasSubtitles": "כתוביות", "OptionHasThemeSong": "שיר נושא", "OptionHasThemeVideo": "סרט נושא", - "OptionHasTrailer": "טריילר", + "OptionHasTrailer": "קדימון", "OptionHideUser": "הסתר משתמש זה בחלון ההתחברות", "OptionImdbRating": "דירוג IMDb", "OptionLikes": "נבחרים", @@ -348,27 +348,27 @@ "OptionOnAppStartup": "בהפעלת התוכנה", "OptionOnInterval": "כל פרק זמן", "OptionParentalRating": "דירוג בקרת הורים", - "OptionPlayCount": "מספר השמעות", + "OptionPlayCount": "כמות ניגונים", "OptionPlayed": "נוגן", - "OptionPremiereDate": "תאריך שידור ראשון", + "OptionPremiereDate": "תאריך בכורה", "OptionProfileAudio": "צליל", "OptionProfilePhoto": "תמונה", "OptionProfileVideo": "וידאו", "OptionProfileVideoAudio": "צליל וידאו", "OptionResumable": "ניתן להמשיך", - "OptionRuntime": "משך", + "OptionRuntime": "זמן ריצה", "OptionSaturday": "שבת", "OptionSpecialEpisode": "ספיישלים", "OptionSunday": "ראשון", "OptionThursday": "חמישי", - "OptionTrackName": "שם השיר", + "OptionTrackName": "שם הרצועה", "OptionTuesday": "שלישי", "OptionTvdbRating": "דירוג TVDB", "OptionUnairedEpisode": "פרקים שלא שודרו", "OptionUnplayed": "לא נוגן", "OptionWakeFromSleep": "הער ממצב שינה", "OptionWednesday": "רביעי", - "OptionWeekly": "שבועי", + "OptionWeekly": "כל שבוע", "OriginalAirDateValue": "תאריך אוויר מקורי: {0}", "Overview": "סקירה כללית", "PackageInstallCancelled": "ההתקנה של {0} (גירסה {1}) בוטלה.", @@ -836,5 +836,36 @@ "TabNetworking": "תקשורת", "TabDVR": "ממיר-מקליט", "HeaderDVR": "ממיר-מקליט", - "LabelScheduledTaskLastRan": "רץ לאחרונה {0}, במשך {1}." + "LabelScheduledTaskLastRan": "רץ לאחרונה {0}, במשך {1}.", + "LabelTheme": "ערכת נושא:", + "LabelTextSize": "גודל טקסט:", + "LabelTextColor": "צבע טקסט:", + "LabelSyncPlayAccessNone": "מבוטל עבור משתמש זה", + "LabelSyncPlayAccessJoinGroups": "אפשר למשתמש להצטרף לקבוצות", + "LabelSyncPlayAccessCreateAndJoinGroups": "אפשר למשתמש ליצור קבוצות ולהצטרף אליהן", + "LabelSyncPlayLeaveGroup": "עזוב קבוצה", + "LabelSyncPlayNewGroupDescription": "צור קבוצה חדשה", + "LabelSyncPlayNewGroup": "קבוצה חדשה", + "MoreFromValue": "עוד מ{0}", + "Writers": "תסריטאים", + "DailyAt": "כל יום ב-{0}", + "OptionWeekends": "סופי שבוע", + "OptionWeekdays": "ימי חול", + "Unplayed": "לא נוגן", + "OptionSubstring": "מחרוזת משנה", + "OptionReleaseDate": "תאריך שחרור", + "OptionRegex": "ביטוי-רגולרי", + "OptionRandom": "אקראי", + "OptionPoster": "פוסטר", + "OptionNone": "כלום", + "OptionMax": "מקסימום", + "List": "רשימה", + "OptionList": "רשימה", + "OptionIsSD": "הבחנה רגילה (SD)", + "OptionIsHD": "הבחנה גבוהה (HD)", + "OptionExternallyDownloaded": "הורדה חיצונית", + "OptionEveryday": "כל יום", + "OptionEnableExternalContentInSuggestions": "הפעל תוכן חיצוני בהמלצות", + "OptionEnableAccessToAllLibraries": "אפשר גישה לכל הספריות", + "OptionEnableAccessToAllChannels": "אפשר גישה לכל הערוצים" } From 88992fd5e73f619834a34003c35be991ed3d1cf8 Mon Sep 17 00:00:00 2001 From: Verkhaliak Anton Date: Thu, 6 Aug 2020 14:19:23 +0000 Subject: [PATCH 083/124] Translated using Weblate (Russian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/ru/ --- src/strings/ru.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings/ru.json b/src/strings/ru.json index 24d5cdd90f..199d0f24ae 100644 --- a/src/strings/ru.json +++ b/src/strings/ru.json @@ -1521,7 +1521,7 @@ "EnableBlurHashHelp": "Рисунки, которые всё ещё загружаются, будут отображаться с размытым заполнением", "EnableBlurHash": "Включить размытые заполнители для изображений", "ButtonSyncPlay": "SyncPlay", - "ButtonCast": "В ролях", + "ButtonCast": "Транслировать", "TabRepositories": "Репозитории", "MessageNoGenresAvailable": "Разрешить поставщикам метаданных получать жанры из интернета.", "MessageAddRepository": "Если вы хотите добавить репозиторий, нажмите кнопку рядом с заголовком и заполните необходимую информацию.", From e694f0918aa10a211a4b12a8930de0dfd4667334 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 6 Aug 2020 15:55:10 +0100 Subject: [PATCH 084/124] 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 7a7e4857658ca35ab61cd6683da159c6ef8c7701 Mon Sep 17 00:00:00 2001 From: Ahmed Mohamed Date: Thu, 6 Aug 2020 15:20:55 +0000 Subject: [PATCH 085/124] Translated using Weblate (Arabic) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/ar/ --- src/strings/ar.json | 118 ++++++++++++++++++++++++++++++-------------- 1 file changed, 82 insertions(+), 36 deletions(-) diff --git a/src/strings/ar.json b/src/strings/ar.json index a9d3d2de41..eb7d6f4249 100644 --- a/src/strings/ar.json +++ b/src/strings/ar.json @@ -99,9 +99,9 @@ "DeleteUserConfirmation": "هل انت متاكد من انك تريد حذف هذا المستخدم ؟", "DeviceAccessHelp": "هذه الميزة تنطبق حصرياً على الأجهزة التي يمكن التعرف عليها فردياً ولن تمنع المتصفح من الدخول عليها. ترشيح الوصول لأجهزة المستخدم ستمنع المستخدمين من استعمال الأجهزة الجديدة إلى أن يتم اعتمادهم من هنا.", "DrmChannelsNotImported": "القنوات المجهزة بإدارة الحقوق الرقمية DRM لن تورّد.", - "EasyPasswordHelp": "الرمز الشخصي الميسرالخاص بك يمكنك من الاتصال إلى خادم مكتبتك، عبر تطبيقات أمبي على الأجهزة أو الدخول على حسابك في الشبكة الداخلية.", + "EasyPasswordHelp": "الرمز pin الميسر الخاص بك يستخدم للوصول بدون اتصل عبر التطبيقات المدعومة أو الدخول على حسابك في الشبكة الداخلية.", "EnablePhotos": "عرض الصور", - "EnablePhotosHelp": "سيتم اكتشاف الصور وعرضها مع ملفات الوسائط الأخرى", + "EnablePhotosHelp": "سيتم اكتشاف الصور وعرضها مع ملفات الوسائط الأخرى.", "ErrorAddingListingsToSchedulesDirect": "كان هناك خطأ في إضافة الاصطفاف لخدمة \"Schedules Direct\" الخاصة بك. خدمة \"Schedules Direct\" لا تسمح إلا بعدد محدود من الاصطفافات لكل حساب. قد تحتاج إلى تسجيل الدخول إلى موقع \"Schedules Direct\" لإزالة الاصطفافات الأخرى من حسابك قبل المتابعة.", "ErrorAddingMediaPathToVirtualFolder": "كان هناك خطأ في إضافة مسار الوسائط. الرجاء التأكد من صحة المسار وأن خادم أمبي لديه صلاحية الوصول إلى الموقع.", "ErrorAddingTunerDevice": "كان هناك خطأ في إضافة جهاز المولف. الرجاء التأكد من صلاحية الوصول إليه ثم عاود المحاولة.", @@ -111,7 +111,7 @@ "ErrorPleaseSelectLineup": "الرجاء اختيار اصطفاف ثم المحاولة مرة أخرى. إن لم تتوفر أية اصطفافات، فالرجاء التأكد من اسم المستخدم وكلمة المرور الخاصة بك، وتأكد من صحة رمزك البريدي.", "ErrorSavingTvProvider": "كان هناك خطأ في حفظ مزود التلفزة. الرجاء التأكد من صلاحية الوصول إليه ثم عاود المحاولة.", "ExitFullscreen": "الخروج من الشاشة الكاملة", - "ExtractChapterImagesHelp": "استخلاص صور الأبواب سيسمح لتطبيقات أمبي أن تظهر لك قوائم تصويرية لتبويبات الأفلام. هذه العملية قد تكون بطيئة، وتستغل قدرة المعالج بشكل ملحوظ، وقد تحتاج إلى حيازة بضعة غيغابايتات من مساحة التخزين بشكل مؤقت. هذه المهمة تعمل خلال عملية استكشاف المقاطع المرئية، كما يمكن أن تحدد لتكون مهمة ليلية مجدولة. يمكنك جدولة العملية من قسم جدولة المهام. لا ينصح بتشغيل هذه المهمة خلال ساعات الذروة من دخول المستخدمين.", + "ExtractChapterImagesHelp": "استخلاص صور الفصول سيسمح للتطبيقات أن تظهر لك قوائم تصويرية لتبويبات الأفلام. هذه العملية قد تكون بطيئة، وتستغل موارد الجهاز بشكل ملحوظ، وقد تحتاج إلى حيازة بضعة غيغابايتات من مساحة التخزين بشكل مؤقت. هذه المهمة تعمل خلال عملية استكشاف المقاطع المرئية، كما يمكن أن تحدد لتكون مهمة ليلية مجدولة. يمكنك جدولة العملية من قسم جدولة المهام. لا ينصح بتشغيل هذه المهمة خلال ساعات الذروة من دخول المستخدمين.", "FFmpegSavePathNotFound": "لم نستطع تحديد موقع ffmpeg باستخدام المسار الذي أدخلته. سوف نحتاج تطبيق FFprobe أيضاً ويجب أن يتواجد في نفس المكان. إن هذه الأجزاء تكون بالعادة محزومة معاً في نفس ملف الإنزال. الرجاء التأكد من المسار المدخل والمحاولة مرة أخرى.", "FastForward": "التقديم السريع", "FileNotFound": "الملف غير موجود.", @@ -129,7 +129,7 @@ "GuideProviderSelectListings": "إختر المبوبات", "H264CrfHelp": "معامل المعدل الثابت CRF هو الجودة الافتراضية لإعدادات مشفر x264. بإمكانك إعطاء قيمة تتراوح بين 0 و 51، وكلما قلت القيمة فسينتج عن ذلك جودة أفضل (على حساب حجم تخزين أعلى). القيم المعقول تتراوح بين 18 و 28. الافتراضي لـ x264 هي 23، لذا فبإمكانك استخدام هذه القيمة كنقطة بداية.", "EncoderPresetHelp": "اختر قيمة أعلى لتحسين السرة والأداء وقيمة أقل لتحسين الجودة.", - "HardwareAccelerationWarning": "تمكين التسريع بعتاد الحاسوب قد يتسبب في عدم استقرار بعض أنواع الأنظمة. تأكد من أن نظام التشغيل الخاص بك محدث إلى آخر نسخة وأن سواقات الفيديو محدثة أيضاً. إذا واجهت أية صعوبات في تسغيل الفيديو بعد تمكين هذه الخاصية، فعليك إرجاع الإعداد إلى وضعية آلي.", + "HardwareAccelerationWarning": "تمكين التسريع بعتاد الحاسوب قد يتسبب في عدم استقرار بعض أنواع الأنظمة. تأكد من أن نظام التشغيل الخاص بك محدث إلى آخر نسخة وأن سواقات الفيديو محدثة أيضاً. إذا واجهت أية صعوبات في تسغيل الفيديو بعد تمكين هذه الخاصية، فعليك إرجاع الإعداد إلى وضعية بلا None.", "HeaderAccessSchedule": "جدول الدخولات", "HeaderAccessScheduleHelp": "إنشئ جدول دخولات لكي تتمكن من تحديد ساعات للدخول.", "HeaderActiveDevices": "الأجهزة المفعّلة", @@ -145,7 +145,7 @@ "HeaderAllowMediaDeletionFrom": "السماح بحذف الوسائط من قبل", "HeaderApiKey": "مفتاح API", "HeaderApiKeys": "مفاتيح API", - "HeaderApiKeysHelp": "التطبيقات الخارجية تحتاج أن تمتلك مفتاح api لكي تتصل بخادم أمبي. هذه المفاتيح تُصدر عن طريق تسجيل الدخول بحساب أمبي، أو عن طريق منح التطبيق مفتاحاً أصدر يدوياً.", + "HeaderApiKeysHelp": "التطبيقات الخارجية تحتاج أن تمتلك مفتاح api لكي تتصل بالخادم. هذه المفاتيح تُصدر عن طريق تسجيل الدخول بمستخدم عادي، أو عن طريق منح التطبيق مفتاحاً أصدر يدوياً.", "HeaderApp": "التطبيق", "HeaderAudioSettings": "إعدادات الصوت", "HeaderBooks": "الكتب", @@ -232,7 +232,7 @@ "HeaderPreferredMetadataLanguage": "اللغة المفضلة لواصفات البيانات", "HeaderProfile": "الحساب", "HeaderProfileInformation": "معلومات العريضة", - "HeaderProfileServerSettingsHelp": "هذه القيم ستتحكم في كيفية تقديم شكل خادم أمبي في الجهاز", + "HeaderProfileServerSettingsHelp": "هذه القيم ستتحكم في كيفية تقديم شكل الخادم في للعملاء.", "HeaderRecentlyPlayed": "تم تشغيله مؤخراً", "HeaderRecordingPostProcessing": "تطبيق ما-بعد-المعالجة للتسجيل", "HeaderRemoteControl": "التحكم عن بعد", @@ -254,7 +254,7 @@ "HeaderSelectServerCachePath": "إختر مسار كاشة الخادم", "HeaderSelectServerCachePathHelp": "تصفح أو أدخل المسار الذي ترغب أن يُستخدم كاشة لملفات الخادم. يجب أن يكون هذا المجلد قابل للكتابة فيه.", "HeaderSelectTranscodingPath": "إختر المسار المؤقت للتشفير البيني", - "HeaderSelectTranscodingPathHelp": "تصفح أو أدخل المسار الذي ترغب أن يُستخدم للملفات المؤقتة للتشفير البيني. يجب أن يكون هذا المجلد قابل للكتابة فيه.", + "HeaderSelectTranscodingPathHelp": "تصفح أو أدخل المسار الذي ترغب أن يُستخدم لملفات التشفير البيني. يجب أن يكون هذا المجلد قابل للكتابة فيه.", "HeaderSendMessage": "أرسل رسالة", "HeaderSeries": "المسلسلات", "HeaderServerSettings": "إعدادات الخادم", @@ -291,17 +291,17 @@ "HeaderXmlSettings": "إعدادات xml", "HeaderYears": "السنوات", "HeadersFolders": "مجلدات", - "ImportFavoriteChannelsHelp": "عند التفعيل، فقط القنوات التي علّمت في المفضلة على هذا المولف ستورد إلى النظام.", - "ImportMissingEpisodesHelp": "عند التمكين، المعلومات الناقصة للحلقات ستورّد إلى قاعدة بيانات أمبي وستعرض داخل المواسم والمسلسلات. قد تتسبب هذه بأوقات أطول بكثير عند تمشيط المكنبات.", + "ImportFavoriteChannelsHelp": "فقط القنوات التي علّمت في المفضلة على جهاز المولف ستورد.", + "ImportMissingEpisodesHelp": "المعلومات الناقصة للحلقات ستورّد إلى قاعدة بياناتك وستعرض داخل المواسم والمسلسلات. قد تتسبب هذه بأوقات أطول بكثير عند تمشيط المكتبات.", "LabelAbortedByServerShutdown": "(تم إهماله بسبب عملية إغلاق الخادم)", "LabelAccessDay": "يوم الأسبوع:", - "LabelAccessEnd": "تاريخ النهاية", - "LabelAccessStart": "تاريخ البداية", + "LabelAccessEnd": "وقت النهاية:", + "LabelAccessStart": "وقت البداية:", "LabelAirDays": "أيام البث:", "LabelAirTime": "وقت البث:", - "LabelAlbum": "الألبوم", + "LabelAlbum": "الألبوم:", "LabelAlbumArtHelp": "PN المستخدمة في رسومات الألبوم، داخل سمة dlna:profileID في upnp:albumArtURI. بعض الأجهزة تحتاج قيمة محددة، مهما كان حجم الصورة.", - "LabelAlbumArtMaxHeight": "الارتفاع الأقصى لرسومات الألبوم", + "LabelAlbumArtMaxHeight": "الارتفاع الأقصى لرسومات الألبوم:", "LabelAlbumArtMaxHeightHelp": "الدقة القصوى لرسومات الألبوم المظهّرة عبر سمة upnp:albumArtURI.", "LabelAlbumArtMaxWidth": "العرض الأقصى لرسوم الألبوم:", "LabelAlbumArtMaxWidthHelp": "الدقة القصوى لرسومات الألبوم المظهّرة عبر سمة upnp:albumArtURI.", @@ -310,42 +310,42 @@ "LabelAll": "الجميع", "LabelAllowHWTranscoding": "السماح بالتشفير البيني بعتاد الحاسب", "LabelAppName": "اسم التطبيق", - "LabelAppNameExample": "مثال: Sickbeard، NzbDrone", + "LabelAppNameExample": "مثال: Sickbeard، Sonarr", "LabelArtists": "الفنانون:", - "LabelArtistsHelp": "فصل الاستعمالات المتعددة ;", + "LabelArtistsHelp": "افصل بين الفنانين ب ; فاصلة منقوطة.", "LabelAudioLanguagePreference": "اللغة المفضلة للصوت:", "LabelBindToLocalNetworkAddress": "إربطه إلى عنوان شبكة محلي:", - "LabelBindToLocalNetworkAddressHelp": "هذا خياري. امتطي عنوان الآي بي المحلي لربطه بخادم http. إذا ترك فارغاً، فإن الخادم سيربطه بجميع العناوين المتاحة. تغيير هذه القيمة يتطلب إعادة تشغيل خادم أمبي.", - "LabelBlastMessageInterval": "فترات بث رسالة قيد التشغيل (بالثواني)", - "LabelBlastMessageIntervalHelp": "يحدد الفترة بالثواني بين يث رسائل قيد التشغيل", - "LabelCache": "ذاكرة الكاشة", - "LabelCachePath": "مسار ذاكرة الكاشة:", - "LabelCachePathHelp": "حدد موقع مخصص لملفات كاشة الخادم، مثل الصور وغيرها. أترك هذه الخانة فارغة لاستعمال القيمة التلقائية.", + "LabelBindToLocalNetworkAddressHelp": "تجاوز عنوان الآي بي المحلي لربطه بخادم http. إذا ترك فارغاً، فإن الخادم سيربطه بجميع العناوين المتاحة. تغيير هذه القيمة يتطلب إعادة تشغيل خادم جيلليفن.", + "LabelBlastMessageInterval": "فترات بث رسالة قيد التشغيل", + "LabelBlastMessageIntervalHelp": "يحدد الفترة بالثواني بين بث رسائل قيد التشغيل.", + "LabelCache": "مَخبأ (كاش):", + "LabelCachePath": "مسار ذاكرة الكاش:", + "LabelCachePathHelp": "حدد موقع مخصص لملفات الخادم المؤقتة، مثل الصور وغيرها. أترك هذه الخانة فارغة لاستعمال القيمة الافتراضية.", "LabelCancelled": "تم الإلغاء", - "LabelCollection": "المجموعة", + "LabelCollection": "المجموعة:", "LabelCommunityRating": "تقييم المجتمع:", - "LabelContentType": "نوع المحتوى", + "LabelContentType": "نوع المحتوى:", "LabelCountry": "البلد:", "LabelCurrentPassword": "كلمة السر الحالية:", - "LabelCustomCertificatePath": "مسار شهادة ssl مخصص:", + "LabelCustomCertificatePath": "مسار شهادة SSL المخصص:", "LabelCustomCertificatePathHelp": "مسار ملف PKCS # 12 يحتوي على شهادة ومفتاح خاص لتمكين دعم TLS على مجال مخصص.", - "LabelCustomCss": "تنيسق CSS مخصوص:", - "LabelCustomCssHelp": "طبق تنسيق css مخصوصة لواجهة الويب.", + "LabelCustomCss": "تنيسق CSS مخصص:", + "LabelCustomCssHelp": "طبق تنسيقك css المخصص لواجهة الويب.", "LabelCustomDeviceDisplayName": "اسم العرض:", - "LabelCustomDeviceDisplayNameHelp": "أذكر اسم عرض مخصوص أو أتركه فارغاً لاستخدام", + "LabelCustomDeviceDisplayNameHelp": "أذكر اسم عرض مخصوص أو أتركه فارغاً لاستخدام الاسم المبلغ من الجهاز.", "LabelDateAddedBehavior": "كيف يتصرف المحتوى الجديد نحو \"تاريخ الإضافة\" الخاص به:", - "LabelDateAddedBehaviorHelp": "إذا استعرضت قيمة واصفات البيانا فإنها سوف تستخدم قبل أن تستخدم أي من هذه الخيارات.", + "LabelDateAddedBehaviorHelp": "إذا اخذت واصفات البيانات قيمة، فإنها سوف تستخدم قبل أن تستخدم أي من هذه الخيارات.", "LabelDay": "اليوم:", "LabelDeathDate": "تاريخ الوفاة:", - "LabelDefaultUser": "المستخدم الافتراضي", + "LabelDefaultUser": "المستخدم الافتراضي:", "LabelDefaultUserHelp": "لتحديد مكتبة المستخدم التي تظهر على الأجهزة المتصلة. بإمكان الامتطاء على هذه القيمة لكل جهاز عن طريق عرائض الأجهزة.", "LabelDeviceDescription": "وصف الجهاز", - "LabelDidlMode": "طور didl:", + "LabelDidlMode": "طور DIDL:", "LabelDisplayMissingEpisodesWithinSeasons": "أظهر الحلقات المفقودة في مجلدات المواسم", "LabelDisplayName": "الاسم المعروض:", "LabelDisplaySpecialsWithinSeasons": "أظهر الحلقات الخاصة في المواسم التي بثت فيها", "LabelDownMixAudioScale": "تعزيز الصوت عند تقليل توزيع قنوات الصوت:", - "LabelDownMixAudioScaleHelp": "تعزيز الصوت عند تقليل توزيع قنوات الصوت. حدد القيمة بـ 1 للمحافظة على القيمة الأصلية للصوت.", + "LabelDownMixAudioScaleHelp": "تعزيز الصوت عند تقليل توزيع قنوات الصوت. حدد القيمة ب 1 للمحافظة على القيمة الأصلية للصوت.", "LabelDownloadLanguages": "إنزال اللغة:", "LabelDynamicExternalId": "معرفة {0}:", "LabelEasyPinCode": "الرمز الشخصي الميسر:", @@ -354,8 +354,8 @@ "LabelEnableAutomaticPortMap": "فعل الخاصية الآلية في التوفيق بين المنافذ", "LabelEnableAutomaticPortMapHelp": "حاول التوفيق بين المنفذ العالمي والمنفذ المحلي آلياً باستخدام آلية UPnP. هذه الخاصية قد لا تعمل مع بعض أنواع الراوترات.", "LabelEnableBlastAliveMessages": "بث رسائل قيد التشغيل", - "LabelEnableBlastAliveMessagesHelp": "فعل هذه الخاصية إذا كان الخادم لا يكتشف بكفاءة من قبل أجهزة UPnP الأخرى على شبكتك", - "LabelEnableDlnaClientDiscoveryInterval": "فترات استكشاف العملاء (بالثواني)", + "LabelEnableBlastAliveMessagesHelp": "فعل هذه الخاصية إذا كان الخادم لا يكتشف بكفاءة من قبل أجهزة UPnP الأخرى على شبكتك.", + "LabelEnableDlnaClientDiscoveryInterval": "فترات استكشاف العملاء", "LabelEnableDlnaClientDiscoveryIntervalHelp": "يحدد الفترة بالثواني بين عمليات بحث SSDP التي يقوم بها أمبي.", "LabelEnableDlnaDebugLogging": "تفعيل خاصية كشوفات أخطاء DLNA", "LabelEnableDlnaDebugLoggingHelp": "هذه ستنشئ سجلات كشفية ضخمة ولا ينبغي تفعيلها إلا عند الحاجة إليها بغرض استكشاف الأخطاء وحصرها.", @@ -930,7 +930,7 @@ "Backdrops": "خلفيات متغيرة للصفحة", "Backdrop": "خلفية متغيرة للصفحة", "Auto": "تلقائي", - "AuthProviderHelp": "حدد مقدم المصادقات ليتم استخدامه لمصادقة كلمة مرور هذا المستخدم.", + "AuthProviderHelp": "اختار مقدم المصادقة ليتم استخدامه لمصادقة كلمة مرور هذا المستخدم.", "AroundTime": "حول", "AttributeNew": "جديد", "AspectRatio": "نسبة العرض الى الارتفاع", @@ -1038,7 +1038,7 @@ "Director": "المخرج", "DirectPlaying": "بث بدون تحويل الصيغة", "DirectStreaming": "البث المباشر", - "DirectStreamHelp2": "البث المباشر للملف يستخدم طاقة معالجة قليلة جدًا دون أي خسارة في جودة الفيديو.", + "DirectStreamHelp2": "البث المباشر للملف يستخدم قوة معالجة قليلة جدًا دون أي خسارة في جودة الفيديو.", "DirectStreamHelp1": "الوسائط متوافقة مع الجهاز فيما يتعلق بالدقة ونوع الوسائط (H.264 ، AC3 ، إلخ) ، ولكنها في حاوية ملفات غير متوافقة (mkv ، avi ، wmv ، إلخ). سيتم إعادة حزم الفيديو في الوقت الحقيقي قبل بثه إلى الجهاز.", "DetectingDevices": "يتم الكشف عن الأجهزة", "Desktop": "سطح المكتب", @@ -1139,5 +1139,51 @@ "Dislike": "لم يعجبنى", "ButtonSyncPlay": "SyncPlay", "ExtraLarge": "كبير جدا", - "EnableNextVideoInfoOverlayHelp": "في نهاية الفيديو, عرض معلومات عن الفيديو القادم في قائمة التشغيل." + "EnableNextVideoInfoOverlayHelp": "في نهاية الفيديو, عرض معلومات عن الفيديو القادم في قائمة التشغيل.", + "LabelDroppedFrames": "الاطارات الساقطة:", + "LabelDropImageHere": "اسقط صورة هنا، او ضغط تصفح.", + "LabelDisplayOrder": "ترتيب المعروض:", + "LabelDisplayMode": "وضع المعروض:", + "LabelDisplayLanguageHelp": "ترجمة جيلليفين هو مشروع مستمر.", + "LabelDisplayLanguage": "لغة العرض:", + "LabelDiscNumber": "رقم القرص:", + "LabelDeinterlaceMethod": "طريقة تقليل التشابك:", + "LabelDefaultScreen": "الشاشة الافتراضية:", + "LabelDateTimeLocale": "وقت و تاريخ محلي:", + "LabelDateAdded": "تاريخ الاضافة:", + "LabelCustomRating": "تقييم مخصص:", + "LabelCriticRating": "تقييم النقاد:", + "LabelCorruptedFrames": "الإطارات التالفة:", + "LabelChannels": "القنوات:", + "LabelCertificatePasswordHelp": "اذا تطلبت شهادتك الامنية كلمة مرور، من فضلك ادخلها هنا.", + "LabelCertificatePassword": "كلمة مرور الشهادة الامنية:", + "LabelBurnSubtitles": "الترجمات المحروقة:", + "LabelBlockContentWithTags": "احجب العناصر بالعلامات:", + "LabelBitrate": "معدل البت:", + "LabelBirthYear": "عام الميلاد:", + "LabelBirthDate": "تاريخ الميلاد:", + "LabelAutomaticallyRefreshInternetMetadataEvery": "حدث وصف البيانات تلقائيا من الانترنت:", + "LabelAuthProvider": "مقدم التصديق:", + "LabelAudioSampleRate": "سرعة معينة الصوت:", + "LabelAudioCodec": "ترميز الصوت:", + "LabelAudioChannels": "قنوات الصوت:", + "LabelAudioBitrate": "معدل بث الصوت:", + "LabelAudioBitDepth": "عمق بث الصوت:", + "LabelAudio": "الصوت", + "LabelAllowedRemoteAddressesMode": "وضع مرشح عنوان المضيف IP البعيد:", + "LabelAllowedRemoteAddresses": "مرشح عنوان المضيف IP البعيد:", + "LabelAirsBeforeSeason": "عروض بث قبل الموسم:", + "LabelAirsBeforeEpisode": "عروض بث قبل الحلقة:", + "LabelAirsAfterSeason": "عروض بث بعد الموسم:", + "Label3DFormat": "صيغة ثلاثية الابعاد:", + "Kids": "اطفال", + "Items": "عناصر", + "ItemCount": "{0} عنصر", + "InstantMix": "خلط فوري", + "HeaderSyncPlayEnabled": "تزامن اللعب ممكَّن", + "HeaderSyncPlaySelectGroup": "انضم لمجموعة", + "EnableDetailsBannerHelp": "اظهر صوره اللافته اعلى عنصر تفاصيل الصفحة.", + "EnableDetailsBanner": "لافتة التفاصيل", + "EnableDecodingColorDepth10Vp9": "تمكين ترميز ال10 بت عبر العتاد الصلب من اجل VP9", + "EnableDecodingColorDepth10Hevc": "تمكين ترميز ال10 بت عبر العتاد الصلب من اجل HEVC" } From 637c134145a7b5ec3e405dc9a2cd157edd1c66ce Mon Sep 17 00:00:00 2001 From: sharkykh Date: Thu, 6 Aug 2020 15:47:14 +0000 Subject: [PATCH 086/124] Translated using Weblate (Hebrew) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/he/ --- src/strings/he.json | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/strings/he.json b/src/strings/he.json index 58eb6ee1b4..c266f587c2 100644 --- a/src/strings/he.json +++ b/src/strings/he.json @@ -471,7 +471,7 @@ "TabTrailers": "טריילרים", "TabTranscoding": "קידוד", "TabUpcoming": "בקרוב", - "Tags": "תגים", + "Tags": "מילות מפתח", "TellUsAboutYourself": "ספר לנו על עצמך", "ThisWizardWillGuideYou": "אשף זה יעזור לך בהתליך ההתקנה.", "Thursday": "חמישי", @@ -588,7 +588,7 @@ "MessageConfirmRestart": "‫האם אתה בטוח שברצונך לאתחל את שרת ה-Jellyfin‏?", "HeaderThisUserIsCurrentlyDisabled": "משתמש זה אינו פעיל כרגע", "HeaderTaskTriggers": "טריגרים של המשימה", - "HeaderTags": "תגיות", + "HeaderTags": "מילות מפתח", "HeaderStopRecording": "עצור הקלטה", "HeaderSortOrder": "סדר מיון", "HeaderSortBy": "מיין לפי", @@ -867,5 +867,7 @@ "OptionEveryday": "כל יום", "OptionEnableExternalContentInSuggestions": "הפעל תוכן חיצוני בהמלצות", "OptionEnableAccessToAllLibraries": "אפשר גישה לכל הספריות", - "OptionEnableAccessToAllChannels": "אפשר גישה לכל הערוצים" + "OptionEnableAccessToAllChannels": "אפשר גישה לכל הערוצים", + "HeaderSyncPlaySelectGroup": "הצטרף לקבוצה", + "TabUsers": "משתמשים" } From 0cd82da8f2aa441650900d882a82c15f457759e6 Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Thu, 6 Aug 2020 20:59:14 +0200 Subject: [PATCH 087/124] Migrate navdrawer to ES6 --- .eslintignore | 1 - package.json | 1 + src/libraries/navdrawer/navdrawer.js | 639 ++++++++++++++------------- src/scripts/libraryMenu.js | 2 + 4 files changed, 324 insertions(+), 319 deletions(-) diff --git a/.eslintignore b/.eslintignore index 8e3aee83fb..74b18ddcf6 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,4 +2,3 @@ node_modules dist .idea .vscode -src/libraries diff --git a/package.json b/package.json index bf3ac42f97..7b895bc367 100644 --- a/package.json +++ b/package.json @@ -275,6 +275,7 @@ "src/elements/emby-tabs/emby-tabs.js", "src/elements/emby-textarea/emby-textarea.js", "src/elements/emby-toggle/emby-toggle.js", + "src/libraries/navdrawer/navdrawer.js", "src/plugins/backdropScreensaver/plugin.js", "src/plugins/bookPlayer/plugin.js", "src/plugins/bookPlayer/tableOfContents.js", diff --git a/src/libraries/navdrawer/navdrawer.js b/src/libraries/navdrawer/navdrawer.js index 4733c617f3..965b68aee4 100644 --- a/src/libraries/navdrawer/navdrawer.js +++ b/src/libraries/navdrawer/navdrawer.js @@ -1,354 +1,357 @@ -define(["browser", "dom", "css!./navdrawer", "scrollStyles"], function (browser, dom) { - "use strict"; +/* Cleaning this file properly is not neecessary, since it's an outdated library + * and will be replaced soon by a Vue component. + */ - browser = browser.default || browser; +import browser from 'browser'; +import dom from 'dom'; +import 'css!./navdrawer'; +import 'scrollStyles'; - return function (options) { - function getTouches(e) { - return e.changedTouches || e.targetTouches || e.touches; +export default function (options) { + function getTouches(e) { + return e.changedTouches || e.targetTouches || e.touches; + } + + function onMenuTouchStart(e) { + options.target.classList.remove('transition'); + var touches = getTouches(e); + var touch = touches[0] || {}; + menuTouchStartX = touch.clientX; + menuTouchStartY = touch.clientY; + menuTouchStartTime = new Date().getTime(); + } + + function setVelocity(deltaX) { + var time = new Date().getTime() - (menuTouchStartTime || 0); + velocity = Math.abs(deltaX) / time; + } + + function onMenuTouchMove(e) { + var isOpen = self.visible; + var touches = getTouches(e); + var touch = touches[0] || {}; + var endX = touch.clientX || 0; + var endY = touch.clientY || 0; + var deltaX = endX - (menuTouchStartX || 0); + var deltaY = endY - (menuTouchStartY || 0); + setVelocity(deltaX); + + if (isOpen && dragMode !== 1 && deltaX > 0) { + dragMode = 2; } - function onMenuTouchStart(e) { - options.target.classList.remove("transition"); - var touches = getTouches(e); - var touch = touches[0] || {}; - menuTouchStartX = touch.clientX; - menuTouchStartY = touch.clientY; - menuTouchStartTime = new Date().getTime(); + if (dragMode === 0 && (!isOpen || Math.abs(deltaX) >= 10) && Math.abs(deltaY) < 5) { + dragMode = 1; + scrollContainer.addEventListener('scroll', disableEvent); + self.showMask(); + } else if (dragMode === 0 && Math.abs(deltaY) >= 5) { + dragMode = 2; } - function setVelocity(deltaX) { - var time = new Date().getTime() - (menuTouchStartTime || 0); - velocity = Math.abs(deltaX) / time; + if (dragMode === 1) { + newPos = currentPos + deltaX; + self.changeMenuPos(); } + } - function onMenuTouchMove(e) { - var isOpen = self.visible; - var touches = getTouches(e); - var touch = touches[0] || {}; - var endX = touch.clientX || 0; - var endY = touch.clientY || 0; - var deltaX = endX - (menuTouchStartX || 0); - var deltaY = endY - (menuTouchStartY || 0); - setVelocity(deltaX); + function onMenuTouchEnd(e) { + options.target.classList.add('transition'); + scrollContainer.removeEventListener('scroll', disableEvent); + dragMode = 0; + var touches = getTouches(e); + var touch = touches[0] || {}; + var endX = touch.clientX || 0; + var endY = touch.clientY || 0; + var deltaX = endX - (menuTouchStartX || 0); + var deltaY = endY - (menuTouchStartY || 0); + currentPos = deltaX; + self.checkMenuState(deltaX, deltaY); + } - if (isOpen && 1 !== dragMode && deltaX > 0) { - dragMode = 2; - } + function onEdgeTouchStart(e) { + if (isPeeking) { + onMenuTouchMove(e); + } else { + if (((getTouches(e)[0] || {}).clientX || 0) <= options.handleSize) { + isPeeking = true; - if (0 === dragMode && (!isOpen || Math.abs(deltaX) >= 10) && Math.abs(deltaY) < 5) { - dragMode = 1; - scrollContainer.addEventListener("scroll", disableEvent); - self.showMask(); - } else if (0 === dragMode && Math.abs(deltaY) >= 5) { - dragMode = 2; - } - - if (1 === dragMode) { - newPos = currentPos + deltaX; - self.changeMenuPos(); - } - } - - function onMenuTouchEnd(e) { - options.target.classList.add("transition"); - scrollContainer.removeEventListener("scroll", disableEvent); - dragMode = 0; - var touches = getTouches(e); - var touch = touches[0] || {}; - var endX = touch.clientX || 0; - var endY = touch.clientY || 0; - var deltaX = endX - (menuTouchStartX || 0); - var deltaY = endY - (menuTouchStartY || 0); - currentPos = deltaX; - self.checkMenuState(deltaX, deltaY); - } - - function onEdgeTouchStart(e) { - if (isPeeking) { - onMenuTouchMove(e); - } else { - if (((getTouches(e)[0] || {}).clientX || 0) <= options.handleSize) { - isPeeking = true; - - if (e.type === "touchstart") { - dom.removeEventListener(edgeContainer, "touchmove", onEdgeTouchMove, {}); - dom.addEventListener(edgeContainer, "touchmove", onEdgeTouchMove, {}); - } - - onMenuTouchStart(e); + if (e.type === 'touchstart') { + dom.removeEventListener(edgeContainer, 'touchmove', onEdgeTouchMove, {}); + dom.addEventListener(edgeContainer, 'touchmove', onEdgeTouchMove, {}); } + + onMenuTouchStart(e); } } + } - function onEdgeTouchMove(e) { - e.preventDefault(); - e.stopPropagation(); - onEdgeTouchStart(e); + function onEdgeTouchMove(e) { + e.preventDefault(); + e.stopPropagation(); + onEdgeTouchStart(e); + } + + function onEdgeTouchEnd(e) { + if (isPeeking) { + isPeeking = false; + dom.removeEventListener(edgeContainer, 'touchmove', onEdgeTouchMove, {}); + onMenuTouchEnd(e); } + } - function onEdgeTouchEnd(e) { - if (isPeeking) { - isPeeking = false; - dom.removeEventListener(edgeContainer, "touchmove", onEdgeTouchMove, {}); - onMenuTouchEnd(e); - } - } + function disableEvent(e) { + e.preventDefault(); + e.stopPropagation(); + } - function disableEvent(e) { - e.preventDefault(); - e.stopPropagation(); - } + function onBackgroundTouchStart(e) { + var touches = getTouches(e); + var touch = touches[0] || {}; + backgroundTouchStartX = touch.clientX; + backgroundTouchStartTime = new Date().getTime(); + } - function onBackgroundTouchStart(e) { - var touches = getTouches(e); - var touch = touches[0] || {}; - backgroundTouchStartX = touch.clientX; - backgroundTouchStartTime = new Date().getTime(); - } + function onBackgroundTouchMove(e) { + var touches = getTouches(e); + var touch = touches[0] || {}; + var endX = touch.clientX || 0; - function onBackgroundTouchMove(e) { - var touches = getTouches(e); - var touch = touches[0] || {}; - var endX = touch.clientX || 0; - - if (endX <= options.width && self.isVisible) { - countStart++; - var deltaX = endX - (backgroundTouchStartX || 0); - - if (countStart == 1) { - startPoint = deltaX; - } - if (deltaX < 0 && dragMode !== 2) { - dragMode = 1; - newPos = deltaX - startPoint + options.width; - self.changeMenuPos(); - var time = new Date().getTime() - (backgroundTouchStartTime || 0); - velocity = Math.abs(deltaX) / time; - } - } - - e.preventDefault(); - e.stopPropagation(); - } - - function onBackgroundTouchEnd(e) { - var touches = getTouches(e); - var touch = touches[0] || {}; - var endX = touch.clientX || 0; + if (endX <= options.width && self.isVisible) { + countStart++; var deltaX = endX - (backgroundTouchStartX || 0); - self.checkMenuState(deltaX); - countStart = 0; - } - function onMaskTransitionEnd() { - var classList = mask.classList; - - if (!classList.contains("backdrop")) { - classList.add("hide"); + if (countStart == 1) { + startPoint = deltaX; + } + if (deltaX < 0 && dragMode !== 2) { + dragMode = 1; + newPos = deltaX - startPoint + options.width; + self.changeMenuPos(); + var time = new Date().getTime() - (backgroundTouchStartTime || 0); + velocity = Math.abs(deltaX) / time; } } - var self; - var defaults; - var mask; - var newPos = 0; - var currentPos = 0; - var startPoint = 0; - var countStart = 0; - var velocity = 0; - options.target.classList.add("transition"); - var dragMode = 0; - var scrollContainer = options.target.querySelector(".mainDrawer-scrollContainer"); - scrollContainer.classList.add("scrollY"); + e.preventDefault(); + e.stopPropagation(); + } - var TouchMenuLA = function () { - self = this; - defaults = { - width: 260, - handleSize: 10, - disableMask: false, - maxMaskOpacity: 0.5 - }; - this.isVisible = false; - this.initialize(); + function onBackgroundTouchEnd(e) { + var touches = getTouches(e); + var touch = touches[0] || {}; + var endX = touch.clientX || 0; + var deltaX = endX - (backgroundTouchStartX || 0); + self.checkMenuState(deltaX); + countStart = 0; + } + + function onMaskTransitionEnd() { + var classList = mask.classList; + + if (!classList.contains('backdrop')) { + classList.add('hide'); + } + } + + var self; + var defaults; + var mask; + var newPos = 0; + var currentPos = 0; + var startPoint = 0; + var countStart = 0; + var velocity = 0; + options.target.classList.add('transition'); + var dragMode = 0; + var scrollContainer = options.target.querySelector('.mainDrawer-scrollContainer'); + scrollContainer.classList.add('scrollY'); + + var TouchMenuLA = function () { + self = this; + defaults = { + width: 260, + handleSize: 10, + disableMask: false, + maxMaskOpacity: 0.5 }; + this.isVisible = false; + this.initialize(); + }; - TouchMenuLA.prototype.initElements = function () { - options.target.classList.add("touch-menu-la"); - options.target.style.width = options.width + "px"; - options.target.style.left = -options.width + "px"; + TouchMenuLA.prototype.initElements = function () { + options.target.classList.add('touch-menu-la'); + options.target.style.width = options.width + 'px'; + options.target.style.left = -options.width + 'px'; - if (!options.disableMask) { - mask = document.createElement("div"); - mask.className = "tmla-mask hide"; - document.body.appendChild(mask); - dom.addEventListener(mask, dom.whichTransitionEvent(), onMaskTransitionEnd, { - passive: true - }); - } - }; - - var menuTouchStartX; - var menuTouchStartY; - var menuTouchStartTime; - var edgeContainer = document.querySelector(".mainDrawerHandle"); - var isPeeking = false; - - TouchMenuLA.prototype.animateToPosition = function (pos) { - requestAnimationFrame(function () { - options.target.style.transform = pos ? "translateX(" + pos + "px)" : "none"; + if (!options.disableMask) { + mask = document.createElement('div'); + mask.className = 'tmla-mask hide'; + document.body.appendChild(mask); + dom.addEventListener(mask, dom.whichTransitionEvent(), onMaskTransitionEnd, { + passive: true }); - }; + } + }; - TouchMenuLA.prototype.changeMenuPos = function () { - if (newPos <= options.width) { - this.animateToPosition(newPos); - } - }; + var menuTouchStartX; + var menuTouchStartY; + var menuTouchStartTime; + var edgeContainer = document.querySelector('.mainDrawerHandle'); + var isPeeking = false; - TouchMenuLA.prototype.clickMaskClose = function () { - mask.addEventListener("click", function () { + TouchMenuLA.prototype.animateToPosition = function (pos) { + requestAnimationFrame(function () { + options.target.style.transform = pos ? 'translateX(' + pos + 'px)' : 'none'; + }); + }; + + TouchMenuLA.prototype.changeMenuPos = function () { + if (newPos <= options.width) { + this.animateToPosition(newPos); + } + }; + + TouchMenuLA.prototype.clickMaskClose = function () { + mask.addEventListener('click', function () { + self.close(); + }); + }; + + TouchMenuLA.prototype.checkMenuState = function (deltaX, deltaY) { + if (velocity >= 0.4) { + if (deltaX >= 0 || Math.abs(deltaY || 0) >= 70) { + self.open(); + } else { self.close(); - }); - }; - - TouchMenuLA.prototype.checkMenuState = function (deltaX, deltaY) { - if (velocity >= 0.4) { - if (deltaX >= 0 || Math.abs(deltaY || 0) >= 70) { - self.open(); - } else { + } + } else { + if (newPos >= 100) { + self.open(); + } else { + if (newPos) { self.close(); } - } else { - if (newPos >= 100) { - self.open(); - } else { - if (newPos) { - self.close(); - } - } } - }; - - TouchMenuLA.prototype.open = function () { - this.animateToPosition(options.width); - currentPos = options.width; - this.isVisible = true; - options.target.classList.add("drawer-open"); - self.showMask(); - self.invoke(options.onChange); - }; - - TouchMenuLA.prototype.close = function () { - this.animateToPosition(0); - currentPos = 0; - self.isVisible = false; - options.target.classList.remove("drawer-open"); - self.hideMask(); - self.invoke(options.onChange); - }; - - TouchMenuLA.prototype.toggle = function () { - if (self.isVisible) { - self.close(); - } else { - self.open(); - } - }; - - var backgroundTouchStartX; - var backgroundTouchStartTime; - - TouchMenuLA.prototype.showMask = function () { - mask.classList.remove("hide"); - mask.classList.add("backdrop"); - }; - - TouchMenuLA.prototype.hideMask = function () { - mask.classList.add("hide"); - mask.classList.remove("backdrop"); - }; - - TouchMenuLA.prototype.invoke = function (fn) { - if (fn) { - fn.apply(self); - } - }; - - var _edgeSwipeEnabled; - - TouchMenuLA.prototype.setEdgeSwipeEnabled = function (enabled) { - if (!options.disableEdgeSwipe) { - if (browser.touch) { - if (enabled) { - if (!_edgeSwipeEnabled) { - _edgeSwipeEnabled = true; - dom.addEventListener(edgeContainer, "touchstart", onEdgeTouchStart, { - passive: true - }); - dom.addEventListener(edgeContainer, "touchend", onEdgeTouchEnd, { - passive: true - }); - dom.addEventListener(edgeContainer, "touchcancel", onEdgeTouchEnd, { - passive: true - }); - } - } else { - if (_edgeSwipeEnabled) { - _edgeSwipeEnabled = false; - dom.removeEventListener(edgeContainer, "touchstart", onEdgeTouchStart, { - passive: true - }); - dom.removeEventListener(edgeContainer, "touchend", onEdgeTouchEnd, { - passive: true - }); - dom.removeEventListener(edgeContainer, "touchcancel", onEdgeTouchEnd, { - passive: true - }); - } - } - } - } - }; - - TouchMenuLA.prototype.initialize = function () { - options = Object.assign(defaults, options || {}); - - if (browser.edge) { - options.disableEdgeSwipe = true; - } - - self.initElements(); - - if (browser.touch) { - dom.addEventListener(options.target, "touchstart", onMenuTouchStart, { - passive: true - }); - dom.addEventListener(options.target, "touchmove", onMenuTouchMove, { - passive: true - }); - dom.addEventListener(options.target, "touchend", onMenuTouchEnd, { - passive: true - }); - dom.addEventListener(options.target, "touchcancel", onMenuTouchEnd, { - passive: true - }); - dom.addEventListener(mask, "touchstart", onBackgroundTouchStart, { - passive: true - }); - dom.addEventListener(mask, "touchmove", onBackgroundTouchMove, {}); - dom.addEventListener(mask, "touchend", onBackgroundTouchEnd, { - passive: true - }); - dom.addEventListener(mask, "touchcancel", onBackgroundTouchEnd, { - passive: true - }); - } - - self.clickMaskClose(); - }; - - return new TouchMenuLA(); + } }; -}); + + TouchMenuLA.prototype.open = function () { + this.animateToPosition(options.width); + currentPos = options.width; + this.isVisible = true; + options.target.classList.add('drawer-open'); + self.showMask(); + self.invoke(options.onChange); + }; + + TouchMenuLA.prototype.close = function () { + this.animateToPosition(0); + currentPos = 0; + self.isVisible = false; + options.target.classList.remove('drawer-open'); + self.hideMask(); + self.invoke(options.onChange); + }; + + TouchMenuLA.prototype.toggle = function () { + if (self.isVisible) { + self.close(); + } else { + self.open(); + } + }; + + var backgroundTouchStartX; + var backgroundTouchStartTime; + + TouchMenuLA.prototype.showMask = function () { + mask.classList.remove('hide'); + mask.classList.add('backdrop'); + }; + + TouchMenuLA.prototype.hideMask = function () { + mask.classList.add('hide'); + mask.classList.remove('backdrop'); + }; + + TouchMenuLA.prototype.invoke = function (fn) { + if (fn) { + fn.apply(self); + } + }; + + var _edgeSwipeEnabled; + + TouchMenuLA.prototype.setEdgeSwipeEnabled = function (enabled) { + if (!options.disableEdgeSwipe) { + if (browser.touch) { + if (enabled) { + if (!_edgeSwipeEnabled) { + _edgeSwipeEnabled = true; + dom.addEventListener(edgeContainer, 'touchstart', onEdgeTouchStart, { + passive: true + }); + dom.addEventListener(edgeContainer, 'touchend', onEdgeTouchEnd, { + passive: true + }); + dom.addEventListener(edgeContainer, 'touchcancel', onEdgeTouchEnd, { + passive: true + }); + } + } else { + if (_edgeSwipeEnabled) { + _edgeSwipeEnabled = false; + dom.removeEventListener(edgeContainer, 'touchstart', onEdgeTouchStart, { + passive: true + }); + dom.removeEventListener(edgeContainer, 'touchend', onEdgeTouchEnd, { + passive: true + }); + dom.removeEventListener(edgeContainer, 'touchcancel', onEdgeTouchEnd, { + passive: true + }); + } + } + } + } + }; + + TouchMenuLA.prototype.initialize = function () { + options = Object.assign(defaults, options || {}); + + if (browser.edge) { + options.disableEdgeSwipe = true; + } + + self.initElements(); + + if (browser.touch) { + dom.addEventListener(options.target, 'touchstart', onMenuTouchStart, { + passive: true + }); + dom.addEventListener(options.target, 'touchmove', onMenuTouchMove, { + passive: true + }); + dom.addEventListener(options.target, 'touchend', onMenuTouchEnd, { + passive: true + }); + dom.addEventListener(options.target, 'touchcancel', onMenuTouchEnd, { + passive: true + }); + dom.addEventListener(mask, 'touchstart', onBackgroundTouchStart, { + passive: true + }); + dom.addEventListener(mask, 'touchmove', onBackgroundTouchMove, {}); + dom.addEventListener(mask, 'touchend', onBackgroundTouchEnd, { + passive: true + }); + dom.addEventListener(mask, 'touchcancel', onBackgroundTouchEnd, { + passive: true + }); + } + + self.clickMaskClose(); + }; + + return new TouchMenuLA(); +} diff --git a/src/scripts/libraryMenu.js b/src/scripts/libraryMenu.js index bbe01276ba..6149717647 100644 --- a/src/scripts/libraryMenu.js +++ b/src/scripts/libraryMenu.js @@ -806,6 +806,8 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' navDrawerScrollContainer.addEventListener('click', onMainDrawerClick); return new Promise(function (resolve, reject) { require(['navdrawer'], function (navdrawer) { + navdrawer = navdrawer.default || navdrawer; + navDrawerInstance = new navdrawer(getNavDrawerOptions()); if (!layoutManager.tv) { From f2e1d03ae98925a1788dbb577b608649e9004ee1 Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Thu, 6 Aug 2020 21:15:16 +0200 Subject: [PATCH 088/124] Migrate scroller to ES6 --- package.json | 1 + src/controllers/list.js | 2 +- src/libraries/scroller.js | 1714 ++++++++++++++++++------------------- 3 files changed, 838 insertions(+), 879 deletions(-) diff --git a/package.json b/package.json index 7b895bc367..7ed4cb8b9c 100644 --- a/package.json +++ b/package.json @@ -276,6 +276,7 @@ "src/elements/emby-textarea/emby-textarea.js", "src/elements/emby-toggle/emby-toggle.js", "src/libraries/navdrawer/navdrawer.js", + "src/libraries/scroller.js", "src/plugins/backdropScreensaver/plugin.js", "src/plugins/bookPlayer/plugin.js", "src/plugins/bookPlayer/tableOfContents.js", diff --git a/src/controllers/list.js b/src/controllers/list.js index afa0a60fa8..14c6768757 100644 --- a/src/controllers/list.js +++ b/src/controllers/list.js @@ -1,4 +1,4 @@ -define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager', 'cardBuilder', 'loading', 'connectionManager', 'alphaNumericShortcuts', 'scroller', 'playbackManager', 'alphaPicker', 'emby-itemscontainer', 'emby-scroller'], function (globalize, listView, layoutManager, userSettings, focusManager, cardBuilder, loading, connectionManager, AlphaNumericShortcuts, scroller, playbackManager, AlphaPicker) { +define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager', 'cardBuilder', 'loading', 'connectionManager', 'alphaNumericShortcuts', 'playbackManager', 'alphaPicker', 'emby-itemscontainer', 'emby-scroller'], function (globalize, listView, layoutManager, userSettings, focusManager, cardBuilder, loading, connectionManager, AlphaNumericShortcuts, playbackManager, AlphaPicker) { 'use strict'; playbackManager = playbackManager.default || playbackManager; diff --git a/src/libraries/scroller.js b/src/libraries/scroller.js index cc75dcdeef..03c6306511 100644 --- a/src/libraries/scroller.js +++ b/src/libraries/scroller.js @@ -1,928 +1,886 @@ -define(['browser', 'layoutManager', 'dom', 'focusManager', 'ResizeObserver', 'scrollStyles'], function (browser, layoutManager, dom, focusManager, ResizeObserver) { - 'use strict'; +/* Cleaning this file properly is not neecessary, since it's an outdated library + * and will be replaced soon by a Vue component. + */ - browser = browser.default || browser; - focusManager = focusManager.default || focusManager; + import browser from 'browser'; +import layoutManager from 'layoutManager'; +import dom from 'dom'; +import focusManager from 'focusManager'; +import ResizeObserver from 'ResizeObserver'; +import 'scrollStyles'; - /** +/** * Return type of the value. * * @param {Mixed} value * * @return {String} */ - function type(value) { - if (value == null) { - return String(value); - } - - if (typeof value === 'object' || typeof value === 'function') { - return Object.prototype.toString.call(value).match(/\s([a-z]+)/i)[1].toLowerCase() || 'object'; - } - - return typeof value; +function type(value) { + if (value == null) { + return String(value); } - /** - * Disables an event it was triggered on and unbinds itself. - * - * @param {Event} event - * - * @return {Void} - */ - function disableOneEvent(event) { - /*jshint validthis:true */ - event.preventDefault(); - event.stopPropagation(); - this.removeEventListener(event.type, disableOneEvent); + if (typeof value === 'object' || typeof value === 'function') { + return Object.prototype.toString.call(value).match(/\s([a-z]+)/i)[1].toLowerCase() || 'object'; } - /** - * Make sure that number is within the limits. - * - * @param {Number} number - * @param {Number} min - * @param {Number} max - * - * @return {Number} - */ - function within(number, min, max) { - return number < min ? min : number > max ? max : number; + return typeof value; +} + +/** + * Disables an event it was triggered on and unbinds itself. + * + * @param {Event} event + * + * @return {Void} + */ +function disableOneEvent(event) { + /*jshint validthis:true */ + event.preventDefault(); + event.stopPropagation(); + this.removeEventListener(event.type, disableOneEvent); +} + +/** + * Make sure that number is within the limits. + * + * @param {Number} number + * @param {Number} min + * @param {Number} max + * + * @return {Number} + */ +function within(number, min, max) { + return number < min ? min : number > max ? max : number; +} + +// Other global values +var dragMouseEvents = ['mousemove', 'mouseup']; +var dragTouchEvents = ['touchmove', 'touchend']; +var wheelEvent = (document.implementation.hasFeature('Event.wheel', '3.0') ? 'wheel' : 'mousewheel'); +var interactiveElements = ['INPUT', 'SELECT', 'TEXTAREA']; + +var scrollerFactory = function (frame, options) { + // Extend options + var o = Object.assign({}, { + slidee: null, // Selector, DOM element, or jQuery object with DOM element representing SLIDEE. + horizontal: false, // Switch to horizontal mode. + + // Scrolling + mouseWheel: true, + scrollBy: 0, // Pixels or items to move per one mouse scroll. 0 to disable scrolling + + // Dragging + dragSource: null, // Selector or DOM element for catching dragging events. Default is FRAME. + mouseDragging: 1, // Enable navigation by dragging the SLIDEE with mouse cursor. + touchDragging: 1, // Enable navigation by dragging the SLIDEE with touch events. + dragThreshold: 3, // Distance in pixels before Sly recognizes dragging. + intervactive: null, // Selector for special interactive elements. + + // Mixed options + speed: 0 // Animations speed in milliseconds. 0 to disable animations. + + }, options); + + var isSmoothScrollSupported = 'scrollBehavior' in document.documentElement.style; + + // native scroll is a must with touch input + // also use native scroll when scrolling vertically in desktop mode - excluding horizontal because the mouse wheel support is choppy at the moment + // in cases with firefox, if the smooth scroll api is supported then use that because their implementation is very good + if (options.allowNativeScroll === false) { + options.enableNativeScroll = false; + } else if (isSmoothScrollSupported && ((browser.firefox && !layoutManager.tv) || options.allowNativeSmoothScroll)) { + // native smooth scroll + options.enableNativeScroll = true; + } else if (options.requireAnimation && (browser.animate || browser.supportsCssAnimation())) { + // transform is the only way to guarantee animation + options.enableNativeScroll = false; + } else if (!layoutManager.tv || !browser.animate) { + options.enableNativeScroll = true; } - // Other global values - var dragMouseEvents = ['mousemove', 'mouseup']; - var dragTouchEvents = ['touchmove', 'touchend']; - var wheelEvent = (document.implementation.hasFeature('Event.wheel', '3.0') ? 'wheel' : 'mousewheel'); - var interactiveElements = ['INPUT', 'SELECT', 'TEXTAREA']; - var tmpArray = []; - var time; - - // Math shorthands - var abs = Math.abs; - var sqrt = Math.sqrt; - var pow = Math.pow; - var round = Math.round; - var max = Math.max; - var min = Math.min; - - var scrollerFactory = function (frame, options) { - - // Extend options - var o = Object.assign({}, { - slidee: null, // Selector, DOM element, or jQuery object with DOM element representing SLIDEE. - horizontal: false, // Switch to horizontal mode. - - // Scrolling - mouseWheel: true, - scrollBy: 0, // Pixels or items to move per one mouse scroll. 0 to disable scrolling - - // Dragging - dragSource: null, // Selector or DOM element for catching dragging events. Default is FRAME. - mouseDragging: 1, // Enable navigation by dragging the SLIDEE with mouse cursor. - touchDragging: 1, // Enable navigation by dragging the SLIDEE with touch events. - dragThreshold: 3, // Distance in pixels before Sly recognizes dragging. - intervactive: null, // Selector for special interactive elements. - - // Mixed options - speed: 0 // Animations speed in milliseconds. 0 to disable animations. - - }, options); - - var isSmoothScrollSupported = 'scrollBehavior' in document.documentElement.style; - - // native scroll is a must with touch input - // also use native scroll when scrolling vertically in desktop mode - excluding horizontal because the mouse wheel support is choppy at the moment - // in cases with firefox, if the smooth scroll api is supported then use that because their implementation is very good - if (options.allowNativeScroll === false) { - options.enableNativeScroll = false; - } else if (isSmoothScrollSupported && ((browser.firefox && !layoutManager.tv) || options.allowNativeSmoothScroll)) { - // native smooth scroll - options.enableNativeScroll = true; - } else if (options.requireAnimation && (browser.animate || browser.supportsCssAnimation())) { - - // transform is the only way to guarantee animation - options.enableNativeScroll = false; - } else if (!layoutManager.tv || !browser.animate) { - - options.enableNativeScroll = true; - } - - // Need this for the magic wheel. With the animated scroll the magic wheel will run off of the screen - if (browser.web0s) { - options.enableNativeScroll = true; - } - - // Private variables - var self = this; - self.options = o; - - // Frame - var slideeElement = o.slidee ? o.slidee : sibling(frame.firstChild)[0]; - self._pos = { - start: 0, - center: 0, - end: 0, - cur: 0, - dest: 0 - }; - - var transform = !options.enableNativeScroll; - - // Miscellaneous - var scrollSource = frame; - var dragSourceElement = o.dragSource ? o.dragSource : frame; - var dragging = { - released: 1 - }; - var scrolling = { - last: 0, - delta: 0, - resetTime: 200 - }; - - // Expose properties - self.initialized = 0; - self.slidee = slideeElement; - self.options = o; - self.dragging = dragging; - - var nativeScrollElement = frame; - - function sibling(n, elem) { - var matched = []; - - for (; n; n = n.nextSibling) { - if (n.nodeType === 1 && n !== elem) { - matched.push(n); - } - } - return matched; - } - - var requiresReflow = true; - - var frameSize = 0; - var slideeSize = 0; - function ensureSizeInfo() { - - if (requiresReflow) { - - requiresReflow = false; - - // Reset global variables - frameSize = o.horizontal ? (frame).offsetWidth : (frame).offsetHeight; - - slideeSize = o.scrollWidth || Math.max(slideeElement[o.horizontal ? 'offsetWidth' : 'offsetHeight'], slideeElement[o.horizontal ? 'scrollWidth' : 'scrollHeight']); - - // Set position limits & relativess - self._pos.end = max(slideeSize - frameSize, 0); - } - } - - /** - * Loading function. - * - * Populate arrays, set sizes, bind events, ... - * - * @param {Boolean} [isInit] Whether load is called from within self.init(). - * @return {Void} - */ - function load(isInit) { - - requiresReflow = true; - - if (!isInit) { - - ensureSizeInfo(); - - // Fix possible overflowing - var pos = self._pos; - self.slideTo(within(pos.dest, pos.start, pos.end)); - } - } - - function initFrameResizeObserver() { - - var observerOptions = {}; - - self.frameResizeObserver = new ResizeObserver(onResize, observerOptions); - - self.frameResizeObserver.observe(frame); - } - - self.reload = function () { - load(); - }; - - self.getScrollEventName = function () { - return transform ? 'scrollanimate' : 'scroll'; - }; - - self.getScrollSlider = function () { - return slideeElement; - }; - - self.getScrollFrame = function () { - return frame; - }; - - function nativeScrollTo(container, pos, immediate) { - - if (container.scroll) { - if (o.horizontal) { - - container.scroll({ - left: pos, - behavior: immediate ? 'instant' : 'smooth' - }); - } else { - - container.scroll({ - top: pos, - behavior: immediate ? 'instant' : 'smooth' - }); - } - } else if (!immediate && container.scrollTo) { - if (o.horizontal) { - container.scrollTo(Math.round(pos), 0); - } else { - container.scrollTo(0, Math.round(pos)); - } - } else { - if (o.horizontal) { - container.scrollLeft = Math.round(pos); - } else { - container.scrollTop = Math.round(pos); - } - } - } - - var lastAnimate; - - /** - * Animate to a position. - * - * @param {Int} newPos New position. - * @param {Bool} immediate Reposition immediately without an animation. - * - * @return {Void} - */ - self.slideTo = function (newPos, immediate, fullItemPos) { - - ensureSizeInfo(); - var pos = self._pos; - - newPos = within(newPos, pos.start, pos.end); - - if (!transform) { - - nativeScrollTo(nativeScrollElement, newPos, immediate); - return; - } - - // Update the animation object - var from = pos.cur; - immediate = immediate || dragging.init || !o.speed; - - var now = new Date().getTime(); - - if (o.autoImmediate) { - if (!immediate && (now - (lastAnimate || 0)) <= 50) { - immediate = true; - } - } - - if (!immediate && o.skipSlideToWhenVisible && fullItemPos && fullItemPos.isVisible) { - - return; - } - - // Start animation rendering - // NOTE the dependency was modified here to fix a scrollbutton issue - pos.dest = newPos; - renderAnimateWithTransform(from, newPos, immediate); - lastAnimate = now; - }; - - function setStyleProperty(elem, name, value, speed, resetTransition) { - - var style = elem.style; - - if (resetTransition || browser.edge) { - style.transition = 'none'; - void elem.offsetWidth; - } - - style.transition = 'transform ' + speed + 'ms ease-out'; - style[name] = value; - } - - function dispatchScrollEventIfNeeded() { - if (o.dispatchScrollEvent) { - frame.dispatchEvent(new CustomEvent(self.getScrollEventName(), { - bubbles: true, - cancelable: false - })); - } - } - - function renderAnimateWithTransform(fromPosition, toPosition, immediate) { - - var speed = o.speed; - - if (immediate) { - speed = o.immediateSpeed || 50; - } - - if (o.horizontal) { - setStyleProperty(slideeElement, 'transform', 'translateX(' + (-round(toPosition)) + 'px)', speed); - } else { - setStyleProperty(slideeElement, 'transform', 'translateY(' + (-round(toPosition)) + 'px)', speed); - } - self._pos.cur = toPosition; - - dispatchScrollEventIfNeeded(); - } - - function getBoundingClientRect(elem) { - - // Support: BlackBerry 5, iOS 3 (original iPhone) - // If we don't have gBCR, just use 0,0 rather than error - if (elem.getBoundingClientRect) { - return elem.getBoundingClientRect(); - } else { - return { top: 0, left: 0 }; - } - } - - /** - * Returns the position object. - * - * @param {Mixed} item - * - * @return {Object} - */ - self.getPos = function (item) { - - var scrollElement = transform ? slideeElement : nativeScrollElement; - var slideeOffset = getBoundingClientRect(scrollElement); - var itemOffset = getBoundingClientRect(item); - - var slideeStartPos = o.horizontal ? slideeOffset.left : slideeOffset.top; - var slideeEndPos = o.horizontal ? slideeOffset.right : slideeOffset.bottom; - - var offset = o.horizontal ? itemOffset.left - slideeOffset.left : itemOffset.top - slideeOffset.top; - - var size = o.horizontal ? itemOffset.width : itemOffset.height; - if (!size && size !== 0) { - size = item[o.horizontal ? 'offsetWidth' : 'offsetHeight']; - } - - var centerOffset = o.centerOffset || 0; - - if (!transform) { - centerOffset = 0; - if (o.horizontal) { - offset += nativeScrollElement.scrollLeft; - } else { - offset += nativeScrollElement.scrollTop; - } - } - - ensureSizeInfo(); - - var currentStart = self._pos.cur; - var currentEnd = currentStart + frameSize; - - console.debug('offset:' + offset + ' currentStart:' + currentStart + ' currentEnd:' + currentEnd); - var isVisible = offset >= currentStart && (offset + size) <= currentEnd; - - return { - start: offset, - center: offset + centerOffset - (frameSize / 2) + (size / 2), - end: offset - frameSize + size, - size: size, - isVisible: isVisible - }; - }; - - self.getCenterPosition = function (item) { - - ensureSizeInfo(); - - var pos = self.getPos(item); - return within(pos.center, pos.start, pos.end); - }; - - function dragInitSlidee(event) { - var isTouch = event.type === 'touchstart'; - - // Ignore when already in progress, or interactive element in non-touch navivagion - if (dragging.init || !isTouch && isInteractive(event.target)) { - return; - } - - // SLIDEE dragging conditions - if (!(isTouch ? o.touchDragging : o.mouseDragging && event.which < 2)) { - return; - } - - if (!isTouch) { - // prevents native image dragging in Firefox - event.preventDefault(); - } - - // Reset dragging object - dragging.released = 0; - - // Properties used in dragHandler - dragging.init = 0; - dragging.source = event.target; - dragging.touch = isTouch; - var pointer = isTouch ? event.touches[0] : event; - dragging.initX = pointer.pageX; - dragging.initY = pointer.pageY; - dragging.initPos = self._pos.cur; - dragging.start = +new Date(); - dragging.time = 0; - dragging.path = 0; - dragging.delta = 0; - dragging.locked = 0; - dragging.pathToLock = isTouch ? 30 : 10; - - // Bind dragging events - if (transform) { - - if (isTouch) { - dragTouchEvents.forEach(function (eventName) { - dom.addEventListener(document, eventName, dragHandler, { - passive: true - }); - }); - } else { - dragMouseEvents.forEach(function (eventName) { - dom.addEventListener(document, eventName, dragHandler, { - passive: true - }); - }); - } - } - } - - /** - * Handler for dragging scrollbar handle or SLIDEE. - * - * @param {Event} event - * - * @return {Void} - */ - function dragHandler(event) { - dragging.released = event.type === 'mouseup' || event.type === 'touchend'; - var pointer = dragging.touch ? event[dragging.released ? 'changedTouches' : 'touches'][0] : event; - dragging.pathX = pointer.pageX - dragging.initX; - dragging.pathY = pointer.pageY - dragging.initY; - dragging.path = sqrt(pow(dragging.pathX, 2) + pow(dragging.pathY, 2)); - dragging.delta = o.horizontal ? dragging.pathX : dragging.pathY; - - if (!dragging.released && dragging.path < 1) { - return; - } - - // We haven't decided whether this is a drag or not... - if (!dragging.init) { - // If the drag path was very short, maybe it's not a drag? - if (dragging.path < o.dragThreshold) { - // If the pointer was released, the path will not become longer and it's - // definitely not a drag. If not released yet, decide on next iteration - return dragging.released ? dragEnd() : undefined; - } else { - // If dragging path is sufficiently long we can confidently start a drag - // if drag is in different direction than scroll, ignore it - if (o.horizontal ? abs(dragging.pathX) > abs(dragging.pathY) : abs(dragging.pathX) < abs(dragging.pathY)) { - dragging.init = 1; - } else { - return dragEnd(); - } - } - } - - //event.preventDefault(); - - // Disable click on a source element, as it is unwelcome when dragging - if (!dragging.locked && dragging.path > dragging.pathToLock) { - dragging.locked = 1; - dragging.source.addEventListener('click', disableOneEvent); - } - - // Cancel dragging on release - if (dragging.released) { - dragEnd(); - } - - self.slideTo(round(dragging.initPos - dragging.delta)); - } - - /** - * Stops dragging and cleans up after it. - * - * @return {Void} - */ - function dragEnd() { - dragging.released = true; - - dragTouchEvents.forEach(function (eventName) { - dom.removeEventListener(document, eventName, dragHandler, { - passive: true - }); - }); - - dragMouseEvents.forEach(function (eventName) { - dom.removeEventListener(document, eventName, dragHandler, { - passive: true - }); - }); - - // Make sure that disableOneEvent is not active in next tick. - setTimeout(function () { - dragging.source.removeEventListener('click', disableOneEvent); - }); - - dragging.init = 0; - } - - /** - * Check whether element is interactive. - * - * @return {Boolean} - */ - function isInteractive(element) { - - while (element) { - - if (interactiveElements.indexOf(element.tagName) !== -1) { - return true; - } - - element = element.parentNode; - } - return false; - } - - /** - * Mouse wheel delta normalization. - * - * @param {Event} event - * - * @return {Int} - */ - function normalizeWheelDelta(event) { - // JELLYFIN MOD: Only use deltaX for horizontal scroll and remove IE8 support - scrolling.curDelta = o.horizontal ? event.deltaX : event.deltaY; - // END JELLYFIN MOD - - if (transform) { - scrolling.curDelta /= event.deltaMode === 1 ? 3 : 100; - } - return scrolling.curDelta; - } - - /** - * Mouse scrolling handler. - * - * @param {Event} event - * - * @return {Void} - */ - function scrollHandler(event) { - - ensureSizeInfo(); - var pos = self._pos; - // Ignore if there is no scrolling to be done - if (!o.scrollBy || pos.start === pos.end) { - return; - } - var delta = normalizeWheelDelta(event); - - if (transform) { - // Trap scrolling only when necessary and/or requested - if (delta > 0 && pos.dest < pos.end || delta < 0 && pos.dest > pos.start) { - //stopDefault(event, 1); - } - - self.slideBy(o.scrollBy * delta); - } else { - - if (isSmoothScrollSupported) { - delta *= 12; - } - - if (o.horizontal) { - nativeScrollElement.scrollLeft += delta; - } else { - nativeScrollElement.scrollTop += delta; - } - } - } - - /** - * Destroys instance and everything it created. - * - * @return {Void} - */ - self.destroy = function () { - - if (self.frameResizeObserver) { - self.frameResizeObserver.disconnect(); - self.frameResizeObserver = null; - } - - // Reset native FRAME element scroll - dom.removeEventListener(frame, 'scroll', resetScroll, { - passive: true - }); - - dom.removeEventListener(scrollSource, wheelEvent, scrollHandler, { - passive: true - }); - - dom.removeEventListener(dragSourceElement, 'touchstart', dragInitSlidee, { - passive: true - }); - - dom.removeEventListener(frame, 'click', onFrameClick, { - passive: true, - capture: true - }); - - dom.removeEventListener(dragSourceElement, 'mousedown', dragInitSlidee, { - //passive: true - }); - - // Reset initialized status and return the instance - self.initialized = 0; - return self; - }; - - var contentRect = {}; - - function onResize(entries) { - - var entry = entries[0]; - - if (entry) { - - var newRect = entry.contentRect; - - // handle element being hidden - if (newRect.width === 0 || newRect.height === 0) { - return; - } - - if (newRect.width !== contentRect.width || newRect.height !== contentRect.height) { - - contentRect = newRect; - - load(false); - } - } - } - - function resetScroll() { - if (o.horizontal) { - this.scrollLeft = 0; - } else { - this.scrollTop = 0; - } - } - - function onFrameClick(e) { - if (e.which === 1) { - var focusableParent = focusManager.focusableParent(e.target); - if (focusableParent && focusableParent !== document.activeElement) { - focusableParent.focus(); - } - } - } - - self.getScrollPosition = function () { - - if (transform) { - return self._pos.cur; - } - - if (o.horizontal) { - return nativeScrollElement.scrollLeft; - } else { - return nativeScrollElement.scrollTop; - } - }; - - self.getScrollSize = function () { - - if (transform) { - return slideeSize; - } - - if (o.horizontal) { - return nativeScrollElement.scrollWidth; - } else { - return nativeScrollElement.scrollHeight; - } - }; - - /** - * Initialize. - * - * @return {Object} - */ - self.init = function () { - if (self.initialized) { - return; - } - - if (!transform) { - if (o.horizontal) { - if (layoutManager.desktop && !o.hideScrollbar) { - nativeScrollElement.classList.add('scrollX'); - } else { - nativeScrollElement.classList.add('scrollX'); - nativeScrollElement.classList.add('hiddenScrollX'); - - if (layoutManager.tv && o.allowNativeSmoothScroll !== false) { - nativeScrollElement.classList.add('smoothScrollX'); - } - } - - if (o.forceHideScrollbars) { - nativeScrollElement.classList.add('hiddenScrollX-forced'); - } - } else { - if (layoutManager.desktop && !o.hideScrollbar) { - nativeScrollElement.classList.add('scrollY'); - } else { - nativeScrollElement.classList.add('scrollY'); - nativeScrollElement.classList.add('hiddenScrollY'); - - if (layoutManager.tv && o.allowNativeSmoothScroll !== false) { - nativeScrollElement.classList.add('smoothScrollY'); - } - } - - if (o.forceHideScrollbars) { - nativeScrollElement.classList.add('hiddenScrollY-forced'); - } - } - } else { - frame.style.overflow = 'hidden'; - slideeElement.style['will-change'] = 'transform'; - slideeElement.style.transition = 'transform ' + o.speed + 'ms ease-out'; - - if (o.horizontal) { - slideeElement.classList.add('animatedScrollX'); - } else { - slideeElement.classList.add('animatedScrollY'); - } - } - - if (transform || layoutManager.tv) { - // This can prevent others from being able to listen to mouse events - dom.addEventListener(dragSourceElement, 'mousedown', dragInitSlidee, { - //passive: true - }); - } - - initFrameResizeObserver(); - - if (transform) { - - dom.addEventListener(dragSourceElement, 'touchstart', dragInitSlidee, { - passive: true - }); - - if (!o.horizontal) { - dom.addEventListener(frame, 'scroll', resetScroll, { - passive: true - }); - } - - if (o.mouseWheel) { - // Scrolling navigation - dom.addEventListener(scrollSource, wheelEvent, scrollHandler, { - passive: true - }); - } - - } else if (o.horizontal) { - - // Don't bind to mouse events with vertical scroll since the mouse wheel can handle this natively - - if (o.mouseWheel) { - // Scrolling navigation - dom.addEventListener(scrollSource, wheelEvent, scrollHandler, { - passive: true - }); - } - } - - dom.addEventListener(frame, 'click', onFrameClick, { - passive: true, - capture: true - }); - - // Mark instance as initialized - self.initialized = 1; - - // Load - load(true); - - // Return instance - return self; - }; + // Need this for the magic wheel. With the animated scroll the magic wheel will run off of the screen + if (browser.web0s) { + options.enableNativeScroll = true; + } + + // Private variables + var self = this; + self.options = o; + + // Frame + var slideeElement = o.slidee ? o.slidee : sibling(frame.firstChild)[0]; + self._pos = { + start: 0, + center: 0, + end: 0, + cur: 0, + dest: 0 }; + var transform = !options.enableNativeScroll; + + // Miscellaneous + var scrollSource = frame; + var dragSourceElement = o.dragSource ? o.dragSource : frame; + var dragging = { + released: 1 + }; + var scrolling = { + last: 0, + delta: 0, + resetTime: 200 + }; + + // Expose properties + self.initialized = 0; + self.slidee = slideeElement; + self.options = o; + self.dragging = dragging; + + var nativeScrollElement = frame; + + function sibling(n, elem) { + var matched = []; + + for (; n; n = n.nextSibling) { + if (n.nodeType === 1 && n !== elem) { + matched.push(n); + } + } + return matched; + } + + var requiresReflow = true; + + var frameSize = 0; + var slideeSize = 0; + function ensureSizeInfo() { + if (requiresReflow) { + requiresReflow = false; + + // Reset global variables + frameSize = o.horizontal ? (frame).offsetWidth : (frame).offsetHeight; + + slideeSize = o.scrollWidth || Math.max(slideeElement[o.horizontal ? 'offsetWidth' : 'offsetHeight'], slideeElement[o.horizontal ? 'scrollWidth' : 'scrollHeight']); + + // Set position limits & relativess + self._pos.end = Math.max(slideeSize - frameSize, 0); + } + } + /** - * Slide SLIDEE by amount of pixels. + * Loading function. * - * @param {Int} delta Pixels/Items. Positive means forward, negative means backward. - * @param {Bool} immediate Reposition immediately without an animation. + * Populate arrays, set sizes, bind events, ... * + * @param {Boolean} [isInit] Whether load is called from within self.init(). * @return {Void} */ - scrollerFactory.prototype.slideBy = function (delta, immediate) { - if (!delta) { + function load(isInit) { + requiresReflow = true; + + if (!isInit) { + ensureSizeInfo(); + + // Fix possible overflowing + var pos = self._pos; + self.slideTo(within(pos.dest, pos.start, pos.end)); + } + } + + function initFrameResizeObserver() { + var observerOptions = {}; + + self.frameResizeObserver = new ResizeObserver(onResize, observerOptions); + + self.frameResizeObserver.observe(frame); + } + + self.reload = function () { + load(); + }; + + self.getScrollEventName = function () { + return transform ? 'scrollanimate' : 'scroll'; + }; + + self.getScrollSlider = function () { + return slideeElement; + }; + + self.getScrollFrame = function () { + return frame; + }; + + function nativeScrollTo(container, pos, immediate) { + if (container.scroll) { + if (o.horizontal) { + container.scroll({ + left: pos, + behavior: immediate ? 'instant' : 'smooth' + }); + } else { + container.scroll({ + top: pos, + behavior: immediate ? 'instant' : 'smooth' + }); + } + } else if (!immediate && container.scrollTo) { + if (o.horizontal) { + container.scrollTo(Math.Math.round(pos), 0); + } else { + container.scrollTo(0, Math.Math.round(pos)); + } + } else { + if (o.horizontal) { + container.scrollLeft = Math.Math.round(pos); + } else { + container.scrollTop = Math.Math.round(pos); + } + } + } + + var lastAnimate; + + /** + * Animate to a position. + * + * @param {Int} newPos New position. + * @param {Bool} immediate Reposition immediately without an animation. + * + * @return {Void} + */ + self.slideTo = function (newPos, immediate, fullItemPos) { + ensureSizeInfo(); + var pos = self._pos; + + newPos = within(newPos, pos.start, pos.end); + + if (!transform) { + nativeScrollTo(nativeScrollElement, newPos, immediate); return; } - this.slideTo(this._pos.dest + delta, immediate); - }; - /** - * Core method for handling `toLocation` methods. - * - * @param {String} location - * @param {Mixed} item - * @param {Bool} immediate - * - * @return {Void} - */ - scrollerFactory.prototype.to = function (location, item, immediate) { - // Optional arguments logic - if (type(item) === 'boolean') { - immediate = item; - item = undefined; - } + // Update the animation object + var from = pos.cur; + immediate = immediate || dragging.init || !o.speed; - if (item === undefined) { - this.slideTo(this._pos[location], immediate); - } else { - var itemPos = this.getPos(item); + var now = new Date().getTime(); - if (itemPos) { - this.slideTo(itemPos[location], immediate, itemPos); + if (o.autoImmediate) { + if (!immediate && (now - (lastAnimate || 0)) <= 50) { + immediate = true; } } + + if (!immediate && o.skipSlideToWhenVisible && fullItemPos && fullItemPos.isVisible) { + return; + } + + // Start animation rendering + // NOTE the dependency was modified here to fix a scrollbutton issue + pos.dest = newPos; + renderAnimateWithTransform(from, newPos, immediate); + lastAnimate = now; }; + function setStyleProperty(elem, name, value, speed, resetTransition) { + var style = elem.style; + + if (resetTransition || browser.edge) { + style.transition = 'none'; + void elem.offsetWidth; + } + + style.transition = 'transform ' + speed + 'ms ease-out'; + style[name] = value; + } + + function dispatchScrollEventIfNeeded() { + if (o.dispatchScrollEvent) { + frame.dispatchEvent(new CustomEvent(self.getScrollEventName(), { + bubbles: true, + cancelable: false + })); + } + } + + function renderAnimateWithTransform(fromPosition, toPosition, immediate) { + var speed = o.speed; + + if (immediate) { + speed = o.immediateSpeed || 50; + } + + if (o.horizontal) { + setStyleProperty(slideeElement, 'transform', 'translateX(' + (-Math.round(toPosition)) + 'px)', speed); + } else { + setStyleProperty(slideeElement, 'transform', 'translateY(' + (-Math.round(toPosition)) + 'px)', speed); + } + self._pos.cur = toPosition; + + dispatchScrollEventIfNeeded(); + } + + function getBoundingClientRect(elem) { + // Support: BlackBerry 5, iOS 3 (original iPhone) + // If we don't have gBCR, just use 0,0 rather than error + if (elem.getBoundingClientRect) { + return elem.getBoundingClientRect(); + } else { + return { top: 0, left: 0 }; + } + } + /** - * Animate element or the whole SLIDEE to the start of the frame. + * Returns the position object. * - * @param {Mixed} item Item DOM element, or index starting at 0. Omitting will animate SLIDEE. - * @param {Bool} immediate Reposition immediately without an animation. + * @param {Mixed} item + * + * @return {Object} + */ + self.getPos = function (item) { + var scrollElement = transform ? slideeElement : nativeScrollElement; + var slideeOffset = getBoundingClientRect(scrollElement); + var itemOffset = getBoundingClientRect(item); + + var offset = o.horizontal ? itemOffset.left - slideeOffset.left : itemOffset.top - slideeOffset.top; + + var size = o.horizontal ? itemOffset.width : itemOffset.height; + if (!size && size !== 0) { + size = item[o.horizontal ? 'offsetWidth' : 'offsetHeight']; + } + + var centerOffset = o.centerOffset || 0; + + if (!transform) { + centerOffset = 0; + if (o.horizontal) { + offset += nativeScrollElement.scrollLeft; + } else { + offset += nativeScrollElement.scrollTop; + } + } + + ensureSizeInfo(); + + var currentStart = self._pos.cur; + var currentEnd = currentStart + frameSize; + + console.debug('offset:' + offset + ' currentStart:' + currentStart + ' currentEnd:' + currentEnd); + var isVisible = offset >= currentStart && (offset + size) <= currentEnd; + + return { + start: offset, + center: offset + centerOffset - (frameSize / 2) + (size / 2), + end: offset - frameSize + size, + size: size, + isVisible: isVisible + }; + }; + + self.getCenterPosition = function (item) { + ensureSizeInfo(); + + var pos = self.getPos(item); + return within(pos.center, pos.start, pos.end); + }; + + function dragInitSlidee(event) { + var isTouch = event.type === 'touchstart'; + + // Ignore when already in progress, or interactive element in non-touch navivagion + if (dragging.init || !isTouch && isInteractive(event.target)) { + return; + } + + // SLIDEE dragging conditions + if (!(isTouch ? o.touchDragging : o.mouseDragging && event.which < 2)) { + return; + } + + if (!isTouch) { + // prevents native image dragging in Firefox + event.preventDefault(); + } + + // Reset dragging object + dragging.released = 0; + + // Properties used in dragHandler + dragging.init = 0; + dragging.source = event.target; + dragging.touch = isTouch; + var pointer = isTouch ? event.touches[0] : event; + dragging.initX = pointer.pageX; + dragging.initY = pointer.pageY; + dragging.initPos = self._pos.cur; + dragging.start = +new Date(); + dragging.time = 0; + dragging.path = 0; + dragging.delta = 0; + dragging.locked = 0; + dragging.pathToLock = isTouch ? 30 : 10; + + // Bind dragging events + if (transform) { + if (isTouch) { + dragTouchEvents.forEach(function (eventName) { + dom.addEventListener(document, eventName, dragHandler, { + passive: true + }); + }); + } else { + dragMouseEvents.forEach(function (eventName) { + dom.addEventListener(document, eventName, dragHandler, { + passive: true + }); + }); + } + } + } + + /** + * Handler for dragging scrollbar handle or SLIDEE. + * + * @param {Event} event * * @return {Void} */ - scrollerFactory.prototype.toStart = function (item, immediate) { - this.to('start', item, immediate); - }; + function dragHandler(event) { + dragging.released = event.type === 'mouseup' || event.type === 'touchend'; + var pointer = dragging.touch ? event[dragging.released ? 'changedTouches' : 'touches'][0] : event; + dragging.pathX = pointer.pageX - dragging.initX; + dragging.pathY = pointer.pageY - dragging.initY; + dragging.path = Math.sqrt(Math.pow(dragging.pathX, 2) + Math.pow(dragging.pathY, 2)); + dragging.delta = o.horizontal ? dragging.pathX : dragging.pathY; + + if (!dragging.released && dragging.path < 1) { + return; + } + + // We haven't decided whether this is a drag or not... + if (!dragging.init) { + // If the drag path was very short, maybe it's not a drag? + if (dragging.path < o.dragThreshold) { + // If the pointer was released, the path will not become longer and it's + // definitely not a drag. If not released yet, decide on next iteration + return dragging.released ? dragEnd() : undefined; + } else { + // If dragging path is sufficiently long we can confidently start a drag + // if drag is in different direction than scroll, ignore it + if (o.horizontal ? Math.abs(dragging.pathX) > Math.abs(dragging.pathY) : Math.abs(dragging.pathX) < Math.abs(dragging.pathY)) { + dragging.init = 1; + } else { + return dragEnd(); + } + } + } + + //event.preventDefault(); + + // Disable click on a source element, as it is unwelcome when dragging + if (!dragging.locked && dragging.path > dragging.pathToLock) { + dragging.locked = 1; + dragging.source.addEventListener('click', disableOneEvent); + } + + // Cancel dragging on release + if (dragging.released) { + dragEnd(); + } + + self.slideTo(Math.round(dragging.initPos - dragging.delta)); + } /** - * Animate element or the whole SLIDEE to the end of the frame. - * - * @param {Mixed} item Item DOM element, or index starting at 0. Omitting will animate SLIDEE. - * @param {Bool} immediate Reposition immediately without an animation. + * Stops dragging and cleans up after it. * * @return {Void} */ - scrollerFactory.prototype.toEnd = function (item, immediate) { - this.to('end', item, immediate); - }; + function dragEnd() { + dragging.released = true; + + dragTouchEvents.forEach(function (eventName) { + dom.removeEventListener(document, eventName, dragHandler, { + passive: true + }); + }); + + dragMouseEvents.forEach(function (eventName) { + dom.removeEventListener(document, eventName, dragHandler, { + passive: true + }); + }); + + // Make sure that disableOneEvent is not active in next tick. + setTimeout(function () { + dragging.source.removeEventListener('click', disableOneEvent); + }); + + dragging.init = 0; + } /** - * Animate element or the whole SLIDEE to the center of the frame. + * Check whether element is interactive. * - * @param {Mixed} item Item DOM element, or index starting at 0. Omitting will animate SLIDEE. - * @param {Bool} immediate Reposition immediately without an animation. + * @return {Boolean} + */ + function isInteractive(element) { + while (element) { + if (interactiveElements.indexOf(element.tagName) !== -1) { + return true; + } + + element = element.parentNode; + } + return false; + } + + /** + * Mouse wheel delta normalization. + * + * @param {Event} event + * + * @return {Int} + */ + function normalizeWheelDelta(event) { + // JELLYFIN MOD: Only use deltaX for horizontal scroll and remove IE8 support + scrolling.curDelta = o.horizontal ? event.deltaX : event.deltaY; + // END JELLYFIN MOD + + if (transform) { + scrolling.curDelta /= event.deltaMode === 1 ? 3 : 100; + } + return scrolling.curDelta; + } + + /** + * Mouse scrolling handler. + * + * @param {Event} event * * @return {Void} */ - scrollerFactory.prototype.toCenter = function (item, immediate) { - this.to('center', item, immediate); + function scrollHandler(event) { + ensureSizeInfo(); + var pos = self._pos; + // Ignore if there is no scrolling to be done + if (!o.scrollBy || pos.start === pos.end) { + return; + } + var delta = normalizeWheelDelta(event); + + if (transform) { + // Trap scrolling only when necessary and/or requested + if (delta > 0 && pos.dest < pos.end || delta < 0 && pos.dest > pos.start) { + //stopDefault(event, 1); + } + + self.slideBy(o.scrollBy * delta); + } else { + if (isSmoothScrollSupported) { + delta *= 12; + } + + if (o.horizontal) { + nativeScrollElement.scrollLeft += delta; + } else { + nativeScrollElement.scrollTop += delta; + } + } + } + + /** + * Destroys instance and everything it created. + * + * @return {Void} + */ + self.destroy = function () { + if (self.frameResizeObserver) { + self.frameResizeObserver.disconnect(); + self.frameResizeObserver = null; + } + + // Reset native FRAME element scroll + dom.removeEventListener(frame, 'scroll', resetScroll, { + passive: true + }); + + dom.removeEventListener(scrollSource, wheelEvent, scrollHandler, { + passive: true + }); + + dom.removeEventListener(dragSourceElement, 'touchstart', dragInitSlidee, { + passive: true + }); + + dom.removeEventListener(frame, 'click', onFrameClick, { + passive: true, + capture: true + }); + + dom.removeEventListener(dragSourceElement, 'mousedown', dragInitSlidee, { + //passive: true + }); + + // Reset initialized status and return the instance + self.initialized = 0; + return self; }; - scrollerFactory.create = function (frame, options) { - var instance = new scrollerFactory(frame, options); - return Promise.resolve(instance); + var contentRect = {}; + + function onResize(entries) { + var entry = entries[0]; + + if (entry) { + var newRect = entry.contentRect; + + // handle element being hidden + if (newRect.width === 0 || newRect.height === 0) { + return; + } + + if (newRect.width !== contentRect.width || newRect.height !== contentRect.height) { + contentRect = newRect; + + load(false); + } + } + } + + function resetScroll() { + if (o.horizontal) { + this.scrollLeft = 0; + } else { + this.scrollTop = 0; + } + } + + function onFrameClick(e) { + if (e.which === 1) { + var focusableParent = focusManager.focusableParent(e.target); + if (focusableParent && focusableParent !== document.activeElement) { + focusableParent.focus(); + } + } + } + + self.getScrollPosition = function () { + if (transform) { + return self._pos.cur; + } + + if (o.horizontal) { + return nativeScrollElement.scrollLeft; + } else { + return nativeScrollElement.scrollTop; + } }; - return scrollerFactory; -}); + self.getScrollSize = function () { + if (transform) { + return slideeSize; + } + + if (o.horizontal) { + return nativeScrollElement.scrollWidth; + } else { + return nativeScrollElement.scrollHeight; + } + }; + + /** + * Initialize. + * + * @return {Object} + */ + self.init = function () { + if (self.initialized) { + return; + } + + if (!transform) { + if (o.horizontal) { + if (layoutManager.desktop && !o.hideScrollbar) { + nativeScrollElement.classList.add('scrollX'); + } else { + nativeScrollElement.classList.add('scrollX'); + nativeScrollElement.classList.add('hiddenScrollX'); + + if (layoutManager.tv && o.allowNativeSmoothScroll !== false) { + nativeScrollElement.classList.add('smoothScrollX'); + } + } + + if (o.forceHideScrollbars) { + nativeScrollElement.classList.add('hiddenScrollX-forced'); + } + } else { + if (layoutManager.desktop && !o.hideScrollbar) { + nativeScrollElement.classList.add('scrollY'); + } else { + nativeScrollElement.classList.add('scrollY'); + nativeScrollElement.classList.add('hiddenScrollY'); + + if (layoutManager.tv && o.allowNativeSmoothScroll !== false) { + nativeScrollElement.classList.add('smoothScrollY'); + } + } + + if (o.forceHideScrollbars) { + nativeScrollElement.classList.add('hiddenScrollY-forced'); + } + } + } else { + frame.style.overflow = 'hidden'; + slideeElement.style['will-change'] = 'transform'; + slideeElement.style.transition = 'transform ' + o.speed + 'ms ease-out'; + + if (o.horizontal) { + slideeElement.classList.add('animatedScrollX'); + } else { + slideeElement.classList.add('animatedScrollY'); + } + } + + if (transform || layoutManager.tv) { + // This can prevent others from being able to listen to mouse events + dom.addEventListener(dragSourceElement, 'mousedown', dragInitSlidee, { + //passive: true + }); + } + + initFrameResizeObserver(); + + if (transform) { + dom.addEventListener(dragSourceElement, 'touchstart', dragInitSlidee, { + passive: true + }); + + if (!o.horizontal) { + dom.addEventListener(frame, 'scroll', resetScroll, { + passive: true + }); + } + + if (o.mouseWheel) { + // Scrolling navigation + dom.addEventListener(scrollSource, wheelEvent, scrollHandler, { + passive: true + }); + } + } else if (o.horizontal) { + // Don't bind to mouse events with vertical scroll since the mouse wheel can handle this natively + + if (o.mouseWheel) { + // Scrolling navigation + dom.addEventListener(scrollSource, wheelEvent, scrollHandler, { + passive: true + }); + } + } + + dom.addEventListener(frame, 'click', onFrameClick, { + passive: true, + capture: true + }); + + // Mark instance as initialized + self.initialized = 1; + + // Load + load(true); + + // Return instance + return self; + }; +}; + +/** + * Slide SLIDEE by amount of pixels. + * + * @param {Int} delta Pixels/Items. Positive means forward, negative means backward. + * @param {Bool} immediate Reposition immediately without an animation. + * + * @return {Void} + */ +scrollerFactory.prototype.slideBy = function (delta, immediate) { + if (!delta) { + return; + } + this.slideTo(this._pos.dest + delta, immediate); +}; + +/** + * Core method for handling `toLocation` methods. + * + * @param {String} location + * @param {Mixed} item + * @param {Bool} immediate + * + * @return {Void} + */ +scrollerFactory.prototype.to = function (location, item, immediate) { + // Optional arguments logic + if (type(item) === 'boolean') { + immediate = item; + item = undefined; + } + + if (item === undefined) { + this.slideTo(this._pos[location], immediate); + } else { + var itemPos = this.getPos(item); + + if (itemPos) { + this.slideTo(itemPos[location], immediate, itemPos); + } + } +}; + +/** + * Animate element or the whole SLIDEE to the start of the frame. + * + * @param {Mixed} item Item DOM element, or index starting at 0. Omitting will animate SLIDEE. + * @param {Bool} immediate Reposition immediately without an animation. + * + * @return {Void} + */ +scrollerFactory.prototype.toStart = function (item, immediate) { + this.to('start', item, immediate); +}; + +/** + * Animate element or the whole SLIDEE to the end of the frame. + * + * @param {Mixed} item Item DOM element, or index starting at 0. Omitting will animate SLIDEE. + * @param {Bool} immediate Reposition immediately without an animation. + * + * @return {Void} + */ +scrollerFactory.prototype.toEnd = function (item, immediate) { + this.to('end', item, immediate); +}; + +/** + * Animate element or the whole SLIDEE to the center of the frame. + * + * @param {Mixed} item Item DOM element, or index starting at 0. Omitting will animate SLIDEE. + * @param {Bool} immediate Reposition immediately without an animation. + * + * @return {Void} + */ +scrollerFactory.prototype.toCenter = function (item, immediate) { + this.to('center', item, immediate); +}; + +scrollerFactory.create = function (frame, options) { + var instance = new scrollerFactory(frame, options); + return Promise.resolve(instance); +}; + +export default scrollerFactory; From e7d6bc288956e3643c1c204bd413cf48725ccd17 Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Thu, 6 Aug 2020 22:25:14 +0200 Subject: [PATCH 089/124] Don't math twice --- src/libraries/scroller.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libraries/scroller.js b/src/libraries/scroller.js index 03c6306511..ada13403bf 100644 --- a/src/libraries/scroller.js +++ b/src/libraries/scroller.js @@ -229,15 +229,15 @@ var scrollerFactory = function (frame, options) { } } else if (!immediate && container.scrollTo) { if (o.horizontal) { - container.scrollTo(Math.Math.round(pos), 0); + container.scrollTo(Math.round(pos), 0); } else { - container.scrollTo(0, Math.Math.round(pos)); + container.scrollTo(0, Math.round(pos)); } } else { if (o.horizontal) { - container.scrollLeft = Math.Math.round(pos); + container.scrollLeft = Math.round(pos); } else { - container.scrollTop = Math.Math.round(pos); + container.scrollTop = Math.round(pos); } } } From 161488616f5dc789f54c8965f955a08d5402001a Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Thu, 6 Aug 2020 22:28:52 +0200 Subject: [PATCH 090/124] Fix suggestions --- src/scripts/scrollHelper.js | 59 +++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/src/scripts/scrollHelper.js b/src/scripts/scrollHelper.js index 020888a2e3..1ce30768af 100644 --- a/src/scripts/scrollHelper.js +++ b/src/scripts/scrollHelper.js @@ -98,40 +98,43 @@ function centerOnFocus(e, scrollSlider, horizontal) { function centerOnFocusHorizontal(e) { centerOnFocus(e, this, true); } + function centerOnFocusVertical(e) { centerOnFocus(e, this, false); } -export default { - getPosition: getPosition, - centerFocus: { - on: function (element, horizontal) { - if (horizontal) { - dom.addEventListener(element, 'focus', centerOnFocusHorizontal, { - capture: true, - passive: true - }); - } else { - dom.addEventListener(element, 'focus', centerOnFocusVertical, { - capture: true, - passive: true - }); - } - }, - off: function (element, horizontal) { - if (horizontal) { - dom.removeEventListener(element, 'focus', centerOnFocusHorizontal, { - capture: true, - passive: true - }); - } else { - dom.removeEventListener(element, 'focus', centerOnFocusVertical, { - capture: true, - passive: true - }); - } +export const centerFocus = { + on: function (element, horizontal) { + if (horizontal) { + dom.addEventListener(element, 'focus', centerOnFocusHorizontal, { + capture: true, + passive: true + }); + } else { + dom.addEventListener(element, 'focus', centerOnFocusVertical, { + capture: true, + passive: true + }); } }, + off: function (element, horizontal) { + if (horizontal) { + dom.removeEventListener(element, 'focus', centerOnFocusHorizontal, { + capture: true, + passive: true + }); + } else { + dom.removeEventListener(element, 'focus', centerOnFocusVertical, { + capture: true, + passive: true + }); + } + } +} + +export default { + getPosition: getPosition, + centerFocus: centerFocus, toCenter: toCenter, toStart: toStart }; From 453079fe068250dca9d4964ad15692e474b7b8af Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Thu, 6 Aug 2020 22:48:26 +0200 Subject: [PATCH 091/124] Fix require --- src/components/guide/guide.js | 3 ++- src/scripts/scrollHelper.js | 4 +--- src/scripts/serverNotifications.js | 6 +++--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/components/guide/guide.js b/src/components/guide/guide.js index 50eb55f834..05fa2b608d 100644 --- a/src/components/guide/guide.js +++ b/src/components/guide/guide.js @@ -4,7 +4,8 @@ define(['require', 'inputManager', 'browser', 'globalize', 'connectionManager', playbackManager = playbackManager.default || playbackManager; browser = browser.default || browser; loading = loading.default || loading; - focusManager = focusManager.default || focusManager;scrollHelper = scrollHelper.default || scrollHelper; + focusManager = focusManager.default || focusManager; + scrollHelper = scrollHelper.default || scrollHelper; serverNotifications = serverNotifications.default || serverNotifications; function showViewSettings(instance) { diff --git a/src/scripts/scrollHelper.js b/src/scripts/scrollHelper.js index 1ce30768af..b867123683 100644 --- a/src/scripts/scrollHelper.js +++ b/src/scripts/scrollHelper.js @@ -3,7 +3,6 @@ import dom from 'dom'; import 'scrollStyles'; function getBoundingClientRect(elem) { - // Support: BlackBerry 5, iOS 3 (original iPhone) // If we don't have gBCR, just use 0,0 rather than error if (elem.getBoundingClientRect) { @@ -14,7 +13,6 @@ function getBoundingClientRect(elem) { } export function getPosition(scrollContainer, item, horizontal) { - const slideeOffset = getBoundingClientRect(scrollContainer); const itemOffset = getBoundingClientRect(item); @@ -130,7 +128,7 @@ export const centerFocus = { }); } } -} +}; export default { getPosition: getPosition, diff --git a/src/scripts/serverNotifications.js b/src/scripts/serverNotifications.js index 83af40c4e6..2345cf327a 100644 --- a/src/scripts/serverNotifications.js +++ b/src/scripts/serverNotifications.js @@ -15,12 +15,12 @@ function notifyApp() { function displayMessage(cmd) { const args = cmd.Arguments; if (args.TimeoutMs) { - require(['toast'], function (toast) { + import('toast').then((toast) => { toast({ title: args.Header, text: args.Text }); }); } else { - require(['alert'], function (alert) { - alert.default({ title: args.Header, text: args.Text }); + import('alert').then((alert) => { + alert({ title: args.Header, text: args.Text }); }); } } From 4e860a57286b517867b795ef2b0da7e1b538e743 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 6 Aug 2020 21:12:22 +0000 Subject: [PATCH 092/124] Bump css-loader from 4.2.0 to 4.2.1 Bumps [css-loader](https://github.com/webpack-contrib/css-loader) from 4.2.0 to 4.2.1. - [Release notes](https://github.com/webpack-contrib/css-loader/releases) - [Changelog](https://github.com/webpack-contrib/css-loader/blob/master/CHANGELOG.md) - [Commits](https://github.com/webpack-contrib/css-loader/compare/v4.2.0...v4.2.1) 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 bf3ac42f97..146ee850d5 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "babel-loader": "^8.0.6", "browser-sync": "^2.26.12", "copy-webpack-plugin": "^5.1.1", - "css-loader": "^4.2.0", + "css-loader": "^4.2.1", "cssnano": "^4.1.10", "del": "^5.1.0", "eslint": "^7.6.0", diff --git a/yarn.lock b/yarn.lock index 3ec7f21487..ad55e16176 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3020,10 +3020,10 @@ css-has-pseudo@^0.10.0: postcss "^7.0.6" postcss-selector-parser "^5.0.0-rc.4" -css-loader@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-4.2.0.tgz#b57efb92ac8f0cd85bf92d89df9634ef1f51b8bf" - integrity sha512-ko7a9b0iFpWtk9eSI/C8IICvZeGtYnjxYjw45rJprokXj/+kBd/siX4vAIBq9Uij8Jubc4jL1EvSnTjCEwaHSw== +css-loader@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-4.2.1.tgz#9f48fd7eae1219d629a3f085ba9a9102ca1141a7" + integrity sha512-MoqmF1if7Z0pZIEXA4ZF9PgtCXxWbfzfJM+3p+OYfhcrwcqhaCRb74DSnfzRl7e024xEiCRn5hCvfUbTf2sgFA== dependencies: camelcase "^6.0.0" cssesc "^3.0.0" From 232ccb6b9fce607adf6bb4319f0bd6abd40ad48f Mon Sep 17 00:00:00 2001 From: Julien Machiels Date: Thu, 6 Aug 2020 23:26:51 +0200 Subject: [PATCH 093/124] Update src/scripts/serverNotifications.js Co-authored-by: Dmitry Lyzo <56478732+dmitrylyzo@users.noreply.github.com> --- src/scripts/serverNotifications.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripts/serverNotifications.js b/src/scripts/serverNotifications.js index 2345cf327a..c84377c880 100644 --- a/src/scripts/serverNotifications.js +++ b/src/scripts/serverNotifications.js @@ -15,7 +15,7 @@ function notifyApp() { function displayMessage(cmd) { const args = cmd.Arguments; if (args.TimeoutMs) { - import('toast').then((toast) => { + import('toast').then(({default: toast}) => { toast({ title: args.Header, text: args.Text }); }); } else { From 915a423997914e09b020dc8cd8dfc15614518886 Mon Sep 17 00:00:00 2001 From: Julien Machiels Date: Thu, 6 Aug 2020 23:26:59 +0200 Subject: [PATCH 094/124] Update src/scripts/serverNotifications.js Co-authored-by: Dmitry Lyzo <56478732+dmitrylyzo@users.noreply.github.com> --- src/scripts/serverNotifications.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripts/serverNotifications.js b/src/scripts/serverNotifications.js index c84377c880..2566d148f6 100644 --- a/src/scripts/serverNotifications.js +++ b/src/scripts/serverNotifications.js @@ -19,7 +19,7 @@ function displayMessage(cmd) { toast({ title: args.Header, text: args.Text }); }); } else { - import('alert').then((alert) => { + import('alert').then(({default: alert}) => { alert({ title: args.Header, text: args.Text }); }); } From 878ab482cb43545ba7b91670baec1a8ba49a1759 Mon Sep 17 00:00:00 2001 From: Julien Machiels Date: Thu, 6 Aug 2020 23:27:26 +0200 Subject: [PATCH 095/124] Update src/libraries/scroller.js Co-authored-by: Dmitry Lyzo <56478732+dmitrylyzo@users.noreply.github.com> --- src/libraries/scroller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/scroller.js b/src/libraries/scroller.js index ada13403bf..c460ec5b2c 100644 --- a/src/libraries/scroller.js +++ b/src/libraries/scroller.js @@ -2,7 +2,7 @@ * and will be replaced soon by a Vue component. */ - import browser from 'browser'; +import browser from 'browser'; import layoutManager from 'layoutManager'; import dom from 'dom'; import focusManager from 'focusManager'; From 7658cd5900ddc2c46c1edb449a72eaf3394fdb19 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 7 Aug 2020 07:41:49 +0100 Subject: [PATCH 096/124] Migration of logoScreenSaver to ES6 module --- package.json | 1 + src/plugins/logoScreensaver/plugin.js | 308 +++++++++++++------------- 2 files changed, 155 insertions(+), 154 deletions(-) diff --git a/package.json b/package.json index 8038de5f90..42e236d670 100644 --- a/package.json +++ b/package.json @@ -159,6 +159,7 @@ "src/components/sanatizefilename.js", "src/components/scrollManager.js", "src/plugins/htmlVideoPlayer/plugin.js", + "src/plugins/logoScreensaver/plugin.js", "src/components/search/searchfields.js", "src/components/search/searchresults.js", "src/components/settingshelper.js", diff --git a/src/plugins/logoScreensaver/plugin.js b/src/plugins/logoScreensaver/plugin.js index bdd1d34e79..61b8f8a6d6 100644 --- a/src/plugins/logoScreensaver/plugin.js +++ b/src/plugins/logoScreensaver/plugin.js @@ -1,165 +1,165 @@ -define(['pluginManager'], function (pluginManager) { - return function () { - var self = this; +import pluginManager from 'pluginManager'; - self.name = 'Logo ScreenSaver'; - self.type = 'screensaver'; - self.id = 'logoscreensaver'; - self.supportsAnonymous = true; +export default function () { + const self = this; - var interval; + self.name = 'Logo ScreenSaver'; + self.type = 'screensaver'; + self.id = 'logoscreensaver'; + self.supportsAnonymous = true; - function animate() { - var animations = [ + let interval; - bounceInLeft, - bounceInRight, - swing, - tada, - wobble, - rotateIn, - rotateOut - ]; + function animate() { + const animations = [ - var elem = document.querySelector('.logoScreenSaverImage'); + bounceInLeft, + bounceInRight, + swing, + tada, + wobble, + rotateIn, + rotateOut + ]; - if (elem && elem.animate) { - var random = getRandomInt(0, animations.length - 1); + const elem = document.querySelector('.logoScreenSaverImage'); - animations[random](elem, 1); + if (elem && elem.animate) { + const random = getRandomInt(0, animations.length - 1); + + animations[random](elem, 1); + } + } + + function getRandomInt(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; + } + + function bounceInLeft(elem, iterations) { + const keyframes = [ + { transform: 'translate3d(-3000px, 0, 0)', opacity: '0', offset: 0 }, + { transform: 'translate3d(25px, 0, 0)', opacity: '1', offset: 0.6 }, + { transform: 'translate3d(-100px, 0, 0)', offset: 0.75 }, + { transform: 'translate3d(5px, 0, 0)', offset: 0.9 }, + { transform: 'none', opacity: '1', offset: 1 }]; + const timing = { duration: 900, iterations: iterations, easing: 'cubic-bezier(0.215, 0.610, 0.355, 1.000)' }; + return elem.animate(keyframes, timing); + } + + function bounceInRight(elem, iterations) { + const keyframes = [ + { transform: 'translate3d(3000px, 0, 0)', opacity: '0', offset: 0 }, + { transform: 'translate3d(-25px, 0, 0)', opacity: '1', offset: 0.6 }, + { transform: 'translate3d(100px, 0, 0)', offset: 0.75 }, + { transform: 'translate3d(-5px, 0, 0)', offset: 0.9 }, + { transform: 'none', opacity: '1', offset: 1 }]; + const timing = { duration: 900, iterations: iterations, easing: 'cubic-bezier(0.215, 0.610, 0.355, 1.000)' }; + return elem.animate(keyframes, timing); + } + + function swing(elem, iterations) { + const keyframes = [ + { transform: 'translate(0%)', offset: 0 }, + { transform: 'rotate3d(0, 0, 1, 15deg)', offset: 0.2 }, + { transform: 'rotate3d(0, 0, 1, -10deg)', offset: 0.4 }, + { transform: 'rotate3d(0, 0, 1, 5deg)', offset: 0.6 }, + { transform: 'rotate3d(0, 0, 1, -5deg)', offset: 0.8 }, + { transform: 'rotate3d(0, 0, 1, 0deg)', offset: 1 }]; + const timing = { duration: 900, iterations: iterations }; + return elem.animate(keyframes, timing); + } + + function tada(elem, iterations) { + const keyframes = [ + { transform: 'scale3d(1, 1, 1)', offset: 0 }, + { transform: 'scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg)', offset: 0.1 }, + { transform: 'scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg)', offset: 0.2 }, + { transform: 'scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg)', offset: 0.3 }, + { transform: 'scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg)', offset: 0.4 }, + { transform: 'scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg)', offset: 0.5 }, + { transform: 'scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg)', offset: 0.6 }, + { transform: 'scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg)', offset: 0.7 }, + { transform: 'scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg)', offset: 0.8 }, + { transform: 'scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg)', offset: 0.9 }, + { transform: 'scale3d(1, 1, 1)', offset: 1 }]; + const timing = { duration: 900, iterations: iterations }; + return elem.animate(keyframes, timing); + } + + function wobble(elem, iterations) { + const keyframes = [ + { transform: 'translate(0%)', offset: 0 }, + { transform: 'translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg)', offset: 0.15 }, + { transform: 'translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg)', offset: 0.45 }, + { transform: 'translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg)', offset: 0.6 }, + { transform: 'translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg)', offset: 0.75 }, + { transform: 'translateX(0%)', offset: 1 }]; + const timing = { duration: 900, iterations: iterations }; + return elem.animate(keyframes, timing); + } + + function rotateIn(elem, iterations) { + const keyframes = [{ transform: 'rotate3d(0, 0, 1, -200deg)', opacity: '0', transformOrigin: 'center', offset: 0 }, + { transform: 'none', opacity: '1', transformOrigin: 'center', offset: 1 }]; + const timing = { duration: 900, iterations: iterations }; + return elem.animate(keyframes, timing); + } + + function rotateOut(elem, iterations) { + const keyframes = [{ transform: 'none', opacity: '1', transformOrigin: 'center', offset: 0 }, + { transform: 'rotate3d(0, 0, 1, 200deg)', opacity: '0', transformOrigin: 'center', offset: 1 }]; + const timing = { duration: 900, iterations: iterations }; + return elem.animate(keyframes, timing); + } + + function fadeOut(elem, iterations) { + const keyframes = [ + { opacity: '1', offset: 0 }, + { opacity: '0', offset: 1 }]; + const timing = { duration: 400, iterations: iterations }; + return elem.animate(keyframes, timing); + } + + function stopInterval() { + if (interval) { + clearInterval(interval); + interval = null; + } + } + + self.show = function () { + import('css!' + pluginManager.mapPath(self, 'style.css')).then(() => { + let elem = document.querySelector('.logoScreenSaver'); + + if (!elem) { + elem = document.createElement('div'); + elem.classList.add('logoScreenSaver'); + document.body.appendChild(elem); + + elem.innerHTML = ''; } - } - function getRandomInt(min, max) { - return Math.floor(Math.random() * (max - min + 1)) + min; - } - - function bounceInLeft(elem, iterations) { - var keyframes = [ - { transform: 'translate3d(-3000px, 0, 0)', opacity: '0', offset: 0 }, - { transform: 'translate3d(25px, 0, 0)', opacity: '1', offset: 0.6 }, - { transform: 'translate3d(-100px, 0, 0)', offset: 0.75 }, - { transform: 'translate3d(5px, 0, 0)', offset: 0.9 }, - { transform: 'none', opacity: '1', offset: 1 }]; - var timing = { duration: 900, iterations: iterations, easing: 'cubic-bezier(0.215, 0.610, 0.355, 1.000)' }; - return elem.animate(keyframes, timing); - } - - function bounceInRight(elem, iterations) { - var keyframes = [ - { transform: 'translate3d(3000px, 0, 0)', opacity: '0', offset: 0 }, - { transform: 'translate3d(-25px, 0, 0)', opacity: '1', offset: 0.6 }, - { transform: 'translate3d(100px, 0, 0)', offset: 0.75 }, - { transform: 'translate3d(-5px, 0, 0)', offset: 0.9 }, - { transform: 'none', opacity: '1', offset: 1 }]; - var timing = { duration: 900, iterations: iterations, easing: 'cubic-bezier(0.215, 0.610, 0.355, 1.000)' }; - return elem.animate(keyframes, timing); - } - - function swing(elem, iterations) { - var keyframes = [ - { transform: 'translate(0%)', offset: 0 }, - { transform: 'rotate3d(0, 0, 1, 15deg)', offset: 0.2 }, - { transform: 'rotate3d(0, 0, 1, -10deg)', offset: 0.4 }, - { transform: 'rotate3d(0, 0, 1, 5deg)', offset: 0.6 }, - { transform: 'rotate3d(0, 0, 1, -5deg)', offset: 0.8 }, - { transform: 'rotate3d(0, 0, 1, 0deg)', offset: 1 }]; - var timing = { duration: 900, iterations: iterations }; - return elem.animate(keyframes, timing); - } - - function tada(elem, iterations) { - var keyframes = [ - { transform: 'scale3d(1, 1, 1)', offset: 0 }, - { transform: 'scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg)', offset: 0.1 }, - { transform: 'scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg)', offset: 0.2 }, - { transform: 'scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg)', offset: 0.3 }, - { transform: 'scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg)', offset: 0.4 }, - { transform: 'scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg)', offset: 0.5 }, - { transform: 'scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg)', offset: 0.6 }, - { transform: 'scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg)', offset: 0.7 }, - { transform: 'scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg)', offset: 0.8 }, - { transform: 'scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg)', offset: 0.9 }, - { transform: 'scale3d(1, 1, 1)', offset: 1 }]; - var timing = { duration: 900, iterations: iterations }; - return elem.animate(keyframes, timing); - } - - function wobble(elem, iterations) { - var keyframes = [ - { transform: 'translate(0%)', offset: 0 }, - { transform: 'translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg)', offset: 0.15 }, - { transform: 'translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg)', offset: 0.45 }, - { transform: 'translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg)', offset: 0.6 }, - { transform: 'translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg)', offset: 0.75 }, - { transform: 'translateX(0%)', offset: 1 }]; - var timing = { duration: 900, iterations: iterations }; - return elem.animate(keyframes, timing); - } - - function rotateIn(elem, iterations) { - var keyframes = [{ transform: 'rotate3d(0, 0, 1, -200deg)', opacity: '0', transformOrigin: 'center', offset: 0 }, - { transform: 'none', opacity: '1', transformOrigin: 'center', offset: 1 }]; - var timing = { duration: 900, iterations: iterations }; - return elem.animate(keyframes, timing); - } - - function rotateOut(elem, iterations) { - var keyframes = [{ transform: 'none', opacity: '1', transformOrigin: 'center', offset: 0 }, - { transform: 'rotate3d(0, 0, 1, 200deg)', opacity: '0', transformOrigin: 'center', offset: 1 }]; - var timing = { duration: 900, iterations: iterations }; - return elem.animate(keyframes, timing); - } - - function fadeOut(elem, iterations) { - var keyframes = [ - { opacity: '1', offset: 0 }, - { opacity: '0', offset: 1 }]; - var timing = { duration: 400, iterations: iterations }; - return elem.animate(keyframes, timing); - } - - function stopInterval() { - if (interval) { - clearInterval(interval); - interval = null; - } - } - - self.show = function () { - require(['css!' + pluginManager.mapPath(self, 'style.css')], function () { - var elem = document.querySelector('.logoScreenSaver'); - - if (!elem) { - elem = document.createElement('div'); - elem.classList.add('logoScreenSaver'); - document.body.appendChild(elem); - - elem.innerHTML = ''; - } - - stopInterval(); - interval = setInterval(animate, 3000); - }); - }; - - self.hide = function () { stopInterval(); - - var elem = document.querySelector('.logoScreenSaver'); - - if (elem) { - var onAnimationFinish = function () { - elem.parentNode.removeChild(elem); - }; - - if (elem.animate) { - var animation = fadeOut(elem, 1); - animation.onfinish = onAnimationFinish; - } else { - onAnimationFinish(); - } - } - }; + interval = setInterval(animate, 3000); + }); }; -}); + + self.hide = function () { + stopInterval(); + + const elem = document.querySelector('.logoScreenSaver'); + + if (elem) { + const onAnimationFinish = function () { + elem.parentNode.removeChild(elem); + }; + + if (elem.animate) { + const animation = fadeOut(elem, 1); + animation.onfinish = onAnimationFinish; + } else { + onAnimationFinish(); + } + } + }; +} From 6eface92394a99b4705d27bcf403cf8f2369443b Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 7 Aug 2020 07:42:09 +0100 Subject: [PATCH 097/124] Migration of playAccessValidation to ES6 module --- package.json | 1 + src/plugins/playAccessValidation/plugin.js | 37 +++++++++------------- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index 42e236d670..00381203b1 100644 --- a/package.json +++ b/package.json @@ -160,6 +160,7 @@ "src/components/scrollManager.js", "src/plugins/htmlVideoPlayer/plugin.js", "src/plugins/logoScreensaver/plugin.js", + "src/plugins/playAccessValidation/plugin.js", "src/components/search/searchfields.js", "src/components/search/searchresults.js", "src/components/settingshelper.js", diff --git a/src/plugins/playAccessValidation/plugin.js b/src/plugins/playAccessValidation/plugin.js index 5148d2b821..a9fbeda9a9 100644 --- a/src/plugins/playAccessValidation/plugin.js +++ b/src/plugins/playAccessValidation/plugin.js @@ -1,33 +1,26 @@ -define(['connectionManager', 'globalize'], function (connectionManager, globalize) { - 'use strict'; +import connectionManager from 'connectionManager'; +import globalize from 'globalize'; - function getRequirePromise(deps) { - return new Promise(function (resolve, reject) { - require(deps, resolve); - }); - } +function showErrorMessage() { + return import('alert').then(({default: alert}) => { + return alert(globalize.translate('MessagePlayAccessRestricted')); + }); +} - function showErrorMessage() { - return getRequirePromise(['alert']).then(function (alert) { - return alert(globalize.translate('MessagePlayAccessRestricted')).then(function () { - return Promise.reject(); - }); - }); - } - - function PlayAccessValidation() { +class PlayAccessValidation { + constructor() { this.name = 'Playback validation'; this.type = 'preplayintercept'; this.id = 'playaccessvalidation'; this.order = -2; } - PlayAccessValidation.prototype.intercept = function (options) { - var item = options.item; + intercept(options) { + const item = options.item; if (!item) { return Promise.resolve(); } - var serverId = item.ServerId; + const serverId = item.ServerId; if (!serverId) { return Promise.resolve(); } @@ -44,7 +37,7 @@ define(['connectionManager', 'globalize'], function (connectionManager, globaliz return showErrorMessage(); }); - }; + } +} - return PlayAccessValidation; -}); +export default PlayAccessValidation; From 0b786a5205b8f587ad1f9fede76a0bd1855a02fa Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 7 Aug 2020 08:13:51 +0100 Subject: [PATCH 098/124] Fix Linting --- src/libraries/screensavermanager.js | 36 +++++++++++++---------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/src/libraries/screensavermanager.js b/src/libraries/screensavermanager.js index 557b31e0f4..ca35d38a98 100644 --- a/src/libraries/screensavermanager.js +++ b/src/libraries/screensavermanager.js @@ -1,5 +1,5 @@ -define(["events", "playbackManager", "pluginManager", "inputManager", "connectionManager", "userSettings"], function (events, playbackManager, pluginManager, inputManager, connectionManager, userSettings) { - "use strict"; +define(['events', 'playbackManager', 'pluginManager', 'inputManager', 'connectionManager', 'userSettings'], function (events, playbackManager, pluginManager, inputManager, connectionManager, userSettings) { + 'use strict'; playbackManager = playbackManager.default || playbackManager; @@ -15,23 +15,22 @@ define(["events", "playbackManager", "pluginManager", "inputManager", "connectio return new Date().getTime() - lastFunctionalEvent; } - events.on(playbackManager, "playbackstop", function (e, stopInfo) { + events.on(playbackManager, 'playbackstop', function (e, stopInfo) { var state = stopInfo.state; - if (state.NowPlayingItem && state.NowPlayingItem.MediaType == "Video") { + if (state.NowPlayingItem && state.NowPlayingItem.MediaType == 'Video') { lastFunctionalEvent = new Date().getTime(); } }); function getScreensaverPlugin(isLoggedIn) { - var option; try { - option = userSettings.get("screensaver", false); + option = userSettings.get('screensaver', false); } catch (err) { - option = isLoggedIn ? "backdropscreensaver" : "logoscreensaver"; + option = isLoggedIn ? 'backdropscreensaver' : 'logoscreensaver'; } - var plugins = pluginManager.ofType("screensaver"); + var plugins = pluginManager.ofType('screensaver'); for (var i = 0, length = plugins.length; i < length; i++) { var plugin = plugins[i]; @@ -45,42 +44,40 @@ define(["events", "playbackManager", "pluginManager", "inputManager", "connectio } function ScreenSaverManager() { - var self = this; var activeScreenSaver; function showScreenSaver(screensaver) { - if (activeScreenSaver) { - throw new Error("An existing screensaver is already active."); + throw new Error('An existing screensaver is already active.'); } - console.debug("Showing screensaver " + screensaver.name); + console.debug('Showing screensaver ' + screensaver.name); screensaver.show(); activeScreenSaver = screensaver; if (screensaver.hideOnClick !== false) { - window.addEventListener("click", hide, true); + window.addEventListener('click', hide, true); } if (screensaver.hideOnMouse !== false) { - window.addEventListener("mousemove", hide, true); + window.addEventListener('mousemove', hide, true); } if (screensaver.hideOnKey !== false) { - window.addEventListener("keydown", hide, true); + window.addEventListener('keydown', hide, true); } } function hide() { if (activeScreenSaver) { - console.debug("Hiding screensaver"); + console.debug('Hiding screensaver'); activeScreenSaver.hide(); activeScreenSaver = null; } - window.removeEventListener("click", hide, true); - window.removeEventListener("mousemove", hide, true); - window.removeEventListener("keydown", hide, true); + window.removeEventListener('click', hide, true); + window.removeEventListener('mousemove', hide, true); + window.removeEventListener('keydown', hide, true); } self.isShowing = function () { @@ -107,7 +104,6 @@ define(["events", "playbackManager", "pluginManager", "inputManager", "connectio }; function onInterval() { - if (self.isShowing()) { return; } From 0872f3f0010263d48a3b546031d215a7f2c3c3b4 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 7 Aug 2020 09:27:11 +0100 Subject: [PATCH 099/124] Apply suggestions --- src/controllers/list.js | 49 +++++++++++++---------------- src/libraries/screensavermanager.js | 4 +-- 2 files changed, 23 insertions(+), 30 deletions(-) diff --git a/src/controllers/list.js b/src/controllers/list.js index 979bb76a20..2c3b966c4b 100644 --- a/src/controllers/list.js +++ b/src/controllers/list.js @@ -184,9 +184,9 @@ import 'emby-scroller'; const values = instance.getSortValues(); const sortBy = values.sortBy; - for (let i = 0, length = options.length; i < length; i++) { - if (sortBy === options[i].value) { - btnSortText.innerHTML = globalize.translate('SortByValue', options[i].name); + for (const option of options) { + if (sortBy === option.value) { + btnSortText.innerHTML = globalize.translate('SortByValue', option.name); break; } } @@ -407,18 +407,18 @@ import 'emby-scroller'; } function hideOrShowAll(elems, hide) { - for (let i = 0, length = elems.length; i < length; i++) { + for (const elem of elems) { if (hide) { - elems[i].classList.add('hide'); + elem.classList.add('hide'); } else { - elems[i].classList.remove('hide'); + elem.classList.remove('hide'); } } } function bindAll(elems, eventName, fn) { - for (let i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener(eventName, fn); + for (const elem of elems) { + elem.addEventListener(eventName, fn); } } @@ -724,16 +724,15 @@ class ItemsView { const btnViewSettings = view.querySelectorAll('.btnViewSettings'); - for (let i = 0, length = btnViewSettings.length; i < length; i++) { - btnViewSettings[i].addEventListener('click', showViewSettingsMenu.bind(this)); + for (const btnViewSetting of btnViewSettings) { + btnViewSetting.addEventListener('click', showViewSettingsMenu.bind(this)); } const filterButtons = view.querySelectorAll('.btnFilter'); this.filterButtons = filterButtons; const hasVisibleFilters = this.getVisibleFilters().length; - for (let i = 0, length = filterButtons.length; i < length; i++) { - const btnFilter = filterButtons[i]; + for (const btnFilter of filterButtons) { btnFilter.addEventListener('click', showFilterMenu.bind(this)); if (hasVisibleFilters) { @@ -744,10 +743,9 @@ class ItemsView { } const sortButtons = view.querySelectorAll('.btnSort'); - let i; - let length; - for (this.sortButtons = sortButtons, i = 0, length = sortButtons.length; i < length; i++) { - const sortButton = sortButtons[i]; + + this.sortButtons = sortButtons; + for (const sortButton of sortButtons) { sortButton.addEventListener('click', showSortMenu.bind(this)); if (params.type !== 'nextup') { @@ -886,10 +884,9 @@ class ItemsView { } getDefaultSortBy() { - const params = this.params; - const sortNameOption = this.getNameSortOption(params); + const sortNameOption = this.getNameSortOption(this.params); - if (params.type) { + if (this.params.type) { return sortNameOption.value; } @@ -898,16 +895,15 @@ class ItemsView { getSortMenuOptions() { const sortBy = []; - const params = this.params; - if (params.type === 'Programs') { + if (this.params.type === 'Programs') { sortBy.push({ name: globalize.translate('AirDate'), value: 'StartDate,SortName' }); } - let option = this.getNameSortOption(params); + let option = this.getNameSortOption(this.params); if (option) { sortBy.push(option); @@ -925,7 +921,7 @@ class ItemsView { sortBy.push(option); } - if (params.type !== 'Programs') { + if (this.params.type !== 'Programs') { sortBy.push({ name: globalize.translate('DateAdded'), value: 'DateCreated,SortName' @@ -938,8 +934,8 @@ class ItemsView { sortBy.push(option); } - if (!params.type) { - option = this.getNameSortOption(params); + if (!this.params.type) { + option = this.getNameSortOption(this.params); sortBy.push({ name: globalize.translate('Folders'), value: 'IsFolder,' + option.value @@ -1054,8 +1050,7 @@ class ItemsView { const filterButtons = this.filterButtons; if (filterButtons.length) { - for (let i = 0, length = filterButtons.length; i < length; i++) { - const btnFilter = filterButtons[i]; + for (const btnFilter of filterButtons) { let bubble = btnFilter.querySelector('.filterButtonBubble'); if (!bubble) { diff --git a/src/libraries/screensavermanager.js b/src/libraries/screensavermanager.js index 61d128ff14..5c24ec63d0 100644 --- a/src/libraries/screensavermanager.js +++ b/src/libraries/screensavermanager.js @@ -34,9 +34,7 @@ function getScreensaverPlugin(isLoggedIn) { const plugins = pluginManager.ofType('screensaver'); - for (let i = 0, length = plugins.length; i < length; i++) { - const plugin = plugins[i]; - + for (const plugin of plugins) { if (plugin.id === option) { return plugin; } From 93151b883bb6ce5a34deadbbf3c80a038019df50 Mon Sep 17 00:00:00 2001 From: LapinoLapidus Date: Fri, 7 Aug 2020 09:20:37 +0000 Subject: [PATCH 100/124] Translated using Weblate (Dutch) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/nl/ --- src/strings/nl.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/strings/nl.json b/src/strings/nl.json index 860783b083..36b947855d 100644 --- a/src/strings/nl.json +++ b/src/strings/nl.json @@ -161,7 +161,7 @@ "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.", - "DirectStreamHelp2": "Direct streamen van een bestand gebruikt weinig processor kracht zonder verlies van beeldkwaliteit.", + "DirectStreamHelp2": "Direct streamen van een bestand gebruikt weinig processorkracht zonder verlies van beeldkwaliteit.", "DirectStreaming": "Direct streamen", "Director": "Regiseur", "Directors": "Regisseurs", @@ -378,7 +378,7 @@ "HeaderPreferredMetadataLanguage": "Gewenste metadata taal", "HeaderProfile": "Profiel", "HeaderProfileInformation": "Profiel Informatie", - "HeaderProfileServerSettingsHelp": "Deze waarden bepalen hoe Jellyfin Server zich zal presenteren aan het apparaat.", + "HeaderProfileServerSettingsHelp": "Deze waarden bepalen hoe de server zich zal presenteren aan het apparaat.", "HeaderRecentlyPlayed": "Recent afgespeeld", "HeaderRecordingOptions": "Opname instellingen", "HeaderRecordingPostProcessing": "Opname nabewerking", @@ -396,13 +396,13 @@ "HeaderSecondsValue": "{0} Seconden", "HeaderSelectCertificatePath": "Selecteer Certificaat Pad", "HeaderSelectMetadataPath": "Selecteer Metadata Pad", - "HeaderSelectMetadataPathHelp": "Blader of voer het pad in dat u wilt gebruiken om metadata in op te slaan. De map moet beschrijfbaar zijn.", + "HeaderSelectMetadataPathHelp": "Blader of voer het pad in dat u wilt gebruiken om metadata in op te slaan. De map moet schrijfbaar zijn.", "HeaderSelectPath": "Selecteer Pad", "HeaderSelectServer": "Selecteer server", "HeaderSelectServerCachePath": "Selecteer Server Cache Pad", "HeaderSelectServerCachePathHelp": "Bladeren of voer het pad in om te gebruiken voor server cache-bestanden. De map moet beschrijfbaar zijn.", "HeaderSelectTranscodingPath": "Selecteer Tijdelijke Transcodeer Pad", - "HeaderSelectTranscodingPathHelp": "Bladeren of voer het pad in om te gebruiken voor het transcoderen van tijdelijke bestanden. De map moet beschrijfbaar zijn.", + "HeaderSelectTranscodingPathHelp": "Blader of voer het pad in om te gebruiken voor het transcoderen van tijdelijke bestanden. De map moet schrijfbaar zijn.", "HeaderSendMessage": "Stuur bericht", "HeaderSeries": "Series", "HeaderSeriesOptions": "Series Opties", @@ -1255,7 +1255,7 @@ "HeaderGenres": "Genres", "HeaderHttpHeaders": "HTTP Headers", "HeaderStatus": "Status", - "AuthProviderHelp": "Selecteer een Authenticatie Provider om het wachtwoord van deze gebruiker te verifiëren.", + "AuthProviderHelp": "Selecteer een authenticatie provider om het wachtwoord van deze gebruiker te verifiëren.", "HeaderFavoriteMovies": "Favoriete Films", "HeaderFavoriteShows": "Favoriete shows", "HeaderFavoriteEpisodes": "Favoriete afleveringen", From c5f6b5057452d348e473074934498e67d0984d8b Mon Sep 17 00:00:00 2001 From: Tobias Hoos Date: Fri, 7 Aug 2020 13:22:37 +0000 Subject: [PATCH 101/124] Translated using Weblate (German) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/de/ --- src/strings/de.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/strings/de.json b/src/strings/de.json index 1c87a95a95..0aa4d2172f 100644 --- a/src/strings/de.json +++ b/src/strings/de.json @@ -1542,5 +1542,8 @@ "ViewAlbumArtist": "Zeige Albumkünstler", "PreviousTrack": "Zum Vorherigen springen", "NextTrack": "Zum Nächsten springen", - "LabelUnstable": "Instabil" + "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:" } From 5efd05617c7b9505302c5c1ab97e7d3339a454f9 Mon Sep 17 00:00:00 2001 From: 4d1m Date: Fri, 7 Aug 2020 15:04:04 +0000 Subject: [PATCH 102/124] Translated using Weblate (Romanian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/ro/ --- src/strings/ro.json | 58 +++++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/src/strings/ro.json b/src/strings/ro.json index f8aa986264..5489e92abf 100644 --- a/src/strings/ro.json +++ b/src/strings/ro.json @@ -61,9 +61,9 @@ "HeaderTaskTriggers": "Declanșatori Sarcini", "HeaderUsers": "Utilizatori", "Help": "Ajutor", - "ImportMissingEpisodesHelp": "Dacă este activată, informația despre episoadele lipsă va fi importată in baza de date Jellyfin și va fi afișată în cadrul serialelor. Aceasta poate cauza un timp semnificativ mai îndelungat la scanarea bibliotecilor.", + "ImportMissingEpisodesHelp": "Informația despre episoadele lipsă va fi importată în baza de date și va fi afișată în cadrul serialelor. Aceasta poate cauza un timp semnificativ mai îndelungat la scanarea bibliotecilor.", "LabelArtists": "Artisti:", - "LabelArtistsHelp": "Separare multiplă utilizând ;", + "LabelArtistsHelp": "Separară înșiruirea artiștilor utilizând ;", "LabelAudioLanguagePreference": "Preferințe de limbă pentru audio:", "LabelCachePath": "Cale pentru depozit:", "LabelCachePathHelp": "Specificați o locație specială pentru fișierele de tip depozit, precum imagini etc. Lasați gol pentru a folosi setarea implicită.", @@ -82,7 +82,7 @@ "LabelMetadataPath": "Cale pentru metadata:", "LabelMetadataPathHelp": "Specificați o locație specială pentru a descărca postere și metadata.", "LabelMinBackdropDownloadWidth": "Lățimea maximă pentru fundalurile descărcate:", - "LabelMovieRecordingPath": "Calea pentru înregistrări filme (opțional):", + "LabelMovieRecordingPath": "Calea pentru înregistrări filme:", "LabelName": "Nume:", "LabelNewPassword": "Parola nouă:", "LabelNewPasswordConfirm": "Confirmă parola nouă:", @@ -96,7 +96,7 @@ "LabelSaveLocalMetadata": "Salvează posterele si metadata în dosarele ce conțin fișierele media", "LabelSaveLocalMetadataHelp": "Salvând posterele și metadata direct in dosarele media, acestea vor fi mai accesibile pentru a fi modificate.", "LabelSelectUsers": "Selectare utilizatori:", - "LabelSeriesRecordingPath": "Calea pentru înregistrări seriale (opțional):", + "LabelSeriesRecordingPath": "Calea pentru înregistrări de seriale:", "LabelStopWhenPossible": "Oprește când este posibil:", "LabelTimeLimitHours": "Limită de timp(ore):", "LabelTranscodingTempPathHelp": "Specificați o cale specială pentru fișierele transcodate trimise clienților. Lasați gol pentru a folosi pe cea implicită în directorul de lucru al serverului.", @@ -133,7 +133,7 @@ "OptionDatePlayed": "Dată Rulare", "OptionDescending": "Descrescător", "OptionDisableUser": "Dezactivați acest utilizator", - "OptionDisableUserHelp": "Dacă este dezactivat, serverul nu va permite nicio conexiune de la acest utilizator. Conexiunile existente vor fi terminate brusc.", + "OptionDisableUserHelp": "Serverul nu va permite nici o conexiune de la acest utilizator. Conexiunile existente vor fi terminate brusc.", "OptionDislikes": "Dislike-uri", "OptionDownloadArtImage": "Fundal", "OptionDownloadBackImage": "Înapoi", @@ -419,7 +419,7 @@ "HeaderExternalIds": "ID-uri Externe:", "HeaderFavoriteBooks": "Cărți Favorite", "HeaderBranding": "Marca", - "HeaderApiKeysHelp": "Aplicațiile externe trebuie să aibă o cheie API pentru a comunica cu Jellyfin Server. Cheile sunt emise prin conectarea cu un cont Jellyfin sau prin acordarea manuală a unei chei aplicației.", + "HeaderApiKeysHelp": "Aplicațiile externe trebuie să aibă o cheie API pentru a comunica cu serverul. Cheile sunt emise prin conectarea cu un cont de utilizator sau prin acordarea manuală a unei chei aplicației.", "Sync": "Sincronizare", "ErrorAddingXmlTvFile": "A apărut o eroare la accesarea fișierului XMLTV. Vă rugăm să vă asigurați că fișierul există și încercați din nou.", "HeaderApiKey": "Cheie API", @@ -459,7 +459,7 @@ "HeaderMyMediaSmall": "Fișierele mele Media ( micșorat )", "HeaderNewApiKey": "Nouă cheie API", "HeaderNewDevices": "Dispozitive noi", - "HeaderKodiMetadataHelp": "Pentru a activa sau dezactiva metadatele NFO, editați o bibliotecă, în configurarea bibliotecii Jellyfin, și localizați secțiunea de salvare a metadatelor.", + "HeaderKodiMetadataHelp": "Pentru a activa sau dezactiva metadatele NFO, editați o bibliotecă, și localizați secțiunea de salvare a metadatelor.", "HeaderNextVideoPlayingInValue": "Următorul video se redă în {0}", "HeaderOnNow": "Pornit Acum", "HeaderOtherItems": "Alte Elemente", @@ -475,7 +475,7 @@ "HeaderPlaybackError": "Eroare la redare", "HeaderPluginInstallation": "Instalare Plugin", "HeaderProfileInformation": "Informații Profil", - "HeaderProfileServerSettingsHelp": "Aceste valori controlează modul în care Jellyfin Server va fi reprezentat in dispozitiv.", + "HeaderProfileServerSettingsHelp": "Aceste valori controlează modul în care serverul va fi reprezentat in dispozitivele clientilor.", "HeaderRecordingOptions": "Opțiuni Înregistrare", "HeaderRecordingPostProcessing": "Post procesarea înregistrării", "HeaderRemoveMediaFolder": "Eliminați Dosarul Media", @@ -614,7 +614,7 @@ "HeaderSelectServer": "Selectați Serverul", "HeaderSelectServerCachePath": "Selectați ruta pentru Server Cache", "HeaderSelectTranscodingPath": "Selectați ruta temporară pentru transcodare", - "HeaderSelectTranscodingPathHelp": "Căutați sau introduceți ruta dosarului de utilizat pentru transcodarea fișierelor temporare. Dosarul trebuie permisiuni de scriere.", + "HeaderSelectTranscodingPathHelp": "Căutați sau introduceți ruta dosarului de utilizat pentru transcodarea fișierelor. Dosarul trebuie permisiuni de scriere.", "HeaderSendMessage": "Trimite Mesaj", "HeaderSeriesOptions": "Opțiuni Seriale", "HeaderSeriesStatus": "Starea Serialelor", @@ -675,7 +675,7 @@ "LabelSeasonNumber": "Numărul sezonului:", "LabelScreensaver": "Protector de ecran:", "LabelScheduledTaskLastRan": "Ultima redare{0}, cu durata {1}.", - "LabelRuntimeMinutes": "Timp de redare (minute):", + "LabelRuntimeMinutes": "Timp de redare:", "LabelRemoteClientBitrateLimitHelp": "O limită de biți per-stream opțională pentru toate dispozitivele din rețea. Acest lucru este util pentru a împiedica dispozitivele să solicite un bitrate mai mare decât poate gestiona conexiunea dvs. de internet. Acest lucru poate duce la creșterea încărcării procesorului pe serverul dvs. pentru a transcoda videoclipurile din zbor la un bitrate mai mic.", "LabelRemoteClientBitrateLimit": "Limită de biți pentru streaming pe Internet (Mbps):", "LabelReleaseDate": "Data lansării:", @@ -720,7 +720,7 @@ "LabelOriginalTitle": "Titlu original:", "LabelOriginalAspectRatio": "Raport aspect original:", "LabelOptionalNetworkPathHelp": "Dacă acest folder este partajat în rețeaua dvs., furnizarea căii de partajare a rețelei poate permite aplicațiilor Jellyfin de pe alte dispozitive să acceseze fișiere media direct.", - "LabelOptionalNetworkPath": "(Optional) Dosar partajat în rețea:", + "LabelOptionalNetworkPath": "Dosar partajat în rețea:", "LabelNumber": "Număr:", "LabelNotificationEnabled": "Activează această notificare", "LabelNewsCategories": "Categoriile știrilor:", @@ -741,7 +741,7 @@ "LabelMinResumeDurationHelp": "Cea mai scurtă lungime video în secunde, care va salva locația de redare și vă va permite să reluați.", "LabelMinResumeDuration": "Durata minimă a reluării:", "LabelMethod": "Metoda:", - "LabelMetadataSaversHelp": "Alegeți formatele de fișiere pentru a vă salva metadatele.", + "LabelMetadataSaversHelp": "Alegeți formatele de fișiere pentru salvarea metadatele.", "LabelMetadataSavers": "Salvări de metadate:", "LabelMetadataReadersHelp": "Clasificați sursele preferate de metadate locale în ordinea priorității. Primul fișier găsit va fi citit.", "LabelMetadataReaders": "Cititori de metadate:", @@ -761,7 +761,7 @@ "LabelLoginDisclaimerHelp": "Un mesaj care va fi afișat în partea de jos a paginii de conectare.", "LabelLoginDisclaimer": "Act de renunțare la autentificare:", "LabelLockItemToPreventChanges": "Blocați acest element pentru a preveni modificările viitoare", - "LabelLocalHttpServerPortNumberHelp": "Portul TCP pe care serverul HTTP Jellyfin ar trebui să îl utilizeze.", + "LabelLocalHttpServerPortNumberHelp": "Portul TCP pentru serverul HTTP.", "LabelLocalHttpServerPortNumber": "Portul local HTTP:", "LabelLineup": "Echipa:", "LabelLanNetworks": "Rețele LAN:", @@ -788,7 +788,7 @@ "LabelIconMaxWidth": "Lățimea maximă a pictogramei:", "LabelIconMaxHeightHelp": "Rezoluția maximă a pictogramelor expuse via upnp:icon.", "LabelIconMaxHeight": "Înălțimea maximă a pictogramei:", - "LabelHttpsPortHelp": "Portul TCP pe care serverul HTTPS Jellyfin ar trebui sa îl utilizeze.", + "LabelHttpsPortHelp": "Portul TCP pentru serverul HTTPS.", "LabelHttpsPort": "Portul local HTTPS:", "LabelHomeScreenSectionValue": "Secțiunea ecranului de pornire {0}:", "LabelHomeNetworkQuality": "Calitatea pe rețeaua de domiciliu:", @@ -816,7 +816,7 @@ "LabelEndDate": "Data de încheiere:", "LabelEnableSingleImageInDidlLimitHelp": "Unele dispozitive nu vor reda corect dacă mai multe imagini sunt încorporate în Didl.", "LabelEnableSingleImageInDidlLimit": "Limitați la o singură imagine încorporată", - "LabelEnableRealtimeMonitorHelp": "Modificările la fișiere vor fi procesate imediat, pe sistemele de fișiere acceptate.", + "LabelEnableRealtimeMonitorHelp": "Modificările la fișiere vor fi procesate imediat pe sistemele de fișiere acceptate.", "LabelEnableRealtimeMonitor": "Activați monitorizarea în timp real", "LabelEnableHardwareDecodingFor": "Activați decodarea hardware pentru:", "LabelEnableDlnaServerHelp": "Permite dispozitivelor UPnP din rețeaua dvs. să răsfoiască și să redea conținut.", @@ -826,7 +826,7 @@ "LabelEnableDlnaDebugLoggingHelp": "Creați fișiere de jurnal mari și trebuie utilizate numai în funcție de necesități pentru rezolvarea problemelor.", "LabelEnableDlnaDebugLogging": "Activați jurnalul de depanare DLNA", "LabelEnableDlnaClientDiscoveryIntervalHelp": "Determină durata în secunde între căutările SSDP efectuate de Jellyfin.", - "LabelEnableDlnaClientDiscoveryInterval": "Interval de descoperire a clientului (secunde)", + "LabelEnableDlnaClientDiscoveryInterval": "Interval de descoperire a clientului", "LabelEnableBlastAliveMessagesHelp": "Activați acest lucru dacă serverul nu este detectat în mod fiabil de alte dispozitive UPnP din rețeaua dvs.", "LabelEnableBlastAliveMessages": "Trimitere mesaje de disponibilitate", "LabelEnableAutomaticPortMapHelp": "Încercați să mapați automat portul public către portul local prin UPnP. Este posibil să nu funcționeze cu unele modele de router. Modificările nu se vor aplica decât după repornirea serverului.", @@ -874,11 +874,11 @@ "LabelBurnSubtitles": "Imprimă subtitrările:", "LabelBlockContentWithTags": "Blochează articolele cu etichetele:", "LabelBlastMessageIntervalHelp": "Determină durata în secunde între transmiterea mesajele de viață.", - "LabelBlastMessageInterval": "Interval transmitere mesaj viu (secunde)", + "LabelBlastMessageInterval": "Interval transmitere mesaj viu", "LabelBitrate": "Rată de biți:", "LabelBirthYear": "Anul nașterii:", "LabelBirthDate": "Data nașterii:", - "LabelBindToLocalNetworkAddressHelp": "Opțional. Rescrie adresa IP locală pentru a o utiliza serverul http. Dacă este lăsat gol, serverul se va lega la toate adresele disponibile. Modificarea acestei valori necesită repornirea Jellyfin Server.", + "LabelBindToLocalNetworkAddressHelp": "Rescrie adresa IP locală a serverului http. Dacă este lăsat gol, serverul se va lega la toate adresele disponibile. Modificarea acestei valori necesită repornirea Jellyfin Server.", "LabelBindToLocalNetworkAddress": "Utilizează adresa de rețea locală:", "LabelAutomaticallyRefreshInternetMetadataEvery": "Actualizați automat metadatele de pe internet:", "LabelAuthProvider": "Furnizor de autentificare:", @@ -917,7 +917,7 @@ "ItemCount": "{0} articole", "InstantMix": "Mix instant", "InstallingPackage": "Instalare {0} (versiune {1})", - "ImportFavoriteChannelsHelp": "Dacă este activat, vor fi importate numai canalele marcate ca preferate pe dispozitivul tuner.", + "ImportFavoriteChannelsHelp": "Vor fi importate numai canalele marcate ca preferate pe dispozitivul tuner.", "Images": "Imagini", "Identify": "Identifică", "HttpsRequiresCert": "Pentru a activa conexiunile securizate, va trebui să furnizați un certificat SSL de încredere, cum ar fi Let's Encrypt. Vă rugăm să furnizați un certificat sau să dezactivați conexiunile securizate.", @@ -969,14 +969,14 @@ "OptionBlockChannelContent": "Conținut canal Internet", "OptionBlockBooks": "Cărți", "OptionBanner": "Steag", - "OptionAutomaticallyGroupSeriesHelp": "Dacă este activat, seriile distribuite pe mai multe foldere din această bibliotecă vor fi comasate automat într-o singură serie.", + "OptionAutomaticallyGroupSeriesHelp": "Seriile distribuite pe mai multe foldere din această bibliotecă vor fi comasate automat într-o singură serie.", "OptionAutomaticallyGroupSeries": "Fuzionează automat seriile care sunt răspândite pe mai multe foldere", "OptionAuto": "Auto", "OptionArtist": "Artist", "OptionAllowVideoPlaybackTranscoding": "Permiteți redarea video care necesită transcodare", "OptionAllowVideoPlaybackRemuxing": "Permiteți redarea video care necesită conversie fără re-codificare", "OptionAllowSyncTranscoding": "Permiteți descărcarea și sincronizarea media care necesită transcodare", - "OptionAllowMediaPlaybackTranscodingHelp": "Restrângerea accesului la transcodare poate provoca defecțiuni de redare în aplicațiile Jellyfin din cauza formatelor media neacceptate.", + "OptionAllowMediaPlaybackTranscodingHelp": "Restrângerea accesului la transcodare poate provoca defecțiuni de redare în aplicațiile client din cauza formatelor media neacceptate.", "OptionAllowContentDownloading": "Permiteți descărcarea și sincronizarea media", "OptionAllowAudioPlaybackTranscoding": "Permiteți redarea audio care necesită transcodare", "OptionAllUsers": "Toți utilizatorii", @@ -1113,7 +1113,7 @@ "LatestFromLibrary": "Ultimele {0}", "Large": "Mare", "LanNetworksHelp": "Lista separată de virgule a adreselor IP sau a intrărilor de tip IP/mască de rețea pentru rețelele care vor fi luate în considerare în rețeaua locală atunci când se aplică restricțiile de lățime de bandă. Dacă este setat, toate celelalte adrese IP vor fi considerate a fi în rețeaua externă și vor fi supuse restricțiilor de lățime de bandă externe. Dacă este lăsat necompletat, numai subnetul serverului este considerat a fi în rețeaua locală.", - "LabelffmpegPathHelp": "Calea către executabilul ffmpeg, sau dosarul care conține ffmpeg.", + "LabelffmpegPathHelp": "Calea către executabilul ffmpeg sau dosarul care conține ffmpeg.", "LabelffmpegPath": "Calea către FFmpeg:", "LabelZipCode": "Cod poștal:", "LabelYear": "Anul:", @@ -1300,9 +1300,9 @@ "OptionProfileAudio": "Audio", "OptionPosterCard": "Carte de afiș", "OptionPoster": "Afiș", - "OptionPlainVideoItemsHelp": "Dacă este activat, toate videoclipurile sunt reprezentate în DIDL ca „object.item.videoItem” în loc de un tip mai specific, cum ar fi „object.item.videoItem.movie”.", + "OptionPlainVideoItemsHelp": "Toate videoclipurile sunt reprezentate în DIDL ca „object.item.videoItem” în loc de un tip mai specific, cum ar fi „object.item.videoItem.movie”.", "OptionPlainVideoItems": "Afișați toate videoclipurile ca elemente video simple", - "OptionPlainStorageFoldersHelp": "Dacă este activat, toate folderele sunt reprezentate în DIDL ca „object.container.storageFolder” în loc de un tip mai specific, cum ar fi „object.container.person.musicArtist”.", + "OptionPlainStorageFoldersHelp": "Toate dosarele sunt reprezentate în DIDL ca „object.container.storageFolder” în loc de un tip mai specific, cum ar fi „object.container.person.musicArtist”.", "OptionPlainStorageFolders": "Afișați toate dosarele ca dosare simple de stocare", "OptionOnInterval": "La un interval", "OptionOnAppStartup": "La pornirea aplicației", @@ -1315,7 +1315,7 @@ "OptionList": "Listă", "OptionIsSD": "SD", "OptionIsHD": "HD", - "OptionIgnoreTranscodeByteRangeRequestsHelp": "Dacă sunt activate, aceste solicitări vor fi respectate, dar vor ignora antetul intervalului de octeți.", + "OptionIgnoreTranscodeByteRangeRequestsHelp": "Aceste solicitări vor fi respectate, dar vor ignora antetul intervalului de octeți.", "OptionIgnoreTranscodeByteRangeRequests": "Ignorați solicitările pentru transcodarea intervalului de octeți", "OptionHomeVideos": "Fotografii", "OptionHlsSegmentedSubtitles": "Subtitrare segmentată HLS", @@ -1332,7 +1332,7 @@ "OptionEnableExternalContentInSuggestions": "Activați conținut extern în sugestii", "OptionEmbedSubtitles": "Inclus în container", "OptionDownloadLogoImage": "Siglă", - "OptionDownloadImagesInAdvanceHelp": "În mod implicit, majoritatea imaginilor sunt descărcate numai la cererea unei aplicații din Jellyfin. Activați această opțiune pentru a descărca în prealabil toate imaginile, pe măsură ce fișierele media sunt importate. Acest lucru poate provoca scanări ale bibliotecii semnificativ mai lungi.", + "OptionDownloadImagesInAdvanceHelp": "În mod implicit, majoritatea imaginilor sunt descărcate numai la cererea unei aplicații Jellyfin. Activați această opțiune pentru a descărca în avans toate imaginile, pe măsură ce fișiere media noi sunt importate. Acest lucru poate duce la mărirea semnificativă a timpilor de scanare a bibliotecii.", "OptionDownloadImagesInAdvance": "Descărcați imaginile în avans", "OptionDownloadDiscImage": "Disc", "OptionDisplayFolderViewHelp": "Afișați dosarele alături de celelalte biblioteci media. Acest lucru poate fi util dacă doriți să aveți o vizualizare direct în dosar.", @@ -1476,7 +1476,7 @@ "LabelRequireHttps": "Trebuie HTTPS", "LabelStable": "Stabilă", "LabelChromecastVersion": "Versiunea de Chromecast", - "LabelEnableHttpsHelp": "Activează serverul să asculte pe portul HTTPS configurat. Un certificat valid trebuie de asemenea configurat pentru ca să funcţioneze.", + "LabelEnableHttpsHelp": "Ascultă pe portul HTTPS configurat. Un certificat valid trebuie de asemenea configurat pentru ca să funcţioneze.", "LabelEnableHttps": "Activați HTTPS", "HeaderServerAddressSettings": "Setările adresei serverului", "HeaderRemoteAccessSettings": "Setări pentru aces remote", @@ -1539,5 +1539,7 @@ "LabelRepositoryNameHelp": "Un nume personalizat pentru a distinge acest repertoriu de altele adăugate la serverul dvs.", "ClearQueue": "Golește lista de redare", "StopPlayback": "Oprește redarea", - "ViewAlbumArtist": "Vezi artistul albumului" + "ViewAlbumArtist": "Vezi artistul albumului", + "NextTrack": "Sari la următorul", + "LabelUnstable": "Instabil" } From da7cdda13217ddd47121d5ff59447ad3db0486d7 Mon Sep 17 00:00:00 2001 From: 4d1m Date: Fri, 7 Aug 2020 15:22:47 +0000 Subject: [PATCH 103/124] Translated using Weblate (Romanian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/ro/ --- src/strings/ro.json | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/strings/ro.json b/src/strings/ro.json index 5489e92abf..a0f090676b 100644 --- a/src/strings/ro.json +++ b/src/strings/ro.json @@ -1217,7 +1217,7 @@ "ReleaseDate": "Data lansării", "RefreshQueued": "Actualizare adăugată în coadă.", "RefreshMetadata": "Actualizați metadatele", - "RefreshDialogHelp": "Metadatele sunt actualizate pe baza setărilor și a serviciilor de internet care sunt activate în tabloul de bord Jellyfin Server.", + "RefreshDialogHelp": "Metadatele sunt actualizate pe baza setărilor și a serviciilor de internet care sunt activate în tabloul de bord.", "Refresh": "Reîmprospătează", "Recordings": "Înregistrări", "RecordingScheduled": "Înregistrare programată.", @@ -1263,7 +1263,7 @@ "PerfectMatch": "Potrivire perfectă", "People": "Oameni", "PasswordSaved": "Parolă salvată.", - "PasswordResetProviderHelp": "Alegeți un furnizor de resetare a parolei pentru a fi utilizat atunci când acest utilizator solicită o resetare a parolei", + "PasswordResetProviderHelp": "Alegeți un furnizor de resetare a parolei pentru a fi utilizat atunci când acest utilizator solicită o resetare a parolei.", "HeaderResetPassword": "Resetează parola", "PasswordResetConfirmation": "Sigur doriți să resetați parola?", "PasswordResetComplete": "Parola a fost resetată.", @@ -1541,5 +1541,9 @@ "StopPlayback": "Oprește redarea", "ViewAlbumArtist": "Vezi artistul albumului", "NextTrack": "Sari la următorul", - "LabelUnstable": "Instabil" + "LabelUnstable": "Instabil", + "Preview": "Previzualizare", + "SubtitleVerticalPositionHelp": "Numărul de linie unde apare textul. Numerele pozitive indică de sus în jos. Numerele negative indică de jos în sus.", + "LabelSubtitleVerticalPosition": "Poziție verticală:", + "PreviousTrack": "Sari anterior" } From 406e07154c048c1541bf97521b17445bf5ae7c27 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Fri, 7 Aug 2020 13:18:21 -0400 Subject: [PATCH 104/124] Hide download button for books when not supported --- src/controllers/itemDetails/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/controllers/itemDetails/index.js b/src/controllers/itemDetails/index.js index df2855d69a..b2cdc63234 100644 --- a/src/controllers/itemDetails/index.js +++ b/src/controllers/itemDetails/index.js @@ -1,3 +1,4 @@ +import appHost from 'apphost'; import loading from 'loading'; import appRouter from 'appRouter'; import layoutManager from 'layoutManager'; @@ -657,7 +658,7 @@ import 'emby-select'; setPeopleHeader(page, item); loading.hide(); - if (item.Type === 'Book') { + if (item.Type === 'Book' && item.CanDownload && appHost.supports('filedownload')) { hideAll(page, 'btnDownload', true); } From 496fb43cb5ac655b6a0cd4a9de4f2b5e0f760d95 Mon Sep 17 00:00:00 2001 From: Thomas Schwery Date: Fri, 7 Aug 2020 20:29:10 +0000 Subject: [PATCH 105/124] Translated using Weblate (French) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fr/ --- src/strings/fr.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/strings/fr.json b/src/strings/fr.json index 6d8fb68ecf..20b96f3a52 100644 --- a/src/strings/fr.json +++ b/src/strings/fr.json @@ -1542,5 +1542,8 @@ "ViewAlbumArtist": "Voir l'album de l'artiste", "PreviousTrack": "Revenir au précédent", "NextTrack": "Passer au prochain", - "LabelUnstable": "Instable" + "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 :" } From dbb6cd97301cc111ae54f500e683d88759b579c4 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 7 Aug 2020 22:46:12 +0100 Subject: [PATCH 106/124] Lint --- src/libraries/scroller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/scroller.js b/src/libraries/scroller.js index 4bfcb7d5b9..c460ec5b2c 100644 --- a/src/libraries/scroller.js +++ b/src/libraries/scroller.js @@ -883,4 +883,4 @@ scrollerFactory.create = function (frame, options) { return Promise.resolve(instance); }; -export default scrollerFactory; \ No newline at end of file +export default scrollerFactory; From b512187129cfb841bc0b1b8556616150ff4fef16 Mon Sep 17 00:00:00 2001 From: SaddFox Date: Fri, 7 Aug 2020 22:09:25 +0000 Subject: [PATCH 107/124] Translated using Weblate (Slovenian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/sl/ --- src/strings/sl-si.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/strings/sl-si.json b/src/strings/sl-si.json index 423ee7797c..bf9ec21379 100644 --- a/src/strings/sl-si.json +++ b/src/strings/sl-si.json @@ -1429,5 +1429,7 @@ "OptionEnableM2tsModeHelp": "Omogoči m2ts način pri kodiranju v mpegts.", "OptionEnableM2tsMode": "Omogoči M2ts način", "OptionDisplayFolderViewHelp": "Prikaže mape poleg ostalih knjižnic predstavnosti. Uporabno za preprost ogled map.", - "OptionDisplayFolderView": "Prikaži pogled mape za prikaz navadnih map predstavnosti" + "OptionDisplayFolderView": "Prikaži pogled mape za prikaz navadnih map predstavnosti", + "Yesterday": "Včeraj", + "Yes": "Da" } From 28d34b1bae8f8b77458fb98c11f134c8fed1ad52 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 7 Aug 2020 23:13:54 +0100 Subject: [PATCH 108/124] Update src/controllers/list.js Co-authored-by: Dmitry Lyzo <56478732+dmitrylyzo@users.noreply.github.com> --- src/controllers/list.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/list.js b/src/controllers/list.js index 2c3b966c4b..0027c576f1 100644 --- a/src/controllers/list.js +++ b/src/controllers/list.js @@ -399,7 +399,7 @@ import 'emby-scroller'; const instance = this; import('playlistEditor').then(({default: playlistEditor}) => { - new playlistEditor.showEditor({ + new playlistEditor({ items: [], serverId: instance.params.serverId }); From 2d72645aad0ae9dfa94fa9d7e32333d522129499 Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Sat, 8 Aug 2020 03:15:57 +0200 Subject: [PATCH 109/124] 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 110/124] 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 b3166dd2c4132d07ec2436d7abc4efa052b016ec Mon Sep 17 00:00:00 2001 From: SaddFox Date: Fri, 7 Aug 2020 22:20:55 +0000 Subject: [PATCH 111/124] Translated using Weblate (Slovenian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/sl/ --- src/strings/sl-si.json | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/strings/sl-si.json b/src/strings/sl-si.json index bf9ec21379..077ea67804 100644 --- a/src/strings/sl-si.json +++ b/src/strings/sl-si.json @@ -1431,5 +1431,21 @@ "OptionDisplayFolderViewHelp": "Prikaže mape poleg ostalih knjižnic predstavnosti. Uporabno za preprost ogled map.", "OptionDisplayFolderView": "Prikaži pogled mape za prikaz navadnih map predstavnosti", "Yesterday": "Včeraj", - "Yes": "Da" + "Yes": "Da", + "RecommendationStarring": "Nastopa {0}", + "Recordings": "Posnetki", + "RemoveFromCollection": "Odstrani iz zbirke", + "ResumeAt": "Nadaljuj od {0}", + "SaveSubtitlesIntoMediaFolders": "Shrani podnapise v mape predstavnosti", + "ScanForNewAndUpdatedFiles": "Poišči nove in spremenjene datoteke", + "Screenshot": "Posnetek zaslona", + "Screenshots": "Posnetki zaslona", + "Search": "Iskanje", + "ShowAdvancedSettings": "Prikaži napredne nastavitve", + "New": "Novo", + "SubtitleOffset": "Zamik podnapisev", + "Subtitles": "Podnapisi", + "Sunday": "Nedelja", + "TabAdvanced": "Napredno", + "TabAlbums": "Albumi" } From f34a64a607c024bea5df3fd8fac00ed68768ab85 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sat, 8 Aug 2020 11:13:21 +0000 Subject: [PATCH 112/124] Bump whatwg-fetch from 3.3.1 to 3.4.0 Bumps [whatwg-fetch](https://github.com/github/fetch) from 3.3.1 to 3.4.0. - [Release notes](https://github.com/github/fetch/releases) - [Commits](https://github.com/github/fetch/compare/v3.3.1...v3.4.0) 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 ac2ad86e57..d7cd3b7f31 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "sortablejs": "^1.10.2", "swiper": "^5.4.5", "webcomponents.js": "^0.7.24", - "whatwg-fetch": "^3.3.1" + "whatwg-fetch": "^3.4.0" }, "babel": { "presets": [ diff --git a/yarn.lock b/yarn.lock index ad55e16176..fd02458167 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11994,10 +11994,10 @@ webworkify@^1.5.0: resolved "https://registry.yarnpkg.com/webworkify/-/webworkify-1.5.0.tgz#734ad87a774de6ebdd546e1d3e027da5b8f4a42c" integrity sha512-AMcUeyXAhbACL8S2hqqdqOLqvJ8ylmIbNwUIqQujRSouf4+eUFaXbG6F1Rbu+srlJMmxQWsiU7mOJi0nMBfM1g== -whatwg-fetch@^3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.3.1.tgz#6c1acf37dec176b0fd6bc9a74b616bec2f612935" - integrity sha512-faXTmGDcLuEPBpJwb5LQfyxvubKiE+RlbmmweFGKjvIPFj4uHTTfdtTIkdTRhC6OSH9S9eyYbx8kZ0UEaQqYTA== +whatwg-fetch@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.4.0.tgz#e11de14f4878f773fbebcde8871b2c0699af8b30" + integrity sha512-rsum2ulz2iuZH08mJkT0Yi6JnKhwdw4oeyMjokgxd+mmqYSd9cPpOQf01TIWgjxG/U4+QR+AwKq6lSbXVxkyoQ== which-module@^1.0.0: version "1.0.0" From 93f23c3d32d85899406e699a66b1e40e19968899 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sat, 8 Aug 2020 11:18:26 +0000 Subject: [PATCH 113/124] Bump hls.js from 0.14.7 to 0.14.8 Bumps [hls.js](https://github.com/video-dev/hls.js) from 0.14.7 to 0.14.8. - [Release notes](https://github.com/video-dev/hls.js/releases) - [Changelog](https://github.com/video-dev/hls.js/blob/master/docs/release-process.md) - [Commits](https://github.com/video-dev/hls.js/compare/v0.14.7...v0.14.8) 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 ac2ad86e57..9f3b1d9499 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "fast-text-encoding": "^1.0.3", "flv.js": "^1.5.0", "headroom.js": "^0.11.0", - "hls.js": "^0.14.7", + "hls.js": "^0.14.8", "howler": "^2.2.0", "intersection-observer": "^0.11.0", "jellyfin-apiclient": "^1.4.1", diff --git a/yarn.lock b/yarn.lock index ad55e16176..bfe52b7f3e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5465,10 +5465,10 @@ hex-color-regex@^1.1.0: resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== -hls.js@^0.14.7: - version "0.14.7" - resolved "https://registry.yarnpkg.com/hls.js/-/hls.js-0.14.7.tgz#47fbd2662b13121ab17c07aea06b1c07828240cf" - integrity sha512-9JY0D9nwMrfQPRWc8/kEJTKK0TYfDTzIs6Xq+gdCvasRxdvQKQ2T76rdueTkS0AsFV6sQlJN0wxbnI44aRvvUA== +hls.js@^0.14.8: + version "0.14.8" + resolved "https://registry.yarnpkg.com/hls.js/-/hls.js-0.14.8.tgz#c2c6ca7005524c81eece316c2a4a199258bd0590" + integrity sha512-4fh8k/sl1SmYXsT4Om8AY5fKa5tUUtAxup2sffrSMh5MNk4Kt4FOZxbjqTGL5VwkroY1oJ9twSciNQNFbPA/WQ== dependencies: eventemitter3 "^4.0.3" url-toolkit "^2.1.6" From 23be95e450d4873eb5a938cfe3884ec60f083249 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 8 Aug 2020 17:51:10 +0100 Subject: [PATCH 114/124] 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 115/124] 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 af2fab85d12f53a16a8e98b30e03ae7166ad7794 Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Sun, 26 Jul 2020 22:25:54 +0200 Subject: [PATCH 116/124] Migrate add, installed, available plugin pages to ES6 --- package.json | 3 + .../dashboard/plugins/add/index.js | 233 ++++++------ .../dashboard/plugins/available/index.js | 248 ++++++------ .../dashboard/plugins/installed/index.js | 357 +++++++++--------- src/strings/en-us.json | 2 + 5 files changed, 426 insertions(+), 417 deletions(-) diff --git a/package.json b/package.json index 54d5f28189..467dc3b171 100644 --- a/package.json +++ b/package.json @@ -209,6 +209,9 @@ "src/controllers/dashboard/notifications/notification.js", "src/controllers/dashboard/notifications/notifications.js", "src/controllers/dashboard/playback.js", + "src/controllers/dashboard/plugins/add/index.js", + "src/controllers/dashboard/plugins/installed/index.js", + "src/controllers/dashboard/plugins/available/index.js", "src/controllers/dashboard/plugins/repositories/index.js", "src/controllers/dashboard/scheduledtasks/scheduledtask.js", "src/controllers/dashboard/scheduledtasks/scheduledtasks.js", diff --git a/src/controllers/dashboard/plugins/add/index.js b/src/controllers/dashboard/plugins/add/index.js index 6c666b4646..8393970e76 100644 --- a/src/controllers/dashboard/plugins/add/index.js +++ b/src/controllers/dashboard/plugins/add/index.js @@ -1,143 +1,148 @@ -define(['jQuery', 'loading', 'libraryMenu', 'globalize', 'connectionManager', 'emby-button'], function ($, loading, libraryMenu, globalize, connectionManager) { - 'use strict'; +import $ from 'jQuery'; +import loading from 'loading'; +import globalize from 'globalize'; +import 'emby-button'; - loading = loading.default || loading; +function populateHistory(packageInfo, page) { + let html = ''; + const length = Math.min(packageInfo.versions.length, 10); - function populateHistory(packageInfo, page) { - var html = ''; - var length = Math.min(packageInfo.versions.length, 10); - - for (var i = 0; i < length; i++) { - var version = packageInfo.versions[i]; - html += '

' + version.version + '

'; - html += '
' + version.changelog + '
'; - } - - $('#revisionHistory', page).html(html); + for (let i = 0; i < length; i++) { + let version = packageInfo.versions[i]; + html += '

' + version.version + '

'; + html += '
' + version.changelog + '
'; } - function populateVersions(packageInfo, page, installedPlugin) { - var html = ''; + $('#revisionHistory', page).html(html); +} - for (var i = 0; i < packageInfo.versions.length; i++) { - var version = packageInfo.versions[i]; - html += ''; - } +function populateVersions(packageInfo, page, installedPlugin) { + let html = ''; - var selectmenu = $('#selectVersion', page).html(html); - - if (!installedPlugin) { - $('#pCurrentVersion', page).hide().html(''); - } - - var packageVersion = packageInfo.versions[0]; - if (packageVersion) { - selectmenu.val(packageVersion.version); - } + for (let i = 0; i < packageInfo.versions.length; i++) { + const version = packageInfo.versions[i]; + html += ''; } - function renderPackage(pkg, installedPlugins, page) { - var installedPlugin = installedPlugins.filter(function (ip) { - return ip.Name == pkg.name; - })[0]; + const selectmenu = $('#selectVersion', page).html(html); - populateVersions(pkg, page, installedPlugin); - populateHistory(pkg, page); - - $('.pluginName', page).html(pkg.name); - $('#btnInstallDiv', page).removeClass('hide'); - $('#pSelectVersion', page).removeClass('hide'); - - if (pkg.overview) { - $('#overview', page).show().html(pkg.overview); - } else { - $('#overview', page).hide(); - } - - $('#description', page).html(pkg.description); - $('#developer', page).html(pkg.owner); - - if (installedPlugin) { - var currentVersionText = globalize.translate('MessageYouHaveVersionInstalled', '' + installedPlugin.Version + ''); - $('#pCurrentVersion', page).show().html(currentVersionText); - } else { - $('#pCurrentVersion', page).hide().html(''); - } - - loading.hide(); + if (!installedPlugin) { + $('#pCurrentVersion', page).hide().html(''); } - function alertText(options) { - require(['alert'], function ({default: alert}) { - alert(options); - }); + const packageVersion = packageInfo.versions[0]; + if (packageVersion) { + selectmenu.val(packageVersion.version); + } +} + +function renderPackage(pkg, installedPlugins, page) { + const installedPlugin = installedPlugins.filter(function (ip) { + return ip.Name == pkg.name; + })[0]; + + populateVersions(pkg, page, installedPlugin); + populateHistory(pkg, page); + + $('.pluginName', page).html(pkg.name); + $('#btnInstallDiv', page).removeClass('hide'); + $('#pSelectVersion', page).removeClass('hide'); + + if (pkg.overview) { + $('#overview', page).show().html(pkg.overview); + } else { + $('#overview', page).hide(); } - function performInstallation(page, name, guid, version) { - var developer = $('#developer', page).html().toLowerCase(); + $('#description', page).html(pkg.description); + $('#developer', page).html(pkg.owner); - var alertCallback = function () { - loading.show(); - page.querySelector('#btnInstall').disabled = true; - ApiClient.installPlugin(name, guid, version).then(function () { - loading.hide(); - alertText(globalize.translate('MessagePluginInstalled')); - }); - }; + if (installedPlugin) { + const currentVersionText = globalize.translate('MessageYouHaveVersionInstalled', '' + installedPlugin.Version + ''); + $('#pCurrentVersion', page).show().html(currentVersionText); + } else { + $('#pCurrentVersion', page).hide().html(''); + } - if (developer !== 'jellyfin') { + loading.hide(); +} + +function alertText(options) { + import('alert').then(({default: alert}) => { + alert(options); + }); +} + +function performInstallation(page, name, guid, version) { + const developer = $('#developer', page).html().toLowerCase(); + + const alertCallback = function () { + loading.show(); + page.querySelector('#btnInstall').disabled = true; + ApiClient.installPlugin(name, guid, version).then(() => { loading.hide(); - var msg = globalize.translate('MessagePluginInstallDisclaimer'); - msg += '
'; - msg += '
'; - msg += globalize.translate('PleaseConfirmPluginInstallation'); + alertText(globalize.translate('MessagePluginInstalled')); + }).catch(() => { + alertText(globalize.translate('MessagePluginIntallError')); + }); + }; - require(['confirm'], function (confirm) { - confirm.default(msg, globalize.translate('HeaderConfirmPluginInstallation')).then(function () { - alertCallback(); - }, function () { - console.debug('plugin not installed'); - }); + if (developer !== 'jellyfin') { + loading.hide(); + let msg = globalize.translate('MessagePluginInstallDisclaimer'); + msg += '
'; + msg += '
'; + msg += globalize.translate('PleaseConfirmPluginInstallation'); + + import('confirm').then(({default: confirm}) => { + confirm.default(msg, globalize.translate('HeaderConfirmPluginInstallation')).then(function () { + alertCallback(); + }).catch(() => { + console.debug('plugin not installed'); }); - } else { - alertCallback(); - } + }); + } else { + alertCallback(); } +} - return function (view, params) { - $('.addPluginForm', view).on('submit', function () { - loading.show(); - var page = $(this).parents('#addPluginPage')[0]; - var name = params.name; - var guid = params.guid; - ApiClient.getInstalledPlugins().then(function (plugins) { - var installedPlugin = plugins.filter(function (plugin) { - return plugin.Name == name; - })[0]; +export default function(view, params) { + $('.addPluginForm', view).on('submit', function () { + loading.show(); + const page = $(this).parents('#addPluginPage')[0]; + const name = params.name; + const guid = params.guid; + ApiClient.getInstalledPlugins().then(function (plugins) { + const installedPlugin = plugins.filter(function (plugin) { + return plugin.Name == name; + })[0]; - var version = $('#selectVersion', page).val(); - if (installedPlugin && installedPlugin.Version === version) { + const version = $('#selectVersion', page).val(); + if (installedPlugin) { + if (installedPlugin.Version === version) { loading.hide(); Dashboard.alert({ message: globalize.translate('MessageAlreadyInstalled'), title: globalize.translate('HeaderPluginInstallation') }); - } else { - performInstallation(page, name, guid, version); } - }); - return false; + } else { + performInstallation(page, name, guid, version); + } + }).catch(() => { + alertText(globalize.translate('MessageGetInstalledPluginsError')); }); - view.addEventListener('viewshow', function () { - var page = this; - loading.show(); - var name = params.name; - var guid = params.guid; - var promise1 = ApiClient.getPackageInfo(name, guid); - var promise2 = ApiClient.getInstalledPlugins(); - Promise.all([promise1, promise2]).then(function (responses) { - renderPackage(responses[0], responses[1], page); - }); + return false; + }); + view.addEventListener('viewshow', function () { + const page = this; + loading.show(); + const name = params.name; + const guid = params.guid; + const promise1 = ApiClient.getPackageInfo(name, guid); + const promise2 = ApiClient.getInstalledPlugins(); + Promise.all([promise1, promise2]).then(function (responses) { + renderPackage(responses[0], responses[1], page); }); - }; -}); + }); +} diff --git a/src/controllers/dashboard/plugins/available/index.js b/src/controllers/dashboard/plugins/available/index.js index e38365ef55..aa25320d5c 100644 --- a/src/controllers/dashboard/plugins/available/index.js +++ b/src/controllers/dashboard/plugins/available/index.js @@ -1,143 +1,141 @@ -define(['loading', 'libraryMenu', 'globalize', 'cardStyle', 'emby-button', 'emby-checkbox', 'emby-select'], function (loading, libraryMenu, globalize) { - 'use strict'; +import loading from 'loading'; +import libraryMenu from 'libraryMenu'; +import globalize from 'globalize'; +import 'cardStyle'; +import 'emby-button'; +import 'emby-checkbox'; +import 'emby-select'; - loading = loading.default || loading; - - function reloadList(page) { - loading.show(); - var promise1 = ApiClient.getAvailablePlugins(); - var promise2 = ApiClient.getInstalledPlugins(); - Promise.all([promise1, promise2]).then(function (responses) { - populateList({ - catalogElement: page.querySelector('#pluginTiles'), - noItemsElement: page.querySelector('#noPlugins'), - availablePlugins: responses[0], - installedPlugins: responses[1] - }); +function reloadList(page) { + loading.show(); + const promise1 = ApiClient.getAvailablePlugins(); + const promise2 = ApiClient.getInstalledPlugins(); + Promise.all([promise1, promise2]).then(function (responses) { + populateList({ + catalogElement: page.querySelector('#pluginTiles'), + noItemsElement: page.querySelector('#noPlugins'), + availablePlugins: responses[0], + installedPlugins: responses[1] }); + }); +} + +function getHeaderText(category) { + category = category.replace(' ', ''); + if ('Channel' === category) { + category = 'Channels'; + } else if ('Theme' === category) { + category = 'Themes'; + } else if ('LiveTV' === category) { + category = 'HeaderLiveTV'; + } else if ('ScreenSaver' === category) { + category = 'HeaderScreenSavers'; } - function getHeaderText(category) { - category = category.replace(' ', ''); - if (category === 'Channel') { - category = 'Channels'; - } else if (category === 'Theme') { - category = 'Themes'; - } else if (category === 'LiveTV') { - category = 'HeaderLiveTV'; - } else if (category === 'ScreenSaver') { - category = 'HeaderScreenSavers'; + return globalize.translate(category); +} + +function populateList(options) { + const availablePlugins = options.availablePlugins; + const installedPlugins = options.installedPlugins; + + availablePlugins.forEach(function (plugin, index, array) { + plugin.category = plugin.category || 'General'; + plugin.categoryDisplayName = getHeaderText(plugin.category); + array[index] = plugin; + }); + + availablePlugins.sort(function (a, b) { + if (a.category > b.category) { + return 1; + } else if (b.category > a.category) { + return -1; } + if (a.name > b.name) { + return 1; + } else if (b.name > a.name) { + return -1; + } + return 0; + }); - return globalize.translate(category); - } + let currentCategory = null; + let html = ''; - function populateList(options) { - var availablePlugins = options.availablePlugins; - var installedPlugins = options.installedPlugins; - - availablePlugins.forEach(function (plugin, index, array) { - plugin.category = plugin.category || 'General'; - plugin.categoryDisplayName = getHeaderText(plugin.category); - array[index] = plugin; - }); - - availablePlugins.sort(function (a, b) { - if (a.category > b.category) { - return 1; - } else if (b.category > a.category) { - return -1; + for (let i = 0; i < availablePlugins.length; i++) { + const plugin = availablePlugins[i]; + const category = plugin.categoryDisplayName; + if (category != currentCategory) { + if (currentCategory) { + html += '
'; + html += '
'; } - if (a.name > b.name) { - return 1; - } else if (b.name > a.name) { - return -1; - } - return 0; - }); - - var currentCategory = null; - var html = ''; - - for (var i = 0; i < availablePlugins.length; i++) { - var plugin = availablePlugins[i]; - var category = plugin.categoryDisplayName; - if (category != currentCategory) { - if (currentCategory) { - html += '
'; - html += '
'; - } - html += '
'; - html += '

' + category + '

'; - html += '
'; - currentCategory = category; - } - html += getPluginHtml(plugin, options, installedPlugins); + html += '
'; + html += '

' + category + '

'; + html += '
'; + currentCategory = category; } - html += '
'; - html += '
'; + html += getPluginHtml(plugin, options, installedPlugins); + } + html += '
'; + html += '
'; - if (!availablePlugins.length && options.noItemsElement) { - options.noItemsElement.classList.remove('hide'); - } - - options.catalogElement.innerHTML = html; - loading.hide(); + if (!availablePlugins.length && options.noItemsElement) { + options.noItemsElement.classList.remove('hide'); } - function getPluginHtml(plugin, options, installedPlugins) { - var html = ''; - var href = plugin.externalUrl ? plugin.externalUrl : 'addplugin.html?name=' + encodeURIComponent(plugin.name) + '&guid=' + plugin.guid; + options.catalogElement.innerHTML = html; + loading.hide(); +} - if (options.context) { - href += '&context=' + options.context; - } +function getPluginHtml(plugin, options, installedPlugins) { + let html = ''; + let href = plugin.externalUrl ? plugin.externalUrl : 'addplugin.html?name=' + encodeURIComponent(plugin.name) + '&guid=' + plugin.guid; - var target = plugin.externalUrl ? ' target="_blank"' : ''; - html += "
"; - html += '
'; - html += '
'; - html += '
'; - html += ''; - html += ''; - html += ''; - html += '
'; - html += '
'; - html += "
"; - html += plugin.name; - html += '
'; - var installedPlugin = installedPlugins.filter(function (ip) { - return ip.Id == plugin.guid; - })[0]; - html += "
"; - html += installedPlugin ? globalize.translate('LabelVersionInstalled', installedPlugin.Version) : ' '; - html += '
'; - html += '
'; - html += '
'; - return html += '
'; + if (options.context) { + href += '&context=' + options.context; } - function getTabs() { - return [{ - href: 'installedplugins.html', - name: globalize.translate('TabMyPlugins') - }, { - href: 'availableplugins.html', - name: globalize.translate('TabCatalog') - }, { - href: 'repositories.html', - name: globalize.translate('TabRepositories') - }]; - } + const target = plugin.externalUrl ? ' target="_blank"' : ''; + html += "
"; + html += '
'; + html += '
'; + html += '
'; + html += ''; + html += ''; + html += ''; + html += '
'; + html += '
'; + html += "
"; + html += plugin.name; + html += '
'; + const installedPlugin = installedPlugins.filter(function (ip) { + return ip.Id == plugin.guid; + })[0]; + html += "
"; + html += installedPlugin ? globalize.translate('LabelVersionInstalled', installedPlugin.Version) : ' '; + html += '
'; + html += '
'; + html += '
'; + return html += '
'; +} - window.PluginCatalog = { - renderCatalog: populateList - }; +function getTabs() { + return [{ + href: 'installedplugins.html', + name: globalize.translate('TabMyPlugins') + }, { + href: 'availableplugins.html', + name: globalize.translate('TabCatalog') + }, { + href: 'repositories.html', + name: globalize.translate('TabRepositories') + }]; +} - return function (view, params) { - view.addEventListener('viewshow', function () { - libraryMenu.setTabs('plugins', 1, getTabs); - reloadList(this); - }); - }; -}); +export default function (view) { + view.addEventListener('viewshow', function () { + libraryMenu.setTabs('plugins', 1, getTabs); + reloadList(this); + }); +} diff --git a/src/controllers/dashboard/plugins/installed/index.js b/src/controllers/dashboard/plugins/installed/index.js index 80577d227b..20cbe56928 100644 --- a/src/controllers/dashboard/plugins/installed/index.js +++ b/src/controllers/dashboard/plugins/installed/index.js @@ -1,192 +1,193 @@ -define(['loading', 'libraryMenu', 'dom', 'globalize', 'cardStyle', 'emby-button'], function (loading, libraryMenu, dom, globalize) { - 'use strict'; +import loading from 'loading'; +import libraryMenu from 'libraryMenu'; +import dom from 'dom'; +import globalize from 'globalize'; +import 'cardStyle'; +import 'emby-button'; - loading = loading.default || loading; +function deletePlugin(page, uniqueid, name) { + const msg = globalize.translate('UninstallPluginConfirmation', name); - function deletePlugin(page, uniqueid, name) { - var msg = globalize.translate('UninstallPluginConfirmation', name); - - require(['confirm'], function (confirm) { - confirm.default({ - title: globalize.translate('HeaderUninstallPlugin'), - text: msg, - primary: 'delete', - confirmText: globalize.translate('HeaderUninstallPlugin') - }).then(function () { - loading.show(); - ApiClient.uninstallPlugin(uniqueid).then(function () { - reloadList(page); - }); + import('confirm').then(({default: confirm}) => { + confirm.default({ + title: globalize.translate('UninstallPluginHeader'), + text: msg, + primary: 'delete', + confirmText: globalize.translate('UninstallPluginHeader') + }).then(function () { + loading.show(); + ApiClient.uninstallPlugin(uniqueid).then(function () { + reloadList(page); }); }); - } + }); +} - function showNoConfigurationMessage() { - Dashboard.alert({ - message: globalize.translate('MessageNoPluginConfiguration') - }); - } +function showNoConfigurationMessage() { + Dashboard.alert({ + message: globalize.translate('NoPluginConfigurationMessage') + }); +} - function showConnectMessage() { - Dashboard.alert({ - message: globalize.translate('MessagePluginConfigurationRequiresLocalAccess') - }); - } +function showConnectMessage() { + Dashboard.alert({ + message: globalize.translate('MessagePluginConfigurationRequiresLocalAccess') + }); +} - function getPluginCardHtml(plugin, pluginConfigurationPages) { - var configPage = pluginConfigurationPages.filter(function (pluginConfigurationPage) { - return pluginConfigurationPage.PluginId == plugin.Id; - })[0]; - var configPageUrl = configPage ? Dashboard.getConfigurationPageUrl(configPage.Name) : null; - var html = ''; - html += "
"; - html += '
'; - html += '
'; - html += '
'; - html += configPageUrl ? '' : ''; +function getPluginCardHtml(plugin, pluginConfigurationPages) { + const configPage = pluginConfigurationPages.filter(function (pluginConfigurationPage) { + return pluginConfigurationPage.PluginId == plugin.Id; + })[0]; + const configPageUrl = configPage ? Dashboard.getConfigurationPageUrl(configPage.Name) : null; + let html = ''; + html += "
"; + html += '
'; + html += '
'; + html += '
'; + html += configPageUrl ? '' : ''; + html += '
'; + html += '
'; + + if (configPage || plugin.CanUninstall) { + html += '
'; + html += ''; html += '
'; - html += '
'; + } - if (configPage || plugin.CanUninstall) { - html += '
'; - html += ''; - html += '
'; + html += "
"; + html += configPage && configPage.DisplayName ? configPage.DisplayName : plugin.Name; + html += '
'; + html += "
"; + html += plugin.Version; + html += '
'; + html += '
'; + html += '
'; + html += '
'; + return html; +} + +function renderPlugins(page, plugins) { + ApiClient.getJSON(ApiClient.getUrl('web/configurationpages') + '?pageType=PluginConfiguration').then(function (configPages) { + populateList(page, plugins, configPages); + }); +} + +function populateList(page, plugins, pluginConfigurationPages) { + plugins = plugins.sort(function (plugin1, plugin2) { + if (plugin1.Name > plugin2.Name) { + return 1; } - html += "
"; - html += configPage && configPage.DisplayName ? configPage.DisplayName : plugin.Name; - html += '
'; - html += "
"; - html += plugin.Version; - html += '
'; - html += '
'; - html += '
'; - html += '
'; - return html; - } - - function renderPlugins(page, plugins) { - ApiClient.getJSON(ApiClient.getUrl('web/configurationpages') + '?pageType=PluginConfiguration').then(function (configPages) { - populateList(page, plugins, configPages); - }); - } - - function populateList(page, plugins, pluginConfigurationPages) { - plugins = plugins.sort(function (plugin1, plugin2) { - if (plugin1.Name > plugin2.Name) { - return 1; - } - - return -1; - }); - - var html = plugins.map(function (p) { - return getPluginCardHtml(p, pluginConfigurationPages); - }).join(''); - - var installedPluginsElement = page.querySelector('.installedPlugins'); - installedPluginsElement.removeEventListener('click', onInstalledPluginsClick); - installedPluginsElement.addEventListener('click', onInstalledPluginsClick); - - if (plugins.length) { - installedPluginsElement.classList.add('itemsContainer'); - installedPluginsElement.classList.add('vertical-wrap'); - } else { - html += '
'; - html += '

' + globalize.translate('MessageNoPluginsInstalled') + '

'; - html += '

'; - html += globalize.translate('MessageBrowsePluginCatalog'); - html += '

'; - html += '
'; - } - - installedPluginsElement.innerHTML = html; - loading.hide(); - } - - function showPluginMenu(page, elem) { - var card = dom.parentWithClass(elem, 'card'); - var id = card.getAttribute('data-id'); - var name = card.getAttribute('data-name'); - var removable = card.getAttribute('data-removable'); - var configHref = card.querySelector('.cardContent').getAttribute('href'); - var menuItems = []; - - if (configHref) { - menuItems.push({ - name: globalize.translate('ButtonSettings'), - id: 'open', - icon: 'mode_edit' - }); - } - - if (removable === 'true') { - menuItems.push({ - name: globalize.translate('ButtonUninstall'), - id: 'delete', - icon: 'delete' - }); - } - - require(['actionsheet'], function (actionsheet) { - actionsheet.show({ - items: menuItems, - positionTo: elem, - callback: function (resultId) { - switch (resultId) { - case 'open': - Dashboard.navigate(configHref); - break; - case 'delete': - deletePlugin(page, id, name); - break; - } - } - }); - }); - } - - function reloadList(page) { - loading.show(); - ApiClient.getInstalledPlugins().then(function (plugins) { - renderPlugins(page, plugins); - }); - } - - function getTabs() { - return [{ - href: 'installedplugins.html', - name: globalize.translate('TabMyPlugins') - }, { - href: 'availableplugins.html', - name: globalize.translate('TabCatalog') - }, { - href: 'repositories.html', - name: globalize.translate('TabRepositories') - }]; - } - - function onInstalledPluginsClick(e) { - if (dom.parentWithClass(e.target, 'noConfigPluginCard')) { - showNoConfigurationMessage(); - } else if (dom.parentWithClass(e.target, 'connectModePluginCard')) { - showConnectMessage(); - } else { - var btnCardMenu = dom.parentWithClass(e.target, 'btnCardMenu'); - if (btnCardMenu) { - showPluginMenu(dom.parentWithClass(btnCardMenu, 'page'), btnCardMenu); - } - } - } - - pageIdOn('pageshow', 'pluginsPage', function () { - libraryMenu.setTabs('plugins', 0, getTabs); - reloadList(this); + return -1; }); - window.PluginsPage = { - renderPlugins: renderPlugins - }; + let html = plugins.map(function (p) { + return getPluginCardHtml(p, pluginConfigurationPages); + }).join(''); + + const installedPluginsElement = page.querySelector('.installedPlugins'); + installedPluginsElement.removeEventListener('click', onInstalledPluginsClick); + installedPluginsElement.addEventListener('click', onInstalledPluginsClick); + + if (plugins.length) { + installedPluginsElement.classList.add('itemsContainer'); + installedPluginsElement.classList.add('vertical-wrap'); + } else { + html += '
'; + html += '

' + globalize.translate('MessageNoPluginsInstalled') + '

'; + html += '

'; + html += globalize.translate('BrowsePluginCatalogMessage'); + html += '

'; + html += '
'; + } + + installedPluginsElement.innerHTML = html; + loading.hide(); +} + +function showPluginMenu(page, elem) { + const card = dom.parentWithClass(elem, 'card'); + const id = card.getAttribute('data-id'); + const name = card.getAttribute('data-name'); + const removable = card.getAttribute('data-removable'); + const configHref = card.querySelector('.cardContent').getAttribute('href'); + const menuItems = []; + + if (configHref) { + menuItems.push({ + name: globalize.translate('ButtonSettings'), + id: 'open', + icon: 'mode_edit' + }); + } + + if (removable === 'true') { + menuItems.push({ + name: globalize.translate('ButtonUninstall'), + id: 'delete', + icon: 'delete' + }); + } + + import('actionsheet').then(({default: actionsheet}) => { + actionsheet.show({ + items: menuItems, + positionTo: elem, + callback: function (resultId) { + switch (resultId) { + case 'open': + Dashboard.navigate(configHref); + break; + case 'delete': + deletePlugin(page, id, name); + break; + } + } + }); + }); +} + +function reloadList(page) { + loading.show(); + ApiClient.getInstalledPlugins().then(function (plugins) { + renderPlugins(page, plugins); + }); +} + +function getTabs() { + return [{ + href: 'installedplugins.html', + name: globalize.translate('TabMyPlugins') + }, { + href: 'availableplugins.html', + name: globalize.translate('TabCatalog') + }, { + href: 'repositories.html', + name: globalize.translate('TabRepositories') + }]; +} + +function onInstalledPluginsClick(e) { + if (dom.parentWithClass(e.target, 'noConfigPluginCard')) { + showNoConfigurationMessage(); + } else if (dom.parentWithClass(e.target, 'connectModePluginCard')) { + showConnectMessage(); + } else { + const btnCardMenu = dom.parentWithClass(e.target, 'btnCardMenu'); + if (btnCardMenu) { + showPluginMenu(dom.parentWithClass(btnCardMenu, 'page'), btnCardMenu); + } + } +} + +pageIdOn('pageshow', 'pluginsPage', function () { + libraryMenu.setTabs('plugins', 0, getTabs); + reloadList(this); }); + +window.PluginsPage = { + renderPlugins: renderPlugins +}; diff --git a/src/strings/en-us.json b/src/strings/en-us.json index 4dffc49618..5c7e1e267d 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -1282,6 +1282,8 @@ "PleaseRestartServerName": "Please restart Jellyfin Server - {0}.", "PleaseSelectTwoItems": "Please select at least two items.", "MessagePluginInstalled": "The plugin has been successfully installed. Jellyfin Server will need to be restarted for changes to take effect.", + "MessagePluginIntallError": "An error occured while installing the plugin.", + "MessageGetInstalledPluginsError": "An error occured while getting the list of currently installed plugins.", "PreferEmbeddedTitlesOverFileNames": "Prefer embedded titles over filenames", "PreferEmbeddedTitlesOverFileNamesHelp": "This determines the default display title when no internet metadata or local metadata is available.", "PreferEmbeddedEpisodeInfosOverFileNamesHelp": "This uses the episode information from the embedded metadata if available.", From 3180a7a177e104106da5dd08b29c5bd9087252c8 Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Thu, 6 Aug 2020 23:02:00 +0200 Subject: [PATCH 117/124] Fix lint --- src/controllers/dashboard/plugins/available/index.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/controllers/dashboard/plugins/available/index.js b/src/controllers/dashboard/plugins/available/index.js index aa25320d5c..22b28e0f5a 100644 --- a/src/controllers/dashboard/plugins/available/index.js +++ b/src/controllers/dashboard/plugins/available/index.js @@ -22,13 +22,14 @@ function reloadList(page) { function getHeaderText(category) { category = category.replace(' ', ''); - if ('Channel' === category) { + // TODO: Replace with switch + if (category === 'Channel') { category = 'Channels'; - } else if ('Theme' === category) { + } else if (category === 'Theme') { category = 'Themes'; - } else if ('LiveTV' === category) { + } else if (category === 'LiveTV') { category = 'HeaderLiveTV'; - } else if ('ScreenSaver' === category) { + } else if (category === 'ScreenSaver') { category = 'HeaderScreenSavers'; } From 232ba8182ad28d506727d291a6cefbe42be27427 Mon Sep 17 00:00:00 2001 From: Julien Machiels Date: Fri, 7 Aug 2020 23:58:01 +0200 Subject: [PATCH 118/124] Update src/controllers/dashboard/plugins/installed/index.js Co-authored-by: Cameron --- src/controllers/dashboard/plugins/installed/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/dashboard/plugins/installed/index.js b/src/controllers/dashboard/plugins/installed/index.js index 20cbe56928..793adb70d2 100644 --- a/src/controllers/dashboard/plugins/installed/index.js +++ b/src/controllers/dashboard/plugins/installed/index.js @@ -25,7 +25,7 @@ function deletePlugin(page, uniqueid, name) { function showNoConfigurationMessage() { Dashboard.alert({ - message: globalize.translate('NoPluginConfigurationMessage') + message: globalize.translate('MessageNoPluginConfiguration') }); } From 5ccc24d8f54c8d178451a006190cc94e55dd05aa Mon Sep 17 00:00:00 2001 From: Julien Machiels Date: Fri, 7 Aug 2020 23:58:14 +0200 Subject: [PATCH 119/124] Update src/controllers/dashboard/plugins/installed/index.js Co-authored-by: Cameron --- src/controllers/dashboard/plugins/installed/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/dashboard/plugins/installed/index.js b/src/controllers/dashboard/plugins/installed/index.js index 793adb70d2..ed0bbdaa7b 100644 --- a/src/controllers/dashboard/plugins/installed/index.js +++ b/src/controllers/dashboard/plugins/installed/index.js @@ -99,7 +99,7 @@ function populateList(page, plugins, pluginConfigurationPages) { html += '
'; html += '

' + globalize.translate('MessageNoPluginsInstalled') + '

'; html += '

'; - html += globalize.translate('BrowsePluginCatalogMessage'); + html += globalize.translate('MessageBrowsePluginCatalog'); html += '

'; html += '
'; } From 9c56306f05d20f03be14a7584a1d84596fbf6c09 Mon Sep 17 00:00:00 2001 From: Julien Machiels Date: Fri, 7 Aug 2020 23:58:21 +0200 Subject: [PATCH 120/124] Update src/controllers/dashboard/plugins/installed/index.js Co-authored-by: Cameron --- src/controllers/dashboard/plugins/installed/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/dashboard/plugins/installed/index.js b/src/controllers/dashboard/plugins/installed/index.js index ed0bbdaa7b..ef36ceb3d3 100644 --- a/src/controllers/dashboard/plugins/installed/index.js +++ b/src/controllers/dashboard/plugins/installed/index.js @@ -10,7 +10,7 @@ function deletePlugin(page, uniqueid, name) { import('confirm').then(({default: confirm}) => { confirm.default({ - title: globalize.translate('UninstallPluginHeader'), + title: globalize.translate('HeaderUninstallPlugin'), text: msg, primary: 'delete', confirmText: globalize.translate('UninstallPluginHeader') From 2cbcddd5daa3d5d55e967c2cb3a354786e24d557 Mon Sep 17 00:00:00 2001 From: Julien Machiels Date: Fri, 7 Aug 2020 23:58:30 +0200 Subject: [PATCH 121/124] Update src/controllers/dashboard/plugins/installed/index.js Co-authored-by: Cameron --- src/controllers/dashboard/plugins/installed/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/dashboard/plugins/installed/index.js b/src/controllers/dashboard/plugins/installed/index.js index ef36ceb3d3..e5f5b92413 100644 --- a/src/controllers/dashboard/plugins/installed/index.js +++ b/src/controllers/dashboard/plugins/installed/index.js @@ -13,7 +13,7 @@ function deletePlugin(page, uniqueid, name) { title: globalize.translate('HeaderUninstallPlugin'), text: msg, primary: 'delete', - confirmText: globalize.translate('UninstallPluginHeader') + confirmText: globalize.translate('HeaderUninstallPlugin') }).then(function () { loading.show(); ApiClient.uninstallPlugin(uniqueid).then(function () { From c14b20727a7832b7c0f3bc2c32d929f2f43b8a5e Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Sat, 8 Aug 2020 16:48:25 +0200 Subject: [PATCH 122/124] Merge conditions in plugin install --- src/controllers/dashboard/plugins/add/index.js | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/controllers/dashboard/plugins/add/index.js b/src/controllers/dashboard/plugins/add/index.js index 8393970e76..875472a278 100644 --- a/src/controllers/dashboard/plugins/add/index.js +++ b/src/controllers/dashboard/plugins/add/index.js @@ -118,14 +118,12 @@ export default function(view, params) { })[0]; const version = $('#selectVersion', page).val(); - if (installedPlugin) { - if (installedPlugin.Version === version) { - loading.hide(); - Dashboard.alert({ - message: globalize.translate('MessageAlreadyInstalled'), - title: globalize.translate('HeaderPluginInstallation') - }); - } + if (installedPlugin && installedPlugin.Version === version) { + loading.hide(); + Dashboard.alert({ + message: globalize.translate('MessageAlreadyInstalled'), + title: globalize.translate('HeaderPluginInstallation') + }); } else { performInstallation(page, name, guid, version); } From faa78ebff14306164157df129cc014984b8e04db Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Sat, 8 Aug 2020 18:51:27 +0200 Subject: [PATCH 123/124] Fix string --- src/controllers/dashboard/plugins/add/index.js | 4 ++-- src/strings/en-us.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/controllers/dashboard/plugins/add/index.js b/src/controllers/dashboard/plugins/add/index.js index 875472a278..44c46ce11b 100644 --- a/src/controllers/dashboard/plugins/add/index.js +++ b/src/controllers/dashboard/plugins/add/index.js @@ -83,7 +83,7 @@ function performInstallation(page, name, guid, version) { loading.hide(); alertText(globalize.translate('MessagePluginInstalled')); }).catch(() => { - alertText(globalize.translate('MessagePluginIntallError')); + alertText(globalize.translate('MessagePluginInstallError')); }); }; @@ -95,7 +95,7 @@ function performInstallation(page, name, guid, version) { msg += globalize.translate('PleaseConfirmPluginInstallation'); import('confirm').then(({default: confirm}) => { - confirm.default(msg, globalize.translate('HeaderConfirmPluginInstallation')).then(function () { + confirm(msg, globalize.translate('HeaderConfirmPluginInstallation')).then(function () { alertCallback(); }).catch(() => { console.debug('plugin not installed'); diff --git a/src/strings/en-us.json b/src/strings/en-us.json index 5c7e1e267d..638be2efaf 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -1282,7 +1282,7 @@ "PleaseRestartServerName": "Please restart Jellyfin Server - {0}.", "PleaseSelectTwoItems": "Please select at least two items.", "MessagePluginInstalled": "The plugin has been successfully installed. Jellyfin Server will need to be restarted for changes to take effect.", - "MessagePluginIntallError": "An error occured while installing the plugin.", + "MessagePluginInstallError": "An error occured while installing the plugin.", "MessageGetInstalledPluginsError": "An error occured while getting the list of currently installed plugins.", "PreferEmbeddedTitlesOverFileNames": "Prefer embedded titles over filenames", "PreferEmbeddedTitlesOverFileNamesHelp": "This determines the default display title when no internet metadata or local metadata is available.", From 4ffded957dbb50a20b20e6a58324da58a9f55399 Mon Sep 17 00:00:00 2001 From: Moritz Date: Sat, 8 Aug 2020 20:57:10 +0000 Subject: [PATCH 124/124] 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." }