';
+ 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();
+ };
+}
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);
+ }
+ };
+}
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 += '
';
}
- 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 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: [{
@@ -230,36 +233,37 @@ 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(','));
+ libraryBrowser.showLayoutMenu(e.target, this.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
});
});
});
- }
+ };
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 82ab4d4d4b..ca02ede36d 100644
--- a/src/controllers/movies/moviegenres.js
+++ b/src/controllers/movies/moviegenres.js
@@ -1,13 +1,18 @@
-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 globalize from 'globalize';
+import appRouter from 'appRouter';
+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 +42,7 @@ define(['layoutManager', 'loading', 'libraryBrowser', 'cardBuilder', 'lazyLoader
function getPromise() {
loading.show();
- var query = getQuery();
+ const query = getQuery();
return ApiClient.getGenres(ApiClient.getCurrentUserId(), query);
}
@@ -53,18 +58,18 @@ define(['layoutManager', 'loading', 'libraryBrowser', 'cardBuilder', 'lazyLoader
return enableScrollX() ? 'overflowPortrait' : 'portrait';
}
- 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 fillItemsContainer = (entry) => {
+ const elem = entry.target;
+ const id = elem.getAttribute('data-id');
+ const viewStyle = this.getCurrentViewStyle();
+ let limit = viewStyle == 'Thumb' || viewStyle == 'ThumbCard' ? 5 : 9;
if (enableScrollX()) {
limit = 10;
}
- var enableImageTypes = viewStyle == 'Thumb' || viewStyle == 'ThumbCard' ? 'Primary,Backdrop,Thumb' : 'Primary';
- var query = {
+ const enableImageTypes = viewStyle == 'Thumb' || viewStyle == 'ThumbCard' ? 'Primary,Backdrop,Thumb' : 'Primary';
+ const query = {
SortBy: 'SortName',
SortOrder: 'Ascending',
IncludeItemTypes: 'Movie',
@@ -126,17 +131,17 @@ define(['layoutManager', 'loading', 'libraryBrowser', 'cardBuilder', 'lazyLoader
tabContent.querySelector('.btnMoreFromGenre' + id + ' .material-icons').classList.remove('hide');
}
});
- }
+ };
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 +156,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';
@@ -182,37 +187,37 @@ define(['layoutManager', 'loading', 'libraryBrowser', 'cardBuilder', 'lazyLoader
});
}
- function fullyReload() {
- self.preRender();
- self.renderTab();
- }
+ const fullyReload = () => {
+ this.preRender();
+ this.renderTab();
+ };
- var self = this;
- var data = {};
+ 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;
- var promise;
+ this.enableViewSelection = true;
+ let promise;
- self.preRender = function () {
+ this.preRender = function () {
promise = getPromise();
};
- self.renderTab = function () {
+ this.renderTab = function () {
reloadItems(tabContent, promise);
};
- };
-});
+ }
+
+/* eslint-enable indent */
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/controllers/movies/movies.js b/src/controllers/movies/movies.js
index ade9dc4b89..91b428ec68 100644
--- a/src/controllers/movies/movies.js
+++ b/src/controllers/movies/movies.js
@@ -1,12 +1,18 @@
-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 * 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 +21,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 +60,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 +70,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 +157,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 +182,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 +232,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 +264,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 +272,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 +287,23 @@ 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 () {
+ 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..91163c3412 100644
--- a/src/controllers/movies/moviesrecommended.js
+++ b/src/controllers/movies/moviesrecommended.js
@@ -1,7 +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 * as 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';
- playbackManager = playbackManager.default || playbackManager;
+/* eslint-disable indent */
function enableScrollX() {
return !layoutManager.desktop;
@@ -16,7 +29,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 +39,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 +58,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 +80,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 +101,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 +126,7 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu'
html += '
';
html += '
' + title + '
';
- var allowBottomPadding = true;
+ const allowBottomPadding = true;
if (enableScrollX()) {
html += '
';
@@ -141,8 +154,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 +170,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 +182,7 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu'
}
function autoFocus(page) {
- require(['autoFocuser'], function (autoFocuser) {
+ import('autoFocuser').then(({default: autoFocuser}) => {
autoFocuser.autoFocus(page);
});
}
@@ -195,17 +208,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);
@@ -224,9 +236,6 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu'
name: globalize.translate('TabCollections')
}, {
name: globalize.translate('TabGenres')
- }, {
- name: globalize.translate('ButtonSearch'),
- cssClass: 'searchTabButton'
}];
}
@@ -249,13 +258,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);
}
@@ -267,52 +276,50 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu'
mainTabsManager.setTabs(view, currentTabIndex, getTabs, getTabContainers, onBeforeTabChange, onTabChange);
}
- function getTabController(page, index, callback) {
- var depends = [];
+ const getTabController = (page, index, callback) => {
+ 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');
}
- 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;
+ this.tabContent = tabContent;
}
- var controller = tabControllers[index];
+ let controller = tabControllers[index];
if (!controller) {
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',
@@ -335,7 +342,7 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu'
callback(controller);
});
- }
+ };
function preLoadTab(page, index) {
getTabController(page, index, function (controller) {
@@ -347,12 +354,12 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu'
function loadTab(page, index) {
currentTabIndex = index;
- getTabController(page, index, function (controller) {
+ getTabController(page, index, ((controller) => {
if (renderedTabs.indexOf(index) == -1) {
renderedTabs.push(index);
controller.renderTab();
}
- });
+ }));
}
function onPlaybackStop(e, state) {
@@ -370,22 +377,21 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu'
}
}
- 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;
- self.initTab = function () {
- var tabContent = view.querySelector(".pageTabContent[data-index='" + suggestionsTabIndex + "']");
+ this.initTab = function () {
+ let tabContent = view.querySelector(".pageTabContent[data-index='" + suggestionsTabIndex + "']");
initSuggestedTab(view, tabContent);
};
- self.renderTab = function () {
- var tabContent = view.querySelector(".pageTabContent[data-index='" + suggestionsTabIndex + "']");
+ this.renderTab = function () {
+ 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')) {
@@ -405,15 +411,14 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu'
events.on(playbackManager, 'playbackstop', onPlaybackStop);
inputManager.on(window, onInputCommand);
});
- view.addEventListener('viewbeforehide', function (e) {
+ view.addEventListener('viewbeforehide', function () {
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..5f1aa1fe62 100644
--- a/src/controllers/movies/movietrailers.js
+++ b/src/controllers/movies/movietrailers.js
@@ -1,13 +1,20 @@
-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 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] = {
@@ -46,11 +53,11 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', '
return context.savedQueryKey;
}
- function reloadItems() {
+ const reloadItems = () => {
loading.show();
isLoading = true;
- var query = getQuery(tabContent);
- ApiClient.getItems(ApiClient.getCurrentUserId(), query).then(function (result) {
+ const query = getQuery(tabContent);
+ ApiClient.getItems(ApiClient.getCurrentUserId(), query).then((result) => {
function onNextPageClick() {
if (isLoading) {
return;
@@ -75,7 +82,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 +92,8 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', '
sortButton: false,
filterButton: false
});
- var html;
- var viewStyle = self.getCurrentViewStyle();
+ let html;
+ const viewStyle = this.getCurrentViewStyle();
if (viewStyle == 'Thumb') {
html = cardBuilder.getCardsHtml({
@@ -142,22 +149,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,27 +174,26 @@ 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);
loading.hide();
isLoading = false;
});
- }
+ };
- function updateFilterControls(tabContent) {
- var query = getQuery(tabContent);
- self.alphaPicker.value(query.NameStartsWithOrGreater);
- }
+ const updateFilterControls = (tabContent) => {
+ const query = getQuery(tabContent);
+ this.alphaPicker.value(query.NameStartsWithOrGreater);
+ };
- var self = this;
- var data = {};
- var isLoading = false;
+ const data = {};
+ let isLoading = false;
- self.showFilterMenu = function () {
- require(['components/filterdialog/filterdialog'], function ({default: filterDialogFactory}) {
- var filterDialog = new filterDialogFactory({
+ this.showFilterMenu = function () {
+ import('components/filterdialog/filterdialog').then(({default: filterDialogFactory}) => {
+ const filterDialog = new filterDialogFactory({
query: getQuery(tabContent),
mode: 'movies',
serverId: ApiClient.serverId()
@@ -202,21 +206,21 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', '
});
};
- self.getCurrentViewStyle = function () {
+ this.getCurrentViewStyle = function () {
return getPageData(tabContent).view;
};
- function initPage(tabContent) {
- var alphaPickerElement = tabContent.querySelector('.alphaPicker');
- var itemsContainer = tabContent.querySelector('.itemsContainer');
+ const initPage = (tabContent) => {
+ 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({
+ this.alphaPicker = new AlphaPicker({
element: alphaPickerElement,
valueChangeEvent: 'click'
});
@@ -226,7 +230,7 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', '
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({
@@ -260,15 +264,16 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', '
button: e.target
});
});
- }
+ };
initPage(tabContent);
- self.renderTab = function () {
+ this.renderTab = function () {
reloadItems();
updateFilterControls(tabContent);
};
- self.destroy = function () {};
- };
-});
+ this.destroy = function () {};
+ }
+
+/* eslint-enable indent */
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/controllers/music/musicrecommended.js b/src/controllers/music/musicrecommended.js
index db7dac9547..9bc449e2a2 100644
--- a/src/controllers/music/musicrecommended.js
+++ b/src/controllers/music/musicrecommended.js
@@ -191,9 +191,6 @@ import 'flexStyles';
name: globalize.translate('TabSongs')
}, {
name: globalize.translate('TabGenres')
- }, {
- name: globalize.translate('ButtonSearch'),
- cssClass: 'searchTabButton'
}];
}
@@ -295,10 +292,6 @@ import 'flexStyles';
case 6:
depends = 'controllers/music/musicgenres';
break;
-
- case 7:
- depends = 'scripts/searchtab';
- break;
}
import(depends).then(({default: controllerFactory}) => {
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/controllers/shows/tvrecommended.js b/src/controllers/shows/tvrecommended.js
index 4aab69e978..585449d0c0 100644
--- a/src/controllers/shows/tvrecommended.js
+++ b/src/controllers/shows/tvrecommended.js
@@ -30,9 +30,6 @@ import 'emby-button';
name: globalize.translate('TabNetworks')
}, {
name: globalize.translate('TabEpisodes')
- }, {
- name: globalize.translate('ButtonSearch'),
- cssClass: 'searchTabButton'
}];
}
@@ -217,10 +214,6 @@ import 'emby-button';
case 6:
depends = 'controllers/shows/episodes';
break;
-
- case 7:
- depends = 'scripts/searchtab';
- break;
}
import(depends).then(({default: controllerFactory}) => {
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/elements/emby-slider/emby-slider.css b/src/elements/emby-slider/emby-slider.css
index 7661895f15..01221b6cae 100644
--- a/src/elements/emby-slider/emby-slider.css
+++ b/src/elements/emby-slider/emby-slider.css
@@ -230,3 +230,18 @@
margin: 0;
padding: 0.5em 0.75em;
}
+
+/* FIXME: 'sliderContainer' is used to wrap slider's pieces */
+.sliderContainer-settings {
+ margin-bottom: 1.8em;
+ position: relative;
+}
+
+.sliderContainer-settings .mdl-slider-container {
+ height: 2.83em; /* similar to emby-input with its 110% font-size */
+}
+
+.sliderLabel {
+ display: block;
+ margin-bottom: 0.25em;
+}
diff --git a/src/elements/emby-slider/emby-slider.js b/src/elements/emby-slider/emby-slider.js
index 2439331144..b39e24f5e4 100644
--- a/src/elements/emby-slider/emby-slider.js
+++ b/src/elements/emby-slider/emby-slider.js
@@ -150,6 +150,16 @@ import 'emby-input';
this.classList.add('show-focus');
}
+ const topContainer = dom.parentWithClass(this, 'sliderContainer-settings');
+
+ if (topContainer && this.getAttribute('label')) {
+ const label = this.ownerDocument.createElement('label');
+ label.innerHTML = this.getAttribute('label');
+ label.classList.add('sliderLabel');
+ label.htmlFor = this.id;
+ topContainer.insertBefore(label, topContainer.firstChild);
+ }
+
const containerElement = this.parentNode;
containerElement.classList.add('mdl-slider-container');
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/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/libraries/screensavermanager.js b/src/libraries/screensavermanager.js
index 557b31e0f4..5c24ec63d0 100644
--- a/src/libraries/screensavermanager.js
+++ b/src/libraries/screensavermanager.js
@@ -1,134 +1,128 @@
-define(["events", "playbackManager", "pluginManager", "inputManager", "connectionManager", "userSettings"], function (events, playbackManager, pluginManager, inputManager, connectionManager, userSettings) {
- "use strict";
+import events from 'events';
+import playbackManager from 'playbackManager';
+import pluginManager from 'pluginManager';
+import inputManager from 'inputManager';
+import connectionManager from 'connectionManager';
+import * as userSettings from 'userSettings';
- playbackManager = playbackManager.default || playbackManager;
+function getMinIdleTime() {
+ // Returns the minimum amount of idle time required before the screen saver can be displayed
+ //time units used Millisecond
+ return 180000;
+}
- function getMinIdleTime() {
- // Returns the minimum amount of idle time required before the screen saver can be displayed
- //time units used Millisecond
- return 180000;
+let lastFunctionalEvent = 0;
+
+function getFunctionalEventIdleTime() {
+ return new Date().getTime() - lastFunctionalEvent;
+}
+
+events.on(playbackManager, 'playbackstop', function (e, stopInfo) {
+ const state = stopInfo.state;
+ if (state.NowPlayingItem && state.NowPlayingItem.MediaType == 'Video') {
+ lastFunctionalEvent = new Date().getTime();
}
-
- var lastFunctionalEvent = 0;
-
- function getFunctionalEventIdleTime() {
- return new Date().getTime() - lastFunctionalEvent;
- }
-
- events.on(playbackManager, "playbackstop", function (e, stopInfo) {
- var state = stopInfo.state;
- if (state.NowPlayingItem && state.NowPlayingItem.MediaType == "Video") {
- lastFunctionalEvent = new Date().getTime();
- }
- });
-
- function getScreensaverPlugin(isLoggedIn) {
-
- var option;
- try {
- option = userSettings.get("screensaver", false);
- } catch (err) {
- option = isLoggedIn ? "backdropscreensaver" : "logoscreensaver";
- }
-
- var plugins = pluginManager.ofType("screensaver");
-
- for (var i = 0, length = plugins.length; i < length; i++) {
- var plugin = plugins[i];
-
- if (plugin.id === option) {
- return plugin;
- }
- }
-
- return null;
- }
-
- function ScreenSaverManager() {
-
- var self = this;
- var activeScreenSaver;
-
- function showScreenSaver(screensaver) {
-
- if (activeScreenSaver) {
- throw new Error("An existing screensaver is already active.");
- }
-
- console.debug("Showing screensaver " + screensaver.name);
-
- screensaver.show();
- activeScreenSaver = screensaver;
-
- if (screensaver.hideOnClick !== false) {
- window.addEventListener("click", hide, true);
- }
- if (screensaver.hideOnMouse !== false) {
- window.addEventListener("mousemove", hide, true);
- }
- if (screensaver.hideOnKey !== false) {
- window.addEventListener("keydown", hide, true);
- }
- }
-
- function hide() {
- if (activeScreenSaver) {
- console.debug("Hiding screensaver");
- activeScreenSaver.hide();
- activeScreenSaver = null;
- }
-
- window.removeEventListener("click", hide, true);
- window.removeEventListener("mousemove", hide, true);
- window.removeEventListener("keydown", hide, true);
- }
-
- self.isShowing = function () {
- return activeScreenSaver != null;
- };
-
- self.show = function () {
- var isLoggedIn;
- var apiClient = connectionManager.currentApiClient();
-
- if (apiClient && apiClient.isLoggedIn()) {
- isLoggedIn = true;
- }
-
- var screensaver = getScreensaverPlugin(isLoggedIn);
-
- if (screensaver) {
- showScreenSaver(screensaver);
- }
- };
-
- self.hide = function () {
- hide();
- };
-
- function onInterval() {
-
- if (self.isShowing()) {
- return;
- }
-
- if (inputManager.idleTime() < getMinIdleTime()) {
- return;
- }
-
- if (getFunctionalEventIdleTime < getMinIdleTime()) {
- return;
- }
-
- if (playbackManager.isPlayingVideo()) {
- return;
- }
-
- self.show();
- }
-
- setInterval(onInterval, 10000);
- }
-
- return new ScreenSaverManager();
});
+
+function getScreensaverPlugin(isLoggedIn) {
+ let option;
+ try {
+ option = userSettings.get('screensaver', false);
+ } catch (err) {
+ option = isLoggedIn ? 'backdropscreensaver' : 'logoscreensaver';
+ }
+
+ const plugins = pluginManager.ofType('screensaver');
+
+ for (const plugin of plugins) {
+ if (plugin.id === option) {
+ return plugin;
+ }
+ }
+
+ return null;
+}
+
+function ScreenSaverManager() {
+ let activeScreenSaver;
+
+ function showScreenSaver(screensaver) {
+ if (activeScreenSaver) {
+ throw new Error('An existing screensaver is already active.');
+ }
+
+ console.debug('Showing screensaver ' + screensaver.name);
+
+ screensaver.show();
+ activeScreenSaver = screensaver;
+
+ if (screensaver.hideOnClick !== false) {
+ window.addEventListener('click', hide, true);
+ }
+ if (screensaver.hideOnMouse !== false) {
+ window.addEventListener('mousemove', hide, true);
+ }
+ if (screensaver.hideOnKey !== false) {
+ window.addEventListener('keydown', hide, true);
+ }
+ }
+
+ function hide() {
+ if (activeScreenSaver) {
+ console.debug('Hiding screensaver');
+ activeScreenSaver.hide();
+ activeScreenSaver = null;
+ }
+
+ window.removeEventListener('click', hide, true);
+ window.removeEventListener('mousemove', hide, true);
+ window.removeEventListener('keydown', hide, true);
+ }
+
+ this.isShowing = () => {
+ return activeScreenSaver != null;
+ };
+
+ this.show = function () {
+ let isLoggedIn;
+ const apiClient = connectionManager.currentApiClient();
+
+ if (apiClient && apiClient.isLoggedIn()) {
+ isLoggedIn = true;
+ }
+
+ const screensaver = getScreensaverPlugin(isLoggedIn);
+
+ if (screensaver) {
+ showScreenSaver(screensaver);
+ }
+ };
+
+ this.hide = function () {
+ hide();
+ };
+
+ const onInterval = () => {
+ if (this.isShowing()) {
+ return;
+ }
+
+ if (inputManager.idleTime() < getMinIdleTime()) {
+ return;
+ }
+
+ if (getFunctionalEventIdleTime < getMinIdleTime()) {
+ return;
+ }
+
+ if (playbackManager.isPlayingVideo()) {
+ return;
+ }
+
+ this.show();
+ };
+
+ setInterval(onInterval, 10000);
+}
+
+export default new ScreenSaverManager;
diff --git a/src/libraries/scroller.js b/src/libraries/scroller.js
index c0cb3e557c..c460ec5b2c 100644
--- a/src/libraries/scroller.js
+++ b/src/libraries/scroller.js
@@ -1,927 +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;
+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.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;
}
- 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;
diff --git a/src/plugins/chromecastPlayer/plugin.js b/src/plugins/chromecastPlayer/plugin.js
index 88c460c444..f38f9b04ff 100644
--- a/src/plugins/chromecastPlayer/plugin.js
+++ b/src/plugins/chromecastPlayer/plugin.js
@@ -584,7 +584,7 @@ class ChromecastPlayer {
this.isLocalPlayer = false;
this.lastPlayerData = {};
- castSenderApiLoader.load().then(initializeChromecast.bind(this));
+ new castSenderApiLoader.default().load().then(initializeChromecast.bind(this));
}
tryPair(target) {
diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js
index d52f0eb5b3..525372ac88 100644
--- a/src/plugins/htmlVideoPlayer/plugin.js
+++ b/src/plugins/htmlVideoPlayer/plugin.js
@@ -1132,7 +1132,7 @@ function tryRemoveElement(elem) {
*/
getCueCss(appearance, selector) {
return `${selector}::cue {
- ${appearance.text.map((s) => `${s.name}:${s.value}!important;`).join('')}
+ ${appearance.text.map((s) => s.value !== undefined && s.value !== '' ? `${s.name}:${s.value}!important;` : '').join('')}
}`;
}
@@ -1150,7 +1150,7 @@ function tryRemoveElement(elem) {
document.getElementsByTagName('head')[0].appendChild(styleElem);
}
- styleElem.innerHTML = this.getCueCss(subtitleAppearanceHelper.getStyles(userSettings.getSubtitleAppearanceSettings(), true), '.htmlvideoplayer');
+ styleElem.innerHTML = this.getCueCss(subtitleAppearanceHelper.getStyles(userSettings.getSubtitleAppearanceSettings()), '.htmlvideoplayer');
});
}
@@ -1195,17 +1195,28 @@ function tryRemoveElement(elem) {
// download the track json
this.fetchSubtitles(track, item).then(function (data) {
- // show in ui
- console.debug(`downloaded ${data.TrackEvents.length} track events`);
- // add some cues to show the text
- // in safari, the cues need to be added before setting the track mode to showing
- for (const trackEvent of data.TrackEvents) {
- const trackCueObject = window.VTTCue || window.TextTrackCue;
- const cue = new trackCueObject(trackEvent.StartPositionTicks / 10000000, trackEvent.EndPositionTicks / 10000000, normalizeTrackEventText(trackEvent.Text, false));
+ import('userSettings').then((userSettings) => {
+ // show in ui
+ console.debug(`downloaded ${data.TrackEvents.length} track events`);
- trackElement.addCue(cue);
- }
- trackElement.mode = 'showing';
+ const subtitleAppearance = userSettings.getSubtitleAppearanceSettings();
+ 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
+ for (const trackEvent of data.TrackEvents) {
+ const trackCueObject = window.VTTCue || window.TextTrackCue;
+ const cue = new trackCueObject(trackEvent.StartPositionTicks / 10000000, trackEvent.EndPositionTicks / 10000000, normalizeTrackEventText(trackEvent.Text, false));
+
+ if (cue.line === 'auto') {
+ cue.line = cueLine;
+ }
+
+ trackElement.addCue(cue);
+ }
+
+ trackElement.mode = 'showing';
+ });
});
}
diff --git a/src/plugins/htmlVideoPlayer/style.css b/src/plugins/htmlVideoPlayer/style.css
index b83a7816f5..880ea86743 100644
--- a/src/plugins/htmlVideoPlayer/style.css
+++ b/src/plugins/htmlVideoPlayer/style.css
@@ -33,16 +33,22 @@ 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-moveupsubtitles::-webkit-media-text-track-display {
- /* style the text itself */
- margin-top: -2em;
+.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::-webkit-media-text-track-display {
+ max-width: 70%;
+ margin-left: 15%;
}
.videoSubtitles {
position: fixed;
- bottom: 10%;
+ bottom: 0;
text-align: center;
left: 0;
right: 0;
@@ -53,7 +59,6 @@ video::-webkit-media-controls {
.videoSubtitlesInner {
max-width: 70%;
background-color: rgba(0, 0, 0, 0.8);
- padding: 0.25em;
margin: auto;
display: inline-block;
}
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();
+ }
+ }
+ };
+}
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;
diff --git a/src/plugins/sessionPlayer/plugin.js b/src/plugins/sessionPlayer/plugin.js
index fb1f745df3..c68e0d7a4a 100644
--- a/src/plugins/sessionPlayer/plugin.js
+++ b/src/plugins/sessionPlayer/plugin.js
@@ -1,6 +1,7 @@
define(['playbackManager', 'events', 'serverNotifications', 'connectionManager'], function (playbackManager, events, serverNotifications, connectionManager) {
'use strict';
+ serverNotifications = serverNotifications.default || serverNotifications;
playbackManager = playbackManager.default || playbackManager;
function getActivePlayerId() {
diff --git a/src/scripts/libraryMenu.js b/src/scripts/libraryMenu.js
index bbe01276ba..0780916a7c 100644
--- a/src/scripts/libraryMenu.js
+++ b/src/scripts/libraryMenu.js
@@ -1,11 +1,26 @@
-define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', 'viewManager', 'libraryBrowser', 'appRouter', 'apphost', 'playbackManager', 'syncPlayManager', 'groupSelectionMenu', 'browser', 'globalize', 'scripts/imagehelper', 'paper-icon-button-light', 'material-icons', 'scrollStyles', 'flexStyles'], function (dom, layoutManager, inputManager, connectionManager, events, viewManager, libraryBrowser, appRouter, appHost, playbackManager, syncPlayManager, groupSelectionMenu, browser, globalize, imageHelper) {
- 'use strict';
+import dom from 'dom';
+import layoutManager from 'layoutManager';
+import inputManager from 'inputManager';
+import connectionManager from 'connectionManager';
+import events from 'events';
+import viewManager from 'viewManager';
+import appRouter from 'appRouter';
+import appHost from 'apphost';
+import playbackManager from 'playbackManager';
+import syncPlayManager from 'syncPlayManager';
+import groupSelectionMenu from 'groupSelectionMenu';
+import browser from 'browser';
+import globalize from 'globalize';
+import imageHelper from 'scripts/imagehelper';
+import 'paper-icon-button-light';
+import 'material-icons';
+import 'scrollStyles';
+import 'flexStyles';
- playbackManager = playbackManager.default || playbackManager;
- browser = browser.default || browser;
+/* eslint-disable indent */
function renderHeader() {
- var html = '';
+ let html = '';
html += '
';
html += '
';
html += '';
@@ -49,7 +64,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', '
}
function lazyLoadViewMenuBarImages() {
- require(['imageLoader'], function (imageLoader) {
+ import('imageLoader').then(({default: imageLoader}) => {
imageLoader.lazyChildren(skinHeader);
});
}
@@ -59,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;
}
@@ -90,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');
}
@@ -142,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);
@@ -184,20 +199,20 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', '
}
function onCastButtonClicked() {
- var btn = this;
+ const btn = this;
- require(['playerSelectionMenu'], function (playerSelectionMenu) {
+ import('playerSelectionMenu').then(({default: playerSelectionMenu}) => {
playerSelectionMenu.show(btn);
});
}
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');
@@ -207,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');
@@ -253,7 +268,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', '
}
function refreshLibraryInfoInDrawer(user, drawer) {
- var html = '';
+ let html = '';
html += '';
html += '' + globalize.translate('ButtonHome') + '';
@@ -289,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);
}
@@ -316,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 = pageIds.indexOf(currentViewId) != -1;
}
- var pageUrls = link.getAttribute('data-pageurls');
+ let pageUrls = link.getAttribute('data-pageurls');
if (pageUrls) {
pageUrls = pageUrls.split('|');
@@ -338,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);
@@ -349,7 +364,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', '
}
function createToolsMenuList(pluginItems) {
- var links = [{
+ const links = [{
name: globalize.translate('TabServer')
}, {
name: globalize.translate('TabDashboard'),
@@ -461,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({
@@ -482,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 += '';
@@ -501,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) {
@@ -523,7 +538,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', '
function createDashboardMenu(apiClient) {
return getToolsMenuHtml(apiClient).then(function (toolsMenuHtml) {
- var html = '';
+ let html = '';
html += '';
html += '';
html += '';
@@ -534,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 (view.CollectionType == 'livetv') {
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';
@@ -565,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) {
@@ -595,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;
return `
@@ -613,8 +628,8 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', '
`;
}).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);
@@ -643,9 +658,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');
@@ -661,18 +676,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 && itemId === 'channels') {
lnkMediaFolder.classList.add('navMenuOption-selected');
@@ -693,7 +706,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;
@@ -704,7 +717,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');
@@ -741,7 +754,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,8 +778,8 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', '
}
function initHeadRoom(elem) {
- require(['headroom'], function (Headroom) {
- var headroom = new Headroom(elem);
+ import('headroom').then(({default: Headroom}) => {
+ const headroom = new Headroom(elem);
headroom.init();
});
}
@@ -786,7 +799,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 {
@@ -805,7 +818,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', '
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) {
@@ -817,98 +830,98 @@ 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 = {
- 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');
- }
+ 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;
- if (pageTitleElement) {
- pageTitleElement.classList.add('pageTitleWithLogo');
- pageTitleElement.classList.add('pageTitleWithDefaultLogo');
- pageTitleElement.style.backgroundImage = null;
- pageTitleElement.innerHTML = '';
- }
-
- document.title = 'Jellyfin';
- },
- setTitle: function (title) {
- if (title == null) {
- return void LibraryMenu.setDefaultTitle();
- }
-
- if (title === '-') {
- title = '';
- }
-
- var 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) {
+ import('mainTabsManager').then(({default: 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');
}
- };
- var currentPageType;
+
+ if (pageTitleElement) {
+ pageTitleElement.classList.add('pageTitleWithLogo');
+ pageTitleElement.classList.add('pageTitleWithDefaultLogo');
+ pageTitleElement.style.backgroundImage = null;
+ pageTitleElement.innerHTML = '';
+ }
+
+ document.title = 'Jellyfin';
+ }
+
+ function setTitle (title) {
+ if (title == null) {
+ 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) {
- 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) {
@@ -945,7 +958,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 = {
@@ -959,15 +972,32 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', '
updateUserInHeader(user);
});
});
+
events.on(connectionManager, 'localusersignedout', function () {
currentUser = {};
updateUserInHeader();
});
+
events.on(playbackManager, 'playerchange', updateCastIcon);
events.on(syncPlayManager, 'enabled', onSyncPlayEnabled);
events.on(syncPlayManager, 'syncing', onSyncPlaySyncing);
loadNavDrawer();
- return LibraryMenu;
-});
+
+ const LibraryMenu = {
+ getTopParentId: getTopParentId,
+ onHardwareMenuButtonClick: function () {
+ toggleMainDrawer();
+ },
+ setTabs: setTabs,
+ setDefaultTitle: setDefaultTitle,
+ setTitle: setTitle,
+ setTransparentMenu: setTransparentMenu
+ };
+
+ window.LibraryMenu = LibraryMenu;
+
+export default LibraryMenu;
+
+/* eslint-enable indent */
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/routes.js b/src/scripts/routes.js
index 4094a2552f..4bb3eb25d9 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'
@@ -239,7 +254,8 @@ import 'detailtablecss';
});
defineRoute({
- path: '/playbackconfiguration.html',
+ alias: '/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({
diff --git a/src/scripts/scrollHelper.js b/src/scripts/scrollHelper.js
index 82413fe9cf..b867123683 100644
--- a/src/scripts/scrollHelper.js
+++ b/src/scripts/scrollHelper.js
@@ -1,135 +1,138 @@
-define(['focusManager', 'dom', 'scrollStyles'], function (focusManager, dom) {
- 'use strict';
+import focusManager from 'focusManager';
+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) {
- return elem.getBoundingClientRect();
- } else {
- return { top: 0, left: 0 };
- }
+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 };
+ }
+}
+
+export function getPosition(scrollContainer, item, horizontal) {
+ const slideeOffset = getBoundingClientRect(scrollContainer);
+ const itemOffset = getBoundingClientRect(item);
+
+ let offset = horizontal ? itemOffset.left - slideeOffset.left : itemOffset.top - slideeOffset.top;
+ let size = horizontal ? itemOffset.width : itemOffset.height;
+ if (!size && size !== 0) {
+ size = item[horizontal ? 'offsetWidth' : 'offsetHeight'];
}
- function getPosition(scrollContainer, item, horizontal) {
- var slideeOffset = getBoundingClientRect(scrollContainer);
- var itemOffset = getBoundingClientRect(item);
+ const currentStart = horizontal ? scrollContainer.scrollLeft : scrollContainer.scrollTop;
- var offset = horizontal ? itemOffset.left - slideeOffset.left : itemOffset.top - slideeOffset.top;
- var size = horizontal ? itemOffset.width : itemOffset.height;
- if (!size && size !== 0) {
- size = item[horizontal ? 'offsetWidth' : 'offsetHeight'];
- }
+ offset += currentStart;
- var currentStart = horizontal ? scrollContainer.scrollLeft : scrollContainer.scrollTop;
+ const frameSize = horizontal ? scrollContainer.offsetWidth : scrollContainer.offsetHeight;
- offset += currentStart;
+ const currentEnd = currentStart + frameSize;
- var frameSize = horizontal ? scrollContainer.offsetWidth : scrollContainer.offsetHeight;
-
- var currentEnd = currentStart + frameSize;
-
- var isVisible = offset >= currentStart && (offset + size) <= currentEnd;
-
- return {
- start: offset,
- center: (offset - (frameSize / 2) + (size / 2)),
- end: offset - frameSize + size,
- size: size,
- isVisible: isVisible
- };
- }
-
- function toCenter(container, elem, horizontal, skipWhenVisible) {
- var pos = getPosition(container, elem, horizontal);
-
- if (skipWhenVisible && pos.isVisible) {
- return;
- }
-
- if (container.scrollTo) {
- if (horizontal) {
- container.scrollTo(pos.center, 0);
- } else {
- container.scrollTo(0, pos.center);
- }
- } else {
- if (horizontal) {
- container.scrollLeft = Math.round(pos.center);
- } else {
- container.scrollTop = Math.round(pos.center);
- }
- }
- }
-
- function toStart(container, elem, horizontal, skipWhenVisible) {
- var pos = getPosition(container, elem, horizontal);
-
- if (skipWhenVisible && pos.isVisible) {
- return;
- }
-
- if (container.scrollTo) {
- if (horizontal) {
- container.scrollTo(pos.start, 0);
- } else {
- container.scrollTo(0, pos.start);
- }
- } else {
- if (horizontal) {
- container.scrollLeft = Math.round(pos.start);
- } else {
- container.scrollTop = Math.round(pos.start);
- }
- }
- }
-
- function centerOnFocus(e, scrollSlider, horizontal) {
- var focused = focusManager.focusableParent(e.target);
-
- if (focused) {
- toCenter(scrollSlider, focused, horizontal);
- }
- }
-
- function centerOnFocusHorizontal(e) {
- centerOnFocus(e, this, true);
- }
- function centerOnFocusVertical(e) {
- centerOnFocus(e, this, false);
- }
+ const isVisible = offset >= currentStart && (offset + size) <= currentEnd;
return {
- 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
- });
- }
- }
- },
- toCenter: toCenter,
- toStart: toStart
+ start: offset,
+ center: (offset - (frameSize / 2) + (size / 2)),
+ end: offset - frameSize + size,
+ size: size,
+ isVisible: isVisible
};
-});
+}
+
+export function toCenter(container, elem, horizontal, skipWhenVisible) {
+ const pos = getPosition(container, elem, horizontal);
+
+ if (skipWhenVisible && pos.isVisible) {
+ return;
+ }
+
+ if (container.scrollTo) {
+ if (horizontal) {
+ container.scrollTo(pos.center, 0);
+ } else {
+ container.scrollTo(0, pos.center);
+ }
+ } else {
+ if (horizontal) {
+ container.scrollLeft = Math.round(pos.center);
+ } else {
+ container.scrollTop = Math.round(pos.center);
+ }
+ }
+}
+
+export function toStart(container, elem, horizontal, skipWhenVisible) {
+ const pos = getPosition(container, elem, horizontal);
+
+ if (skipWhenVisible && pos.isVisible) {
+ return;
+ }
+
+ if (container.scrollTo) {
+ if (horizontal) {
+ container.scrollTo(pos.start, 0);
+ } else {
+ container.scrollTo(0, pos.start);
+ }
+ } else {
+ if (horizontal) {
+ container.scrollLeft = Math.round(pos.start);
+ } else {
+ container.scrollTop = Math.round(pos.start);
+ }
+ }
+}
+
+function centerOnFocus(e, scrollSlider, horizontal) {
+ const focused = focusManager.focusableParent(e.target);
+
+ if (focused) {
+ toCenter(scrollSlider, focused, horizontal);
+ }
+}
+
+function centerOnFocusHorizontal(e) {
+ centerOnFocus(e, this, true);
+}
+
+function centerOnFocusVertical(e) {
+ centerOnFocus(e, this, false);
+}
+
+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
+};
diff --git a/src/scripts/searchtab.js b/src/scripts/searchtab.js
deleted file mode 100644
index e012b8a4b2..0000000000
--- a/src/scripts/searchtab.js
+++ /dev/null
@@ -1,57 +0,0 @@
-define(['searchFields', 'searchResults', 'events'], function (SearchFields, SearchResults, events) {
- 'use strict';
-
- SearchFields = SearchFields.default || SearchFields;
- SearchResults = SearchResults.default || SearchResults;
-
- function init(instance, tabContent, options) {
- tabContent.innerHTML = '';
- instance.searchFields = new SearchFields({
- element: tabContent.querySelector('.searchFields')
- });
- instance.searchResults = new SearchResults({
- element: tabContent.querySelector('.searchResults'),
- serverId: ApiClient.serverId(),
- parentId: options.parentId,
- collectionType: options.collectionType
- });
- events.on(instance.searchFields, 'search', function (e, value) {
- instance.searchResults.search(value);
- });
- }
-
- function SearchTab(view, tabContent, options) {
- var self = this;
- options = options || {};
- init(this, tabContent, options);
-
- self.preRender = function () {};
-
- self.renderTab = function () {
- var searchFields = this.searchFields;
-
- if (searchFields) {
- searchFields.focus();
- }
- };
- }
-
- SearchTab.prototype.destroy = function () {
- var searchFields = this.searchFields;
-
- if (searchFields) {
- searchFields.destroy();
- }
-
- this.searchFields = null;
- var searchResults = this.searchResults;
-
- if (searchResults) {
- searchResults.destroy();
- }
-
- this.searchResults = null;
- };
-
- return SearchTab;
-});
diff --git a/src/scripts/serverNotifications.js b/src/scripts/serverNotifications.js
index e5fb0bcd61..2566d148f6 100644
--- a/src/scripts/serverNotifications.js
+++ b/src/scripts/serverNotifications.js
@@ -1,213 +1,216 @@
-define(['connectionManager', 'playbackManager', 'syncPlayManager', 'events', 'inputManager', 'focusManager', 'appRouter'], function (connectionManager, playbackManager, syncPlayManager, events, inputManager, focusManager, appRouter) {
- 'use strict';
+import connectionManager from 'connectionManager';
+import playbackManager from 'playbackManager';
+import syncPlayManager from 'syncPlayManager';
+import events from 'events';
+import inputManager from 'inputManager';
+import focusManager from 'focusManager';
+import appRouter from 'appRouter';
- playbackManager = playbackManager.default || playbackManager;
+const serverNotifications = {};
- var serverNotifications = {};
+function notifyApp() {
+ inputManager.notify();
+}
- function notifyApp() {
- inputManager.notify();
- }
-
- function displayMessage(cmd) {
- var args = cmd.Arguments;
- if (args.TimeoutMs) {
- require(['toast'], function (toast) {
- toast({ title: args.Header, text: args.Text });
- });
- } else {
- require(['alert'], function (alert) {
- alert.default({ title: args.Header, text: args.Text });
- });
- }
- }
-
- function displayContent(cmd, apiClient) {
- if (!playbackManager.isPlayingLocally(['Video', 'Book'])) {
- appRouter.showItem(cmd.Arguments.ItemId, apiClient.serverId());
- }
- }
-
- function playTrailers(apiClient, itemId) {
- apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(function (item) {
- playbackManager.playTrailers(item);
+function displayMessage(cmd) {
+ const args = cmd.Arguments;
+ if (args.TimeoutMs) {
+ import('toast').then(({default: toast}) => {
+ toast({ title: args.Header, text: args.Text });
+ });
+ } else {
+ import('alert').then(({default: alert}) => {
+ alert({ title: args.Header, text: args.Text });
});
}
+}
- function processGeneralCommand(cmd, apiClient) {
- console.debug('Received command: ' + cmd.Name);
- switch (cmd.Name) {
- case 'Select':
- inputManager.handleCommand('select');
- return;
- case 'Back':
- inputManager.handleCommand('back');
- return;
- case 'MoveUp':
- inputManager.handleCommand('up');
- return;
- case 'MoveDown':
- inputManager.handleCommand('down');
- return;
- case 'MoveLeft':
- inputManager.handleCommand('left');
- return;
- case 'MoveRight':
- inputManager.handleCommand('right');
- return;
- case 'PageUp':
- inputManager.handleCommand('pageup');
- return;
- case 'PageDown':
- inputManager.handleCommand('pagedown');
- return;
- case 'PlayTrailers':
- playTrailers(apiClient, cmd.Arguments.ItemId);
- break;
- case 'SetRepeatMode':
- playbackManager.setRepeatMode(cmd.Arguments.RepeatMode);
- break;
- case 'SetShuffleQueue':
- playbackManager.setQueueShuffleMode(cmd.Arguments.ShuffleMode);
- break;
- case 'VolumeUp':
- inputManager.handleCommand('volumeup');
- return;
- case 'VolumeDown':
- inputManager.handleCommand('volumedown');
- return;
- case 'ChannelUp':
- inputManager.handleCommand('channelup');
- return;
- case 'ChannelDown':
- inputManager.handleCommand('channeldown');
- return;
- case 'Mute':
- inputManager.handleCommand('mute');
- return;
- case 'Unmute':
- inputManager.handleCommand('unmute');
- return;
- case 'ToggleMute':
- inputManager.handleCommand('togglemute');
- return;
- case 'SetVolume':
- notifyApp();
- playbackManager.setVolume(cmd.Arguments.Volume);
- break;
- case 'SetAudioStreamIndex':
- notifyApp();
- playbackManager.setAudioStreamIndex(parseInt(cmd.Arguments.Index));
- break;
- case 'SetSubtitleStreamIndex':
- notifyApp();
- playbackManager.setSubtitleStreamIndex(parseInt(cmd.Arguments.Index));
- break;
- case 'ToggleFullscreen':
- inputManager.handleCommand('togglefullscreen');
- return;
- case 'GoHome':
- inputManager.handleCommand('home');
- return;
- case 'GoToSettings':
- inputManager.handleCommand('settings');
- return;
- case 'DisplayContent':
- displayContent(cmd, apiClient);
- break;
- case 'GoToSearch':
- inputManager.handleCommand('search');
- return;
- case 'DisplayMessage':
- displayMessage(cmd);
- break;
- case 'ToggleOsd':
- // todo
- break;
- case 'ToggleContextMenu':
- // todo
- break;
- case 'TakeScreenShot':
- // todo
- break;
- case 'SendKey':
- // todo
- break;
- case 'SendString':
- // todo
- focusManager.sendText(cmd.Arguments.String);
- break;
- default:
- console.debug('processGeneralCommand does not recognize: ' + cmd.Name);
- break;
- }
-
- notifyApp();
+function displayContent(cmd, apiClient) {
+ if (!playbackManager.isPlayingLocally(['Video', 'Book'])) {
+ appRouter.showItem(cmd.Arguments.ItemId, apiClient.serverId());
}
+}
- function onMessageReceived(e, msg) {
- var apiClient = this;
- if (msg.MessageType === 'Play') {
- notifyApp();
- var serverId = apiClient.serverInfo().Id;
- if (msg.Data.PlayCommand === 'PlayNext') {
- playbackManager.queueNext({ ids: msg.Data.ItemIds, serverId: serverId });
- } else if (msg.Data.PlayCommand === 'PlayLast') {
- playbackManager.queue({ ids: msg.Data.ItemIds, serverId: serverId });
- } else {
- playbackManager.play({
- ids: msg.Data.ItemIds,
- startPositionTicks: msg.Data.StartPositionTicks,
- mediaSourceId: msg.Data.MediaSourceId,
- audioStreamIndex: msg.Data.AudioStreamIndex,
- subtitleStreamIndex: msg.Data.SubtitleStreamIndex,
- startIndex: msg.Data.StartIndex,
- serverId: serverId
- });
- }
- } else if (msg.MessageType === 'Playstate') {
- if (msg.Data.Command === 'Stop') {
- inputManager.handleCommand('stop');
- } else if (msg.Data.Command === 'Pause') {
- inputManager.handleCommand('pause');
- } else if (msg.Data.Command === 'Unpause') {
- inputManager.handleCommand('play');
- } else if (msg.Data.Command === 'PlayPause') {
- inputManager.handleCommand('playpause');
- } else if (msg.Data.Command === 'Seek') {
- playbackManager.seek(msg.Data.SeekPositionTicks);
- } else if (msg.Data.Command === 'NextTrack') {
- inputManager.handleCommand('next');
- } else if (msg.Data.Command === 'PreviousTrack') {
- inputManager.handleCommand('previous');
- } else {
- notifyApp();
- }
- } else if (msg.MessageType === 'GeneralCommand') {
- var cmd = msg.Data;
- processGeneralCommand(cmd, apiClient);
- } else if (msg.MessageType === 'UserDataChanged') {
- if (msg.Data.UserId === apiClient.getCurrentUserId()) {
- for (var i = 0, length = msg.Data.UserDataList.length; i < length; i++) {
- events.trigger(serverNotifications, 'UserDataChanged', [apiClient, msg.Data.UserDataList[i]]);
- }
- }
- } else if (msg.MessageType === 'SyncPlayCommand') {
- syncPlayManager.processCommand(msg.Data, apiClient);
- } else if (msg.MessageType === 'SyncPlayGroupUpdate') {
- syncPlayManager.processGroupUpdate(msg.Data, apiClient);
- } else {
- events.trigger(serverNotifications, msg.MessageType, [apiClient, msg.Data]);
- }
- }
- function bindEvents(apiClient) {
- events.off(apiClient, 'message', onMessageReceived);
- events.on(apiClient, 'message', onMessageReceived);
- }
-
- connectionManager.getApiClients().forEach(bindEvents);
- events.on(connectionManager, 'apiclientcreated', function (e, newApiClient) {
- bindEvents(newApiClient);
+function playTrailers(apiClient, itemId) {
+ apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(function (item) {
+ playbackManager.playTrailers(item);
});
- return serverNotifications;
+}
+
+function processGeneralCommand(cmd, apiClient) {
+ console.debug('Received command: ' + cmd.Name);
+ switch (cmd.Name) {
+ case 'Select':
+ inputManager.handleCommand('select');
+ return;
+ case 'Back':
+ inputManager.handleCommand('back');
+ return;
+ case 'MoveUp':
+ inputManager.handleCommand('up');
+ return;
+ case 'MoveDown':
+ inputManager.handleCommand('down');
+ return;
+ case 'MoveLeft':
+ inputManager.handleCommand('left');
+ return;
+ case 'MoveRight':
+ inputManager.handleCommand('right');
+ return;
+ case 'PageUp':
+ inputManager.handleCommand('pageup');
+ return;
+ case 'PageDown':
+ inputManager.handleCommand('pagedown');
+ return;
+ case 'PlayTrailers':
+ playTrailers(apiClient, cmd.Arguments.ItemId);
+ break;
+ case 'SetRepeatMode':
+ playbackManager.setRepeatMode(cmd.Arguments.RepeatMode);
+ break;
+ case 'SetShuffleQueue':
+ playbackManager.setQueueShuffleMode(cmd.Arguments.ShuffleMode);
+ break;
+ case 'VolumeUp':
+ inputManager.handleCommand('volumeup');
+ return;
+ case 'VolumeDown':
+ inputManager.handleCommand('volumedown');
+ return;
+ case 'ChannelUp':
+ inputManager.handleCommand('channelup');
+ return;
+ case 'ChannelDown':
+ inputManager.handleCommand('channeldown');
+ return;
+ case 'Mute':
+ inputManager.handleCommand('mute');
+ return;
+ case 'Unmute':
+ inputManager.handleCommand('unmute');
+ return;
+ case 'ToggleMute':
+ inputManager.handleCommand('togglemute');
+ return;
+ case 'SetVolume':
+ notifyApp();
+ playbackManager.setVolume(cmd.Arguments.Volume);
+ break;
+ case 'SetAudioStreamIndex':
+ notifyApp();
+ playbackManager.setAudioStreamIndex(parseInt(cmd.Arguments.Index));
+ break;
+ case 'SetSubtitleStreamIndex':
+ notifyApp();
+ playbackManager.setSubtitleStreamIndex(parseInt(cmd.Arguments.Index));
+ break;
+ case 'ToggleFullscreen':
+ inputManager.handleCommand('togglefullscreen');
+ return;
+ case 'GoHome':
+ inputManager.handleCommand('home');
+ return;
+ case 'GoToSettings':
+ inputManager.handleCommand('settings');
+ return;
+ case 'DisplayContent':
+ displayContent(cmd, apiClient);
+ break;
+ case 'GoToSearch':
+ inputManager.handleCommand('search');
+ return;
+ case 'DisplayMessage':
+ displayMessage(cmd);
+ break;
+ case 'ToggleOsd':
+ // todo
+ break;
+ case 'ToggleContextMenu':
+ // todo
+ break;
+ case 'TakeScreenShot':
+ // todo
+ break;
+ case 'SendKey':
+ // todo
+ break;
+ case 'SendString':
+ // todo
+ focusManager.sendText(cmd.Arguments.String);
+ break;
+ default:
+ console.debug('processGeneralCommand does not recognize: ' + cmd.Name);
+ break;
+ }
+
+ notifyApp();
+}
+
+function onMessageReceived(e, msg) {
+ const apiClient = this;
+ if (msg.MessageType === 'Play') {
+ notifyApp();
+ const serverId = apiClient.serverInfo().Id;
+ if (msg.Data.PlayCommand === 'PlayNext') {
+ playbackManager.queueNext({ ids: msg.Data.ItemIds, serverId: serverId });
+ } else if (msg.Data.PlayCommand === 'PlayLast') {
+ playbackManager.queue({ ids: msg.Data.ItemIds, serverId: serverId });
+ } else {
+ playbackManager.play({
+ ids: msg.Data.ItemIds,
+ startPositionTicks: msg.Data.StartPositionTicks,
+ mediaSourceId: msg.Data.MediaSourceId,
+ audioStreamIndex: msg.Data.AudioStreamIndex,
+ subtitleStreamIndex: msg.Data.SubtitleStreamIndex,
+ startIndex: msg.Data.StartIndex,
+ serverId: serverId
+ });
+ }
+ } else if (msg.MessageType === 'Playstate') {
+ if (msg.Data.Command === 'Stop') {
+ inputManager.handleCommand('stop');
+ } else if (msg.Data.Command === 'Pause') {
+ inputManager.handleCommand('pause');
+ } else if (msg.Data.Command === 'Unpause') {
+ inputManager.handleCommand('play');
+ } else if (msg.Data.Command === 'PlayPause') {
+ inputManager.handleCommand('playpause');
+ } else if (msg.Data.Command === 'Seek') {
+ playbackManager.seek(msg.Data.SeekPositionTicks);
+ } else if (msg.Data.Command === 'NextTrack') {
+ inputManager.handleCommand('next');
+ } else if (msg.Data.Command === 'PreviousTrack') {
+ inputManager.handleCommand('previous');
+ } else {
+ notifyApp();
+ }
+ } else if (msg.MessageType === 'GeneralCommand') {
+ const cmd = msg.Data;
+ processGeneralCommand(cmd, apiClient);
+ } else if (msg.MessageType === 'UserDataChanged') {
+ if (msg.Data.UserId === apiClient.getCurrentUserId()) {
+ for (let i = 0, length = msg.Data.UserDataList.length; i < length; i++) {
+ events.trigger(serverNotifications, 'UserDataChanged', [apiClient, msg.Data.UserDataList[i]]);
+ }
+ }
+ } else if (msg.MessageType === 'SyncPlayCommand') {
+ syncPlayManager.processCommand(msg.Data, apiClient);
+ } else if (msg.MessageType === 'SyncPlayGroupUpdate') {
+ syncPlayManager.processGroupUpdate(msg.Data, apiClient);
+ } else {
+ events.trigger(serverNotifications, msg.MessageType, [apiClient, msg.Data]);
+ }
+}
+function bindEvents(apiClient) {
+ events.off(apiClient, 'message', onMessageReceived);
+ events.on(apiClient, 'message', onMessageReceived);
+}
+
+connectionManager.getApiClients().forEach(bindEvents);
+events.on(connectionManager, 'apiclientcreated', function (e, newApiClient) {
+ bindEvents(newApiClient);
});
+
+export default serverNotifications;
diff --git a/src/scripts/settings/userSettings.js b/src/scripts/settings/userSettings.js
index bd6050b5fe..1235e0fa5f 100644
--- a/src/scripts/settings/userSettings.js
+++ b/src/scripts/settings/userSettings.js
@@ -15,6 +15,10 @@ function saveServerPreferences(instance) {
instance.saveTimeout = setTimeout(onSaveTimeout.bind(instance), 50);
}
+const defaultSubtitleAppearanceSettings = {
+ verticalPosition: -3
+};
+
export class UserSettings {
constructor() {
}
@@ -412,7 +416,7 @@ export class UserSettings {
*/
getSubtitleAppearanceSettings(key) {
key = key || 'localplayersubtitleappearance3';
- return JSON.parse(this.get(key, false) || '{}');
+ return Object.assign(defaultSubtitleAppearanceSettings, JSON.parse(this.get(key, false) || '{}'));
}
/**
diff --git a/src/scripts/shell.js b/src/scripts/shell.js
index 5a2afd9fd4..e42c7792a0 100644
--- a/src/scripts/shell.js
+++ b/src/scripts/shell.js
@@ -1,23 +1,20 @@
-define([], function () {
- 'use strict';
-
- return {
- openUrl: function (url, target) {
- if (window.NativeShell) {
- window.NativeShell.openUrl(url, target);
- } else {
- window.open(url, target || '_blank');
- }
- },
- enableFullscreen: function () {
- if (window.NativeShell) {
- window.NativeShell.enableFullscreen();
- }
- },
- disableFullscreen: function () {
- if (window.NativeShell) {
- window.NativeShell.disableFullscreen();
- }
+// TODO: This seems like a good candidate for deprecation
+export default {
+ openUrl: function (url, target) {
+ if (window.NativeShell) {
+ window.NativeShell.openUrl(url, target);
+ } else {
+ window.open(url, target || '_blank');
}
- };
-});
+ },
+ enableFullscreen: function () {
+ if (window.NativeShell) {
+ window.NativeShell.enableFullscreen();
+ }
+ },
+ disableFullscreen: function () {
+ if (window.NativeShell) {
+ window.NativeShell.disableFullscreen();
+ }
+ }
+};
diff --git a/src/scripts/site.js b/src/scripts/site.js
index 604f9c7bab..d01e2042e6 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();
}
@@ -615,6 +616,7 @@ function initClient() {
}
var localApiClient;
+ let promise;
(function () {
var urlArgs = 'v=' + (window.dashboardVersion || new Date().getDate());
@@ -696,20 +698,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']);
+ 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;
+ window.jQuery = jQuery;
+ }));
// define styles
// TODO determine which of these files can be moved to the components themselves
@@ -820,8 +814,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);
@@ -1115,7 +1109,7 @@ function initClient() {
});
})();
- return onWebComponentsReady();
+ promise.then(onWebComponentsReady);
}
initClient();
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"
}
diff --git a/src/strings/de.json b/src/strings/de.json
index 1c87a95a95..b167c31707 100644
--- a/src/strings/de.json
+++ b/src/strings/de.json
@@ -1542,5 +1542,10 @@
"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:",
+ "MessageGetInstalledPluginsError": "Beim Abrufen der Liste der derzeit installierten Plugins ist ein Fehler aufgetreten.",
+ "MessagePluginInstallError": "Bei der Installation des Plugins ist ein Fehler aufgetreten."
}
diff --git a/src/strings/en-us.json b/src/strings/en-us.json
index 54c8cf8880..638be2efaf 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.",
+ "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.",
"PreferEmbeddedEpisodeInfosOverFileNamesHelp": "This uses the episode information from the embedded metadata if available.",
@@ -1542,5 +1544,8 @@
"ButtonCast": "Cast",
"ButtonPlayer": "Player",
"StopPlayback": "Stop playback",
- "ClearQueue": "Clear queue"
+ "ClearQueue": "Clear queue",
+ "LabelSubtitleVerticalPosition": "Vertical position:",
+ "SubtitleVerticalPositionHelp": "Line number where text appears. Positive numbers indicate top down. Negative numbers indicate bottom up.",
+ "Preview": "Preview"
}
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 :"
}
diff --git a/src/strings/he.json b/src/strings/he.json
index 3db7c86f26..c266f587c2 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,13 +151,13 @@
"LabelAlbum": "אלבום:",
"LabelAlbumArtists": "אלבום אומנים:",
"LabelArtists": "אומנים:",
- "LabelArtistsHelp": "הפרד מרובים באמצעות;",
+ "LabelArtistsHelp": "הפרד אמנים מרובים באמצעות נקודה-פסיק (;).",
"LabelAudioLanguagePreference": "שפת קול מועדפת:",
"LabelBirthDate": "תאריך לידה:",
"LabelBirthYear": "שנת לידה:",
- "LabelBlastMessageInterval": "אינטרוול הודעות דחיפה (בשניות)",
+ "LabelBlastMessageInterval": "תדירות הודעות דחיפה",
"LabelBlastMessageIntervalHelp": "מגדיר את משך הזמן בשניות בין הודעות דחיפה של השרת.",
- "LabelCachePath": "נתיב cache:",
+ "LabelCachePath": "נתיב מטמון:",
"LabelChannels": "ערוצים:",
"LabelCollection": "אוספים:",
"LabelCommunityRating": "דירוג הקהילה:",
@@ -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:",
+ "LabelMetadataPath": "נתיב מטא-דאטה:",
"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": "שידורים חיים",
@@ -302,7 +302,7 @@
"OptionAlbumArtist": "אמן אלבום",
"OptionAllUsers": "כל המשתמשים",
"OptionAllowLinkSharing": "אפשר שיתוף ברשתות חברתיות",
- "OptionAllowMediaPlayback": "הרשה נגינת מדיה",
+ "OptionAllowMediaPlayback": "אפשר ניגון מדיה",
"OptionAllowUserToManageServer": "אפשר למשתמש זה לנהל את השרת",
"OptionArtist": "אמן",
"OptionAscending": "סדר עולה",
@@ -314,12 +314,12 @@
"OptionContinuing": "ממשיך",
"OptionCriticRating": "ציון מבקרים",
"OptionCustomUsers": "מותאם אישית",
- "OptionDaily": "יומי",
+ "OptionDaily": "כל יום",
"OptionDateAdded": "תאריך הוספה",
"OptionDatePlayed": "תאריך ניגון",
"OptionDescending": "סדר יורד",
"OptionDisableUser": "בטל משתמש זה",
- "OptionDisableUserHelp": "אם מבוטל, השרת שלא יאפשר חיבורים ממשתמש זה. חיבורים פעילים יבוטלו מייד.",
+ "OptionDisableUserHelp": "השרת לא יאפשר חיבורים ממשתמש זה. חיבורים פעילים יבוטלו מייד.",
"OptionDislikes": "לא אוהב",
"OptionDownloadArtImage": "עטיפה",
"OptionDownloadBackImage": "גב",
@@ -337,42 +337,42 @@
"OptionHasSubtitles": "כתוביות",
"OptionHasThemeSong": "שיר נושא",
"OptionHasThemeVideo": "סרט נושא",
- "OptionHasTrailer": "טריילר",
+ "OptionHasTrailer": "קדימון",
"OptionHideUser": "הסתר משתמש זה בחלון ההתחברות",
"OptionImdbRating": "דירוג IMDb",
"OptionLikes": "נבחרים",
"OptionMissingEpisode": "פרקים חסרים",
"OptionMonday": "שני",
"OptionNameSort": "שם",
- "OptionNew": "חדש...",
+ "OptionNew": "חדש…",
"OptionOnAppStartup": "בהפעלת התוכנה",
"OptionOnInterval": "כל פרק זמן",
"OptionParentalRating": "דירוג בקרת הורים",
- "OptionPlayCount": "מספר השמעות",
+ "OptionPlayCount": "כמות ניגונים",
"OptionPlayed": "נוגן",
- "OptionPremiereDate": "תאריך שידור ראשון",
+ "OptionPremiereDate": "תאריך בכורה",
"OptionProfileAudio": "צליל",
"OptionProfilePhoto": "תמונה",
"OptionProfileVideo": "וידאו",
"OptionProfileVideoAudio": "צליל וידאו",
"OptionResumable": "ניתן להמשיך",
- "OptionRuntime": "משך",
+ "OptionRuntime": "זמן ריצה",
"OptionSaturday": "שבת",
"OptionSpecialEpisode": "ספיישלים",
"OptionSunday": "ראשון",
"OptionThursday": "חמישי",
- "OptionTrackName": "שם השיר",
+ "OptionTrackName": "שם הרצועה",
"OptionTuesday": "שלישי",
- "OptionTvdbRating": "דירוג Tvdb",
+ "OptionTvdbRating": "דירוג TVDB",
"OptionUnairedEpisode": "פרקים שלא שודרו",
"OptionUnplayed": "לא נוגן",
"OptionWakeFromSleep": "הער ממצב שינה",
"OptionWednesday": "רביעי",
- "OptionWeekly": "שבועי",
+ "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": "הסר מאוספים",
@@ -419,7 +419,7 @@
"SeriesSettings": "הגדרות סדרה",
"SeriesYearToPresent": "{0} - היום",
"ServerNameIsRestarting": "שרת Jellyfin - {0} מופעל מחדש.",
- "ServerNameIsShuttingDown": "שרת Jellyfin - {0} נכבה.",
+ "ServerNameIsShuttingDown": "שרת Jellyfin - {0} בתהליך כיבוי.",
"ServerUpdateNeeded": "שרת אמבי זה צריך להיות מעודכן. כדי להוריד את הגרסה העדכנית ביותר, בקר בכתובת {0}",
"Settings": "הגדרות",
"SettingsSaved": "ההגדרות נשמרו.",
@@ -471,7 +471,7 @@
"TabTrailers": "טריילרים",
"TabTranscoding": "קידוד",
"TabUpcoming": "בקרוב",
- "Tags": "תגים",
+ "Tags": "מילות מפתח",
"TellUsAboutYourself": "ספר לנו על עצמך",
"ThisWizardWillGuideYou": "אשף זה יעזור לך בהתליך ההתקנה.",
"Thursday": "חמישי",
@@ -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} הורדות",
@@ -588,7 +588,7 @@
"MessageConfirmRestart": "האם אתה בטוח שברצונך לאתחל את שרת ה-Jellyfin?",
"HeaderThisUserIsCurrentlyDisabled": "משתמש זה אינו פעיל כרגע",
"HeaderTaskTriggers": "טריגרים של המשימה",
- "HeaderTags": "תגיות",
+ "HeaderTags": "מילות מפתח",
"HeaderStopRecording": "עצור הקלטה",
"HeaderSortOrder": "סדר מיון",
"HeaderSortBy": "מיין לפי",
@@ -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": "אלבום",
@@ -760,5 +760,114 @@
"ButtonSyncPlay": "SyncPlay",
"ButtonPlayer": "נגן",
"StopPlayback": "הפסק הפעלה",
- "ClearQueue": "נקה תור"
+ "ClearQueue": "נקה תור",
+ "DashboardServerName": "שרת: {0}",
+ "DashboardVersionNumber": "גירסה: {0}",
+ "DashboardArchitecture": "ארכיטקטורה: {0}",
+ "DashboardOperatingSystem": "מערכת הפעלה: {0}",
+ "HeaderMyMediaSmall": "המדיה שלי (קטן)",
+ "HeaderMusicQuality": "איכות מוזיקה",
+ "HeaderMediaInfo": "מידע על המדיה",
+ "HeaderMoreLikeThis": "עוד כמו זה",
+ "HeaderMedia": "מדיה",
+ "LabelMetadata": "מטא-דאטה:",
+ "HeaderSelectMetadataPath": "בחר נתיב מטא-דאטה",
+ "NextUp": "הבא בתור",
+ "LabelBaseUrl": "כתובת בסיס:",
+ "LabelEnableHttpsHelp": "האזן על גבי פורט ה-HTTPS המוגדר. חובה לספק תעודה תקינה על מנת שהגדרה זו תכנס לתוקף.",
+ "LabelEnableHttps": "הפעל HTTPS",
+ "LabelEnableHardwareDecodingFor": "הפעל פענוח חומרה עבור:",
+ "LabelEnableDlnaServerHelp": "אפשר למכשירי UPnP ברשת שלך לעיין בתוכן ולנגן אותו.",
+ "LabelEnableAutomaticPortMap": "הפעל מיפוי פורט אוטומטי",
+ "LabelDropImageHere": "גרור תמונה לכאן, או לחץ כדי לעיין.",
+ "LabelDownloadLanguages": "הורד שפות:",
+ "LabelDownMixAudioScale": "הגברת עוצמת שמע כאשר הוא ממוזג:",
+ "LabelDisplaySpecialsWithinSeasons": "הצג פרקים מיוחדים בתוך העונות שבמהלכן הם שודרו",
+ "LabelDisplayName": "שם תצוגה:",
+ "LabelDisplayMode": "מצב תצוגה:",
+ "LabelDisplayLanguageHelp": "תרגום Jellyfin הוא פרויקט מתמשך.",
+ "LabelDisplayLanguage": "שפת תצוגה:",
+ "LabelDidlMode": "מצב DIDL:",
+ "LabelDeviceDescription": "תיאור מכשיר",
+ "LabelDefaultScreen": "מסך ברירת-מחדל:",
+ "LabelCustomDeviceDisplayName": "שם תצוגה:",
+ "LabelImageType": "סוג תמונה:",
+ "LabelHttpsPortHelp": "מספר פורט ה-TCP עבור שרת ה-HTTPS.",
+ "LabelHttpsPort": "מספר פורט HTTPS מקומי:",
+ "LabelGroupMoviesIntoCollections": "אגד סרטים לתוך אוספים",
+ "LabelFriendlyName": "שם ידידותי:",
+ "LabelFormat": "תבנית:",
+ "LabelForgotPasswordUsernameHelp": "הכנס/י את שם המשתמש שלך, אם את/ה זוכר/ת אותו.",
+ "LabelFont": "גופן:",
+ "LabelFolder": "תיקייה:",
+ "LabelFileOrUrl": "קובץ או כתובת אינטרנט:",
+ "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}.",
+ "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": "אפשר גישה לכל הערוצים",
+ "HeaderSyncPlaySelectGroup": "הצטרף לקבוצה",
+ "TabUsers": "משתמשים"
}
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",
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 として表示されるアイコンの最大解像度(高さ)。"
}
diff --git a/src/strings/nl.json b/src/strings/nl.json
index 39b2ea1b5e..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",
@@ -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}",
diff --git a/src/strings/ro.json b/src/strings/ro.json
index f8aa986264..a0f090676b 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:",
@@ -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ă.",
@@ -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,11 @@
"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",
+ "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"
}
diff --git a/src/strings/ru.json b/src/strings/ru.json
index 9089e4bea7..50ab735286 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": "Сохранить изменения",
@@ -1521,7 +1521,7 @@
"EnableBlurHashHelp": "Рисунки, которые всё ещё загружаются, будут отображаться с размытым заполнением",
"EnableBlurHash": "Включить размытые заполнители для изображений",
"ButtonSyncPlay": "SyncPlay",
- "ButtonCast": "В ролях",
+ "ButtonCast": "Транслировать",
"TabRepositories": "Репозитории",
"MessageNoGenresAvailable": "Разрешить поставщикам метаданных получать жанры из интернета.",
"MessageAddRepository": "Если вы хотите добавить репозиторий, нажмите кнопку рядом с заголовком и заполните необходимую информацию.",
@@ -1539,5 +1539,11 @@
"Writers": "Сценаристы",
"ViewAlbumArtist": "Посмотреть альбом исполнителя",
"ClearQueue": "Очистить очередь",
- "ButtonPlayer": "Проигрыватель"
+ "ButtonPlayer": "Проигрыватель",
+ "PreviousTrack": "Перейти к предыдущему",
+ "NextTrack": "Перейти к следующему",
+ "LabelUnstable": "Нестабильная",
+ "LabelSubtitleVerticalPosition": "Вертикальная позиция:",
+ "SubtitleVerticalPositionHelp": "Номер строки, где появляется текст. Положительные числа означают сверху вниз. Отрицательные числа означают снизу вверх.",
+ "Preview": "Предварительный просмотр"
}
diff --git a/src/strings/sl-si.json b/src/strings/sl-si.json
index 2f08ad8b40..077ea67804 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,49 @@
"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",
+ "Yesterday": "Včeraj",
+ "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"
}
diff --git a/yarn.lock b/yarn.lock
index 17c2b176ef..758ded53fc 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"
@@ -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"
@@ -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"
@@ -5464,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"
@@ -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"
@@ -11988,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.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.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"