mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Merge branch 'master' into improve-builds
This commit is contained in:
commit
446dadc0eb
140 changed files with 4232 additions and 3479 deletions
|
@ -63,6 +63,10 @@ progress[aria-valuenow]::before {
|
|||
}
|
||||
|
||||
.adminDrawerLogo {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.layout-mobile .adminDrawerLogo {
|
||||
padding: 1.5em 1em 1.2em;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
margin-bottom: 1em;
|
||||
|
@ -161,7 +165,7 @@ div[data-role=controlgroup] a.ui-btn-active {
|
|||
|
||||
@media all and (min-width: 40em) {
|
||||
.content-primary {
|
||||
padding-top: 7em;
|
||||
padding-top: 4.6em;
|
||||
}
|
||||
|
||||
.withTabs .content-primary {
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
}
|
||||
|
||||
.libraryPage {
|
||||
padding-top: 7em !important;
|
||||
padding-top: 7em;
|
||||
}
|
||||
|
||||
.itemDetailPage {
|
||||
|
@ -242,7 +242,7 @@
|
|||
}
|
||||
|
||||
.mainDrawer-scrollContainer {
|
||||
padding-bottom: 10vh;
|
||||
margin-bottom: 10vh;
|
||||
}
|
||||
|
||||
@media all and (min-width: 40em) {
|
||||
|
@ -313,7 +313,7 @@
|
|||
}
|
||||
|
||||
.dashboardDocument .mainDrawer-scrollContainer {
|
||||
margin-top: 6em !important;
|
||||
margin-top: 4.6em !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1119,3 +1119,50 @@ div:not(.sectionTitleContainer-cards) > .sectionTitle-cards {
|
|||
.itemsViewSettingsContainer > .button-flat {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.layout-mobile #myPreferencesMenuPage {
|
||||
padding-top: 3.75em;
|
||||
}
|
||||
|
||||
.itemDetailsGroup {
|
||||
margin-bottom: 1.5em;
|
||||
}
|
||||
|
||||
.trackSelections {
|
||||
max-width: 44em;
|
||||
}
|
||||
|
||||
.detailsGroupItem,
|
||||
.trackSelections .selectContainer {
|
||||
display: flex;
|
||||
max-width: 44em;
|
||||
margin: 0 0 0.5em !important;
|
||||
}
|
||||
|
||||
.trackSelections .selectContainer {
|
||||
margin: 0 0 0.3em !important;
|
||||
}
|
||||
|
||||
.detailsGroupItem .label,
|
||||
.trackSelections .selectContainer .selectLabel {
|
||||
cursor: default;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
flex-basis: 6.25em;
|
||||
margin: 0 0.6em 0 0;
|
||||
}
|
||||
|
||||
.trackSelections .selectContainer .selectLabel {
|
||||
margin: 0 0.2em 0 0;
|
||||
}
|
||||
|
||||
.trackSelections .selectContainer .detailTrackSelect {
|
||||
font-size: inherit;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.trackSelections .selectContainer .selectArrowContainer .selectArrow {
|
||||
margin-top: 0;
|
||||
font-size: 1.4em;
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ _define("document-register-element", function() {
|
|||
// fetch
|
||||
var fetch = require("whatwg-fetch");
|
||||
_define("fetch", function() {
|
||||
return fetch
|
||||
return fetch;
|
||||
});
|
||||
|
||||
// query-string
|
||||
|
@ -84,13 +84,6 @@ _define("webcomponents", function() {
|
|||
return webcomponents;
|
||||
});
|
||||
|
||||
// libjass
|
||||
var libjass = require("libjass");
|
||||
require("libjass/libjass.css");
|
||||
_define("libjass", function() {
|
||||
return libjass;
|
||||
});
|
||||
|
||||
// libass-wasm
|
||||
var libass_wasm = require("libass-wasm");
|
||||
_define("JavascriptSubtitlesOctopus", function() {
|
||||
|
@ -119,3 +112,20 @@ var polyfill = require("@babel/polyfill/dist/polyfill");
|
|||
_define("polyfill", function () {
|
||||
return polyfill;
|
||||
});
|
||||
|
||||
// domtokenlist-shim
|
||||
var classlist = require("classlist.js");
|
||||
_define("classlist-polyfill", function () {
|
||||
return classlist;
|
||||
});
|
||||
|
||||
// Date-FNS
|
||||
var date_fns = require("date-fns");
|
||||
_define("date-fns", function () {
|
||||
return date_fns;
|
||||
});
|
||||
|
||||
var date_fns_locale = require("date-fns/locale");
|
||||
_define("date-fns/locale", function () {
|
||||
return date_fns_locale;
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
define(["events", "globalize", "dom", "datetime", "userSettings", "serverNotifications", "connectionManager", "emby-button", "listViewStyle"], function (events, globalize, dom, datetime, userSettings, serverNotifications, connectionManager) {
|
||||
define(["events", "globalize", "dom", "date-fns", "dfnshelper", "userSettings", "serverNotifications", "connectionManager", "emby-button", "listViewStyle"], function (events, globalize, dom, datefns, dfnshelper, userSettings, serverNotifications, connectionManager) {
|
||||
"use strict";
|
||||
|
||||
function getEntryHtml(entry, apiClient) {
|
||||
|
@ -16,7 +16,7 @@ define(["events", "globalize", "dom", "datetime", "userSettings", "serverNotific
|
|||
html += '<i class="listItemIcon material-icons" style="width:2em!important;height:2em!important;padding:0;color:transparent;background-color:' + color + ";background-image:url('" + apiClient.getUserImageUrl(entry.UserId, {
|
||||
type: "Primary",
|
||||
tag: entry.UserPrimaryImageTag
|
||||
}) + "');background-repeat:no-repeat;background-position:center center;background-size: cover;\">dvr</i>"
|
||||
}) + "');background-repeat:no-repeat;background-position:center center;background-size: cover;\">dvr</i>";
|
||||
} else {
|
||||
html += '<i class="listItemIcon material-icons" style="background-color:' + color + '">' + icon + '</i>';
|
||||
}
|
||||
|
@ -26,8 +26,7 @@ define(["events", "globalize", "dom", "datetime", "userSettings", "serverNotific
|
|||
html += entry.Name;
|
||||
html += "</div>";
|
||||
html += '<div class="listItemBodyText secondary">';
|
||||
var date = datetime.parseISO8601Date(entry.Date, true);
|
||||
html += datetime.toLocaleString(date).toLowerCase();
|
||||
html += datefns.formatRelative(Date.parse(entry.Date), Date.parse(new Date()), { locale: dfnshelper.getLocale() });
|
||||
html += "</div>";
|
||||
html += '<div class="listItemBodyText secondary listItemBodyText-nowrap">';
|
||||
html += entry.ShortOverview || "";
|
||||
|
|
|
@ -511,9 +511,16 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM
|
|||
return baseRoute;
|
||||
}
|
||||
|
||||
var popstateOccurred = false;
|
||||
window.addEventListener('popstate', function () {
|
||||
popstateOccurred = true;
|
||||
});
|
||||
|
||||
function getHandler(route) {
|
||||
return function (ctx, next) {
|
||||
ctx.isBack = popstateOccurred;
|
||||
handleRoute(ctx, next, route);
|
||||
popstateOccurred = false;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -570,8 +577,8 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM
|
|||
|
||||
function showDirect(path) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
resolveOnNextShow = resolve, page.show(baseUrl()+path)
|
||||
})
|
||||
resolveOnNextShow = resolve, page.show(baseUrl()+path);
|
||||
});
|
||||
}
|
||||
|
||||
function show(path, options) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
define(["appSettings", "browser", "events", "htmlMediaHelper"], function (appSettings, browser, events, htmlMediaHelper) {
|
||||
define(["appSettings", "browser", "events", "htmlMediaHelper", "webSettings"], function (appSettings, browser, events, htmlMediaHelper, webSettings) {
|
||||
"use strict";
|
||||
|
||||
function getBaseProfileOptions(item) {
|
||||
|
@ -276,15 +276,17 @@ define(["appSettings", "browser", "events", "htmlMediaHelper"], function (appSet
|
|||
features.push("otherapppromotions");
|
||||
features.push("displaymode");
|
||||
features.push("targetblank");
|
||||
// allows users to connect to more than one server
|
||||
//features.push("multiserver");
|
||||
features.push("screensaver");
|
||||
|
||||
if (!browser.orsay && !browser.tizen && !browser.msie && (browser.firefox || browser.ps4 || browser.edge || supportsCue())) {
|
||||
webSettings.enableMultiServer().then(enabled => {
|
||||
if (enabled) features.push("multiserver");
|
||||
});
|
||||
|
||||
if (!browser.orsay && !browser.msie && (browser.firefox || browser.ps4 || browser.edge || supportsCue())) {
|
||||
features.push("subtitleappearancesettings");
|
||||
}
|
||||
|
||||
if (!browser.orsay && !browser.tizen) {
|
||||
if (!browser.orsay) {
|
||||
features.push("subtitleburnsettings");
|
||||
}
|
||||
|
||||
|
@ -381,7 +383,7 @@ define(["appSettings", "browser", "events", "htmlMediaHelper"], function (appSet
|
|||
return window.NativeShell.AppHost.getDefaultLayout();
|
||||
}
|
||||
|
||||
return getDefaultLayout()
|
||||
return getDefaultLayout();
|
||||
},
|
||||
getDeviceProfile: getDeviceProfile,
|
||||
init: function () {
|
||||
|
|
|
@ -1,22 +1,29 @@
|
|||
define(["focusManager", "layoutManager"], function (focusManager, layoutManager) {
|
||||
"use strict";
|
||||
/* eslint-disable indent */
|
||||
|
||||
/**
|
||||
* Module for performing auto-focus.
|
||||
* @module components/autoFocuser
|
||||
*/
|
||||
|
||||
import focusManager from "focusManager";
|
||||
import layoutManager from "layoutManager";
|
||||
|
||||
/**
|
||||
* Previously selected element.
|
||||
*/
|
||||
var activeElement;
|
||||
let activeElement;
|
||||
|
||||
/**
|
||||
* Returns true if AutoFocuser is enabled.
|
||||
* Returns _true_ if AutoFocuser is enabled.
|
||||
*/
|
||||
function isEnabled() {
|
||||
export function isEnabled() {
|
||||
return layoutManager.tv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start AutoFocuser
|
||||
* Start AutoFocuser.
|
||||
*/
|
||||
function enable() {
|
||||
export function enable() {
|
||||
if (!isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
@ -28,24 +35,19 @@ define(["focusManager", "layoutManager"], function (focusManager, layoutManager)
|
|||
console.debug("AutoFocuser enabled");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an array from some source.
|
||||
*/
|
||||
var arrayFrom = Array.prototype.from || function (src) {
|
||||
return Array.prototype.slice.call(src);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set focus on a suitable element, taking into account the previously selected.
|
||||
* @param {HTMLElement} [container] - Element to limit scope.
|
||||
* @returns {HTMLElement} Focused element.
|
||||
*/
|
||||
function autoFocus(container) {
|
||||
export function autoFocus(container) {
|
||||
if (!isEnabled()) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
container = container || document.body;
|
||||
|
||||
var candidates = [];
|
||||
let candidates = [];
|
||||
|
||||
if (activeElement) {
|
||||
// These elements are recreated
|
||||
|
@ -62,10 +64,10 @@ define(["focusManager", "layoutManager"], function (focusManager, layoutManager)
|
|||
candidates.push(activeElement);
|
||||
}
|
||||
|
||||
candidates = candidates.concat(arrayFrom(container.querySelectorAll(".btnResume")));
|
||||
candidates = candidates.concat(arrayFrom(container.querySelectorAll(".btnPlay")));
|
||||
candidates = candidates.concat(Array.from(container.querySelectorAll(".btnResume")));
|
||||
candidates = candidates.concat(Array.from(container.querySelectorAll(".btnPlay")));
|
||||
|
||||
var focusedElement;
|
||||
let focusedElement;
|
||||
|
||||
candidates.every(function (element) {
|
||||
if (focusManager.isCurrentlyFocusable(element)) {
|
||||
|
@ -79,7 +81,7 @@ define(["focusManager", "layoutManager"], function (focusManager, layoutManager)
|
|||
|
||||
if (!focusedElement) {
|
||||
// FIXME: Multiple itemsContainers
|
||||
var itemsContainer = container.querySelector(".itemsContainer");
|
||||
const itemsContainer = container.querySelector(".itemsContainer");
|
||||
|
||||
if (itemsContainer) {
|
||||
focusedElement = focusManager.autoFocus(itemsContainer);
|
||||
|
@ -93,9 +95,10 @@ define(["focusManager", "layoutManager"], function (focusManager, layoutManager)
|
|||
return focusedElement;
|
||||
}
|
||||
|
||||
return {
|
||||
isEnabled: isEnabled,
|
||||
enable: enable,
|
||||
autoFocus: autoFocus
|
||||
};
|
||||
});
|
||||
/* eslint-enable indent */
|
||||
|
||||
export default {
|
||||
isEnabled: isEnabled,
|
||||
enable: enable,
|
||||
autoFocus: autoFocus
|
||||
};
|
||||
|
|
|
@ -52,5 +52,5 @@ define(["connectionManager"], function (connectionManager) {
|
|||
currentSlideshow = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,18 +1,7 @@
|
|||
define(["dialogHelper", "loading", "connectionManager", "globalize", "actionsheet", "emby-input", "paper-icon-button-light", "emby-button", "listViewStyle", "material-icons", "formDialogStyle"], function (dialogHelper, loading, connectionManager, globalize, actionsheet) {
|
||||
define(["dom", "dialogHelper", "loading", "connectionManager", "globalize", "actionsheet", "emby-input", "paper-icon-button-light", "emby-button", "listViewStyle", "material-icons", "formDialogStyle"], function (dom, dialogHelper, loading, connectionManager, globalize, actionsheet) {
|
||||
"use strict";
|
||||
|
||||
return function (options) {
|
||||
function parentWithClass(elem, className) {
|
||||
while (!elem.classList || !elem.classList.contains(className)) {
|
||||
elem = elem.parentNode;
|
||||
if (!elem) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return elem;
|
||||
}
|
||||
|
||||
function mapChannel(button, channelId, providerChannelId) {
|
||||
loading.show();
|
||||
var providerId = options.providerId;
|
||||
|
@ -26,7 +15,7 @@ define(["dialogHelper", "loading", "connectionManager", "globalize", "actionshee
|
|||
},
|
||||
dataType: "json"
|
||||
}).then(function (mapping) {
|
||||
var listItem = parentWithClass(button, "listItem");
|
||||
var listItem = dom.parentWithClass(button, "listItem");
|
||||
button.setAttribute("data-providerid", mapping.ProviderChannelId);
|
||||
listItem.querySelector(".secondary").innerHTML = getMappingSecondaryName(mapping, currentMappingOptions.ProviderName);
|
||||
loading.hide();
|
||||
|
@ -34,7 +23,7 @@ define(["dialogHelper", "loading", "connectionManager", "globalize", "actionshee
|
|||
}
|
||||
|
||||
function onChannelsElementClick(e) {
|
||||
var btnMap = parentWithClass(e.target, "btnMap");
|
||||
var btnMap = dom.parentWithClass(e.target, "btnMap");
|
||||
|
||||
if (btnMap) {
|
||||
var channelId = btnMap.getAttribute("data-id");
|
||||
|
|
|
@ -188,9 +188,9 @@ define(['events'], function (events) {
|
|||
return apiClient.getEndpointInfo().then(function (endpoint) {
|
||||
if (endpoint.IsInNetwork) {
|
||||
return apiClient.getPublicSystemInfo().then(function (info) {
|
||||
var localAddress = info.LocalAddress
|
||||
var localAddress = info.LocalAddress;
|
||||
if (!localAddress) {
|
||||
console.debug("No valid local address returned, defaulting to external one")
|
||||
console.debug("No valid local address returned, defaulting to external one");
|
||||
localAddress = serverAddress;
|
||||
}
|
||||
addToCache(serverAddress, localAddress);
|
||||
|
|
|
@ -1,25 +1,12 @@
|
|||
define(['dialogHelper', 'loading', 'apphost', 'layoutManager', 'connectionManager', 'appRouter', 'globalize', 'emby-checkbox', 'emby-input', 'paper-icon-button-light', 'emby-select', 'material-icons', 'css!./../formdialog', 'emby-button', 'flexStyles'], function (dialogHelper, loading, appHost, layoutManager, connectionManager, appRouter, globalize) {
|
||||
define(['dom', 'dialogHelper', 'loading', 'apphost', 'layoutManager', 'connectionManager', 'appRouter', 'globalize', 'emby-checkbox', 'emby-input', 'paper-icon-button-light', 'emby-select', 'material-icons', 'css!./../formdialog', 'emby-button', 'flexStyles'], function (dom, dialogHelper, loading, appHost, layoutManager, connectionManager, appRouter, globalize) {
|
||||
'use strict';
|
||||
|
||||
var currentServerId;
|
||||
|
||||
function parentWithClass(elem, className) {
|
||||
|
||||
while (!elem.classList || !elem.classList.contains(className)) {
|
||||
elem = elem.parentNode;
|
||||
|
||||
if (!elem) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return elem;
|
||||
}
|
||||
|
||||
function onSubmit(e) {
|
||||
loading.show();
|
||||
|
||||
var panel = parentWithClass(this, 'dialog');
|
||||
var panel = dom.parentWithClass(this, 'dialog');
|
||||
|
||||
var collectionId = panel.querySelector('#selectCollectionToAddTo').value;
|
||||
|
||||
|
|
|
@ -7,11 +7,11 @@ define(['loading', 'dialogHelper', 'dom', 'listViewStyle', 'emby-input', 'paper-
|
|||
systemInfo = info;
|
||||
return info;
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function onDialogClosed() {
|
||||
loading.hide()
|
||||
loading.hide();
|
||||
}
|
||||
|
||||
function refreshDirectoryBrowser(page, path, fileOptions, updatePathOnError) {
|
||||
|
@ -24,7 +24,7 @@ define(['loading', 'dialogHelper', 'dom', 'listViewStyle', 'emby-input', 'paper-
|
|||
var promises = [];
|
||||
|
||||
if ("Network" === path) {
|
||||
promises.push(ApiClient.getNetworkDevices())
|
||||
promises.push(ApiClient.getNetworkDevices());
|
||||
} else {
|
||||
if (path) {
|
||||
promises.push(ApiClient.getDirectoryContents(path, fileOptions));
|
||||
|
@ -89,7 +89,7 @@ define(['loading', 'dialogHelper', 'dom', 'listViewStyle', 'emby-input', 'paper-
|
|||
var instruction = options.instruction ? options.instruction + "<br/><br/>" : "";
|
||||
html += '<div class="infoBanner" style="margin-bottom:1.5em;">';
|
||||
html += instruction;
|
||||
html += Globalize.translate("MessageDirectoryPickerInstruction").replace("{0}", "<b>\\\\server</b>").replace("{1}", "<b>\\\\192.168.1.101</b>");
|
||||
html += Globalize.translate("MessageDirectoryPickerInstruction", "<b>\\\\server</b>", "<b>\\\\192.168.1.101</b>");
|
||||
if ("bsd" === systemInfo.OperatingSystem.toLowerCase()) {
|
||||
html += "<br/>";
|
||||
html += "<br/>";
|
||||
|
@ -101,7 +101,7 @@ define(['loading', 'dialogHelper', 'dom', 'listViewStyle', 'emby-input', 'paper-
|
|||
html += Globalize.translate("MessageDirectoryPickerLinuxInstruction");
|
||||
html += "<br/>";
|
||||
}
|
||||
html += "</div>"
|
||||
html += "</div>";
|
||||
}
|
||||
html += '<form style="margin:auto;">';
|
||||
html += '<div class="inputContainer" style="display: flex; align-items: center;">';
|
||||
|
@ -144,13 +144,13 @@ define(['loading', 'dialogHelper', 'dom', 'listViewStyle', 'emby-input', 'paper-
|
|||
function alertText(text) {
|
||||
alertTextWithOptions({
|
||||
text: text
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function alertTextWithOptions(options) {
|
||||
require(["alert"], function(alert) {
|
||||
alert(options)
|
||||
})
|
||||
alert(options);
|
||||
});
|
||||
}
|
||||
|
||||
function validatePath(path, validateWriteable, apiClient) {
|
||||
|
@ -163,21 +163,20 @@ define(['loading', 'dialogHelper', 'dom', 'listViewStyle', 'emby-input', 'paper-
|
|||
}
|
||||
}).catch(function(response) {
|
||||
if (response) {
|
||||
// TODO All alerts (across the project), should use Globalize.translate()
|
||||
if (response.status === 404) {
|
||||
alertText("The path could not be found. Please ensure the path is valid and try again.");
|
||||
alertText(Globalize.translate("PathNotFound"));
|
||||
return Promise.reject();
|
||||
}
|
||||
if (response.status === 500) {
|
||||
if (validateWriteable) {
|
||||
alertText("Jellyfin Server requires write access to this folder. Please ensure write access and try again.");
|
||||
alertText(Globalize.translate("WriteAccessRequired"));
|
||||
} else {
|
||||
alertText("The path could not be found. Please ensure the path is valid and try again.")
|
||||
alertText(Globalize.translate("PathNotFound"));
|
||||
}
|
||||
return Promise.reject()
|
||||
return Promise.reject();
|
||||
}
|
||||
}
|
||||
return Promise.resolve()
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -189,7 +188,7 @@ define(['loading', 'dialogHelper', 'dom', 'listViewStyle', 'emby-input', 'paper-
|
|||
if (lnkPath.classList.contains("lnkFile")) {
|
||||
content.querySelector("#txtDirectoryPickerPath").value = path;
|
||||
} else {
|
||||
refreshDirectoryBrowser(content, path, fileOptions, true)
|
||||
refreshDirectoryBrowser(content, path, fileOptions, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -276,7 +275,7 @@ define(['loading', 'dialogHelper', 'dom', 'listViewStyle', 'emby-input', 'paper-
|
|||
dlg.addEventListener("close", onDialogClosed);
|
||||
dialogHelper.open(dlg);
|
||||
dlg.querySelector(".btnCloseDialog").addEventListener("click", function() {
|
||||
dialogHelper.close(dlg)
|
||||
dialogHelper.close(dlg);
|
||||
});
|
||||
currentDialog = dlg;
|
||||
dlg.querySelector("#txtDirectoryPickerPath").value = initialPath;
|
||||
|
@ -294,9 +293,9 @@ define(['loading', 'dialogHelper', 'dom', 'listViewStyle', 'emby-input', 'paper-
|
|||
if (currentDialog) {
|
||||
dialogHelper.close(currentDialog);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var systemInfo;
|
||||
return directoryBrowser
|
||||
return directoryBrowser;
|
||||
});
|
||||
|
|
|
@ -63,7 +63,7 @@
|
|||
|
||||
<div class="selectContainer fldDateTimeLocale hide">
|
||||
<select is="emby-select" class="selectDateTimeLocale" label="${LabelDateTimeLocale}">
|
||||
<option value="">${AutoBasedOnLanguageSetting}</option>
|
||||
<option value="">${Auto}</option>
|
||||
<option value="ar">Arabic</option>
|
||||
<option value="be-BY">Belarusian (Belarus)</option>
|
||||
<option value="bg-BG">Bulgarian (Bulgaria)</option>
|
||||
|
|
|
@ -1,8 +1,18 @@
|
|||
define([], function () {
|
||||
'use strict';
|
||||
/* eslint-disable indent */
|
||||
|
||||
function parentWithAttribute(elem, name, value) {
|
||||
/**
|
||||
* Useful DOM utilities.
|
||||
* @module components/dom
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns parent of element with specified attribute value.
|
||||
* @param {HTMLElement} elem - Element whose parent need to find.
|
||||
* @param {string} name - Attribute name.
|
||||
* @param {mixed} value - Attribute value.
|
||||
* @returns {HTMLElement} Parent with specified attribute value.
|
||||
*/
|
||||
export function parentWithAttribute(elem, name, value) {
|
||||
while ((value ? elem.getAttribute(name) !== value : !elem.getAttribute(name))) {
|
||||
elem = elem.parentNode;
|
||||
|
||||
|
@ -14,8 +24,13 @@ define([], function () {
|
|||
return elem;
|
||||
}
|
||||
|
||||
function parentWithTag(elem, tagNames) {
|
||||
|
||||
/**
|
||||
* Returns parent of element with one of specified tag names.
|
||||
* @param {HTMLElement} elem - Element whose parent need to find.
|
||||
* @param {(string|Array)} tagNames - Tag name or array of tag names.
|
||||
* @returns {HTMLElement} Parent with one of specified tag names.
|
||||
*/
|
||||
export function parentWithTag(elem, tagNames) {
|
||||
// accept both string and array passed in
|
||||
if (!Array.isArray(tagNames)) {
|
||||
tagNames = [tagNames];
|
||||
|
@ -32,9 +47,14 @@ define([], function () {
|
|||
return elem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns _true_ if class list contains one of specified names.
|
||||
* @param {DOMTokenList} classList - Class list.
|
||||
* @param {Array} classNames - Array of class names.
|
||||
* @returns {boolean} _true_ if class list contains one of specified names.
|
||||
*/
|
||||
function containsAnyClass(classList, classNames) {
|
||||
|
||||
for (var i = 0, length = classNames.length; i < length; i++) {
|
||||
for (let i = 0, length = classNames.length; i < length; i++) {
|
||||
if (classList.contains(classNames[i])) {
|
||||
return true;
|
||||
}
|
||||
|
@ -42,8 +62,13 @@ define([], function () {
|
|||
return false;
|
||||
}
|
||||
|
||||
function parentWithClass(elem, classNames) {
|
||||
|
||||
/**
|
||||
* Returns parent of element with one of specified class names.
|
||||
* @param {HTMLElement} elem - Element whose parent need to find.
|
||||
* @param {(string|Array)} classNames - Class name or array of class names.
|
||||
* @returns {HTMLElement} Parent with one of specified class names.
|
||||
*/
|
||||
export function parentWithClass(elem, classNames) {
|
||||
// accept both string and array passed in
|
||||
if (!Array.isArray(classNames)) {
|
||||
classNames = [classNames];
|
||||
|
@ -60,9 +85,9 @@ define([], function () {
|
|||
return elem;
|
||||
}
|
||||
|
||||
var supportsCaptureOption = false;
|
||||
let supportsCaptureOption = false;
|
||||
try {
|
||||
var opts = Object.defineProperty({}, 'capture', {
|
||||
const opts = Object.defineProperty({}, 'capture', {
|
||||
// eslint-disable-next-line getter-return
|
||||
get: function () {
|
||||
supportsCaptureOption = true;
|
||||
|
@ -73,29 +98,58 @@ define([], function () {
|
|||
console.debug('error checking capture support');
|
||||
}
|
||||
|
||||
function addEventListenerWithOptions(target, type, handler, options) {
|
||||
var optionsOrCapture = options;
|
||||
/**
|
||||
* Adds event listener to specified target.
|
||||
* @param {EventTarget} target - Event target.
|
||||
* @param {string} type - Event type.
|
||||
* @param {function} handler - Event handler.
|
||||
* @param {Object} [options] - Listener options.
|
||||
*/
|
||||
export function addEventListener(target, type, handler, options) {
|
||||
let optionsOrCapture = options || {};
|
||||
if (!supportsCaptureOption) {
|
||||
optionsOrCapture = options.capture;
|
||||
optionsOrCapture = optionsOrCapture.capture;
|
||||
}
|
||||
target.addEventListener(type, handler, optionsOrCapture);
|
||||
}
|
||||
|
||||
function removeEventListenerWithOptions(target, type, handler, options) {
|
||||
var optionsOrCapture = options;
|
||||
/**
|
||||
* Removes event listener from specified target.
|
||||
* @param {EventTarget} target - Event target.
|
||||
* @param {string} type - Event type.
|
||||
* @param {function} handler - Event handler.
|
||||
* @param {Object} [options] - Listener options.
|
||||
*/
|
||||
export function removeEventListener(target, type, handler, options) {
|
||||
let optionsOrCapture = options || {};
|
||||
if (!supportsCaptureOption) {
|
||||
optionsOrCapture = options.capture;
|
||||
optionsOrCapture = optionsOrCapture.capture;
|
||||
}
|
||||
target.removeEventListener(type, handler, optionsOrCapture);
|
||||
}
|
||||
|
||||
var windowSize;
|
||||
var windowSizeEventsBound;
|
||||
/**
|
||||
* Cached window size.
|
||||
*/
|
||||
let windowSize;
|
||||
|
||||
/**
|
||||
* Flag of event listener bound.
|
||||
*/
|
||||
let windowSizeEventsBound;
|
||||
|
||||
/**
|
||||
* Resets cached window size.
|
||||
*/
|
||||
function clearWindowSize() {
|
||||
windowSize = null;
|
||||
}
|
||||
|
||||
function getWindowSize() {
|
||||
/**
|
||||
* Returns window size.
|
||||
* @returns {Object} Window size.
|
||||
*/
|
||||
export function getWindowSize() {
|
||||
if (!windowSize) {
|
||||
windowSize = {
|
||||
innerHeight: window.innerHeight,
|
||||
|
@ -104,46 +158,60 @@ define([], function () {
|
|||
|
||||
if (!windowSizeEventsBound) {
|
||||
windowSizeEventsBound = true;
|
||||
addEventListenerWithOptions(window, "orientationchange", clearWindowSize, { passive: true });
|
||||
addEventListenerWithOptions(window, 'resize', clearWindowSize, { passive: true });
|
||||
addEventListener(window, "orientationchange", clearWindowSize, { passive: true });
|
||||
addEventListener(window, 'resize', clearWindowSize, { passive: true });
|
||||
}
|
||||
}
|
||||
|
||||
return windowSize;
|
||||
}
|
||||
|
||||
var standardWidths = [480, 720, 1280, 1440, 1920, 2560, 3840, 5120, 7680];
|
||||
function getScreenWidth() {
|
||||
var width = window.innerWidth;
|
||||
var height = window.innerHeight;
|
||||
/**
|
||||
* Standard screen widths.
|
||||
*/
|
||||
const standardWidths = [480, 720, 1280, 1440, 1920, 2560, 3840, 5120, 7680];
|
||||
|
||||
/**
|
||||
* Returns screen width.
|
||||
* @returns {number} Screen width.
|
||||
*/
|
||||
export function getScreenWidth() {
|
||||
let width = window.innerWidth;
|
||||
const height = window.innerHeight;
|
||||
|
||||
if (height > width) {
|
||||
width = height * (16.0 / 9.0);
|
||||
}
|
||||
|
||||
var closest = standardWidths.sort(function (a, b) {
|
||||
const closest = standardWidths.sort(function (a, b) {
|
||||
return Math.abs(width - a) - Math.abs(width - b);
|
||||
})[0];
|
||||
|
||||
return closest;
|
||||
}
|
||||
|
||||
var _animationEvent;
|
||||
function whichAnimationEvent() {
|
||||
/**
|
||||
* Name of animation end event.
|
||||
*/
|
||||
let _animationEvent;
|
||||
|
||||
/**
|
||||
* Returns name of animation end event.
|
||||
* @returns {string} Name of animation end event.
|
||||
*/
|
||||
export function whichAnimationEvent() {
|
||||
if (_animationEvent) {
|
||||
return _animationEvent;
|
||||
}
|
||||
|
||||
var t;
|
||||
var el = document.createElement("div");
|
||||
var animations = {
|
||||
const el = document.createElement("div");
|
||||
const animations = {
|
||||
"animation": "animationend",
|
||||
"OAnimation": "oAnimationEnd",
|
||||
"MozAnimation": "animationend",
|
||||
"WebkitAnimation": "webkitAnimationEnd"
|
||||
};
|
||||
for (t in animations) {
|
||||
for (let t in animations) {
|
||||
if (el.style[t] !== undefined) {
|
||||
_animationEvent = animations[t];
|
||||
return animations[t];
|
||||
|
@ -154,26 +222,36 @@ define([], function () {
|
|||
return _animationEvent;
|
||||
}
|
||||
|
||||
function whichAnimationCancelEvent() {
|
||||
|
||||
/**
|
||||
* Returns name of animation cancel event.
|
||||
* @returns {string} Name of animation cancel event.
|
||||
*/
|
||||
export function whichAnimationCancelEvent() {
|
||||
return whichAnimationEvent().replace('animationend', 'animationcancel').replace('AnimationEnd', 'AnimationCancel');
|
||||
}
|
||||
|
||||
var _transitionEvent;
|
||||
function whichTransitionEvent() {
|
||||
/**
|
||||
* Name of transition end event.
|
||||
*/
|
||||
let _transitionEvent;
|
||||
|
||||
/**
|
||||
* Returns name of transition end event.
|
||||
* @returns {string} Name of transition end event.
|
||||
*/
|
||||
export function whichTransitionEvent() {
|
||||
if (_transitionEvent) {
|
||||
return _transitionEvent;
|
||||
}
|
||||
|
||||
var t;
|
||||
var el = document.createElement("div");
|
||||
var transitions = {
|
||||
const el = document.createElement("div");
|
||||
const transitions = {
|
||||
"transition": "transitionend",
|
||||
"OTransition": "oTransitionEnd",
|
||||
"MozTransition": "transitionend",
|
||||
"WebkitTransition": "webkitTransitionEnd"
|
||||
};
|
||||
for (t in transitions) {
|
||||
for (let t in transitions) {
|
||||
if (el.style[t] !== undefined) {
|
||||
_transitionEvent = transitions[t];
|
||||
return transitions[t];
|
||||
|
@ -184,16 +262,17 @@ define([], function () {
|
|||
return _transitionEvent;
|
||||
}
|
||||
|
||||
return {
|
||||
parentWithAttribute: parentWithAttribute,
|
||||
parentWithClass: parentWithClass,
|
||||
parentWithTag: parentWithTag,
|
||||
addEventListener: addEventListenerWithOptions,
|
||||
removeEventListener: removeEventListenerWithOptions,
|
||||
getWindowSize: getWindowSize,
|
||||
getScreenWidth: getScreenWidth,
|
||||
whichTransitionEvent: whichTransitionEvent,
|
||||
whichAnimationEvent: whichAnimationEvent,
|
||||
whichAnimationCancelEvent: whichAnimationCancelEvent
|
||||
};
|
||||
});
|
||||
/* eslint-enable indent */
|
||||
|
||||
export default {
|
||||
parentWithAttribute: parentWithAttribute,
|
||||
parentWithClass: parentWithClass,
|
||||
parentWithTag: parentWithTag,
|
||||
addEventListener: addEventListener,
|
||||
removeEventListener: removeEventListener,
|
||||
getWindowSize: getWindowSize,
|
||||
getScreenWidth: getScreenWidth,
|
||||
whichTransitionEvent: whichTransitionEvent,
|
||||
whichAnimationEvent: whichAnimationEvent,
|
||||
whichAnimationCancelEvent: whichAnimationCancelEvent
|
||||
};
|
||||
|
|
|
@ -1,18 +1,14 @@
|
|||
define(['multi-download'], function (multiDownload) {
|
||||
'use strict';
|
||||
import multiDownload from "multi-download";
|
||||
|
||||
return {
|
||||
download: function (items) {
|
||||
export function download(items) {
|
||||
|
||||
if (window.NativeShell) {
|
||||
items.map(function (item) {
|
||||
window.NativeShell.downloadFile(item.url);
|
||||
});
|
||||
} else {
|
||||
multiDownload(items.map(function (item) {
|
||||
return item.url;
|
||||
}));
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
if (window.NativeShell) {
|
||||
items.map(function (item) {
|
||||
window.NativeShell.downloadFile(item.url);
|
||||
});
|
||||
} else {
|
||||
multiDownload(items.map(function (item) {
|
||||
return item.url;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,13 @@
|
|||
define([], function () {
|
||||
'use strict';
|
||||
export function fileExists(path) {
|
||||
if (window.NativeShell && window.NativeShell.FileSystem) {
|
||||
return window.NativeShell.FileSystem.fileExists(path);
|
||||
}
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
return {
|
||||
fileExists: function (path) {
|
||||
if (window.NativeShell && window.NativeShell.FileSystem) {
|
||||
return window.NativeShell.FileSystem.fileExists(path);
|
||||
}
|
||||
return Promise.reject();
|
||||
},
|
||||
directoryExists: function (path) {
|
||||
if (window.NativeShell && window.NativeShell.FileSystem) {
|
||||
return window.NativeShell.FileSystem.directoryExists(path);
|
||||
}
|
||||
return Promise.reject();
|
||||
}
|
||||
};
|
||||
});
|
||||
export function directoryExists(path) {
|
||||
if (window.NativeShell && window.NativeShell.FileSystem) {
|
||||
return window.NativeShell.FileSystem.directoryExists(path);
|
||||
}
|
||||
return Promise.reject();
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
define(["dialogHelper", "globalize", "connectionManager", "events", "browser", "require", "emby-checkbox", "emby-collapse", "css!./style"], function (dialogHelper, globalize, connectionManager, events, browser, require) {
|
||||
define(["dom", "dialogHelper", "globalize", "connectionManager", "events", "browser", "require", "emby-checkbox", "emby-collapse", "css!./style"], function (dom, dialogHelper, globalize, connectionManager, events, browser, require) {
|
||||
"use strict";
|
||||
|
||||
function renderOptions(context, selector, cssClass, items, isCheckedFn) {
|
||||
|
@ -106,16 +106,6 @@ define(["dialogHelper", "globalize", "connectionManager", "events", "browser", "
|
|||
events.trigger(instance, "filterchange");
|
||||
}
|
||||
|
||||
function parentWithClass(elem, className) {
|
||||
while (!elem.classList || !elem.classList.contains(className)) {
|
||||
elem = elem.parentNode;
|
||||
if (!elem) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return elem;
|
||||
}
|
||||
|
||||
function setVisibility(context, options) {
|
||||
if (options.mode == "livetvchannels" || options.mode == "albums" || options.mode == "artists" || options.mode == "albumartists" || options.mode == "songs") {
|
||||
hideByClass(context, "videoStandard");
|
||||
|
@ -320,7 +310,7 @@ define(["dialogHelper", "globalize", "connectionManager", "events", "browser", "
|
|||
triggerChange(self);
|
||||
});
|
||||
context.addEventListener("change", function (e) {
|
||||
var chkGenreFilter = parentWithClass(e.target, "chkGenreFilter");
|
||||
var chkGenreFilter = dom.parentWithClass(e.target, "chkGenreFilter");
|
||||
if (chkGenreFilter) {
|
||||
var filterName = chkGenreFilter.getAttribute("data-filter");
|
||||
var filters = query.Genres || "";
|
||||
|
@ -334,7 +324,7 @@ define(["dialogHelper", "globalize", "connectionManager", "events", "browser", "
|
|||
triggerChange(self);
|
||||
return;
|
||||
}
|
||||
var chkTagFilter = parentWithClass(e.target, "chkTagFilter");
|
||||
var chkTagFilter = dom.parentWithClass(e.target, "chkTagFilter");
|
||||
if (chkTagFilter) {
|
||||
var filterName = chkTagFilter.getAttribute("data-filter");
|
||||
var filters = query.Tags || "";
|
||||
|
@ -348,7 +338,7 @@ define(["dialogHelper", "globalize", "connectionManager", "events", "browser", "
|
|||
triggerChange(self);
|
||||
return;
|
||||
}
|
||||
var chkYearFilter = parentWithClass(e.target, "chkYearFilter");
|
||||
var chkYearFilter = dom.parentWithClass(e.target, "chkYearFilter");
|
||||
if (chkYearFilter) {
|
||||
var filterName = chkYearFilter.getAttribute("data-filter");
|
||||
var filters = query.Years || "";
|
||||
|
@ -362,7 +352,7 @@ define(["dialogHelper", "globalize", "connectionManager", "events", "browser", "
|
|||
triggerChange(self);
|
||||
return;
|
||||
}
|
||||
var chkOfficialRatingFilter = parentWithClass(e.target, "chkOfficialRatingFilter");
|
||||
var chkOfficialRatingFilter = dom.parentWithClass(e.target, "chkOfficialRatingFilter");
|
||||
if (chkOfficialRatingFilter) {
|
||||
var filterName = chkOfficialRatingFilter.getAttribute("data-filter");
|
||||
var filters = query.OfficialRatings || "";
|
||||
|
|
|
@ -64,18 +64,18 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
|
|||
} else {
|
||||
var noLibDescription;
|
||||
if (user['Policy'] && user['Policy']['IsAdministrator']) {
|
||||
noLibDescription = Globalize.translate("NoCreatedLibraries", '<a id="button-createLibrary" class="button-link">', '</a>')
|
||||
noLibDescription = Globalize.translate("NoCreatedLibraries", '<a id="button-createLibrary" class="button-link">', '</a>');
|
||||
} else {
|
||||
noLibDescription = Globalize.translate("AskAdminToCreateLibrary");
|
||||
}
|
||||
|
||||
html += '<div class="centerMessage padded-left padded-right">';
|
||||
html += '<h2>' + Globalize.translate("MessageNothingHere") + '</h2>';
|
||||
html += '<p>' + noLibDescription + '</p>'
|
||||
html += '<p>' + noLibDescription + '</p>';
|
||||
html += '</div>';
|
||||
elem.innerHTML = html;
|
||||
|
||||
var createNowLink = elem.querySelector("#button-createLibrary")
|
||||
var createNowLink = elem.querySelector("#button-createLibrary");
|
||||
if (createNowLink) {
|
||||
createNowLink.addEventListener("click", function () {
|
||||
Dashboard.navigate("library.html");
|
||||
|
@ -640,7 +640,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
|
|||
|
||||
if (enableScrollX()) {
|
||||
html += '<div is="emby-scroller" class="padded-top-focusscale padded-bottom-focusscale" data-centerfocus="true">';
|
||||
html += '<div is="emby-itemscontainer" class="itemsContainer scrollSlider focuscontainer-x">'
|
||||
html += '<div is="emby-itemscontainer" class="itemsContainer scrollSlider focuscontainer-x">';
|
||||
} else {
|
||||
html += '<div is="emby-itemscontainer" class="itemsContainer padded-left padded-right vertical-wrap focuscontainer-x">';
|
||||
}
|
||||
|
@ -714,7 +714,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
|
|||
|
||||
if (enableScrollX()) {
|
||||
html += '<div is="emby-scroller" class="padded-top-focusscale padded-bottom-focusscale" data-centerfocus="true">';
|
||||
html += '<div is="emby-itemscontainer" class="itemsContainer scrollSlider focuscontainer-x" data-monitor="videoplayback,markplayed">'
|
||||
html += '<div is="emby-itemscontainer" class="itemsContainer scrollSlider focuscontainer-x" data-monitor="videoplayback,markplayed">';
|
||||
} else {
|
||||
html += '<div is="emby-itemscontainer" class="itemsContainer padded-left padded-right vertical-wrap focuscontainer-x" data-monitor="videoplayback,markplayed">';
|
||||
}
|
||||
|
@ -786,7 +786,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
|
|||
|
||||
if (enableScrollX()) {
|
||||
html += '<div is="emby-scroller" class="padded-top-focusscale padded-bottom-focusscale" data-centerfocus="true">';
|
||||
html += '<div is="emby-itemscontainer" class="itemsContainer scrollSlider focuscontainer-x">'
|
||||
html += '<div is="emby-itemscontainer" class="itemsContainer scrollSlider focuscontainer-x">';
|
||||
} else {
|
||||
html += '<div is="emby-itemscontainer" class="itemsContainer padded-left padded-right vertical-wrap focuscontainer-x">';
|
||||
}
|
||||
|
|
|
@ -171,13 +171,29 @@ define(['appSettings', 'browser', 'events'], function (appSettings, browser, eve
|
|||
|
||||
// Appending #t=xxx to the query string doesn't seem to work with HLS
|
||||
// For plain video files, not all browsers support it either
|
||||
var delay = browser.safari ? 2500 : 0;
|
||||
if (delay) {
|
||||
setTimeout(function () {
|
||||
setCurrentTimeIfNeeded(element, seconds);
|
||||
}, delay);
|
||||
} else {
|
||||
|
||||
if (element.duration >= seconds) {
|
||||
// media is ready, seek immediately
|
||||
setCurrentTimeIfNeeded(element, seconds);
|
||||
} else {
|
||||
// update video player position when media is ready to be sought
|
||||
var events = ["durationchange", "loadeddata", "play", "loadedmetadata"];
|
||||
var onMediaChange = function(e) {
|
||||
if (element.currentTime === 0 && element.duration >= seconds) {
|
||||
// seek only when video position is exactly zero,
|
||||
// as this is true only if video hasn't started yet or
|
||||
// user rewound to the very beginning
|
||||
// (but rewinding cannot happen as the first event with media of non-empty duration)
|
||||
console.debug(`seeking to ${seconds} on ${e.type} event`);
|
||||
setCurrentTimeIfNeeded(element, seconds);
|
||||
events.map(function(name) {
|
||||
element.removeEventListener(name, onMediaChange);
|
||||
});
|
||||
}
|
||||
};
|
||||
events.map(function (name) {
|
||||
element.addEventListener(name, onMediaChange);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -80,7 +80,6 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa
|
|||
if (track) {
|
||||
var format = (track.Codec || '').toLowerCase();
|
||||
if (format === 'ssa' || format === 'ass') {
|
||||
// libjass is needed here
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -117,8 +116,9 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa
|
|||
});
|
||||
}
|
||||
|
||||
function normalizeTrackEventText(text) {
|
||||
return text.replace(/\\N/gi, '\n');
|
||||
function normalizeTrackEventText(text, useHtml) {
|
||||
var result = text.replace(/\\N/gi, '\n').replace(/\r/gi, '');
|
||||
return useHtml ? result.replace(/\n/gi, '<br>') : result;
|
||||
}
|
||||
|
||||
function setTracks(elem, tracks, item, mediaSource) {
|
||||
|
@ -568,19 +568,19 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa
|
|||
self.resetSubtitleOffset = function() {
|
||||
currentTrackOffset = 0;
|
||||
showTrackOffset = false;
|
||||
}
|
||||
};
|
||||
|
||||
self.enableShowingSubtitleOffset = function() {
|
||||
showTrackOffset = true;
|
||||
}
|
||||
};
|
||||
|
||||
self.disableShowingSubtitleOffset = function() {
|
||||
showTrackOffset = false;
|
||||
}
|
||||
};
|
||||
|
||||
self.isShowingSubtitleOffsetEnabled = function() {
|
||||
return showTrackOffset;
|
||||
}
|
||||
};
|
||||
|
||||
function getTextTrack() {
|
||||
var videoElement = self._mediaElement;
|
||||
|
@ -652,7 +652,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa
|
|||
|
||||
self.getSubtitleOffset = function() {
|
||||
return currentTrackOffset;
|
||||
}
|
||||
};
|
||||
|
||||
function isAudioStreamSupported(stream, deviceProfile) {
|
||||
|
||||
|
@ -1020,7 +1020,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa
|
|||
xhr.onerror = function (e) {
|
||||
reject(e);
|
||||
decrementFetchQueue();
|
||||
}
|
||||
};
|
||||
|
||||
xhr.send();
|
||||
});
|
||||
|
@ -1047,101 +1047,38 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa
|
|||
lastCustomTrackMs = 0;
|
||||
}
|
||||
|
||||
function renderWithSubtitlesOctopus(videoElement, track, item) {
|
||||
function renderSsaAss(videoElement, track, item) {
|
||||
var attachments = self._currentPlayOptions.mediaSource.MediaAttachments || [];
|
||||
var apiClient = connectionManager.getApiClient(item);
|
||||
var options = {
|
||||
video: videoElement,
|
||||
subUrl: getTextTrackUrl(track, item),
|
||||
fonts: attachments.map(function (i) {
|
||||
return i.DeliveryUrl;
|
||||
return apiClient.getUrl(i.DeliveryUrl);
|
||||
}),
|
||||
workerUrl: appRouter.baseUrl() + "/libraries/subtitles-octopus-worker.js",
|
||||
legacyWorkerUrl: appRouter.baseUrl() + "/libraries/subtitles-octopus-worker-legacy.js",
|
||||
onError: function() {
|
||||
htmlMediaHelper.onErrorInternal(self, 'mediadecodeerror');
|
||||
}
|
||||
},
|
||||
|
||||
// new octopus options; override all, even defaults
|
||||
renderMode: 'blend',
|
||||
dropAllAnimations: false,
|
||||
libassMemoryLimit: 40,
|
||||
libassGlyphLimit: 40,
|
||||
targetFps: 24,
|
||||
prescaleTradeoff: 0.8,
|
||||
softHeightLimit: 1080,
|
||||
hardHeightLimit: 2160,
|
||||
resizeVariation: 0.2,
|
||||
renderAhead: 90
|
||||
};
|
||||
require(['JavascriptSubtitlesOctopus'], function(SubtitlesOctopus) {
|
||||
currentSubtitlesOctopus = new SubtitlesOctopus(options);
|
||||
});
|
||||
}
|
||||
|
||||
function renderWithLibjass(videoElement, track, item) {
|
||||
|
||||
var rendererSettings = {};
|
||||
|
||||
if (browser.ps4) {
|
||||
// Text outlines are not rendering very well
|
||||
rendererSettings.enableSvg = false;
|
||||
} else if (browser.edge || browser.msie) {
|
||||
// svg not rendering at all
|
||||
rendererSettings.enableSvg = false;
|
||||
}
|
||||
|
||||
// probably safer to just disable everywhere
|
||||
rendererSettings.enableSvg = false;
|
||||
|
||||
require(['libjass', 'ResizeObserver'], function (libjass, ResizeObserver) {
|
||||
|
||||
libjass.ASS.fromUrl(getTextTrackUrl(track, item)).then(function (ass) {
|
||||
|
||||
var clock = new libjass.renderers.ManualClock();
|
||||
currentClock = clock;
|
||||
|
||||
// Create a DefaultRenderer using the video element and the ASS object
|
||||
var renderer = new libjass.renderers.WebRenderer(ass, clock, videoElement.parentNode, rendererSettings);
|
||||
|
||||
currentAssRenderer = renderer;
|
||||
|
||||
renderer.addEventListener("ready", function () {
|
||||
try {
|
||||
renderer.resize(videoElement.offsetWidth, videoElement.offsetHeight, 0, 0);
|
||||
|
||||
if (!self._resizeObserver) {
|
||||
self._resizeObserver = new ResizeObserver(onVideoResize, {});
|
||||
self._resizeObserver.observe(videoElement);
|
||||
}
|
||||
//clock.pause();
|
||||
} catch (ex) {
|
||||
//alert(ex);
|
||||
}
|
||||
});
|
||||
}, function () {
|
||||
htmlMediaHelper.onErrorInternal(self, 'mediadecodeerror');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function renderSsaAss(videoElement, track, item) {
|
||||
if (supportsCanvas() && supportsWebWorkers()) {
|
||||
console.debug('rendering subtitles with SubtitlesOctopus');
|
||||
renderWithSubtitlesOctopus(videoElement, track, item);
|
||||
} else {
|
||||
console.debug('rendering subtitles with libjass');
|
||||
renderWithLibjass(videoElement, track, item);
|
||||
}
|
||||
}
|
||||
|
||||
function onVideoResize() {
|
||||
if (browser.iOS) {
|
||||
// the new sizes will be delayed for about 500ms with wkwebview
|
||||
setTimeout(resetVideoRendererSize, 500);
|
||||
} else {
|
||||
resetVideoRendererSize();
|
||||
}
|
||||
}
|
||||
|
||||
function resetVideoRendererSize() {
|
||||
var renderer = currentAssRenderer;
|
||||
if (renderer) {
|
||||
var videoElement = self._mediaElement;
|
||||
var width = videoElement.offsetWidth;
|
||||
var height = videoElement.offsetHeight;
|
||||
console.debug('videoElement resized: ' + width + 'x' + height);
|
||||
renderer.resize(width, height, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
function requiresCustomSubtitlesElement() {
|
||||
|
||||
// after a system update, ps4 isn't showing anything when creating a track element dynamically
|
||||
|
@ -1231,7 +1168,6 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa
|
|||
if (!itemHelper.isLocalItem(item) || track.IsExternal) {
|
||||
var format = (track.Codec || '').toLowerCase();
|
||||
if (format === 'ssa' || format === 'ass') {
|
||||
// libjass is needed here
|
||||
renderSsaAss(videoElement, track, item);
|
||||
return;
|
||||
}
|
||||
|
@ -1274,7 +1210,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa
|
|||
data.TrackEvents.forEach(function (trackEvent) {
|
||||
|
||||
var trackCueObject = window.VTTCue || window.TextTrackCue;
|
||||
var cue = new trackCueObject(trackEvent.StartPositionTicks / 10000000, trackEvent.EndPositionTicks / 10000000, normalizeTrackEventText(trackEvent.Text));
|
||||
var cue = new trackCueObject(trackEvent.StartPositionTicks / 10000000, trackEvent.EndPositionTicks / 10000000, normalizeTrackEventText(trackEvent.Text, false));
|
||||
|
||||
trackElement.addCue(cue);
|
||||
});
|
||||
|
@ -1315,8 +1251,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa
|
|||
}
|
||||
|
||||
if (selectedTrackEvent && selectedTrackEvent.Text) {
|
||||
|
||||
subtitleTextElement.innerHTML = normalizeTrackEventText(selectedTrackEvent.Text);
|
||||
subtitleTextElement.innerHTML = normalizeTrackEventText(selectedTrackEvent.Text, true);
|
||||
subtitleTextElement.classList.remove('hide');
|
||||
|
||||
} else {
|
||||
|
@ -1493,11 +1428,11 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa
|
|||
}
|
||||
|
||||
if (browser.safari || browser.iOS || browser.iPad) {
|
||||
list.push('AirPlay')
|
||||
list.push('AirPlay');
|
||||
}
|
||||
|
||||
list.push('SetBrightness');
|
||||
list.push("SetAspectRatio")
|
||||
list.push("SetAspectRatio");
|
||||
|
||||
return list;
|
||||
}
|
||||
|
@ -1620,11 +1555,11 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa
|
|||
if (video) {
|
||||
if (isEnabled) {
|
||||
video.requestAirPlay().catch(function(err) {
|
||||
console.error("Error requesting AirPlay", err)
|
||||
console.error("Error requesting AirPlay", err);
|
||||
});
|
||||
} else {
|
||||
document.exitAirPLay().catch(function(err) {
|
||||
console.error("Error exiting AirPlay", err)
|
||||
console.error("Error exiting AirPlay", err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1757,12 +1692,12 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa
|
|||
var mediaElement = this._mediaElement;
|
||||
if (mediaElement) {
|
||||
if ("auto" === val) {
|
||||
mediaElement.style.removeProperty("object-fit")
|
||||
mediaElement.style.removeProperty("object-fit");
|
||||
} else {
|
||||
mediaElement.style["object-fit"] = val
|
||||
mediaElement.style["object-fit"] = val;
|
||||
}
|
||||
}
|
||||
this._currentAspectRatio = val
|
||||
this._currentAspectRatio = val;
|
||||
};
|
||||
|
||||
HtmlVideoPlayer.prototype.getAspectRatio = function () {
|
||||
|
@ -1779,7 +1714,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa
|
|||
}, {
|
||||
name: "Fill",
|
||||
id: "fill"
|
||||
}]
|
||||
}];
|
||||
};
|
||||
|
||||
HtmlVideoPlayer.prototype.togglePictureInPicture = function () {
|
||||
|
|
|
@ -1,74 +0,0 @@
|
|||
define(["datetime"], function (datetime) {
|
||||
"use strict";
|
||||
|
||||
function humaneDate(date_str) {
|
||||
var format;
|
||||
var time_formats = [
|
||||
[90, "a minute"],
|
||||
[3600, "minutes", 60],
|
||||
[5400, "an hour"],
|
||||
[86400, "hours", 3600],
|
||||
[129600, "a day"],
|
||||
[604800, "days", 86400],
|
||||
[907200, "a week"],
|
||||
[2628e3, "weeks", 604800],
|
||||
[3942e3, "a month"],
|
||||
[31536e3, "months", 2628e3],
|
||||
[47304e3, "a year"],
|
||||
[31536e5, "years", 31536e3]
|
||||
];
|
||||
var dt = new Date();
|
||||
var date = datetime.parseISO8601Date(date_str, true);
|
||||
var seconds = (dt - date) / 1000.0;
|
||||
var i = 0;
|
||||
|
||||
if (seconds < 0) {
|
||||
seconds = Math.abs(seconds);
|
||||
}
|
||||
// eslint-disable-next-line no-cond-assign
|
||||
for (; format = time_formats[i++];) {
|
||||
if (seconds < format[0]) {
|
||||
if (2 == format.length) {
|
||||
return format[1] + " ago";
|
||||
}
|
||||
|
||||
return Math.round(seconds / format[2]) + " " + format[1] + " ago";
|
||||
}
|
||||
}
|
||||
|
||||
if (seconds > 47304e5) {
|
||||
return Math.round(seconds / 47304e5) + " centuries ago";
|
||||
}
|
||||
|
||||
return date_str;
|
||||
}
|
||||
|
||||
function humaneElapsed(firstDateStr, secondDateStr) {
|
||||
// TODO replace this whole script with a library or something
|
||||
var dateOne = new Date(firstDateStr);
|
||||
var dateTwo = new Date(secondDateStr);
|
||||
var delta = (dateTwo.getTime() - dateOne.getTime()) / 1e3;
|
||||
var days = Math.floor(delta % 31536e3 / 86400);
|
||||
var hours = Math.floor(delta % 31536e3 % 86400 / 3600);
|
||||
var minutes = Math.floor(delta % 31536e3 % 86400 % 3600 / 60);
|
||||
var seconds = Math.round(delta % 31536e3 % 86400 % 3600 % 60);
|
||||
var elapsed = "";
|
||||
elapsed += 1 == days ? days + " day " : "";
|
||||
elapsed += days > 1 ? days + " days " : "";
|
||||
elapsed += 1 == hours ? hours + " hour " : "";
|
||||
elapsed += hours > 1 ? hours + " hours " : "";
|
||||
elapsed += 1 == minutes ? minutes + " minute " : "";
|
||||
elapsed += minutes > 1 ? minutes + " minutes " : "";
|
||||
elapsed += elapsed.length > 0 ? "and " : "";
|
||||
elapsed += 1 == seconds ? seconds + " second" : "";
|
||||
elapsed += 0 == seconds || seconds > 1 ? seconds + " seconds" : "";
|
||||
return elapsed;
|
||||
}
|
||||
|
||||
window.humaneDate = humaneDate;
|
||||
window.humaneElapsed = humaneElapsed;
|
||||
return {
|
||||
humaneDate: humaneDate,
|
||||
humaneElapsed: humaneElapsed
|
||||
};
|
||||
});
|
|
@ -1,4 +1,4 @@
|
|||
define(['loading', 'apphost', 'dialogHelper', 'connectionManager', 'imageLoader', 'browser', 'layoutManager', 'scrollHelper', 'globalize', 'require', 'emby-checkbox', 'paper-icon-button-light', 'emby-button', 'formDialogStyle', 'cardStyle'], function (loading, appHost, dialogHelper, connectionManager, imageLoader, browser, layoutManager, scrollHelper, globalize, require) {
|
||||
define(['dom', 'loading', 'apphost', 'dialogHelper', 'connectionManager', 'imageLoader', 'browser', 'layoutManager', 'scrollHelper', 'globalize', 'require', 'emby-checkbox', 'paper-icon-button-light', 'emby-button', 'formDialogStyle', 'cardStyle'], function (dom, loading, appHost, dialogHelper, connectionManager, imageLoader, browser, layoutManager, scrollHelper, globalize, require) {
|
||||
'use strict';
|
||||
|
||||
var enableFocusTransform = !browser.slow && !browser.edge;
|
||||
|
@ -109,7 +109,7 @@ define(['loading', 'apphost', 'dialogHelper', 'connectionManager', 'imageLoader'
|
|||
html += '<span style="margin-right: 10px;">';
|
||||
|
||||
var startAtDisplay = totalRecordCount ? startIndex + 1 : 0;
|
||||
html += startAtDisplay + '-' + recordsEnd + ' of ' + totalRecordCount;
|
||||
html += globalize.translate("ListPaging", startAtDisplay, recordsEnd, totalRecordCount);
|
||||
|
||||
html += '</span>';
|
||||
|
||||
|
@ -126,21 +126,7 @@ define(['loading', 'apphost', 'dialogHelper', 'connectionManager', 'imageLoader'
|
|||
return html;
|
||||
}
|
||||
|
||||
function parentWithClass(elem, className) {
|
||||
|
||||
while (!elem.classList || !elem.classList.contains(className)) {
|
||||
elem = elem.parentNode;
|
||||
|
||||
if (!elem) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return elem;
|
||||
}
|
||||
|
||||
function downloadRemoteImage(page, apiClient, url, type, provider) {
|
||||
|
||||
var options = getBaseRemoteOptions();
|
||||
|
||||
options.Type = type;
|
||||
|
@ -152,7 +138,7 @@ define(['loading', 'apphost', 'dialogHelper', 'connectionManager', 'imageLoader'
|
|||
apiClient.downloadRemoteImage(options).then(function () {
|
||||
|
||||
hasChanges = true;
|
||||
var dlg = parentWithClass(page, 'dialog');
|
||||
var dlg = dom.parentWithClass(page, 'dialog');
|
||||
dialogHelper.close(dlg);
|
||||
});
|
||||
}
|
||||
|
@ -162,7 +148,6 @@ define(['loading', 'apphost', 'dialogHelper', 'connectionManager', 'imageLoader'
|
|||
}
|
||||
|
||||
function getRemoteImageHtml(image, imageType, apiClient) {
|
||||
|
||||
var tagName = layoutManager.tv ? 'button' : 'div';
|
||||
var enableFooterButtons = !layoutManager.tv;
|
||||
|
||||
|
@ -293,7 +278,6 @@ define(['loading', 'apphost', 'dialogHelper', 'connectionManager', 'imageLoader'
|
|||
}
|
||||
|
||||
function initEditor(page, apiClient) {
|
||||
|
||||
page.querySelector('#selectBrowsableImageType').addEventListener('change', function () {
|
||||
browsableImageType = this.value;
|
||||
browsableImageStartIndex = 0;
|
||||
|
@ -319,14 +303,14 @@ define(['loading', 'apphost', 'dialogHelper', 'connectionManager', 'imageLoader'
|
|||
|
||||
page.addEventListener('click', function (e) {
|
||||
|
||||
var btnDownloadRemoteImage = parentWithClass(e.target, 'btnDownloadRemoteImage');
|
||||
var btnDownloadRemoteImage = dom.parentWithClass(e.target, 'btnDownloadRemoteImage');
|
||||
if (btnDownloadRemoteImage) {
|
||||
var card = parentWithClass(btnDownloadRemoteImage, 'card');
|
||||
var card = dom.parentWithClass(btnDownloadRemoteImage, 'card');
|
||||
downloadRemoteImage(page, apiClient, card.getAttribute('data-imageurl'), card.getAttribute('data-imagetype'), card.getAttribute('data-imageprovider'));
|
||||
return;
|
||||
}
|
||||
|
||||
var btnImageCard = parentWithClass(e.target, 'btnImageCard');
|
||||
var btnImageCard = dom.parentWithClass(e.target, 'btnImageCard');
|
||||
if (btnImageCard) {
|
||||
downloadRemoteImage(page, apiClient, btnImageCard.getAttribute('data-imageurl'), btnImageCard.getAttribute('data-imagetype'), btnImageCard.getAttribute('data-imageprovider'));
|
||||
}
|
||||
|
@ -334,7 +318,6 @@ define(['loading', 'apphost', 'dialogHelper', 'connectionManager', 'imageLoader'
|
|||
}
|
||||
|
||||
function showEditor(itemId, serverId, itemType) {
|
||||
|
||||
loading.show();
|
||||
|
||||
require(['text!./imagedownloader.template.html'], function (template) {
|
||||
|
@ -380,7 +363,6 @@ define(['loading', 'apphost', 'dialogHelper', 'connectionManager', 'imageLoader'
|
|||
}
|
||||
|
||||
function onDialogClosed() {
|
||||
|
||||
var dlg = this;
|
||||
|
||||
if (layoutManager.tv) {
|
||||
|
@ -397,9 +379,7 @@ define(['loading', 'apphost', 'dialogHelper', 'connectionManager', 'imageLoader'
|
|||
|
||||
return {
|
||||
show: function (itemId, serverId, itemType, imageType) {
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
|
||||
currentResolve = resolve;
|
||||
currentReject = reject;
|
||||
hasChanges = false;
|
||||
|
|
|
@ -1,165 +1,168 @@
|
|||
define(["inputManager", "layoutManager"], function (inputManager, layoutManager) {
|
||||
"use strict";
|
||||
/**
|
||||
* Module for performing keyboard navigation.
|
||||
* @module components/input/keyboardnavigation
|
||||
*/
|
||||
|
||||
/**
|
||||
* Key name mapping.
|
||||
*/
|
||||
// Add more to support old browsers
|
||||
var KeyNames = {
|
||||
13: "Enter",
|
||||
19: "Pause",
|
||||
27: "Escape",
|
||||
32: "Space",
|
||||
37: "ArrowLeft",
|
||||
38: "ArrowUp",
|
||||
39: "ArrowRight",
|
||||
40: "ArrowDown",
|
||||
// MediaRewind (Tizen/WebOS)
|
||||
412: "MediaRewind",
|
||||
// MediaStop (Tizen/WebOS)
|
||||
413: "MediaStop",
|
||||
// MediaPlay (Tizen/WebOS)
|
||||
415: "MediaPlay",
|
||||
// MediaFastForward (Tizen/WebOS)
|
||||
417: "MediaFastForward",
|
||||
// Back (WebOS)
|
||||
461: "Back",
|
||||
// Back (Tizen)
|
||||
10009: "Back",
|
||||
// MediaTrackPrevious (Tizen)
|
||||
10232: "MediaTrackPrevious",
|
||||
// MediaTrackNext (Tizen)
|
||||
10233: "MediaTrackNext",
|
||||
// MediaPlayPause (Tizen)
|
||||
10252: "MediaPlayPause"
|
||||
};
|
||||
import inputManager from "inputManager";
|
||||
import layoutManager from "layoutManager";
|
||||
|
||||
/**
|
||||
* Keys used for keyboard navigation.
|
||||
*/
|
||||
var NavigationKeys = ["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"];
|
||||
/**
|
||||
* Key name mapping.
|
||||
*/
|
||||
const KeyNames = {
|
||||
13: "Enter",
|
||||
19: "Pause",
|
||||
27: "Escape",
|
||||
32: "Space",
|
||||
37: "ArrowLeft",
|
||||
38: "ArrowUp",
|
||||
39: "ArrowRight",
|
||||
40: "ArrowDown",
|
||||
// MediaRewind (Tizen/WebOS)
|
||||
412: "MediaRewind",
|
||||
// MediaStop (Tizen/WebOS)
|
||||
413: "MediaStop",
|
||||
// MediaPlay (Tizen/WebOS)
|
||||
415: "MediaPlay",
|
||||
// MediaFastForward (Tizen/WebOS)
|
||||
417: "MediaFastForward",
|
||||
// Back (WebOS)
|
||||
461: "Back",
|
||||
// Back (Tizen)
|
||||
10009: "Back",
|
||||
// MediaTrackPrevious (Tizen)
|
||||
10232: "MediaTrackPrevious",
|
||||
// MediaTrackNext (Tizen)
|
||||
10233: "MediaTrackNext",
|
||||
// MediaPlayPause (Tizen)
|
||||
10252: "MediaPlayPause"
|
||||
};
|
||||
|
||||
var hasFieldKey = false;
|
||||
try {
|
||||
hasFieldKey = "key" in new KeyboardEvent("keydown");
|
||||
} catch (e) {
|
||||
console.error("error checking 'key' field");
|
||||
/**
|
||||
* Keys used for keyboard navigation.
|
||||
*/
|
||||
const NavigationKeys = ["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"];
|
||||
|
||||
let hasFieldKey = false;
|
||||
try {
|
||||
hasFieldKey = "key" in new KeyboardEvent("keydown");
|
||||
} catch (e) {
|
||||
console.error("error checking 'key' field");
|
||||
}
|
||||
|
||||
if (!hasFieldKey) {
|
||||
// Add [a..z]
|
||||
for (let i = 65; i <= 90; i++) {
|
||||
KeyNames[i] = String.fromCharCode(i).toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasFieldKey) {
|
||||
// Add [a..z]
|
||||
for (var i = 65; i <= 90; i++) {
|
||||
KeyNames[i] = String.fromCharCode(i).toLowerCase();
|
||||
/**
|
||||
* Returns key name from event.
|
||||
*
|
||||
* @param {KeyboardEvent} event - Keyboard event.
|
||||
* @return {string} Key name.
|
||||
*/
|
||||
export function getKeyName(event) {
|
||||
return KeyNames[event.keyCode] || event.key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns _true_ if key is used for navigation.
|
||||
*
|
||||
* @param {string} key - Key name.
|
||||
* @return {boolean} _true_ if key is used for navigation.
|
||||
*/
|
||||
export function isNavigationKey(key) {
|
||||
return NavigationKeys.indexOf(key) != -1;
|
||||
}
|
||||
|
||||
export function enable() {
|
||||
document.addEventListener("keydown", function (e) {
|
||||
const key = getKeyName(e);
|
||||
|
||||
// Ignore navigation keys for non-TV
|
||||
if (!layoutManager.tv && isNavigationKey(key)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns key name from event.
|
||||
*
|
||||
* @param {KeyboardEvent} keyboard event
|
||||
* @return {string} key name
|
||||
*/
|
||||
function getKeyName(event) {
|
||||
return KeyNames[event.keyCode] || event.key;
|
||||
}
|
||||
let capture = true;
|
||||
|
||||
/**
|
||||
* Returns _true_ if key is used for navigation.
|
||||
*
|
||||
* @param {string} key name
|
||||
* @return {boolean} _true_ if key is used for navigation
|
||||
*/
|
||||
function isNavigationKey(key) {
|
||||
return NavigationKeys.indexOf(key) != -1;
|
||||
}
|
||||
switch (key) {
|
||||
case "ArrowLeft":
|
||||
inputManager.handle("left");
|
||||
break;
|
||||
case "ArrowUp":
|
||||
inputManager.handle("up");
|
||||
break;
|
||||
case "ArrowRight":
|
||||
inputManager.handle("right");
|
||||
break;
|
||||
case "ArrowDown":
|
||||
inputManager.handle("down");
|
||||
break;
|
||||
|
||||
function enable() {
|
||||
document.addEventListener("keydown", function (e) {
|
||||
var key = getKeyName(e);
|
||||
case "Back":
|
||||
inputManager.handle("back");
|
||||
break;
|
||||
|
||||
// Ignore navigation keys for non-TV
|
||||
if (!layoutManager.tv && isNavigationKey(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var capture = true;
|
||||
|
||||
switch (key) {
|
||||
case "ArrowLeft":
|
||||
inputManager.handle("left");
|
||||
break;
|
||||
case "ArrowUp":
|
||||
inputManager.handle("up");
|
||||
break;
|
||||
case "ArrowRight":
|
||||
inputManager.handle("right");
|
||||
break;
|
||||
case "ArrowDown":
|
||||
inputManager.handle("down");
|
||||
break;
|
||||
|
||||
case "Back":
|
||||
case "Escape":
|
||||
if (layoutManager.tv) {
|
||||
inputManager.handle("back");
|
||||
break;
|
||||
|
||||
case "Escape":
|
||||
if (layoutManager.tv) {
|
||||
inputManager.handle("back");
|
||||
} else {
|
||||
capture = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case "MediaPlay":
|
||||
inputManager.handle("play");
|
||||
break;
|
||||
case "Pause":
|
||||
inputManager.handle("pause");
|
||||
break;
|
||||
case "MediaPlayPause":
|
||||
inputManager.handle("playpause");
|
||||
break;
|
||||
case "MediaRewind":
|
||||
inputManager.handle("rewind");
|
||||
break;
|
||||
case "MediaFastForward":
|
||||
inputManager.handle("fastforward");
|
||||
break;
|
||||
case "MediaStop":
|
||||
inputManager.handle("stop");
|
||||
break;
|
||||
case "MediaTrackPrevious":
|
||||
inputManager.handle("previoustrack");
|
||||
break;
|
||||
case "MediaTrackNext":
|
||||
inputManager.handle("nexttrack");
|
||||
break;
|
||||
|
||||
default:
|
||||
} else {
|
||||
capture = false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
if (capture) {
|
||||
console.debug("disabling default event handling");
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
}
|
||||
case "MediaPlay":
|
||||
inputManager.handle("play");
|
||||
break;
|
||||
case "Pause":
|
||||
inputManager.handle("pause");
|
||||
break;
|
||||
case "MediaPlayPause":
|
||||
inputManager.handle("playpause");
|
||||
break;
|
||||
case "MediaRewind":
|
||||
inputManager.handle("rewind");
|
||||
break;
|
||||
case "MediaFastForward":
|
||||
inputManager.handle("fastforward");
|
||||
break;
|
||||
case "MediaStop":
|
||||
inputManager.handle("stop");
|
||||
break;
|
||||
case "MediaTrackPrevious":
|
||||
inputManager.handle("previoustrack");
|
||||
break;
|
||||
case "MediaTrackNext":
|
||||
inputManager.handle("nexttrack");
|
||||
break;
|
||||
|
||||
// Gamepad initialisation. No script is required if no gamepads are present at init time, saving a bit of resources.
|
||||
// Whenever the gamepad is connected, we hand all the control of the gamepad to gamepadtokey.js by removing the event handler
|
||||
function attachGamepadScript(e) {
|
||||
console.log("Gamepad connected! Attaching gamepadtokey.js script");
|
||||
window.removeEventListener("gamepadconnected", attachGamepadScript);
|
||||
require(["components/input/gamepadtokey"]);
|
||||
}
|
||||
default:
|
||||
capture = false;
|
||||
}
|
||||
|
||||
// No need to check for gamepads manually at load time, the eventhandler will be fired for that
|
||||
window.addEventListener("gamepadconnected", attachGamepadScript);
|
||||
if (capture) {
|
||||
console.debug("disabling default event handling");
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
enable: enable,
|
||||
getKeyName: getKeyName,
|
||||
isNavigationKey: isNavigationKey
|
||||
};
|
||||
});
|
||||
// Gamepad initialisation. No script is required if no gamepads are present at init time, saving a bit of resources.
|
||||
// Whenever the gamepad is connected, we hand all the control of the gamepad to gamepadtokey.js by removing the event handler
|
||||
function attachGamepadScript(e) {
|
||||
console.log("Gamepad connected! Attaching gamepadtokey.js script");
|
||||
window.removeEventListener("gamepadconnected", attachGamepadScript);
|
||||
require(["components/input/gamepadtokey"]);
|
||||
}
|
||||
|
||||
// No need to check for gamepads manually at load time, the eventhandler will be fired for that
|
||||
window.addEventListener("gamepadconnected", attachGamepadScript);
|
||||
|
||||
export default {
|
||||
enable: enable,
|
||||
getKeyName: getKeyName,
|
||||
isNavigationKey: isNavigationKey
|
||||
};
|
||||
|
|
|
@ -116,7 +116,7 @@ define(["dialogHelper", "require", "layoutManager", "globalize", "userSettings",
|
|||
}
|
||||
|
||||
function createAttribute(label, value) {
|
||||
return '<span class="mediaInfoLabel">' + label + '</span><span class="mediaInfoAttribute">' + value + "</span>"
|
||||
return '<span class="mediaInfoLabel">' + label + '</span><span class="mediaInfoAttribute">' + value + "</span>";
|
||||
}
|
||||
|
||||
function showMediaInfoMore(itemId, serverId, template) {
|
||||
|
|
|
@ -296,8 +296,6 @@ define(["dialogHelper", "loading", "connectionManager", "require", "globalize",
|
|||
|
||||
var html = "";
|
||||
|
||||
var providerIds = item.ProviderIds || {};
|
||||
|
||||
for (var i = 0, length = idList.length; i < length; i++) {
|
||||
|
||||
var idInfo = idList[i];
|
||||
|
@ -306,9 +304,12 @@ define(["dialogHelper", "loading", "connectionManager", "require", "globalize",
|
|||
|
||||
html += '<div class="inputContainer">';
|
||||
|
||||
var idLabel = globalize.translate("LabelDynamicExternalId").replace("{0}", idInfo.Name);
|
||||
var fullName = idInfo.Name;
|
||||
if (idInfo.Type) {
|
||||
fullName = idInfo.Name + " " + globalize.translate(idInfo.Type);
|
||||
}
|
||||
|
||||
var value = providerIds[idInfo.Key] || "";
|
||||
var idLabel = globalize.translate("LabelDynamicExternalId", fullName);
|
||||
|
||||
html += '<input is="emby-input" class="txtLookupId" data-providerkey="' + idInfo.Key + '" id="' + id + '" label="' + idLabel + '"/>';
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct
|
|||
html += "<option value='" + culture.TwoLetterISORegionName + "'>" + culture.DisplayName + "</option>";
|
||||
}
|
||||
select.innerHTML = html;
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function populateRefreshInterval(select) {
|
||||
|
@ -120,7 +120,7 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct
|
|||
html += plugin.Name;
|
||||
html += "</h3>";
|
||||
html += "</div>";
|
||||
i > 0 ? html += '<button type="button" is="paper-icon-button-light" title="' + globalize.translate("ButtonUp") + '" class="btnSortableMoveUp btnSortable" data-pluginindex="' + i + '"><i class="material-icons keyboard_arrow_up"></i></button>' : plugins.length > 1 && (html += '<button type="button" is="paper-icon-button-light" title="' + globalize.translate("ButtonDown") + '" class="btnSortableMoveDown btnSortable" data-pluginindex="' + i + '"><i class="material-icons keyboard_arrow_down"></i></button>'), html += "</div>"
|
||||
i > 0 ? html += '<button type="button" is="paper-icon-button-light" title="' + globalize.translate("ButtonUp") + '" class="btnSortableMoveUp btnSortable" data-pluginindex="' + i + '"><i class="material-icons keyboard_arrow_up"></i></button>' : plugins.length > 1 && (html += '<button type="button" is="paper-icon-button-light" title="' + globalize.translate("ButtonDown") + '" class="btnSortableMoveDown btnSortable" data-pluginindex="' + i + '"><i class="material-icons keyboard_arrow_down"></i></button>'), html += "</div>";
|
||||
}
|
||||
html += "</div>";
|
||||
html += '<div class="fieldDescription">' + globalize.translate("LabelMetadataDownloadersHelp") + "</div>";
|
||||
|
@ -265,10 +265,10 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct
|
|||
renderMetadataFetchers(parent, availableOptions, {});
|
||||
renderSubtitleFetchers(parent, availableOptions, {});
|
||||
renderImageFetchers(parent, availableOptions, {});
|
||||
availableOptions.SubtitleFetchers.length ? parent.querySelector(".subtitleDownloadSettings").classList.remove("hide") : parent.querySelector(".subtitleDownloadSettings").classList.add("hide")
|
||||
availableOptions.SubtitleFetchers.length ? parent.querySelector(".subtitleDownloadSettings").classList.remove("hide") : parent.querySelector(".subtitleDownloadSettings").classList.add("hide");
|
||||
}).catch(function() {
|
||||
return Promise.resolve();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function adjustSortableListElement(elem) {
|
||||
|
@ -296,8 +296,8 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct
|
|||
Type: type
|
||||
}, currentLibraryOptions.TypeOptions.push(typeOptions));
|
||||
var availableOptions = getTypeOptions(currentAvailableOptions || {}, type);
|
||||
(new ImageOptionsEditor).show(type, typeOptions, availableOptions)
|
||||
})
|
||||
(new ImageOptionsEditor).show(type, typeOptions, availableOptions);
|
||||
});
|
||||
}
|
||||
|
||||
function onImageFetchersContainerClick(e) {
|
||||
|
@ -315,12 +315,12 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct
|
|||
var list = dom.parentWithClass(li, "paperList");
|
||||
if (btnSortable.classList.contains("btnSortableMoveDown")) {
|
||||
var next = li.nextSibling;
|
||||
next && (li.parentNode.removeChild(li), next.parentNode.insertBefore(li, next.nextSibling))
|
||||
next && (li.parentNode.removeChild(li), next.parentNode.insertBefore(li, next.nextSibling));
|
||||
} else {
|
||||
var prev = li.previousSibling;
|
||||
prev && (li.parentNode.removeChild(li), prev.parentNode.insertBefore(li, prev))
|
||||
prev && (li.parentNode.removeChild(li), prev.parentNode.insertBefore(li, prev));
|
||||
}
|
||||
Array.prototype.forEach.call(list.querySelectorAll(".sortableOption"), adjustSortableListElement)
|
||||
Array.prototype.forEach.call(list.querySelectorAll(".sortableOption"), adjustSortableListElement);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -407,13 +407,13 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct
|
|||
|
||||
function setSubtitleFetchersIntoOptions(parent, options) {
|
||||
options.DisabledSubtitleFetchers = Array.prototype.map.call(Array.prototype.filter.call(parent.querySelectorAll(".chkSubtitleFetcher"), function(elem) {
|
||||
return !elem.checked
|
||||
return !elem.checked;
|
||||
}), function(elem) {
|
||||
return elem.getAttribute("data-pluginname")
|
||||
return elem.getAttribute("data-pluginname");
|
||||
});
|
||||
|
||||
options.SubtitleFetcherOrder = Array.prototype.map.call(parent.querySelectorAll(".subtitleFetcherItem"), function(elem) {
|
||||
return elem.getAttribute("data-pluginname")
|
||||
return elem.getAttribute("data-pluginname");
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -455,13 +455,13 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct
|
|||
}
|
||||
|
||||
typeOptions.ImageFetchers = Array.prototype.map.call(Array.prototype.filter.call(section.querySelectorAll(".chkImageFetcher"), function(elem) {
|
||||
return elem.checked
|
||||
return elem.checked;
|
||||
}), function(elem) {
|
||||
return elem.getAttribute("data-pluginname")
|
||||
return elem.getAttribute("data-pluginname");
|
||||
});
|
||||
|
||||
typeOptions.ImageFetcherOrder = Array.prototype.map.call(section.querySelectorAll(".imageFetcherItem"), function(elem) {
|
||||
return elem.getAttribute("data-pluginname")
|
||||
return elem.getAttribute("data-pluginname");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -505,20 +505,20 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct
|
|||
SaveSubtitlesWithMedia: parent.querySelector("#chkSaveSubtitlesLocally").checked,
|
||||
RequirePerfectSubtitleMatch: parent.querySelector("#chkRequirePerfectMatch").checked,
|
||||
MetadataSavers: Array.prototype.map.call(Array.prototype.filter.call(parent.querySelectorAll(".chkMetadataSaver"), function(elem) {
|
||||
return elem.checked
|
||||
return elem.checked;
|
||||
}), function(elem) {
|
||||
return elem.getAttribute("data-pluginname")
|
||||
return elem.getAttribute("data-pluginname");
|
||||
}),
|
||||
TypeOptions: []
|
||||
};
|
||||
|
||||
options.LocalMetadataReaderOrder = Array.prototype.map.call(parent.querySelectorAll(".localReaderOption"), function(elem) {
|
||||
return elem.getAttribute("data-pluginname")
|
||||
return elem.getAttribute("data-pluginname");
|
||||
});
|
||||
options.SubtitleDownloadLanguages = Array.prototype.map.call(Array.prototype.filter.call(parent.querySelectorAll(".chkSubtitleLanguage"), function(elem) {
|
||||
return elem.checked
|
||||
return elem.checked;
|
||||
}), function(elem) {
|
||||
return elem.getAttribute("data-lang")
|
||||
return elem.getAttribute("data-lang");
|
||||
});
|
||||
setSubtitleFetchersIntoOptions(parent, options);
|
||||
setMetadataFetchersIntoOptions(parent, options);
|
||||
|
@ -531,7 +531,7 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct
|
|||
function getOrderedPlugins(plugins, configuredOrder) {
|
||||
plugins = plugins.slice(0);
|
||||
plugins.sort(function(a, b) {
|
||||
return a = configuredOrder.indexOf(a.Name), b = configuredOrder.indexOf(b.Name), a < b ? -1 : a > b ? 1 : 0
|
||||
return a = configuredOrder.indexOf(a.Name), b = configuredOrder.indexOf(b.Name), a < b ? -1 : a > b ? 1 : 0;
|
||||
});
|
||||
return plugins;
|
||||
}
|
||||
|
@ -558,10 +558,10 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct
|
|||
parent.querySelector("#chkSkipIfAudioTrackPresent").checked = options.SkipSubtitlesIfAudioTrackMatches;
|
||||
parent.querySelector("#chkRequirePerfectMatch").checked = options.RequirePerfectSubtitleMatch;
|
||||
Array.prototype.forEach.call(parent.querySelectorAll(".chkMetadataSaver"), function(elem) {
|
||||
elem.checked = options.MetadataSavers ? -1 !== options.MetadataSavers.indexOf(elem.getAttribute("data-pluginname")) : "true" === elem.getAttribute("data-defaultenabled")
|
||||
elem.checked = options.MetadataSavers ? -1 !== options.MetadataSavers.indexOf(elem.getAttribute("data-pluginname")) : "true" === elem.getAttribute("data-defaultenabled");
|
||||
});
|
||||
Array.prototype.forEach.call(parent.querySelectorAll(".chkSubtitleLanguage"), function(elem) {
|
||||
elem.checked = !!options.SubtitleDownloadLanguages && -1 !== options.SubtitleDownloadLanguages.indexOf(elem.getAttribute("data-lang"))
|
||||
elem.checked = !!options.SubtitleDownloadLanguages && -1 !== options.SubtitleDownloadLanguages.indexOf(elem.getAttribute("data-lang"));
|
||||
});
|
||||
renderMetadataReaders(parent, getOrderedPlugins(parent.availableOptions.MetadataReaders, options.LocalMetadataReaderOrder || []));
|
||||
renderMetadataFetchers(parent, parent.availableOptions, options);
|
||||
|
@ -578,5 +578,5 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct
|
|||
getLibraryOptions: getLibraryOptions,
|
||||
setLibraryOptions: setLibraryOptions,
|
||||
setAdvancedVisible: setAdvancedVisible
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -188,5 +188,5 @@ define(["pluginManager"], function (pluginManager) {
|
|||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -465,7 +465,12 @@ define(['itemHelper', 'dom', 'layoutManager', 'dialogHelper', 'datetime', 'loadi
|
|||
var id = "txt1" + idInfo.Key;
|
||||
var formatString = idInfo.UrlFormatString || '';
|
||||
|
||||
var labelText = globalize.translate('LabelDynamicExternalId').replace('{0}', idInfo.Name);
|
||||
var fullName = idInfo.Name;
|
||||
if (idInfo.Type) {
|
||||
fullName = idInfo.Name + " " + globalize.translate(idInfo.Type);
|
||||
}
|
||||
|
||||
var labelText = globalize.translate('LabelDynamicExternalId', fullName);
|
||||
|
||||
html += '<div class="inputContainer">';
|
||||
html += '<div class="flex align-items-center">';
|
||||
|
|
|
@ -173,15 +173,15 @@ define(['serverNotifications', 'playbackManager', 'events', 'globalize', 'requir
|
|||
};
|
||||
|
||||
if (status === 'completed') {
|
||||
notification.title = globalize.translate('PackageInstallCompleted').replace('{0}', installation.Name + ' ' + installation.Version);
|
||||
notification.title = globalize.translate('PackageInstallCompleted', installation.Name, installation.Version);
|
||||
notification.vibrate = true;
|
||||
} else if (status === 'cancelled') {
|
||||
notification.title = globalize.translate('PackageInstallCancelled').replace('{0}', installation.Name + ' ' + installation.Version);
|
||||
notification.title = globalize.translate('PackageInstallCancelled', installation.Name, installation.Version);
|
||||
} else if (status === 'failed') {
|
||||
notification.title = globalize.translate('PackageInstallFailed').replace('{0}', installation.Name + ' ' + installation.Version);
|
||||
notification.title = globalize.translate('PackageInstallFailed', installation.Name, installation.Version);
|
||||
notification.vibrate = true;
|
||||
} else if (status === 'progress') {
|
||||
notification.title = globalize.translate('InstallingPackage').replace('{0}', installation.Name + ' ' + installation.Version);
|
||||
notification.title = globalize.translate('InstallingPackage', installation.Name, installation.Version);
|
||||
|
||||
notification.actions =
|
||||
[
|
||||
|
|
|
@ -1633,29 +1633,29 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
|
|||
self.supportSubtitleOffset = function(player) {
|
||||
player = player || self._currentPlayer;
|
||||
return player && 'setSubtitleOffset' in player;
|
||||
}
|
||||
};
|
||||
|
||||
self.enableShowingSubtitleOffset = function(player) {
|
||||
player = player || self._currentPlayer;
|
||||
player.enableShowingSubtitleOffset();
|
||||
}
|
||||
};
|
||||
|
||||
self.disableShowingSubtitleOffset = function(player) {
|
||||
player = player || self._currentPlayer;
|
||||
if (player.disableShowingSubtitleOffset) {
|
||||
player.disableShowingSubtitleOffset();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
self.isShowingSubtitleOffsetEnabled = function(player) {
|
||||
player = player || self._currentPlayer;
|
||||
return player.isShowingSubtitleOffsetEnabled();
|
||||
}
|
||||
};
|
||||
|
||||
self.isSubtitleStreamExternal = function(index, player) {
|
||||
var stream = getSubtitleStream(player, index);
|
||||
return stream ? getDeliveryMethod(stream) === 'External' : false;
|
||||
}
|
||||
};
|
||||
|
||||
self.setSubtitleOffset = function (value, player) {
|
||||
player = player || self._currentPlayer;
|
||||
|
@ -1669,12 +1669,12 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
|
|||
if (player.getSubtitleOffset) {
|
||||
return player.getSubtitleOffset();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
self.canHandleOffsetOnCurrentSubtitle = function(player) {
|
||||
var index = self.getSubtitleStreamIndex(player);
|
||||
return index !== -1 && self.isSubtitleStreamExternal(index, player);
|
||||
}
|
||||
};
|
||||
|
||||
self.seek = function (ticks, player) {
|
||||
|
||||
|
|
|
@ -1,24 +1,10 @@
|
|||
define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager', 'connectionManager', 'userSettings', 'appRouter', 'globalize', 'emby-input', 'paper-icon-button-light', 'emby-select', 'material-icons', 'css!./../formdialog', 'emby-button'], function (shell, dialogHelper, loading, layoutManager, playbackManager, connectionManager, userSettings, appRouter, globalize) {
|
||||
define(['dom', 'shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager', 'connectionManager', 'userSettings', 'appRouter', 'globalize', 'emby-input', 'paper-icon-button-light', 'emby-select', 'material-icons', 'css!./../formdialog', 'emby-button'], function (dom, shell, dialogHelper, loading, layoutManager, playbackManager, connectionManager, userSettings, appRouter, globalize) {
|
||||
'use strict';
|
||||
|
||||
var currentServerId;
|
||||
|
||||
function parentWithClass(elem, className) {
|
||||
|
||||
while (!elem.classList || !elem.classList.contains(className)) {
|
||||
elem = elem.parentNode;
|
||||
|
||||
if (!elem) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return elem;
|
||||
}
|
||||
|
||||
function onSubmit(e) {
|
||||
|
||||
var panel = parentWithClass(this, 'dialog');
|
||||
var panel = dom.parentWithClass(this, 'dialog');
|
||||
|
||||
var playlistId = panel.querySelector('#selectPlaylistToAddTo').value;
|
||||
var apiClient = connectionManager.getApiClient(currentServerId);
|
||||
|
@ -35,11 +21,9 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager',
|
|||
}
|
||||
|
||||
function createPlaylist(apiClient, dlg) {
|
||||
|
||||
loading.show();
|
||||
|
||||
var url = apiClient.getUrl("Playlists", {
|
||||
|
||||
Name: dlg.querySelector('#txtNewPlaylistName').value,
|
||||
Ids: dlg.querySelector('.fldSelectedItemIds').value || '',
|
||||
userId: apiClient.getCurrentUserId()
|
||||
|
@ -50,9 +34,7 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager',
|
|||
type: "POST",
|
||||
url: url,
|
||||
dataType: "json"
|
||||
|
||||
}).then(function (result) {
|
||||
|
||||
loading.hide();
|
||||
|
||||
var id = result.Id;
|
||||
|
@ -63,16 +45,13 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager',
|
|||
}
|
||||
|
||||
function redirectToPlaylist(apiClient, id) {
|
||||
|
||||
appRouter.showItem(id, apiClient.serverId());
|
||||
}
|
||||
|
||||
function addToPlaylist(apiClient, dlg, id) {
|
||||
|
||||
var itemIds = dlg.querySelector('.fldSelectedItemIds').value || '';
|
||||
|
||||
if (id === 'queue') {
|
||||
|
||||
playbackManager.queue({
|
||||
serverId: apiClient.serverId(),
|
||||
ids: itemIds.split(',')
|
||||
|
@ -85,7 +64,6 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager',
|
|||
loading.show();
|
||||
|
||||
var url = apiClient.getUrl("Playlists/" + id + "/Items", {
|
||||
|
||||
Ids: itemIds,
|
||||
userId: apiClient.getCurrentUserId()
|
||||
});
|
||||
|
@ -95,7 +73,6 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager',
|
|||
url: url
|
||||
|
||||
}).then(function () {
|
||||
|
||||
loading.hide();
|
||||
|
||||
dlg.submitted = true;
|
||||
|
@ -108,7 +85,6 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager',
|
|||
}
|
||||
|
||||
function populatePlaylists(editorOptions, panel) {
|
||||
|
||||
var select = panel.querySelector('#selectPlaylistToAddTo');
|
||||
|
||||
loading.hide();
|
||||
|
@ -116,7 +92,6 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager',
|
|||
panel.querySelector('.newPlaylistInfo').classList.add('hide');
|
||||
|
||||
var options = {
|
||||
|
||||
Recursive: true,
|
||||
IncludeItemTypes: "Playlist",
|
||||
SortBy: 'SortName',
|
||||
|
@ -125,7 +100,6 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager',
|
|||
|
||||
var apiClient = connectionManager.getApiClient(currentServerId);
|
||||
apiClient.getItems(apiClient.getCurrentUserId(), options).then(function (result) {
|
||||
|
||||
var html = '';
|
||||
|
||||
if (editorOptions.enableAddToPlayQueue !== false && playbackManager.isPlaying()) {
|
||||
|
@ -135,7 +109,6 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager',
|
|||
html += '<option value="">' + globalize.translate('OptionNew') + '</option>';
|
||||
|
||||
html += result.Items.map(function (i) {
|
||||
|
||||
return '<option value="' + i.Id + '">' + i.Name + '</option>';
|
||||
});
|
||||
|
||||
|
@ -159,7 +132,6 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager',
|
|||
}
|
||||
|
||||
function getEditorHtml(items) {
|
||||
|
||||
var html = '';
|
||||
|
||||
html += '<div class="formDialogContent smoothScrollY" style="padding-top:2em;">';
|
||||
|
@ -195,7 +167,6 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager',
|
|||
}
|
||||
|
||||
function initEditor(content, options, items) {
|
||||
|
||||
content.querySelector('#selectPlaylistToAddTo').addEventListener('change', function () {
|
||||
if (this.value) {
|
||||
content.querySelector('.newPlaylistInfo').classList.add('hide');
|
||||
|
@ -235,7 +206,6 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager',
|
|||
}
|
||||
|
||||
PlaylistEditor.prototype.show = function (options) {
|
||||
|
||||
var items = options.items || {};
|
||||
currentServerId = options.serverId;
|
||||
|
||||
|
@ -272,7 +242,6 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager',
|
|||
initEditor(dlg, options, items);
|
||||
|
||||
dlg.querySelector('.btnCancel').addEventListener('click', function () {
|
||||
|
||||
dialogHelper.close(dlg);
|
||||
});
|
||||
|
||||
|
@ -281,7 +250,6 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager',
|
|||
}
|
||||
|
||||
return dialogHelper.open(dlg).then(function () {
|
||||
|
||||
if (layoutManager.tv) {
|
||||
centerFocus(dlg.querySelector('.formDialogContent'), false, false);
|
||||
}
|
||||
|
|
|
@ -1,19 +1,6 @@
|
|||
define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'connectionManager', 'appRouter', 'globalize', 'emby-input', 'emby-checkbox', 'paper-icon-button-light', 'emby-select', 'material-icons', 'css!./../formdialog', 'emby-button'], function (shell, dialogHelper, loading, layoutManager, connectionManager, appRouter, globalize) {
|
||||
define(['dom', 'shell', 'dialogHelper', 'loading', 'layoutManager', 'connectionManager', 'appRouter', 'globalize', 'emby-input', 'emby-checkbox', 'paper-icon-button-light', 'emby-select', 'material-icons', 'css!./../formdialog', 'emby-button'], function (dom, shell, dialogHelper, loading, layoutManager, connectionManager, appRouter, globalize) {
|
||||
'use strict';
|
||||
|
||||
function parentWithClass(elem, className) {
|
||||
|
||||
while (!elem.classList || !elem.classList.contains(className)) {
|
||||
elem = elem.parentNode;
|
||||
|
||||
if (!elem) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return elem;
|
||||
}
|
||||
|
||||
function getEditorHtml() {
|
||||
|
||||
var html = '';
|
||||
|
@ -65,7 +52,7 @@ define(['shell', 'dialogHelper', 'loading', 'layoutManager', 'connectionManager'
|
|||
loading.show();
|
||||
|
||||
var instance = this;
|
||||
var dlg = parentWithClass(e.target, 'dialog');
|
||||
var dlg = dom.parentWithClass(e.target, 'dialog');
|
||||
var options = instance.options;
|
||||
|
||||
var apiClient = connectionManager.getApiClient(options.serverId);
|
||||
|
|
|
@ -1,96 +1,90 @@
|
|||
// From https://github.com/parshap/node-sanitize-filename
|
||||
|
||||
define([], function () {
|
||||
'use strict';
|
||||
const illegalRe = /[\/\?<>\\:\*\|":]/g;
|
||||
// eslint-disable-next-line no-control-regex
|
||||
const controlRe = /[\x00-\x1f\x80-\x9f]/g;
|
||||
const reservedRe = /^\.+$/;
|
||||
const windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i;
|
||||
const windowsTrailingRe = /[\. ]+$/;
|
||||
|
||||
var illegalRe = /[\/\?<>\\:\*\|":]/g;
|
||||
// eslint-disable-next-line no-control-regex
|
||||
var controlRe = /[\x00-\x1f\x80-\x9f]/g;
|
||||
var reservedRe = /^\.+$/;
|
||||
var windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i;
|
||||
var windowsTrailingRe = /[\. ]+$/;
|
||||
function isHighSurrogate(codePoint) {
|
||||
return codePoint >= 0xd800 && codePoint <= 0xdbff;
|
||||
}
|
||||
|
||||
function isHighSurrogate(codePoint) {
|
||||
return codePoint >= 0xd800 && codePoint <= 0xdbff;
|
||||
function isLowSurrogate(codePoint) {
|
||||
return codePoint >= 0xdc00 && codePoint <= 0xdfff;
|
||||
}
|
||||
|
||||
function getByteLength(string) {
|
||||
if (typeof string !== "string") {
|
||||
throw new Error("Input must be string");
|
||||
}
|
||||
|
||||
function isLowSurrogate(codePoint) {
|
||||
return codePoint >= 0xdc00 && codePoint <= 0xdfff;
|
||||
}
|
||||
|
||||
function getByteLength(string) {
|
||||
if (typeof string !== "string") {
|
||||
throw new Error("Input must be string");
|
||||
}
|
||||
|
||||
var charLength = string.length;
|
||||
var byteLength = 0;
|
||||
var codePoint = null;
|
||||
var prevCodePoint = null;
|
||||
for (var i = 0; i < charLength; i++) {
|
||||
codePoint = string.charCodeAt(i);
|
||||
// handle 4-byte non-BMP chars
|
||||
// low surrogate
|
||||
if (isLowSurrogate(codePoint)) {
|
||||
// when parsing previous hi-surrogate, 3 is added to byteLength
|
||||
if (prevCodePoint != null && isHighSurrogate(prevCodePoint)) {
|
||||
byteLength += 1;
|
||||
} else {
|
||||
byteLength += 3;
|
||||
}
|
||||
} else if (codePoint <= 0x7f) {
|
||||
const charLength = string.length;
|
||||
let byteLength = 0;
|
||||
let codePoint = null;
|
||||
let prevCodePoint = null;
|
||||
for (let i = 0; i < charLength; i++) {
|
||||
codePoint = string.charCodeAt(i);
|
||||
// handle 4-byte non-BMP chars
|
||||
// low surrogate
|
||||
if (isLowSurrogate(codePoint)) {
|
||||
// when parsing previous hi-surrogate, 3 is added to byteLength
|
||||
if (prevCodePoint != null && isHighSurrogate(prevCodePoint)) {
|
||||
byteLength += 1;
|
||||
} else if (codePoint >= 0x80 && codePoint <= 0x7ff) {
|
||||
byteLength += 2;
|
||||
} else if (codePoint >= 0x800 && codePoint <= 0xffff) {
|
||||
} else {
|
||||
byteLength += 3;
|
||||
}
|
||||
prevCodePoint = codePoint;
|
||||
} else if (codePoint <= 0x7f) {
|
||||
byteLength += 1;
|
||||
} else if (codePoint >= 0x80 && codePoint <= 0x7ff) {
|
||||
byteLength += 2;
|
||||
} else if (codePoint >= 0x800 && codePoint <= 0xffff) {
|
||||
byteLength += 3;
|
||||
}
|
||||
|
||||
return byteLength;
|
||||
prevCodePoint = codePoint;
|
||||
}
|
||||
|
||||
function truncate(string, byteLength) {
|
||||
if (typeof string !== "string") {
|
||||
throw new Error("Input must be string");
|
||||
}
|
||||
return byteLength;
|
||||
}
|
||||
|
||||
var charLength = string.length;
|
||||
var curByteLength = 0;
|
||||
var codePoint;
|
||||
var segment;
|
||||
|
||||
for (var i = 0; i < charLength; i += 1) {
|
||||
codePoint = string.charCodeAt(i);
|
||||
segment = string[i];
|
||||
|
||||
if (isHighSurrogate(codePoint) && isLowSurrogate(string.charCodeAt(i + 1))) {
|
||||
i += 1;
|
||||
segment += string[i];
|
||||
}
|
||||
|
||||
curByteLength += getByteLength(segment);
|
||||
|
||||
if (curByteLength === byteLength) {
|
||||
return string.slice(0, i + 1);
|
||||
} else if (curByteLength > byteLength) {
|
||||
return string.slice(0, i - segment.length + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return string;
|
||||
function truncate(string, byteLength) {
|
||||
if (typeof string !== "string") {
|
||||
throw new Error("Input must be string");
|
||||
}
|
||||
|
||||
return {
|
||||
sanitize: function (input, replacement) {
|
||||
var sanitized = input
|
||||
.replace(illegalRe, replacement)
|
||||
.replace(controlRe, replacement)
|
||||
.replace(reservedRe, replacement)
|
||||
.replace(windowsReservedRe, replacement)
|
||||
.replace(windowsTrailingRe, replacement);
|
||||
return truncate(sanitized, 255);
|
||||
const charLength = string.length;
|
||||
let curByteLength = 0;
|
||||
let codePoint;
|
||||
let segment;
|
||||
|
||||
for (let i = 0; i < charLength; i += 1) {
|
||||
codePoint = string.charCodeAt(i);
|
||||
segment = string[i];
|
||||
|
||||
if (isHighSurrogate(codePoint) && isLowSurrogate(string.charCodeAt(i + 1))) {
|
||||
i += 1;
|
||||
segment += string[i];
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
curByteLength += getByteLength(segment);
|
||||
|
||||
if (curByteLength === byteLength) {
|
||||
return string.slice(0, i + 1);
|
||||
} else if (curByteLength > byteLength) {
|
||||
return string.slice(0, i - segment.length + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
export function sanitize(input, replacement) {
|
||||
const sanitized = input
|
||||
.replace(illegalRe, replacement)
|
||||
.replace(controlRe, replacement)
|
||||
.replace(reservedRe, replacement)
|
||||
.replace(windowsReservedRe, replacement)
|
||||
.replace(windowsTrailingRe, replacement);
|
||||
return truncate(sanitized, 255);
|
||||
}
|
||||
|
|
|
@ -1,38 +1,46 @@
|
|||
define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManager) {
|
||||
"use strict";
|
||||
/* eslint-disable indent */
|
||||
|
||||
/**
|
||||
* Module for controlling scroll behavior.
|
||||
* @module components/scrollManager
|
||||
*/
|
||||
|
||||
import dom from "dom";
|
||||
import browser from "browser";
|
||||
import layoutManager from "layoutManager";
|
||||
|
||||
/**
|
||||
* Scroll time in ms.
|
||||
*/
|
||||
var ScrollTime = 270;
|
||||
const ScrollTime = 270;
|
||||
|
||||
/**
|
||||
* Epsilon for comparing values.
|
||||
*/
|
||||
var Epsilon = 1e-6;
|
||||
const Epsilon = 1e-6;
|
||||
|
||||
// FIXME: Need to scroll to top of page to fully show the top menu. This can be solved by some marker of top most elements or their containers
|
||||
/**
|
||||
* Returns minimum vertical scroll.
|
||||
* Scroll less than that value will be zeroed.
|
||||
*
|
||||
* @return {number} minimum vertical scroll
|
||||
* @return {number} Minimum vertical scroll.
|
||||
*/
|
||||
function minimumScrollY() {
|
||||
var topMenu = document.querySelector(".headerTop");
|
||||
const topMenu = document.querySelector(".headerTop");
|
||||
if (topMenu) {
|
||||
return topMenu.clientHeight;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
var supportsSmoothScroll = "scrollBehavior" in document.documentElement.style;
|
||||
const supportsSmoothScroll = "scrollBehavior" in document.documentElement.style;
|
||||
|
||||
var supportsScrollToOptions = false;
|
||||
let supportsScrollToOptions = false;
|
||||
try {
|
||||
var elem = document.createElement("div");
|
||||
const elem = document.createElement("div");
|
||||
|
||||
var opts = Object.defineProperty({}, "behavior", {
|
||||
const opts = Object.defineProperty({}, "behavior", {
|
||||
// eslint-disable-next-line getter-return
|
||||
get: function () {
|
||||
supportsScrollToOptions = true;
|
||||
|
@ -47,10 +55,10 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage
|
|||
/**
|
||||
* Returns value clamped by range [min, max].
|
||||
*
|
||||
* @param {number} value clamped value
|
||||
* @param {number} min begining of range
|
||||
* @param {number} max ending of range
|
||||
* @return {number} clamped value
|
||||
* @param {number} value - Clamped value.
|
||||
* @param {number} min - Begining of range.
|
||||
* @param {number} max - Ending of range.
|
||||
* @return {number} Clamped value.
|
||||
*/
|
||||
function clamp(value, min, max) {
|
||||
return value <= min ? min : value >= max ? max : value;
|
||||
|
@ -60,15 +68,15 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage
|
|||
* Returns the required delta to fit range 1 into range 2.
|
||||
* In case of range 1 is bigger than range 2 returns delta to fit most out of range part.
|
||||
*
|
||||
* @param {number} begin1 begining of range 1
|
||||
* @param {number} end1 ending of range 1
|
||||
* @param {number} begin2 begining of range 2
|
||||
* @param {number} end2 ending of range 2
|
||||
* @return {number} delta: <0 move range1 to the left, >0 - to the right
|
||||
* @param {number} begin1 - Begining of range 1.
|
||||
* @param {number} end1 - Ending of range 1.
|
||||
* @param {number} begin2 - Begining of range 2.
|
||||
* @param {number} end2 - Ending of range 2.
|
||||
* @return {number} Delta: <0 move range1 to the left, >0 - to the right.
|
||||
*/
|
||||
function fitRange(begin1, end1, begin2, end2) {
|
||||
var delta1 = begin1 - begin2;
|
||||
var delta2 = end2 - end1;
|
||||
const delta1 = begin1 - begin2;
|
||||
const delta2 = end2 - end1;
|
||||
if (delta1 < 0 && delta1 < delta2) {
|
||||
return -delta1;
|
||||
} else if (delta2 < 0) {
|
||||
|
@ -80,13 +88,21 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage
|
|||
/**
|
||||
* Ease value.
|
||||
*
|
||||
* @param {number} t value in range [0, 1]
|
||||
* @return {number} eased value in range [0, 1]
|
||||
* @param {number} t - Value in range [0, 1].
|
||||
* @return {number} Eased value in range [0, 1].
|
||||
*/
|
||||
function ease(t) {
|
||||
return t*(2 - t); // easeOutQuad === ease-out
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} Rect
|
||||
* @property {number} left - X coordinate of top-left corner.
|
||||
* @property {number} top - Y coordinate of top-left corner.
|
||||
* @property {number} width - Width.
|
||||
* @property {number} height - Height.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Document scroll wrapper helps to unify scrolling and fix issues of some browsers.
|
||||
*
|
||||
|
@ -100,41 +116,68 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage
|
|||
*
|
||||
* Tizen 5 Browser/Native: scrolls documentElement (and window); has a document.scrollingElement
|
||||
*/
|
||||
function DocumentScroller() {
|
||||
}
|
||||
|
||||
DocumentScroller.prototype = {
|
||||
class DocumentScroller {
|
||||
/**
|
||||
* Horizontal scroll position.
|
||||
* @type {number}
|
||||
*/
|
||||
get scrollLeft() {
|
||||
return window.pageXOffset;
|
||||
},
|
||||
}
|
||||
|
||||
set scrollLeft(val) {
|
||||
window.scroll(val, window.pageYOffset);
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Vertical scroll position.
|
||||
* @type {number}
|
||||
*/
|
||||
get scrollTop() {
|
||||
return window.pageYOffset;
|
||||
},
|
||||
}
|
||||
|
||||
set scrollTop(val) {
|
||||
window.scroll(window.pageXOffset, val);
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Horizontal scroll size (scroll width).
|
||||
* @type {number}
|
||||
*/
|
||||
get scrollWidth() {
|
||||
return Math.max(document.documentElement.scrollWidth, document.body.scrollWidth);
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Vertical scroll size (scroll height).
|
||||
* @type {number}
|
||||
*/
|
||||
get scrollHeight() {
|
||||
return Math.max(document.documentElement.scrollHeight, document.body.scrollHeight);
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Horizontal client size (client width).
|
||||
* @type {number}
|
||||
*/
|
||||
get clientWidth() {
|
||||
return Math.min(document.documentElement.clientWidth, document.body.clientWidth);
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Vertical client size (client height).
|
||||
* @type {number}
|
||||
*/
|
||||
get clientHeight() {
|
||||
return Math.min(document.documentElement.clientHeight, document.body.clientHeight);
|
||||
},
|
||||
}
|
||||
|
||||
getBoundingClientRect: function() {
|
||||
/**
|
||||
* Returns bounding client rect.
|
||||
* @return {Rect} Bounding client rect.
|
||||
*/
|
||||
getBoundingClientRect() {
|
||||
// Make valid viewport coordinates: documentElement.getBoundingClientRect returns rect of entire document relative to viewport
|
||||
return {
|
||||
left: 0,
|
||||
|
@ -142,26 +185,34 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage
|
|||
width: this.clientWidth,
|
||||
height: this.clientHeight
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
scrollTo: function() {
|
||||
/**
|
||||
* Scrolls window.
|
||||
* @param {...mixed} args See window.scrollTo.
|
||||
*/
|
||||
scrollTo() {
|
||||
window.scrollTo.apply(window, arguments);
|
||||
}
|
||||
};
|
||||
|
||||
var documentScroller = new DocumentScroller();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns parent element that can be scrolled. If no such, returns documentElement.
|
||||
* Default (document) scroller.
|
||||
*/
|
||||
const documentScroller = new DocumentScroller();
|
||||
|
||||
/**
|
||||
* Returns parent element that can be scrolled. If no such, returns document scroller.
|
||||
*
|
||||
* @param {HTMLElement} element element for which parent is being searched
|
||||
* @param {boolean} vertical search for vertical scrollable parent
|
||||
* @param {HTMLElement} element - Element for which parent is being searched.
|
||||
* @param {boolean} vertical - Search for vertical scrollable parent.
|
||||
* @param {HTMLElement|DocumentScroller} Parent element that can be scrolled or document scroller.
|
||||
*/
|
||||
function getScrollableParent(element, vertical) {
|
||||
if (element) {
|
||||
var nameScroll = "scrollWidth";
|
||||
var nameClient = "clientWidth";
|
||||
var nameClass = "scrollX";
|
||||
let nameScroll = "scrollWidth";
|
||||
let nameClient = "clientWidth";
|
||||
let nameClass = "scrollX";
|
||||
|
||||
if (vertical) {
|
||||
nameScroll = "scrollHeight";
|
||||
|
@ -169,7 +220,7 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage
|
|||
nameClass = "scrollY";
|
||||
}
|
||||
|
||||
var parent = element.parentElement;
|
||||
let parent = element.parentElement;
|
||||
|
||||
while (parent) {
|
||||
// Skip 'emby-scroller' because it scrolls by itself
|
||||
|
@ -187,20 +238,20 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage
|
|||
|
||||
/**
|
||||
* @typedef {Object} ScrollerData
|
||||
* @property {number} scrollPos current scroll position
|
||||
* @property {number} scrollSize scroll size
|
||||
* @property {number} clientSize client size
|
||||
* @property {number} scrollPos - Current scroll position.
|
||||
* @property {number} scrollSize - Scroll size.
|
||||
* @property {number} clientSize - Client size.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns scroll data for specified orientation.
|
||||
* Returns scroller data for specified orientation.
|
||||
*
|
||||
* @param {HTMLElement} scroller scroller
|
||||
* @param {boolean} vertical vertical scroll data
|
||||
* @return {ScrollerData} scroll data
|
||||
* @param {HTMLElement} scroller - Scroller.
|
||||
* @param {boolean} vertical - Vertical scroller data.
|
||||
* @return {ScrollerData} Scroller data.
|
||||
*/
|
||||
function getScrollerData(scroller, vertical) {
|
||||
var data = {};
|
||||
let data = {};
|
||||
|
||||
if (!vertical) {
|
||||
data.scrollPos = scroller.scrollLeft;
|
||||
|
@ -218,14 +269,14 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage
|
|||
/**
|
||||
* Returns position of child of scroller for specified orientation.
|
||||
*
|
||||
* @param {HTMLElement} scroller scroller
|
||||
* @param {HTMLElement} element child of scroller
|
||||
* @param {boolean} vertical vertical scroll
|
||||
* @return {number} child position
|
||||
* @param {HTMLElement} scroller - Scroller.
|
||||
* @param {HTMLElement} element - Child of scroller.
|
||||
* @param {boolean} vertical - Vertical scroll.
|
||||
* @return {number} Child position.
|
||||
*/
|
||||
function getScrollerChildPos(scroller, element, vertical) {
|
||||
var elementRect = element.getBoundingClientRect();
|
||||
var scrollerRect = scroller.getBoundingClientRect();
|
||||
const elementRect = element.getBoundingClientRect();
|
||||
const scrollerRect = scroller.getBoundingClientRect();
|
||||
|
||||
if (!vertical) {
|
||||
return scroller.scrollLeft + elementRect.left - scrollerRect.left;
|
||||
|
@ -237,21 +288,21 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage
|
|||
/**
|
||||
* Returns scroll position for element.
|
||||
*
|
||||
* @param {ScrollerData} scrollerData scroller data
|
||||
* @param {number} elementPos child element position
|
||||
* @param {number} elementSize child element size
|
||||
* @param {boolean} centered scroll to center
|
||||
* @return {number} scroll position
|
||||
* @param {ScrollerData} scrollerData - Scroller data.
|
||||
* @param {number} elementPos - Child element position.
|
||||
* @param {number} elementSize - Child element size.
|
||||
* @param {boolean} centered - Scroll to center.
|
||||
* @return {number} Scroll position.
|
||||
*/
|
||||
function calcScroll(scrollerData, elementPos, elementSize, centered) {
|
||||
var maxScroll = scrollerData.scrollSize - scrollerData.clientSize;
|
||||
const maxScroll = scrollerData.scrollSize - scrollerData.clientSize;
|
||||
|
||||
var scroll;
|
||||
let scroll;
|
||||
|
||||
if (centered) {
|
||||
scroll = elementPos + (elementSize - scrollerData.clientSize) / 2;
|
||||
} else {
|
||||
var delta = fitRange(elementPos, elementPos + elementSize - 1, scrollerData.scrollPos, scrollerData.scrollPos + scrollerData.clientSize - 1);
|
||||
const delta = fitRange(elementPos, elementPos + elementSize - 1, scrollerData.scrollPos, scrollerData.scrollPos + scrollerData.clientSize - 1);
|
||||
scroll = scrollerData.scrollPos - delta;
|
||||
}
|
||||
|
||||
|
@ -261,14 +312,14 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage
|
|||
/**
|
||||
* Calls scrollTo function in proper way.
|
||||
*
|
||||
* @param {HTMLElement} scroller scroller
|
||||
* @param {ScrollToOptions} options scroll options
|
||||
* @param {HTMLElement} scroller - Scroller.
|
||||
* @param {ScrollToOptions} options - Scroll options.
|
||||
*/
|
||||
function scrollToHelper(scroller, options) {
|
||||
if ("scrollTo" in scroller) {
|
||||
if (!supportsScrollToOptions) {
|
||||
var scrollX = (options.left !== undefined ? options.left : scroller.scrollLeft);
|
||||
var scrollY = (options.top !== undefined ? options.top : scroller.scrollTop);
|
||||
const scrollX = (options.left !== undefined ? options.left : scroller.scrollLeft);
|
||||
const scrollY = (options.top !== undefined ? options.top : scroller.scrollTop);
|
||||
scroller.scrollTo(scrollX, scrollY);
|
||||
} else {
|
||||
scroller.scrollTo(options);
|
||||
|
@ -286,14 +337,14 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage
|
|||
/**
|
||||
* Performs built-in scroll.
|
||||
*
|
||||
* @param {HTMLElement} xScroller horizontal scroller
|
||||
* @param {number} scrollX horizontal coordinate
|
||||
* @param {HTMLElement} yScroller vertical scroller
|
||||
* @param {number} scrollY vertical coordinate
|
||||
* @param {boolean} smooth smooth scrolling
|
||||
* @param {HTMLElement} xScroller - Horizontal scroller.
|
||||
* @param {number} scrollX - Horizontal coordinate.
|
||||
* @param {HTMLElement} yScroller - Vertical scroller.
|
||||
* @param {number} scrollY - Vertical coordinate.
|
||||
* @param {boolean} smooth - Smooth scrolling.
|
||||
*/
|
||||
function builtinScroll(xScroller, scrollX, yScroller, scrollY, smooth) {
|
||||
var scrollBehavior = smooth ? "smooth" : "instant";
|
||||
const scrollBehavior = smooth ? "smooth" : "instant";
|
||||
|
||||
if (xScroller !== yScroller) {
|
||||
scrollToHelper(xScroller, {left: scrollX, behavior: scrollBehavior});
|
||||
|
@ -303,7 +354,10 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage
|
|||
}
|
||||
}
|
||||
|
||||
var scrollTimer;
|
||||
/**
|
||||
* Requested frame for animated scroll.
|
||||
*/
|
||||
let scrollTimer;
|
||||
|
||||
/**
|
||||
* Resets scroll timer to stop scrolling.
|
||||
|
@ -316,29 +370,29 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage
|
|||
/**
|
||||
* Performs animated scroll.
|
||||
*
|
||||
* @param {HTMLElement} xScroller horizontal scroller
|
||||
* @param {number} scrollX horizontal coordinate
|
||||
* @param {HTMLElement} yScroller vertical scroller
|
||||
* @param {number} scrollY vertical coordinate
|
||||
* @param {HTMLElement} xScroller - Horizontal scroller.
|
||||
* @param {number} scrollX - Horizontal coordinate.
|
||||
* @param {HTMLElement} yScroller - Vertical scroller.
|
||||
* @param {number} scrollY - Vertical coordinate.
|
||||
*/
|
||||
function animateScroll(xScroller, scrollX, yScroller, scrollY) {
|
||||
|
||||
var ox = xScroller.scrollLeft;
|
||||
var oy = yScroller.scrollTop;
|
||||
var dx = scrollX - ox;
|
||||
var dy = scrollY - oy;
|
||||
const ox = xScroller.scrollLeft;
|
||||
const oy = yScroller.scrollTop;
|
||||
const dx = scrollX - ox;
|
||||
const dy = scrollY - oy;
|
||||
|
||||
if (Math.abs(dx) < Epsilon && Math.abs(dy) < Epsilon) {
|
||||
return;
|
||||
}
|
||||
|
||||
var start;
|
||||
let start;
|
||||
|
||||
function scrollAnim(currentTimestamp) {
|
||||
|
||||
start = start || currentTimestamp;
|
||||
|
||||
var k = Math.min(1, (currentTimestamp - start) / ScrollTime);
|
||||
let k = Math.min(1, (currentTimestamp - start) / ScrollTime);
|
||||
|
||||
if (k === 1) {
|
||||
resetScrollTimer();
|
||||
|
@ -348,8 +402,8 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage
|
|||
|
||||
k = ease(k);
|
||||
|
||||
var x = ox + dx*k;
|
||||
var y = oy + dy*k;
|
||||
const x = ox + dx*k;
|
||||
const y = oy + dy*k;
|
||||
|
||||
builtinScroll(xScroller, x, yScroller, y, false);
|
||||
|
||||
|
@ -362,11 +416,11 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage
|
|||
/**
|
||||
* Performs scroll.
|
||||
*
|
||||
* @param {HTMLElement} xScroller horizontal scroller
|
||||
* @param {number} scrollX horizontal coordinate
|
||||
* @param {HTMLElement} yScroller vertical scroller
|
||||
* @param {number} scrollY vertical coordinate
|
||||
* @param {boolean} smooth smooth scrolling
|
||||
* @param {HTMLElement} xScroller - Horizontal scroller.
|
||||
* @param {number} scrollX - Horizontal coordinate.
|
||||
* @param {HTMLElement} yScroller - Vertical scroller.
|
||||
* @param {number} scrollY - Vertical coordinate.
|
||||
* @param {boolean} smooth - Smooth scrolling.
|
||||
*/
|
||||
function doScroll(xScroller, scrollX, yScroller, scrollY, smooth) {
|
||||
|
||||
|
@ -403,26 +457,26 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage
|
|||
/**
|
||||
* Returns true if scroll manager is enabled.
|
||||
*/
|
||||
var isEnabled = function() {
|
||||
export function isEnabled() {
|
||||
return layoutManager.tv;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrolls the document to a given position.
|
||||
*
|
||||
* @param {number} scrollX horizontal coordinate
|
||||
* @param {number} scrollY vertical coordinate
|
||||
* @param {boolean} [smooth=false] smooth scrolling
|
||||
* @param {number} scrollX - Horizontal coordinate.
|
||||
* @param {number} scrollY - Vertical coordinate.
|
||||
* @param {boolean} [smooth=false] - Smooth scrolling.
|
||||
*/
|
||||
var scrollTo = function(scrollX, scrollY, smooth) {
|
||||
export function scrollTo(scrollX, scrollY, smooth) {
|
||||
|
||||
smooth = !!smooth;
|
||||
|
||||
// Scroller is document itself by default
|
||||
var scroller = getScrollableParent(null, false);
|
||||
const scroller = getScrollableParent(null, false);
|
||||
|
||||
var xScrollerData = getScrollerData(scroller, false);
|
||||
var yScrollerData = getScrollerData(scroller, true);
|
||||
const xScrollerData = getScrollerData(scroller, false);
|
||||
const yScrollerData = getScrollerData(scroller, true);
|
||||
|
||||
scrollX = clamp(Math.round(scrollX), 0, xScrollerData.scrollSize - xScrollerData.clientSize);
|
||||
scrollY = clamp(Math.round(scrollY), 0, yScrollerData.scrollSize - yScrollerData.clientSize);
|
||||
|
@ -433,39 +487,39 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage
|
|||
/**
|
||||
* Scrolls the document to a given element.
|
||||
*
|
||||
* @param {HTMLElement} element target element of scroll task
|
||||
* @param {boolean} [smooth=false] smooth scrolling
|
||||
* @param {HTMLElement} element - Target element of scroll task.
|
||||
* @param {boolean} [smooth=false] - Smooth scrolling.
|
||||
*/
|
||||
var scrollToElement = function(element, smooth) {
|
||||
export function scrollToElement(element, smooth) {
|
||||
|
||||
smooth = !!smooth;
|
||||
|
||||
var scrollCenterX = true;
|
||||
var scrollCenterY = true;
|
||||
let scrollCenterX = true;
|
||||
let scrollCenterY = true;
|
||||
|
||||
var offsetParent = element.offsetParent;
|
||||
const offsetParent = element.offsetParent;
|
||||
|
||||
// In Firefox offsetParent.offsetParent is BODY
|
||||
var isFixed = offsetParent && (!offsetParent.offsetParent || window.getComputedStyle(offsetParent).position === "fixed");
|
||||
const isFixed = offsetParent && (!offsetParent.offsetParent || window.getComputedStyle(offsetParent).position === "fixed");
|
||||
|
||||
// Scroll fixed elements to nearest edge (or do not scroll at all)
|
||||
if (isFixed) {
|
||||
scrollCenterX = scrollCenterY = false;
|
||||
}
|
||||
|
||||
var xScroller = getScrollableParent(element, false);
|
||||
var yScroller = getScrollableParent(element, true);
|
||||
const xScroller = getScrollableParent(element, false);
|
||||
const yScroller = getScrollableParent(element, true);
|
||||
|
||||
var elementRect = element.getBoundingClientRect();
|
||||
const elementRect = element.getBoundingClientRect();
|
||||
|
||||
var xScrollerData = getScrollerData(xScroller, false);
|
||||
var yScrollerData = getScrollerData(yScroller, true);
|
||||
const xScrollerData = getScrollerData(xScroller, false);
|
||||
const yScrollerData = getScrollerData(yScroller, true);
|
||||
|
||||
var xPos = getScrollerChildPos(xScroller, element, false);
|
||||
var yPos = getScrollerChildPos(yScroller, element, true);
|
||||
const xPos = getScrollerChildPos(xScroller, element, false);
|
||||
const yPos = getScrollerChildPos(yScroller, element, true);
|
||||
|
||||
var scrollX = calcScroll(xScrollerData, xPos, elementRect.width, scrollCenterX);
|
||||
var scrollY = calcScroll(yScrollerData, yPos, elementRect.height, scrollCenterY);
|
||||
const scrollX = calcScroll(xScrollerData, xPos, elementRect.width, scrollCenterX);
|
||||
let scrollY = calcScroll(yScrollerData, yPos, elementRect.height, scrollCenterY);
|
||||
|
||||
// HACK: Scroll to top for top menu because it is hidden
|
||||
// FIXME: Need a marker to scroll top/bottom
|
||||
|
@ -490,9 +544,10 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage
|
|||
}, {capture: true});
|
||||
}
|
||||
|
||||
return {
|
||||
isEnabled: isEnabled,
|
||||
scrollTo: scrollTo,
|
||||
scrollToElement: scrollToElement
|
||||
};
|
||||
});
|
||||
/* eslint-enable indent */
|
||||
|
||||
export default {
|
||||
isEnabled: isEnabled,
|
||||
scrollTo: scrollTo,
|
||||
scrollToElement: scrollToElement
|
||||
};
|
||||
|
|
|
@ -10,12 +10,6 @@ define([], function () {
|
|||
}
|
||||
|
||||
},
|
||||
canExec: false,
|
||||
exec: function (options) {
|
||||
// options.path
|
||||
// options.arguments
|
||||
return Promise.reject();
|
||||
},
|
||||
enableFullscreen: function () {
|
||||
if (window.NativeShell) {
|
||||
window.NativeShell.enableFullscreen();
|
||||
|
|
|
@ -116,8 +116,8 @@ define(['apphost', 'userSettings', 'browser', 'events', 'pluginManager', 'backdr
|
|||
|
||||
var linkUrl = info.stylesheetPath;
|
||||
unloadTheme();
|
||||
var link = document.createElement('link');
|
||||
|
||||
var link = document.createElement('link');
|
||||
link.setAttribute('rel', 'stylesheet');
|
||||
link.setAttribute('type', 'text/css');
|
||||
link.onload = function () {
|
||||
|
|
|
@ -1,8 +1,18 @@
|
|||
define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'focusManager', 'browser', 'apphost', 'loading', 'css!./style', 'material-icons', 'paper-icon-button-light'], function (dialogHelper, inputManager, connectionManager, layoutManager, focusManager, browser, appHost, loading) {
|
||||
/**
|
||||
* Image viewer component
|
||||
* @module components/slideshow/slideshow
|
||||
*/
|
||||
define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'focusManager', 'browser', 'apphost', 'css!./style', 'material-icons', 'paper-icon-button-light'], function (dialogHelper, inputManager, connectionManager, layoutManager, focusManager, browser, appHost) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* 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";
|
||||
|
||||
|
@ -11,7 +21,6 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
|
|||
}
|
||||
|
||||
if (item.ImageTags && item.ImageTags[options.type]) {
|
||||
|
||||
options.tag = item.ImageTags[options.type];
|
||||
return apiClient.getScaledImageUrl(item.Id, options);
|
||||
}
|
||||
|
@ -27,8 +36,14 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
|
|||
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";
|
||||
|
||||
|
@ -46,19 +61,19 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
|
|||
return null;
|
||||
}
|
||||
|
||||
function getImgUrl(item, original) {
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
var apiClient = connectionManager.getApiClient(item.ServerId);
|
||||
var imageOptions = {};
|
||||
|
||||
if (!original) {
|
||||
imageOptions.maxWidth = screen.availWidth;
|
||||
}
|
||||
if (item.BackdropImageTags && item.BackdropImageTags.length) {
|
||||
return getBackdropImageUrl(item, imageOptions, apiClient);
|
||||
} else {
|
||||
|
||||
if (item.MediaType === 'Photo' && original) {
|
||||
if (item.MediaType === 'Photo') {
|
||||
return apiClient.getItemDownloadUrl(item.Id);
|
||||
}
|
||||
imageOptions.type = "Primary";
|
||||
|
@ -66,15 +81,25 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 '<button is="paper-icon-button-light" class="autoSize ' + cssClass + '"' + tabIndex + autoFocus + '><i class="material-icons slideshowButtonIcon ' + icon + '"></i></button>';
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
|
@ -83,23 +108,31 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
|
|||
}
|
||||
|
||||
return function (options) {
|
||||
|
||||
var self = this;
|
||||
/** Initialized instance of Swiper. */
|
||||
var swiperInstance;
|
||||
var dlg;
|
||||
var currentTimeout;
|
||||
var currentIntervalMs;
|
||||
/** Initialized instance of the dialog containing the Swiper instance. */
|
||||
var dialog;
|
||||
/** Options of the slideshow components */
|
||||
var currentOptions;
|
||||
var currentIndex;
|
||||
/** ID of the timeout used to hide the OSD. */
|
||||
var hideTimeout;
|
||||
/** Last coordinates of the mouse pointer. */
|
||||
var lastMouseMoveData;
|
||||
/** Visibility status of the OSD. */
|
||||
var _osdOpen = false;
|
||||
|
||||
// small hack since this is not possible anyway
|
||||
if (browser.chromecast) {
|
||||
options.interactive = false;
|
||||
}
|
||||
// Use autoplay on Chromecast since it is non-interactive.
|
||||
options.interactive = !browser.chromecast;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
dlg = dialogHelper.createDialog({
|
||||
dialog = dialogHelper.createDialog({
|
||||
exitAnimationDuration: options.interactive ? 400 : 800,
|
||||
size: 'fullscreen',
|
||||
autoFocus: false,
|
||||
|
@ -108,17 +141,15 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
|
|||
removeOnClose: true
|
||||
});
|
||||
|
||||
dlg.classList.add('slideshowDialog');
|
||||
dialog.classList.add('slideshowDialog');
|
||||
|
||||
var html = '';
|
||||
|
||||
if (options.interactive) {
|
||||
html += '<div class="slideshowSwiperContainer"><div class="swiper-wrapper"></div></div>';
|
||||
|
||||
if (options.interactive && !layoutManager.tv) {
|
||||
var actionButtonsOnTop = layoutManager.mobile;
|
||||
|
||||
html += '<div>';
|
||||
html += '<div class="slideshowSwiperContainer"><div class="swiper-wrapper"></div></div>';
|
||||
|
||||
html += getIcon('keyboard_arrow_left', 'btnSlideshowPrevious slideshowButton hide-mouse-idle-tv', false);
|
||||
html += getIcon('keyboard_arrow_right', 'btnSlideshowNext slideshowButton hide-mouse-idle-tv', false);
|
||||
|
||||
|
@ -137,7 +168,7 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
|
|||
if (!actionButtonsOnTop) {
|
||||
html += '<div class="slideshowBottomBar hide">';
|
||||
|
||||
html += getIcon('pause', 'btnSlideshowPause slideshowButton', true, true);
|
||||
html += getIcon('play_arrow', 'btnSlideshowPause slideshowButton', true, true);
|
||||
if (appHost.supports('filedownload')) {
|
||||
html += getIcon('file_download', 'btnDownload slideshowButton', true);
|
||||
}
|
||||
|
@ -148,33 +179,28 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
|
|||
html += '</div>';
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
|
||||
} else {
|
||||
html += '<div class="slideshowImage"></div><h1 class="slideshowImageText"></h1>';
|
||||
}
|
||||
|
||||
dlg.innerHTML = html;
|
||||
dialog.innerHTML = html;
|
||||
|
||||
if (options.interactive) {
|
||||
dlg.querySelector('.btnSlideshowExit').addEventListener('click', function (e) {
|
||||
|
||||
dialogHelper.close(dlg);
|
||||
if (options.interactive && !layoutManager.tv) {
|
||||
dialog.querySelector('.btnSlideshowExit').addEventListener('click', function (e) {
|
||||
dialogHelper.close(dialog);
|
||||
});
|
||||
dlg.querySelector('.btnSlideshowNext').addEventListener('click', nextImage);
|
||||
dlg.querySelector('.btnSlideshowPrevious').addEventListener('click', previousImage);
|
||||
|
||||
var btnPause = dlg.querySelector('.btnSlideshowPause');
|
||||
var btnPause = dialog.querySelector('.btnSlideshowPause');
|
||||
if (btnPause) {
|
||||
btnPause.addEventListener('click', playPause);
|
||||
}
|
||||
|
||||
var btnDownload = dlg.querySelector('.btnDownload');
|
||||
var btnDownload = dialog.querySelector('.btnDownload');
|
||||
if (btnDownload) {
|
||||
btnDownload.addEventListener('click', download);
|
||||
}
|
||||
|
||||
var btnShare = dlg.querySelector('.btnShare');
|
||||
var btnShare = dialog.querySelector('.btnShare');
|
||||
if (btnShare) {
|
||||
btnShare.addEventListener('click', share);
|
||||
}
|
||||
|
@ -182,78 +208,104 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
|
|||
|
||||
setUserScalable(true);
|
||||
|
||||
dialogHelper.open(dlg).then(function () {
|
||||
|
||||
dialogHelper.open(dialog).then(function () {
|
||||
setUserScalable(false);
|
||||
stopInterval();
|
||||
});
|
||||
|
||||
inputManager.on(window, onInputCommand);
|
||||
document.addEventListener((window.PointerEvent ? 'pointermove' : 'mousemove'), onPointerMove);
|
||||
|
||||
dlg.addEventListener('close', onDialogClosed);
|
||||
dialog.addEventListener('close', onDialogClosed);
|
||||
|
||||
if (options.interactive) {
|
||||
loadSwiper(dlg);
|
||||
}
|
||||
loadSwiper(dialog, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles OSD changes when the autoplay is started.
|
||||
*/
|
||||
function onAutoplayStart() {
|
||||
var btnSlideshowPause = dlg.querySelector('.btnSlideshowPause i');
|
||||
var btnSlideshowPause = dialog.querySelector('.btnSlideshowPause i');
|
||||
if (btnSlideshowPause) {
|
||||
btnSlideshowPause.classList.remove("play_arrow");
|
||||
btnSlideshowPause.classList.add("pause");
|
||||
btnSlideshowPause.classList.replace("play_arrow", "pause");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles OSD changes when the autoplay is stopped.
|
||||
*/
|
||||
function onAutoplayStop() {
|
||||
var btnSlideshowPause = dlg.querySelector('.btnSlideshowPause i');
|
||||
var btnSlideshowPause = dialog.querySelector('.btnSlideshowPause i');
|
||||
if (btnSlideshowPause) {
|
||||
btnSlideshowPause.classList.remove("pause");
|
||||
btnSlideshowPause.classList.add("play_arrow");
|
||||
btnSlideshowPause.classList.replace("pause", "play_arrow");
|
||||
}
|
||||
}
|
||||
|
||||
function loadSwiper(dlg) {
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
dlg.querySelector('.swiper-wrapper').innerHTML = currentOptions.slides.map(getSwiperSlideHtmlFromSlide).join('');
|
||||
slides = currentOptions.slides;
|
||||
} else {
|
||||
dlg.querySelector('.swiper-wrapper').innerHTML = currentOptions.items.map(getSwiperSlideHtmlFromItem).join('');
|
||||
slides = currentOptions.items;
|
||||
}
|
||||
|
||||
require(['swiper'], function (Swiper) {
|
||||
|
||||
swiperInstance = new Swiper(dlg.querySelector('.slideshowSwiperContainer'), {
|
||||
// Optional parameters
|
||||
swiperInstance = new Swiper(dialog.querySelector('.slideshowSwiperContainer'), {
|
||||
direction: 'horizontal',
|
||||
loop: options.loop !== false,
|
||||
autoplay: {
|
||||
delay: options.interval || 8000
|
||||
// Loop is disabled due to the virtual slides option not supporting it.
|
||||
loop: false,
|
||||
autoplay: !options.interactive,
|
||||
keyboard: {
|
||||
enabled: true
|
||||
},
|
||||
// Disable preloading of all images
|
||||
preloadImages: false,
|
||||
// Enable lazy loading
|
||||
lazy: true,
|
||||
loadPrevNext: true,
|
||||
disableOnInteraction: false,
|
||||
preloadImages: true,
|
||||
slidesPerView: 1,
|
||||
slidesPerColumn: 1,
|
||||
initialSlide: options.startIndex || 0,
|
||||
speed: 240
|
||||
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 (layoutManager.mobile) {
|
||||
pause();
|
||||
} else {
|
||||
play();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getSwiperSlideHtmlFromItem(item) {
|
||||
/**
|
||||
* 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({
|
||||
imageUrl: getImgUrl(item),
|
||||
originalImage: getImgUrl(item, true),
|
||||
|
@ -264,11 +316,17 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 += '<div class="swiper-slide" data-imageurl="' + item.imageUrl + '" data-original="' + item.originalImage + '" data-itemid="' + item.Id + '" data-serverid="' + item.ServerId + '">';
|
||||
html += '<img data-src="' + item.imageUrl + '" class="swiper-lazy swiper-slide-img">';
|
||||
html += '<div class="swiper-slide" data-original="' + item.originalImage + '" data-itemid="' + item.Id + '" data-serverid="' + item.ServerId + '">';
|
||||
html += '<div class="slider-zoom-container">';
|
||||
html += '<img src="' + item.originalImage + '" class="swiper-slide-img">';
|
||||
html += '</div>';
|
||||
if (item.title || item.subtitle) {
|
||||
html += '<div class="slideText">';
|
||||
html += '<div class="slideTextInner">';
|
||||
|
@ -290,42 +348,18 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
|
|||
return html;
|
||||
}
|
||||
|
||||
function previousImage() {
|
||||
if (swiperInstance) {
|
||||
swiperInstance.slidePrev();
|
||||
} else {
|
||||
stopInterval();
|
||||
showNextImage(currentIndex - 1);
|
||||
}
|
||||
}
|
||||
|
||||
function nextImage() {
|
||||
if (swiperInstance) {
|
||||
|
||||
if (options.loop === false) {
|
||||
|
||||
if (swiperInstance.activeIndex >= swiperInstance.slides.length - 1) {
|
||||
dialogHelper.close(dlg);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
swiperInstance.slideNext();
|
||||
} else {
|
||||
stopInterval();
|
||||
showNextImage(currentIndex + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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-imageurl'),
|
||||
shareUrl: slide.getAttribute('data-original'),
|
||||
itemId: slide.getAttribute('data-itemid'),
|
||||
serverId: slide.getAttribute('data-serverid')
|
||||
};
|
||||
|
@ -336,8 +370,10 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a download for the currently displayed slide.
|
||||
*/
|
||||
function download() {
|
||||
|
||||
var imageInfo = getCurrentImageInfo();
|
||||
|
||||
require(['fileDownloader'], function (fileDownloader) {
|
||||
|
@ -345,8 +381,10 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Shares the currently displayed slide using the browser's built-in sharing feature.
|
||||
*/
|
||||
function share() {
|
||||
|
||||
var imageInfo = getCurrentImageInfo();
|
||||
|
||||
navigator.share({
|
||||
|
@ -354,20 +392,29 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = !dlg.querySelector('.btnSlideshowPause i').classList.contains("pause");
|
||||
var paused = !dialog.querySelector('.btnSlideshowPause i').classList.contains("pause");
|
||||
if (paused) {
|
||||
play();
|
||||
} else {
|
||||
|
@ -375,8 +422,10 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the dialog and destroys the Swiper instance.
|
||||
*/
|
||||
function onDialogClosed() {
|
||||
|
||||
var swiper = swiperInstance;
|
||||
if (swiper) {
|
||||
swiper.destroy(true, true);
|
||||
|
@ -387,53 +436,38 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
|
|||
document.removeEventListener((window.PointerEvent ? 'pointermove' : 'mousemove'), onPointerMove);
|
||||
}
|
||||
|
||||
function startInterval(options) {
|
||||
|
||||
currentOptions = options;
|
||||
|
||||
stopInterval();
|
||||
createElements(options);
|
||||
|
||||
if (!options.interactive) {
|
||||
currentIntervalMs = options.interval || 11000;
|
||||
showNextImage(options.startIndex || 0, true);
|
||||
}
|
||||
}
|
||||
|
||||
var _osdOpen = false;
|
||||
|
||||
function isOsdOpen() {
|
||||
return _osdOpen;
|
||||
}
|
||||
|
||||
function getOsdBottom() {
|
||||
return dlg.querySelector('.slideshowBottomBar');
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the OSD.
|
||||
*/
|
||||
function showOsd() {
|
||||
|
||||
var bottom = getOsdBottom();
|
||||
var bottom = dialog.querySelector('.slideshowBottomBar');
|
||||
if (bottom) {
|
||||
slideUpToShow(bottom);
|
||||
startHideTimer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the OSD.
|
||||
*/
|
||||
function hideOsd() {
|
||||
|
||||
var bottom = getOsdBottom();
|
||||
var bottom = dialog.querySelector('.slideshowBottomBar');
|
||||
if (bottom) {
|
||||
slideDownToHide(bottom);
|
||||
}
|
||||
}
|
||||
|
||||
var hideTimeout;
|
||||
|
||||
/**
|
||||
* Starts the timer used to automatically hide the OSD.
|
||||
*/
|
||||
function startHideTimer() {
|
||||
stopHideTimer();
|
||||
hideTimeout = setTimeout(hideOsd, 4000);
|
||||
hideTimeout = setTimeout(hideOsd, 3000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the timer used to automatically hide the OSD.
|
||||
*/
|
||||
function stopHideTimer() {
|
||||
if (hideTimeout) {
|
||||
clearTimeout(hideTimeout);
|
||||
|
@ -441,71 +475,76 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
|
|||
}
|
||||
}
|
||||
|
||||
function slideUpToShow(elem) {
|
||||
|
||||
if (!elem.classList.contains('hide')) {
|
||||
/**
|
||||
* Shows the OSD by sliding it into view.
|
||||
* @param {HTMLElement} element - Element containing the OSD.
|
||||
*/
|
||||
function slideUpToShow(element) {
|
||||
if (!element.classList.contains('hide')) {
|
||||
return;
|
||||
}
|
||||
|
||||
_osdOpen = true;
|
||||
elem.classList.remove('hide');
|
||||
element.classList.remove('hide');
|
||||
|
||||
var onFinish = function () {
|
||||
focusManager.focus(elem.querySelector('.btnSlideshowPause'));
|
||||
focusManager.focus(element.querySelector('.btnSlideshowPause'));
|
||||
};
|
||||
|
||||
if (!elem.animate) {
|
||||
if (!element.animate) {
|
||||
onFinish();
|
||||
return;
|
||||
}
|
||||
|
||||
requestAnimationFrame(function () {
|
||||
|
||||
var keyframes = [
|
||||
{ transform: 'translate3d(0,' + elem.offsetHeight + 'px,0)', opacity: '.3', offset: 0 },
|
||||
{ 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' };
|
||||
elem.animate(keyframes, timing).onfinish = onFinish;
|
||||
element.animate(keyframes, timing).onfinish = onFinish;
|
||||
});
|
||||
}
|
||||
|
||||
function slideDownToHide(elem) {
|
||||
|
||||
if (elem.classList.contains('hide')) {
|
||||
/**
|
||||
* 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 () {
|
||||
elem.classList.add('hide');
|
||||
element.classList.add('hide');
|
||||
_osdOpen = false;
|
||||
};
|
||||
|
||||
if (!elem.animate) {
|
||||
if (!element.animate) {
|
||||
onFinish();
|
||||
return;
|
||||
}
|
||||
|
||||
requestAnimationFrame(function () {
|
||||
|
||||
var keyframes = [
|
||||
{ transform: 'translate3d(0,0,0)', opacity: '1', offset: 0 },
|
||||
{ transform: 'translate3d(0,' + elem.offsetHeight + 'px,0)', opacity: '.3', offset: 1 }
|
||||
{ transform: 'translate3d(0,' + element.offsetHeight + 'px,0)', opacity: '.3', offset: 1 }
|
||||
];
|
||||
var timing = { duration: 300, iterations: 1, easing: 'ease-out' };
|
||||
elem.animate(keyframes, timing).onfinish = onFinish;
|
||||
element.animate(keyframes, timing).onfinish = onFinish;
|
||||
});
|
||||
}
|
||||
|
||||
var lastMouseMoveData;
|
||||
|
||||
function onPointerMove(e) {
|
||||
|
||||
var pointerType = e.pointerType || (layoutManager.mobile ? 'touch' : 'mouse');
|
||||
/**
|
||||
* 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 = e.screenX || 0;
|
||||
var eventY = e.screenY || 0;
|
||||
var eventX = event.screenX || 0;
|
||||
var eventY = event.screenY || 0;
|
||||
|
||||
var obj = lastMouseMoveData;
|
||||
if (!obj) {
|
||||
|
@ -528,125 +567,46 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
|
|||
}
|
||||
}
|
||||
|
||||
function onInputCommand(e) {
|
||||
|
||||
switch (e.detail.command) {
|
||||
|
||||
case 'left':
|
||||
if (!isOsdOpen()) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
previousImage();
|
||||
}
|
||||
break;
|
||||
case 'right':
|
||||
if (!isOsdOpen()) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
nextImage();
|
||||
}
|
||||
break;
|
||||
/**
|
||||
* 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':
|
||||
case 'play':
|
||||
case 'playpause':
|
||||
case 'pause':
|
||||
showOsd();
|
||||
break;
|
||||
case 'play':
|
||||
play();
|
||||
break;
|
||||
case 'pause':
|
||||
pause();
|
||||
break;
|
||||
case 'playpause':
|
||||
playPause();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function showNextImage(index, skipPreload) {
|
||||
|
||||
index = Math.max(0, index);
|
||||
if (index >= currentOptions.items.length) {
|
||||
index = 0;
|
||||
}
|
||||
currentIndex = index;
|
||||
|
||||
var options = currentOptions;
|
||||
var items = options.items;
|
||||
var item = items[index];
|
||||
var imgUrl = getImgUrl(item);
|
||||
|
||||
var onSrcLoaded = function () {
|
||||
var cardImageContainer = dlg.querySelector('.slideshowImage');
|
||||
|
||||
var newCardImageContainer = document.createElement('div');
|
||||
newCardImageContainer.className = cardImageContainer.className;
|
||||
|
||||
if (options.cover) {
|
||||
newCardImageContainer.classList.add('slideshowImage-cover');
|
||||
}
|
||||
|
||||
newCardImageContainer.style.backgroundImage = "url('" + imgUrl + "')";
|
||||
newCardImageContainer.classList.add('hide');
|
||||
cardImageContainer.parentNode.appendChild(newCardImageContainer);
|
||||
|
||||
if (options.showTitle) {
|
||||
dlg.querySelector('.slideshowImageText').innerHTML = item.Name;
|
||||
} else {
|
||||
dlg.querySelector('.slideshowImageText').innerHTML = '';
|
||||
}
|
||||
|
||||
newCardImageContainer.classList.remove('hide');
|
||||
var onAnimationFinished = function () {
|
||||
|
||||
var parentNode = cardImageContainer.parentNode;
|
||||
if (parentNode) {
|
||||
parentNode.removeChild(cardImageContainer);
|
||||
}
|
||||
};
|
||||
|
||||
if (newCardImageContainer.animate) {
|
||||
|
||||
var keyframes = [
|
||||
{ opacity: '0', offset: 0 },
|
||||
{ opacity: '1', offset: 1 }
|
||||
];
|
||||
var timing = { duration: 1200, iterations: 1 };
|
||||
newCardImageContainer.animate(keyframes, timing).onfinish = onAnimationFinished;
|
||||
} else {
|
||||
onAnimationFinished();
|
||||
}
|
||||
|
||||
stopInterval();
|
||||
currentTimeout = setTimeout(function () {
|
||||
showNextImage(index + 1, true);
|
||||
|
||||
}, currentIntervalMs);
|
||||
};
|
||||
|
||||
if (!skipPreload) {
|
||||
var img = new Image();
|
||||
img.onload = onSrcLoaded;
|
||||
img.src = imgUrl;
|
||||
} else {
|
||||
onSrcLoaded();
|
||||
}
|
||||
}
|
||||
|
||||
function stopInterval() {
|
||||
if (currentTimeout) {
|
||||
clearTimeout(currentTimeout);
|
||||
currentTimeout = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the slideshow component.
|
||||
*/
|
||||
self.show = function () {
|
||||
startInterval(options);
|
||||
createElements(options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Hides the slideshow element.
|
||||
*/
|
||||
self.hide = function () {
|
||||
|
||||
var dialog = dlg;
|
||||
var dialog = dialog;
|
||||
if (dialog) {
|
||||
|
||||
dialogHelper.close(dialog);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -2,15 +2,11 @@ define(['require', 'globalize', 'appSettings', 'apphost', 'focusManager', 'loadi
|
|||
"use strict";
|
||||
|
||||
function populateLanguages(select, languages) {
|
||||
|
||||
var html = "";
|
||||
|
||||
html += "<option value=''>" + globalize.translate('AnyLanguage') + "</option>";
|
||||
|
||||
for (var i = 0, length = languages.length; i < length; i++) {
|
||||
|
||||
var culture = languages[i];
|
||||
|
||||
html += "<option value='" + culture.ThreeLetterISOLanguageName + "'>" + culture.DisplayName + "</option>";
|
||||
}
|
||||
|
||||
|
@ -18,7 +14,6 @@ define(['require', 'globalize', 'appSettings', 'apphost', 'focusManager', 'loadi
|
|||
}
|
||||
|
||||
function getSubtitleAppearanceObject(context) {
|
||||
|
||||
var appearanceSettings = {};
|
||||
|
||||
appearanceSettings.textSize = context.querySelector('#selectTextSize').value;
|
||||
|
@ -102,14 +97,12 @@ define(['require', 'globalize', 'appSettings', 'apphost', 'focusManager', 'loadi
|
|||
}
|
||||
|
||||
function onSubmit(e) {
|
||||
|
||||
var self = this;
|
||||
var apiClient = connectionManager.getApiClient(self.options.serverId);
|
||||
var userId = self.options.userId;
|
||||
var userSettings = self.options.userSettings;
|
||||
|
||||
userSettings.setUserInfo(userId, apiClient).then(function () {
|
||||
|
||||
var enableSaveConfirmation = self.options.enableSaveConfirmation;
|
||||
save(self, self.options.element, userId, userSettings, apiClient, enableSaveConfirmation);
|
||||
});
|
||||
|
@ -118,6 +111,7 @@ define(['require', 'globalize', 'appSettings', 'apphost', 'focusManager', 'loadi
|
|||
if (e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -197,9 +191,7 @@ define(['require', 'globalize', 'appSettings', 'apphost', 'focusManager', 'loadi
|
|||
var userSettings = self.options.userSettings;
|
||||
|
||||
apiClient.getUser(userId).then(function (user) {
|
||||
|
||||
userSettings.setUserInfo(userId, apiClient).then(function () {
|
||||
|
||||
self.dataLoaded = true;
|
||||
|
||||
var appearanceSettings = userSettings.getSubtitleAppearanceSettings(self.options.appearanceKey);
|
||||
|
@ -214,7 +206,6 @@ define(['require', 'globalize', 'appSettings', 'apphost', 'focusManager', 'loadi
|
|||
};
|
||||
|
||||
SubtitleSettings.prototype.destroy = function () {
|
||||
|
||||
this.options = null;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
<form style="margin:0 auto;">
|
||||
|
||||
<div class="verticalSection">
|
||||
|
||||
<h2 class="sectionTitle">
|
||||
${Subtitles}
|
||||
</h2>
|
||||
|
@ -9,6 +7,7 @@
|
|||
<div class="selectContainer">
|
||||
<select is="emby-select" id="selectSubtitleLanguage" label="${LabelPreferredSubtitleLanguage}"></select>
|
||||
</div>
|
||||
|
||||
<div class="selectContainer">
|
||||
<select is="emby-select" id="selectSubtitlePlaybackMode" label="${LabelSubtitlePlaybackMode}">
|
||||
<option value="Default">${Default}</option>
|
||||
|
@ -23,6 +22,7 @@
|
|||
<div class="fieldDescription subtitlesOnlyForcedHelp subtitlesHelp hide">${OnlyForcedSubtitlesHelp}</div>
|
||||
<div class="fieldDescription subtitlesNoneHelp subtitlesHelp hide">${NoSubtitlesHelp}</div>
|
||||
</div>
|
||||
|
||||
<div class="selectContainer fldBurnIn hide">
|
||||
<select is="emby-select" id="selectSubtitleBurnIn" label="${LabelBurnSubtitles}">
|
||||
<option value="">${Auto}</option>
|
||||
|
@ -34,7 +34,6 @@
|
|||
</div>
|
||||
|
||||
<div class="verticalSection subtitleAppearanceSection hide">
|
||||
|
||||
<h2 class="sectionTitle">
|
||||
${HeaderSubtitleAppearance}
|
||||
</h2>
|
||||
|
@ -61,6 +60,7 @@
|
|||
<option value="extralarge">${ExtraLarge}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="selectContainer">
|
||||
<select is="emby-select" id="selectFont" label="${LabelFont}">
|
||||
<option value="">${Default}</option>
|
||||
|
@ -71,12 +71,15 @@
|
|||
<option value="smallcaps">${SmallCaps}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="inputContainer hide">
|
||||
<input is="emby-input" id="inputTextBackground" label="${LabelTextBackgroundColor}" type="text" />
|
||||
</div>
|
||||
|
||||
<div class="inputContainer hide">
|
||||
<input is="emby-input" id="inputTextColor" label="${LabelTextColor}" type="text" />
|
||||
</div>
|
||||
|
||||
<div class="selectContainer">
|
||||
<select is="emby-select" id="selectDropShadow" label="${LabelDropShadow}">
|
||||
<option value="none">${None}</option>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
define(['playbackManager', 'text!./subtitlesync.template.html', 'css!./subtitlesync'], function (playbackManager, template, css) {
|
||||
define(['playbackManager', 'layoutManager', 'text!./subtitlesync.template.html', 'css!./subtitlesync'], function (playbackManager, layoutManager, template, css) {
|
||||
"use strict";
|
||||
|
||||
var player;
|
||||
|
@ -10,6 +10,7 @@ define(['playbackManager', 'text!./subtitlesync.template.html', 'css!./subtitles
|
|||
function init(instance) {
|
||||
|
||||
var parent = document.createElement('div');
|
||||
document.body.appendChild(parent);
|
||||
parent.innerHTML = template;
|
||||
|
||||
subtitleSyncSlider = parent.querySelector(".subtitleSyncSlider");
|
||||
|
@ -17,11 +18,19 @@ define(['playbackManager', 'text!./subtitlesync.template.html', 'css!./subtitles
|
|||
subtitleSyncCloseButton = parent.querySelector(".subtitleSync-closeButton");
|
||||
subtitleSyncContainer = parent.querySelector(".subtitleSyncContainer");
|
||||
|
||||
if (layoutManager.tv) {
|
||||
subtitleSyncSlider.classList.add("focusable");
|
||||
// HACK: Delay to give time for registered element attach (Firefox)
|
||||
setTimeout(function () {
|
||||
subtitleSyncSlider.enableKeyboardDragging();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
subtitleSyncContainer.classList.add("hide");
|
||||
|
||||
subtitleSyncTextField.updateOffset = function(offset) {
|
||||
this.textContent = offset + "s";
|
||||
}
|
||||
};
|
||||
|
||||
subtitleSyncTextField.addEventListener("keypress", function(event) {
|
||||
|
||||
|
@ -57,7 +66,7 @@ define(['playbackManager', 'text!./subtitlesync.template.html', 'css!./subtitles
|
|||
subtitleSyncSlider.updateOffset = function(percent) {
|
||||
// default value is 0s = 50%
|
||||
this.value = percent === undefined ? 50 : percent;
|
||||
}
|
||||
};
|
||||
|
||||
subtitleSyncSlider.addEventListener("change", function () {
|
||||
// set new offset
|
||||
|
@ -87,8 +96,6 @@ define(['playbackManager', 'text!./subtitlesync.template.html', 'css!./subtitles
|
|||
SubtitleSync.prototype.toggle("forceToHide");
|
||||
});
|
||||
|
||||
document.body.appendChild(parent);
|
||||
|
||||
instance.element = parent;
|
||||
}
|
||||
|
||||
|
@ -125,7 +132,7 @@ define(['playbackManager', 'text!./subtitlesync.template.html', 'css!./subtitles
|
|||
elem.parentNode.removeChild(elem);
|
||||
this.element = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
SubtitleSync.prototype.toggle = function(action) {
|
||||
|
||||
|
@ -159,7 +166,7 @@ define(['playbackManager', 'text!./subtitlesync.template.html', 'css!./subtitles
|
|||
}
|
||||
/* eslint-enable no-fallthrough */
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return SubtitleSync;
|
||||
});
|
||||
|
|
3
src/config.example.json
Normal file
3
src/config.example.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"multiserver": true
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globalize", "loading", "connectionManager", "playMethodHelper", "cardBuilder", "imageLoader", "components/activitylog", "scripts/imagehelper", "indicators", "humanedate", "listViewStyle", "emby-button", "flexStyles", "emby-button", "emby-itemscontainer"], function (datetime, events, itemHelper, serverNotifications, dom, globalize, loading, connectionManager, playMethodHelper, cardBuilder, imageLoader, ActivityLog, imageHelper, indicators) {
|
||||
define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globalize", "date-fns", "dfnshelper", "loading", "connectionManager", "playMethodHelper", "cardBuilder", "imageLoader", "components/activitylog", "scripts/imagehelper", "indicators", "listViewStyle", "emby-button", "flexStyles", "emby-button", "emby-itemscontainer"], function (datetime, events, itemHelper, serverNotifications, dom, globalize, datefns, dfnshelper, loading, connectionManager, playMethodHelper, cardBuilder, imageLoader, ActivityLog, imageHelper, indicators) {
|
||||
"use strict";
|
||||
|
||||
function showPlaybackInfo(btn, session) {
|
||||
|
@ -467,10 +467,11 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa
|
|||
getNowPlayingName: function (session) {
|
||||
var imgUrl = "";
|
||||
var nowPlayingItem = session.NowPlayingItem;
|
||||
|
||||
// FIXME: It seems that, sometimes, server sends date in the future, so date-fns displays messages like 'in less than a minute'. We should fix
|
||||
// how dates are returned by the server when the session is active and show something like 'Active now', instead of past/future sentences
|
||||
if (!nowPlayingItem) {
|
||||
return {
|
||||
html: "Last seen " + humaneDate(session.LastActivityDate),
|
||||
html: globalize.translate("LastSeen", datefns.formatDistanceToNow(Date.parse(session.LastActivityDate), dfnshelper.localeWithSuffix)),
|
||||
image: imgUrl
|
||||
};
|
||||
}
|
||||
|
|
|
@ -29,5 +29,5 @@ define(["datetime", "loading", "apphost", "listViewStyle", "emby-button", "flexS
|
|||
loading.hide();
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -49,12 +49,12 @@ define(["loading", "libraryMenu", "globalize", "listViewStyle", "emby-button"],
|
|||
}
|
||||
page.querySelector(".notificationList").innerHTML = html;
|
||||
loading.hide();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
return function(view, params) {
|
||||
view.addEventListener("viewshow", function() {
|
||||
reload(view);
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -84,7 +84,7 @@ define(["jQuery", "loading", "libraryMenu", "globalize", "connectionManager", "e
|
|||
}
|
||||
|
||||
if (installedPlugin) {
|
||||
var currentVersionText = globalize.translate("MessageYouHaveVersionInstalled").replace("{0}", "<strong>" + installedPlugin.Version + "</strong>");
|
||||
var currentVersionText = globalize.translate("MessageYouHaveVersionInstalled", "<strong>" + installedPlugin.Version + "</strong>");
|
||||
$("#pCurrentVersion", page).show().html(currentVersionText);
|
||||
} else {
|
||||
$("#pCurrentVersion", page).hide().html("");
|
||||
|
|
|
@ -116,7 +116,7 @@ define(["loading", "libraryMenu", "globalize", "cardStyle", "emby-button", "emby
|
|||
return ip.Id == plugin.guid;
|
||||
})[0];
|
||||
html += "<div class='cardText cardText-secondary'>";
|
||||
html += installedPlugin ? globalize.translate("LabelVersionInstalled").replace("{0}", installedPlugin.Version) : " ";
|
||||
html += installedPlugin ? globalize.translate("LabelVersionInstalled", installedPlugin.Version) : " ";
|
||||
html += "</div>";
|
||||
html += "</div>";
|
||||
html += "</div>";
|
||||
|
|
|
@ -2,7 +2,7 @@ define(["loading", "libraryMenu", "dom", "globalize", "cardStyle", "emby-button"
|
|||
"use strict";
|
||||
|
||||
function deletePlugin(page, uniqueid, name) {
|
||||
var msg = globalize.translate("UninstallPluginConfirmation").replace("{0}", name);
|
||||
var msg = globalize.translate("UninstallPluginConfirmation", name);
|
||||
|
||||
require(["confirm"], function (confirm) {
|
||||
confirm({
|
||||
|
|
|
@ -75,17 +75,19 @@ define(["jQuery", "loading", "datetime", "dom", "globalize", "emby-input", "emby
|
|||
html += "</div>";
|
||||
context.querySelector(".taskTriggers").innerHTML = html;
|
||||
},
|
||||
// TODO: Replace this mess with date-fns and remove datetime completely
|
||||
getTriggerFriendlyName: function (trigger) {
|
||||
if ("DailyTrigger" == trigger.Type) {
|
||||
return "Daily at " + ScheduledTaskPage.getDisplayTime(trigger.TimeOfDayTicks);
|
||||
return globalize.translate("DailyAt", ScheduledTaskPage.getDisplayTime(trigger.TimeOfDayTicks));
|
||||
}
|
||||
|
||||
if ("WeeklyTrigger" == trigger.Type) {
|
||||
return trigger.DayOfWeek + "s at " + ScheduledTaskPage.getDisplayTime(trigger.TimeOfDayTicks);
|
||||
// TODO: The day of week isn't localised as well
|
||||
return globalize.translate("WeeklyAt", trigger.DayOfWeek, ScheduledTaskPage.getDisplayTime(trigger.TimeOfDayTicks));
|
||||
}
|
||||
|
||||
if ("SystemEventTrigger" == trigger.Type && "WakeFromSleep" == trigger.SystemEvent) {
|
||||
return "On wake from sleep";
|
||||
return globalize.translate("OnWakeFromSleep");
|
||||
}
|
||||
|
||||
if (trigger.Type == "IntervalTrigger") {
|
||||
|
@ -93,23 +95,23 @@ define(["jQuery", "loading", "datetime", "dom", "globalize", "emby-input", "emby
|
|||
var hours = trigger.IntervalTicks / 36e9;
|
||||
|
||||
if (hours == 0.25) {
|
||||
return "Every 15 minutes";
|
||||
return globalize.translate("EveryXMinutes", "15");
|
||||
}
|
||||
if (hours == 0.5) {
|
||||
return "Every 30 minutes";
|
||||
return globalize.translate("EveryXMinutes", "30");
|
||||
}
|
||||
if (hours == 0.75) {
|
||||
return "Every 45 minutes";
|
||||
return globalize.translate("EveryXMinutes", "45");
|
||||
}
|
||||
if (hours == 1) {
|
||||
return "Every hour";
|
||||
return globalize.translate("EveryHour");
|
||||
}
|
||||
|
||||
return "Every " + hours + " hours";
|
||||
return globalize.translate("EveryXHours", hours);
|
||||
}
|
||||
|
||||
if (trigger.Type == "StartupTrigger") {
|
||||
return "On application startup";
|
||||
return globalize.translate("OnApplicationStartup");
|
||||
}
|
||||
|
||||
return trigger.Type;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
define(["jQuery", "loading", "events", "globalize", "serverNotifications", "humanedate", "listViewStyle", "emby-button"], function($, loading, events, globalize, serverNotifications) {
|
||||
define(["jQuery", "loading", "events", "globalize", "serverNotifications", "date-fns", "dfnshelper", "listViewStyle", "emby-button"], function ($, loading, events, globalize, serverNotifications, datefns, dfnshelper) {
|
||||
"use strict";
|
||||
|
||||
function reloadList(page) {
|
||||
|
@ -7,7 +7,7 @@ define(["jQuery", "loading", "events", "globalize", "serverNotifications", "huma
|
|||
}).then(function(tasks) {
|
||||
populateList(page, tasks);
|
||||
loading.hide();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function populateList(page, tasks) {
|
||||
|
@ -66,7 +66,10 @@ define(["jQuery", "loading", "events", "globalize", "serverNotifications", "huma
|
|||
var html = "";
|
||||
if (task.State === "Idle") {
|
||||
if (task.LastExecutionResult) {
|
||||
html += globalize.translate("LabelScheduledTaskLastRan").replace("{0}", humaneDate(task.LastExecutionResult.EndTimeUtc)).replace("{1}", humaneElapsed(task.LastExecutionResult.StartTimeUtc, task.LastExecutionResult.EndTimeUtc));
|
||||
var endtime = Date.parse(task.LastExecutionResult.EndTimeUtc);
|
||||
var starttime = Date.parse(task.LastExecutionResult.StartTimeUtc);
|
||||
html += globalize.translate("LabelScheduledTaskLastRan", datefns.formatDistanceToNow(endtime, dfnshelper.localeWithSuffix),
|
||||
datefns.formatDistance(starttime, endtime, { locale: dfnshelper.getLocale() }));
|
||||
if (task.LastExecutionResult.Status === "Failed") {
|
||||
html += " <span style='color:#FF0000;'>(" + globalize.translate("LabelFailed") + ")</span>";
|
||||
} else if (task.LastExecutionResult.Status === "Cancelled") {
|
||||
|
@ -152,7 +155,7 @@ define(["jQuery", "loading", "events", "globalize", "serverNotifications", "huma
|
|||
ApiClient.startScheduledTask(id).then(function() {
|
||||
updateTaskButton(button, "Running");
|
||||
reloadList(view);
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
$(".divScheduledTasks", view).on("click", ".btnStopTask", function() {
|
||||
|
@ -161,7 +164,7 @@ define(["jQuery", "loading", "events", "globalize", "serverNotifications", "huma
|
|||
ApiClient.stopScheduledTask(id).then(function() {
|
||||
updateTaskButton(button, "");
|
||||
reloadList(view);
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
view.addEventListener("viewbeforehide", function() {
|
||||
|
@ -175,5 +178,5 @@ define(["jQuery", "loading", "events", "globalize", "serverNotifications", "huma
|
|||
reloadList(view);
|
||||
events.on(serverNotifications, "ScheduledTasksInfo", onScheduledTasksUpdate);
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
define(["loading", "dom", "libraryMenu", "globalize", "scripts/imagehelper", "humanedate", "emby-button", "emby-itemscontainer", "cardStyle"], function (loading, dom, libraryMenu, globalize, imageHelper) {
|
||||
define(["loading", "dom", "libraryMenu", "globalize", "scripts/imagehelper", "date-fns", "dfnshelper", "emby-button", "emby-itemscontainer", "cardStyle"], function (loading, dom, libraryMenu, globalize, imageHelper, datefns, dfnshelper) {
|
||||
"use strict";
|
||||
|
||||
function canDelete(deviceId) {
|
||||
|
@ -103,7 +103,7 @@ define(["loading", "dom", "libraryMenu", "globalize", "scripts/imagehelper", "hu
|
|||
|
||||
if (device.LastUserName) {
|
||||
deviceHtml += device.LastUserName;
|
||||
deviceHtml += ", " + humaneDate(device.DateLastActivity);
|
||||
deviceHtml += ", " + datefns.formatDistanceToNow(Date.parse(device.DateLastActivity), dfnshelper.localeWithSuffix);
|
||||
}
|
||||
|
||||
deviceHtml += " ";
|
||||
|
|
|
@ -258,14 +258,14 @@ define(["jQuery", "loading", "fnchecked", "emby-select", "emby-button", "emby-in
|
|||
|
||||
html += "<div>";
|
||||
html += '<a is="emby-linkbutton" href="#" class="lnkEditSubProfile" data-profileindex="' + i + '">';
|
||||
html += "<p>" + Globalize.translate("ValueContainer").replace("{0}", profile.Container || allText) + "</p>";
|
||||
html += "<p>" + Globalize.translate("ValueContainer", profile.Container || allText) + "</p>";
|
||||
|
||||
if ("Video" == profile.Type) {
|
||||
html += "<p>" + Globalize.translate("ValueVideoCodec").replace("{0}", profile.VideoCodec || allText) + "</p>";
|
||||
html += "<p>" + Globalize.translate("ValueAudioCodec").replace("{0}", profile.AudioCodec || allText) + "</p>";
|
||||
html += "<p>" + Globalize.translate("ValueVideoCodec", profile.VideoCodec || allText) + "</p>";
|
||||
html += "<p>" + Globalize.translate("ValueAudioCodec", profile.AudioCodec || allText) + "</p>";
|
||||
} else {
|
||||
if ("Audio" == profile.Type) {
|
||||
html += "<p>" + Globalize.translate("ValueCodec").replace("{0}", profile.AudioCodec || allText) + "</p>";
|
||||
html += "<p>" + Globalize.translate("ValueCodec", profile.AudioCodec || allText) + "</p>";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -319,14 +319,14 @@ define(["jQuery", "loading", "fnchecked", "emby-select", "emby-button", "emby-in
|
|||
html += "<div>";
|
||||
html += '<a is="emby-linkbutton" href="#" class="lnkEditSubProfile" data-profileindex="' + i + '">';
|
||||
html += "<p>Protocol: " + (profile.Protocol || "Http") + "</p>";
|
||||
html += "<p>" + Globalize.translate("ValueContainer").replace("{0}", profile.Container || allText) + "</p>";
|
||||
html += "<p>" + Globalize.translate("ValueContainer", profile.Container || allText) + "</p>";
|
||||
|
||||
if ("Video" == profile.Type) {
|
||||
html += "<p>" + Globalize.translate("ValueVideoCodec").replace("{0}", profile.VideoCodec || allText) + "</p>";
|
||||
html += "<p>" + Globalize.translate("ValueAudioCodec").replace("{0}", profile.AudioCodec || allText) + "</p>";
|
||||
html += "<p>" + Globalize.translate("ValueVideoCodec", profile.VideoCodec || allText) + "</p>";
|
||||
html += "<p>" + Globalize.translate("ValueAudioCodec", profile.AudioCodec || allText) + "</p>";
|
||||
} else {
|
||||
if ("Audio" == profile.Type) {
|
||||
html += "<p>" + Globalize.translate("ValueCodec").replace("{0}", profile.AudioCodec || allText) + "</p>";
|
||||
html += "<p>" + Globalize.translate("ValueCodec", profile.AudioCodec || allText) + "</p>";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -404,11 +404,11 @@ define(["jQuery", "loading", "fnchecked", "emby-select", "emby-button", "emby-in
|
|||
|
||||
html += "<div>";
|
||||
html += '<a is="emby-linkbutton" href="#" class="lnkEditSubProfile" data-profileindex="' + i + '">';
|
||||
html += "<p>" + Globalize.translate("ValueContainer").replace("{0}", profile.Container || allText) + "</p>";
|
||||
html += "<p>" + Globalize.translate("ValueContainer", profile.Container || allText) + "</p>";
|
||||
|
||||
if (profile.Conditions && profile.Conditions.length) {
|
||||
html += "<p>";
|
||||
html += Globalize.translate("ValueConditions").replace("{0}", profile.Conditions.map(function (c) {
|
||||
html += Globalize.translate("ValueConditions", profile.Conditions.map(function (c) {
|
||||
return c.Property;
|
||||
}).join(", "));
|
||||
html += "</p>";
|
||||
|
@ -476,11 +476,11 @@ define(["jQuery", "loading", "fnchecked", "emby-select", "emby-button", "emby-in
|
|||
|
||||
html += "<div>";
|
||||
html += '<a is="emby-linkbutton" href="#" class="lnkEditSubProfile" data-profileindex="' + i + '">';
|
||||
html += "<p>" + Globalize.translate("ValueCodec").replace("{0}", profile.Codec || allText) + "</p>";
|
||||
html += "<p>" + Globalize.translate("ValueCodec", profile.Codec || allText) + "</p>";
|
||||
|
||||
if (profile.Conditions && profile.Conditions.length) {
|
||||
html += "<p>";
|
||||
html += Globalize.translate("ValueConditions").replace("{0}", profile.Conditions.map(function (c) {
|
||||
html += Globalize.translate("ValueConditions", profile.Conditions.map(function (c) {
|
||||
return c.Property;
|
||||
}).join(", "));
|
||||
html += "</p>";
|
||||
|
@ -547,20 +547,20 @@ define(["jQuery", "loading", "fnchecked", "emby-select", "emby-button", "emby-in
|
|||
|
||||
html += "<div>";
|
||||
html += '<a is="emby-linkbutton" href="#" class="lnkEditSubProfile" data-profileindex="' + i + '">';
|
||||
html += "<p>" + Globalize.translate("ValueContainer").replace("{0}", profile.Container || allText) + "</p>";
|
||||
html += "<p>" + Globalize.translate("ValueContainer", profile.Container || allText) + "</p>";
|
||||
|
||||
if ("Video" == profile.Type) {
|
||||
html += "<p>" + Globalize.translate("ValueVideoCodec").replace("{0}", profile.VideoCodec || allText) + "</p>";
|
||||
html += "<p>" + Globalize.translate("ValueAudioCodec").replace("{0}", profile.AudioCodec || allText) + "</p>";
|
||||
html += "<p>" + Globalize.translate("ValueVideoCodec", profile.VideoCodec || allText) + "</p>";
|
||||
html += "<p>" + Globalize.translate("ValueAudioCodec", profile.AudioCodec || allText) + "</p>";
|
||||
} else {
|
||||
if ("Audio" == profile.Type) {
|
||||
html += "<p>" + Globalize.translate("ValueCodec").replace("{0}", profile.AudioCodec || allText) + "</p>";
|
||||
html += "<p>" + Globalize.translate("ValueCodec", profile.AudioCodec || allText) + "</p>";
|
||||
}
|
||||
}
|
||||
|
||||
if (profile.Conditions && profile.Conditions.length) {
|
||||
html += "<p>";
|
||||
html += Globalize.translate("ValueConditions").replace("{0}", profile.Conditions.map(function (c) {
|
||||
html += Globalize.translate("ValueConditions", profile.Conditions.map(function (c) {
|
||||
return c.Property;
|
||||
}).join(", "));
|
||||
html += "</p>";
|
||||
|
|
|
@ -14,6 +14,7 @@ define(["jQuery", "loading", "globalize", "dom", "libraryMenu"], function ($, lo
|
|||
$("#txtVaapiDevice", page).val(config.VaapiDevice || "");
|
||||
page.querySelector("#selectEncoderPreset").value = config.EncoderPreset || "";
|
||||
page.querySelector("#txtH264Crf").value = config.H264Crf || "";
|
||||
page.querySelector("#selectDeinterlaceMethod").value = config.DeinterlaceMethod || "";
|
||||
page.querySelector("#chkEnableSubtitleExtraction").checked = config.EnableSubtitleExtraction || false;
|
||||
page.querySelector("#chkEnableThrottling").checked = config.EnableThrottling || false;
|
||||
page.querySelector("#selectVideoDecoder").dispatchEvent(new CustomEvent("change", {
|
||||
|
@ -58,6 +59,7 @@ define(["jQuery", "loading", "globalize", "dom", "libraryMenu"], function ($, lo
|
|||
config.VaapiDevice = $("#txtVaapiDevice", form).val();
|
||||
config.EncoderPreset = form.querySelector("#selectEncoderPreset").value;
|
||||
config.H264Crf = parseInt(form.querySelector("#txtH264Crf").value || "0");
|
||||
config.DeinterlaceMethod = form.querySelector("#selectDeinterlaceMethod").value;
|
||||
config.EnableSubtitleExtraction = form.querySelector("#chkEnableSubtitleExtraction").checked;
|
||||
config.EnableThrottling = form.querySelector("#chkEnableThrottling").checked;
|
||||
config.HardwareDecodingCodecs = Array.prototype.map.call(Array.prototype.filter.call(form.querySelectorAll(".chkDecodeCodec"), function (c) {
|
||||
|
|
|
@ -591,7 +591,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti
|
|||
try {
|
||||
var birthday = datetime.parseISO8601Date(item.PremiereDate, true).toDateString();
|
||||
itemBirthday.classList.remove("hide");
|
||||
itemBirthday.innerHTML = globalize.translate("BirthDateValue").replace("{0}", birthday);
|
||||
itemBirthday.innerHTML = globalize.translate("BirthDateValue", birthday);
|
||||
} catch (err) {
|
||||
itemBirthday.classList.add("hide");
|
||||
}
|
||||
|
@ -605,7 +605,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti
|
|||
try {
|
||||
var deathday = datetime.parseISO8601Date(item.EndDate, true).toDateString();
|
||||
itemDeathDate.classList.remove("hide");
|
||||
itemDeathDate.innerHTML = globalize.translate("DeathDateValue").replace("{0}", deathday);
|
||||
itemDeathDate.innerHTML = globalize.translate("DeathDateValue", deathday);
|
||||
} catch (err) {
|
||||
itemDeathDate.classList.add("hide");
|
||||
}
|
||||
|
@ -618,7 +618,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti
|
|||
if ("Person" == item.Type && item.ProductionLocations && item.ProductionLocations.length) {
|
||||
var gmap = '<a is="emby-linkbutton" class="button-link textlink" target="_blank" href="https://maps.google.com/maps?q=' + item.ProductionLocations[0] + '">' + item.ProductionLocations[0] + "</a>";
|
||||
itemBirthLocation.classList.remove("hide");
|
||||
itemBirthLocation.innerHTML = globalize.translate("BirthPlaceValue").replace("{0}", gmap);
|
||||
itemBirthLocation.innerHTML = globalize.translate("BirthPlaceValue", gmap);
|
||||
} else {
|
||||
itemBirthLocation.classList.add("hide");
|
||||
}
|
||||
|
@ -703,26 +703,9 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti
|
|||
}
|
||||
}
|
||||
|
||||
function renderUserInfo(page, item) {
|
||||
var lastPlayedElement = page.querySelector(".itemLastPlayed");
|
||||
|
||||
if (item.UserData && item.UserData.LastPlayedDate) {
|
||||
lastPlayedElement.classList.remove("hide");
|
||||
var datePlayed = datetime.parseISO8601Date(item.UserData.LastPlayedDate);
|
||||
lastPlayedElement.innerHTML = globalize.translate("DatePlayed") + ": " + datetime.toLocaleDateString(datePlayed) + " " + datetime.getDisplayTime(datePlayed);
|
||||
} else {
|
||||
lastPlayedElement.classList.add("hide");
|
||||
}
|
||||
}
|
||||
|
||||
function renderLinks(linksElem, item) {
|
||||
var html = [];
|
||||
|
||||
if (item.DateCreated && itemHelper.enableDateAddedDisplay(item)) {
|
||||
var dateCreated = datetime.parseISO8601Date(item.DateCreated);
|
||||
html.push(globalize.translate("AddedOnValue", datetime.toLocaleDateString(dateCreated) + " " + datetime.getDisplayTime(dateCreated)));
|
||||
}
|
||||
|
||||
var links = [];
|
||||
|
||||
if (!layoutManager.tv && item.HomePageUrl) {
|
||||
|
@ -736,7 +719,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti
|
|||
}
|
||||
|
||||
if (links.length) {
|
||||
html.push(globalize.translate("LinksValue", links.join(", ")));
|
||||
html.push(links.join(", "));
|
||||
}
|
||||
|
||||
linksElem.innerHTML = html.join(", ");
|
||||
|
@ -1032,13 +1015,17 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti
|
|||
context: context
|
||||
}) + '">' + p.Name + "</a>";
|
||||
}).join(", ");
|
||||
var elem = page.querySelector(".genres");
|
||||
elem.innerHTML = globalize.translate(genres.length > 1 ? "GenresValue" : "GenreValue", html);
|
||||
|
||||
var genresLabel = page.querySelector(".genresLabel");
|
||||
genresLabel.innerHTML = globalize.translate(genres.length > 1 ? "Genres" : "Genre");
|
||||
var genresValue = page.querySelector(".genres");
|
||||
genresValue.innerHTML = html;
|
||||
|
||||
var genresGroup = page.querySelector(".genresGroup");
|
||||
if (genres.length) {
|
||||
elem.classList.remove("hide");
|
||||
genresGroup.classList.remove("hide");
|
||||
} else {
|
||||
elem.classList.add("hide");
|
||||
genresGroup.classList.add("hide");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1056,13 +1043,17 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti
|
|||
context: context
|
||||
}) + '">' + p.Name + "</a>";
|
||||
}).join(", ");
|
||||
var elem = page.querySelector(".directors");
|
||||
elem.innerHTML = globalize.translate(directors.length > 1 ? "DirectorsValue" : "DirectorValue", html);
|
||||
|
||||
var directorsLabel = page.querySelector(".directorsLabel");
|
||||
directorsLabel.innerHTML = globalize.translate(directors.length > 1 ? "Directors" : "Director");
|
||||
var directorsValue = page.querySelector(".directors");
|
||||
directorsValue.innerHTML = html;
|
||||
|
||||
var directorsGroup = page.querySelector(".directorsGroup");
|
||||
if (directors.length) {
|
||||
elem.classList.remove("hide");
|
||||
directorsGroup.classList.remove("hide");
|
||||
} else {
|
||||
elem.classList.add("hide");
|
||||
directorsGroup.classList.add("hide");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1120,7 +1111,6 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti
|
|||
|
||||
reloadUserDataButtons(page, item);
|
||||
renderLinks(externalLinksElem, item);
|
||||
renderUserInfo(page, item);
|
||||
renderTags(page, item);
|
||||
renderSeriesAirTime(page, item, isStatic);
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ define(["globalize", "loading", "libraryMenu", "emby-checkbox", "emby-button", "
|
|||
}, {
|
||||
href: "metadatanfo.html",
|
||||
name: Globalize.translate("TabNfoSettings")
|
||||
}]
|
||||
}];
|
||||
}
|
||||
|
||||
return function(view, params) {
|
||||
|
@ -27,7 +27,7 @@ define(["globalize", "loading", "libraryMenu", "emby-checkbox", "emby-button", "
|
|||
view.querySelector("#chkSaveMetadataHidden").checked = config.SaveMetadataHidden;
|
||||
});
|
||||
ApiClient.getNamedConfiguration("metadata").then(function(metadata) {
|
||||
loadMetadataConfig(this, metadata)
|
||||
loadMetadataConfig(this, metadata);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -67,5 +67,5 @@ define(["globalize", "loading", "libraryMenu", "emby-checkbox", "emby-button", "
|
|||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -48,7 +48,7 @@ define(["layoutManager", "cardBuilder", "apphost", "imageLoader", "loading", "sc
|
|||
}
|
||||
|
||||
function getBackdropShape() {
|
||||
return enableScrollX() ? "overflowBackdrop" : "backdrop"
|
||||
return enableScrollX() ? "overflowBackdrop" : "backdrop";
|
||||
}
|
||||
|
||||
function renderActiveRecordings(context, promise) {
|
||||
|
|
|
@ -7,10 +7,10 @@ define(["jQuery", "dom", "loading", "libraryMenu", "listViewStyle"], function($,
|
|||
html += "<option value=''></option>";
|
||||
for (var i = 0, length = languages.length; i < length; i++) {
|
||||
var culture = languages[i];
|
||||
html += "<option value='" + culture.TwoLetterISOLanguageName + "'>" + culture.DisplayName + "</option>"
|
||||
html += "<option value='" + culture.TwoLetterISOLanguageName + "'>" + culture.DisplayName + "</option>";
|
||||
}
|
||||
select.innerHTML = html
|
||||
})
|
||||
select.innerHTML = html;
|
||||
});
|
||||
}
|
||||
|
||||
function populateCountries(select) {
|
||||
|
@ -19,25 +19,25 @@ define(["jQuery", "dom", "loading", "libraryMenu", "listViewStyle"], function($,
|
|||
html += "<option value=''></option>";
|
||||
for (var i = 0, length = allCountries.length; i < length; i++) {
|
||||
var culture = allCountries[i];
|
||||
html += "<option value='" + culture.TwoLetterISORegionName + "'>" + culture.DisplayName + "</option>"
|
||||
html += "<option value='" + culture.TwoLetterISORegionName + "'>" + culture.DisplayName + "</option>";
|
||||
}
|
||||
select.innerHTML = html
|
||||
})
|
||||
select.innerHTML = html;
|
||||
});
|
||||
}
|
||||
|
||||
function loadPage(page) {
|
||||
var promises = [ApiClient.getServerConfiguration(), populateLanguages(page.querySelector("#selectLanguage")), populateCountries(page.querySelector("#selectCountry"))];
|
||||
Promise.all(promises).then(function(responses) {
|
||||
var config = responses[0];
|
||||
page.querySelector("#selectLanguage").value = config.PreferredMetadataLanguage || "", page.querySelector("#selectCountry").value = config.MetadataCountryCode || "", loading.hide()
|
||||
})
|
||||
page.querySelector("#selectLanguage").value = config.PreferredMetadataLanguage || "", page.querySelector("#selectCountry").value = config.MetadataCountryCode || "", loading.hide();
|
||||
});
|
||||
}
|
||||
|
||||
function onSubmit() {
|
||||
var form = this;
|
||||
return loading.show(), ApiClient.getServerConfiguration().then(function(config) {
|
||||
config.PreferredMetadataLanguage = form.querySelector("#selectLanguage").value, config.MetadataCountryCode = form.querySelector("#selectCountry").value, ApiClient.updateServerConfiguration(config).then(Dashboard.processServerConfigurationUpdateResult)
|
||||
}), !1
|
||||
config.PreferredMetadataLanguage = form.querySelector("#selectLanguage").value, config.MetadataCountryCode = form.querySelector("#selectCountry").value, ApiClient.updateServerConfiguration(config).then(Dashboard.processServerConfigurationUpdateResult);
|
||||
}), !1;
|
||||
}
|
||||
|
||||
function getTabs() {
|
||||
|
@ -53,12 +53,12 @@ define(["jQuery", "dom", "loading", "libraryMenu", "listViewStyle"], function($,
|
|||
}, {
|
||||
href: "metadatanfo.html",
|
||||
name: Globalize.translate("TabNfoSettings")
|
||||
}]
|
||||
}];
|
||||
}
|
||||
|
||||
$(document).on("pageinit", "#metadataImagesConfigurationPage", function() {
|
||||
$(".metadataImagesConfigurationForm").off("submit", onSubmit).on("submit", onSubmit)
|
||||
$(".metadataImagesConfigurationForm").off("submit", onSubmit).on("submit", onSubmit);
|
||||
}).on("pageshow", "#metadataImagesConfigurationPage", function() {
|
||||
libraryMenu.setTabs("metadata", 2, getTabs), loading.show(), loadPage(this)
|
||||
})
|
||||
libraryMenu.setTabs("metadata", 2, getTabs), loading.show(), loadPage(this);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -91,21 +91,21 @@ define(["events", "layoutManager", "inputManager", "userSettings", "libraryMenu"
|
|||
|
||||
switch (recommendation.RecommendationType) {
|
||||
case "SimilarToRecentlyPlayed":
|
||||
title = Globalize.translate("RecommendationBecauseYouWatched").replace("{0}", recommendation.BaselineItemName);
|
||||
title = Globalize.translate("RecommendationBecauseYouWatched", recommendation.BaselineItemName);
|
||||
break;
|
||||
|
||||
case "SimilarToLikedItem":
|
||||
title = Globalize.translate("RecommendationBecauseYouLike").replace("{0}", recommendation.BaselineItemName);
|
||||
title = Globalize.translate("RecommendationBecauseYouLike", recommendation.BaselineItemName);
|
||||
break;
|
||||
|
||||
case "HasDirectorFromRecentlyPlayed":
|
||||
case "HasLikedDirector":
|
||||
title = Globalize.translate("RecommendationDirectedBy").replace("{0}", recommendation.BaselineItemName);
|
||||
title = Globalize.translate("RecommendationDirectedBy", recommendation.BaselineItemName);
|
||||
break;
|
||||
|
||||
case "HasActorFromRecentlyPlayed":
|
||||
case "HasLikedActor":
|
||||
title = Globalize.translate("RecommendationStarring").replace("{0}", recommendation.BaselineItemName);
|
||||
title = Globalize.translate("RecommendationStarring", recommendation.BaselineItemName);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ define(["jQuery", "loading", "libraryMenu"], function ($, loading, libraryMenu)
|
|||
}
|
||||
|
||||
$(document).on("pageinit", "#playbackConfigurationPage", function () {
|
||||
$(".playbackConfigurationForm").off("submit", onSubmit).on("submit", onSubmit)
|
||||
$(".playbackConfigurationForm").off("submit", onSubmit).on("submit", onSubmit);
|
||||
}).on("pageshow", "#playbackConfigurationPage", function () {
|
||||
loading.show();
|
||||
libraryMenu.setTabs("playback", 1, getTabs);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
define(["displaySettings", "userSettingsBuilder", "userSettings", "autoFocuser"], function (DisplaySettings, userSettingsBuilder, currentUserSettings, autoFocuser) {
|
||||
define(["displaySettings", "userSettings", "autoFocuser"], function (DisplaySettings, userSettings, autoFocuser) {
|
||||
"use strict";
|
||||
|
||||
return function (view, params) {
|
||||
|
@ -11,7 +11,7 @@ define(["displaySettings", "userSettingsBuilder", "userSettings", "autoFocuser"]
|
|||
var settingsInstance;
|
||||
var hasChanges;
|
||||
var userId = params.userId || ApiClient.getCurrentUserId();
|
||||
var userSettings = userId === ApiClient.getCurrentUserId() ? currentUserSettings : new userSettingsBuilder();
|
||||
var currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new userSettings();
|
||||
view.addEventListener("viewshow", function () {
|
||||
window.addEventListener("beforeunload", onBeforeUnload);
|
||||
|
||||
|
@ -22,7 +22,7 @@ define(["displaySettings", "userSettingsBuilder", "userSettings", "autoFocuser"]
|
|||
serverId: ApiClient.serverId(),
|
||||
userId: userId,
|
||||
element: view.querySelector(".settingsContainer"),
|
||||
userSettings: userSettings,
|
||||
userSettings: currentSettings,
|
||||
enableSaveButton: false,
|
||||
enableSaveConfirmation: false,
|
||||
autoFocus: autoFocuser.isEnabled()
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
define(["homescreenSettings", "userSettingsBuilder", "dom", "globalize", "loading", "userSettings", "autoFocuser", "listViewStyle"], function (HomescreenSettings, userSettingsBuilder, dom, globalize, loading, currentUserSettings, autoFocuser) {
|
||||
define(["homescreenSettings", "dom", "globalize", "loading", "userSettings", "autoFocuser", "listViewStyle"], function (HomescreenSettings, dom, globalize, loading, userSettings, autoFocuser) {
|
||||
"use strict";
|
||||
|
||||
return function (view, params) {
|
||||
|
@ -11,7 +11,7 @@ define(["homescreenSettings", "userSettingsBuilder", "dom", "globalize", "loadin
|
|||
var homescreenSettingsInstance;
|
||||
var hasChanges;
|
||||
var userId = params.userId || ApiClient.getCurrentUserId();
|
||||
var userSettings = userId === ApiClient.getCurrentUserId() ? currentUserSettings : new userSettingsBuilder();
|
||||
var currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new userSettings();
|
||||
view.addEventListener("viewshow", function () {
|
||||
window.addEventListener("beforeunload", onBeforeUnload);
|
||||
|
||||
|
@ -22,7 +22,7 @@ define(["homescreenSettings", "userSettingsBuilder", "dom", "globalize", "loadin
|
|||
serverId: ApiClient.serverId(),
|
||||
userId: userId,
|
||||
element: view.querySelector(".homeScreenSettingsContainer"),
|
||||
userSettings: userSettings,
|
||||
userSettings: currentSettings,
|
||||
enableSaveButton: false,
|
||||
enableSaveConfirmation: false,
|
||||
autoFocus: autoFocuser.isEnabled()
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
define(["apphost", "connectionManager", "listViewStyle", "emby-button"], function(appHost, connectionManager) {
|
||||
define(["apphost", "connectionManager", "layoutManager", "listViewStyle", "emby-button"], function(appHost, connectionManager, layoutManager) {
|
||||
"use strict";
|
||||
|
||||
return function(view, params) {
|
||||
|
@ -10,6 +10,10 @@ define(["apphost", "connectionManager", "listViewStyle", "emby-button"], functio
|
|||
Dashboard.selectServer();
|
||||
});
|
||||
|
||||
view.querySelector(".clientSettings").addEventListener("click", function () {
|
||||
window.NativeShell.openClientSettings();
|
||||
});
|
||||
|
||||
view.addEventListener("viewshow", function() {
|
||||
// this page can also be used by admins to change user preferences from the user edit page
|
||||
var userId = params.userId || Dashboard.getCurrentUserId();
|
||||
|
@ -21,6 +25,12 @@ define(["apphost", "connectionManager", "listViewStyle", "emby-button"], functio
|
|||
page.querySelector(".lnkPlaybackPreferences").setAttribute("href", "mypreferencesplayback.html?userId=" + userId);
|
||||
page.querySelector(".lnkSubtitlePreferences").setAttribute("href", "mypreferencessubtitles.html?userId=" + userId);
|
||||
|
||||
if (window.NativeShell && window.NativeShell.AppHost.supports("clientsettings")) {
|
||||
page.querySelector(".clientSettings").classList.remove("hide");
|
||||
} else {
|
||||
page.querySelector(".clientSettings").classList.add("hide");
|
||||
}
|
||||
|
||||
if (appHost.supports("multiserver")) {
|
||||
page.querySelector(".selectServer").classList.remove("hide");
|
||||
} else {
|
||||
|
@ -33,6 +43,12 @@ define(["apphost", "connectionManager", "listViewStyle", "emby-button"], functio
|
|||
page.querySelector(".adminSection").classList.add("hide");
|
||||
}
|
||||
|
||||
if (layoutManager.mobile) {
|
||||
page.querySelector(".headerUsername").classList.add("hide");
|
||||
page.querySelector(".adminSection").classList.add("hide");
|
||||
page.querySelector(".userSection").classList.add("hide");
|
||||
}
|
||||
|
||||
ApiClient.getUser(userId).then(function(user) {
|
||||
page.querySelector(".headerUsername").innerHTML = user.Name;
|
||||
if (!user.Policy.IsAdministrator) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
define(["playbackSettings", "userSettingsBuilder", "dom", "globalize", "loading", "userSettings", "autoFocuser", "listViewStyle"], function (PlaybackSettings, userSettingsBuilder, dom, globalize, loading, currentUserSettings, autoFocuser) {
|
||||
define(["playbackSettings", "dom", "globalize", "loading", "userSettings", "autoFocuser", "listViewStyle"], function (PlaybackSettings, dom, globalize, loading, userSettings, autoFocuser) {
|
||||
"use strict";
|
||||
|
||||
return function (view, params) {
|
||||
|
@ -11,7 +11,7 @@ define(["playbackSettings", "userSettingsBuilder", "dom", "globalize", "loading"
|
|||
var settingsInstance;
|
||||
var hasChanges;
|
||||
var userId = params.userId || ApiClient.getCurrentUserId();
|
||||
var userSettings = userId === ApiClient.getCurrentUserId() ? currentUserSettings : new userSettingsBuilder();
|
||||
var currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new userSettings();
|
||||
view.addEventListener("viewshow", function () {
|
||||
window.addEventListener("beforeunload", onBeforeUnload);
|
||||
|
||||
|
@ -22,7 +22,7 @@ define(["playbackSettings", "userSettingsBuilder", "dom", "globalize", "loading"
|
|||
serverId: ApiClient.serverId(),
|
||||
userId: userId,
|
||||
element: view.querySelector(".settingsContainer"),
|
||||
userSettings: userSettings,
|
||||
userSettings: currentSettings,
|
||||
enableSaveButton: false,
|
||||
enableSaveConfirmation: false,
|
||||
autoFocus: autoFocuser.isEnabled()
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
define(["subtitleSettings", "userSettingsBuilder", "userSettings", "autoFocuser"], function (SubtitleSettings, userSettingsBuilder, currentUserSettings, autoFocuser) {
|
||||
define(["subtitleSettings", "userSettings", "autoFocuser"], function (SubtitleSettings, userSettings, autoFocuser) {
|
||||
"use strict";
|
||||
|
||||
return function (view, params) {
|
||||
|
@ -11,7 +11,7 @@ define(["subtitleSettings", "userSettingsBuilder", "userSettings", "autoFocuser"
|
|||
var subtitleSettingsInstance;
|
||||
var hasChanges;
|
||||
var userId = params.userId || ApiClient.getCurrentUserId();
|
||||
var userSettings = userId === ApiClient.getCurrentUserId() ? currentUserSettings : new userSettingsBuilder();
|
||||
var currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new userSettings();
|
||||
view.addEventListener("viewshow", function () {
|
||||
window.addEventListener("beforeunload", onBeforeUnload);
|
||||
|
||||
|
@ -22,7 +22,7 @@ define(["subtitleSettings", "userSettingsBuilder", "userSettings", "autoFocuser"
|
|||
serverId: ApiClient.serverId(),
|
||||
userId: userId,
|
||||
element: view.querySelector(".settingsContainer"),
|
||||
userSettings: userSettings,
|
||||
userSettings: currentSettings,
|
||||
enableSaveButton: false,
|
||||
enableSaveConfirmation: false,
|
||||
autoFocus: autoFocuser.isEnabled()
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
define(["loading", "dom", "globalize", "humanedate", "paper-icon-button-light", "cardStyle", "emby-button", "indicators", "flexStyles"], function (loading, dom, globalize) {
|
||||
define(["loading", "dom", "globalize", "date-fns", "dfnshelper", "paper-icon-button-light", "cardStyle", "emby-button", "indicators", "flexStyles"], function (loading, dom, globalize, datefns, dfnshelper) {
|
||||
"use strict";
|
||||
|
||||
function deleteUser(page, id) {
|
||||
|
@ -125,10 +125,11 @@ define(["loading", "dom", "globalize", "humanedate", "paper-icon-button-light",
|
|||
html += "</div>";
|
||||
return html + "</div>";
|
||||
}
|
||||
|
||||
// FIXME: It seems that, sometimes, server sends date in the future, so date-fns displays messages like 'in less than a minute'. We should fix
|
||||
// how dates are returned by the server when the session is active and show something like 'Active now', instead of past/future sentences
|
||||
function getLastSeenText(lastActivityDate) {
|
||||
if (lastActivityDate) {
|
||||
return "Last seen " + humaneDate(lastActivityDate);
|
||||
return globalize.translate("LastSeen", datefns.formatDistanceToNow(Date.parse(lastActivityDate), dfnshelper.localeWithSuffix));
|
||||
}
|
||||
|
||||
return "";
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
height: 1.83em;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
border: 2px solid currentcolor;
|
||||
border: 0.14em solid currentcolor;
|
||||
border-radius: 0.14em;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding-left: 24px;
|
||||
}
|
||||
|
||||
.radio-label-block {
|
||||
|
@ -31,67 +30,82 @@
|
|||
border: none;
|
||||
}
|
||||
|
||||
.mdl-radio__outer-circle {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
left: 0;
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
border: 2px solid currentcolor;
|
||||
.mdl-radio__circles {
|
||||
position: relative;
|
||||
margin-right: 0.54em;
|
||||
width: 1.08em;
|
||||
height: 1.08em;
|
||||
border-radius: 50%;
|
||||
z-index: 2;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mdl-radio__button:checked + .mdl-radio__label + .mdl-radio__outer-circle {
|
||||
border: 2px solid #00a4dc;
|
||||
.mdl-radio__circles svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.mdl-radio__button:disabled + .mdl-radio__label + .mdl-radio__outer-circle {
|
||||
border: 2px solid rgba(0, 0, 0, 0.26);
|
||||
.mdl-radio__button:disabled + .mdl-radio__circles {
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
.mdl-radio__button:disabled + .mdl-radio__circles .mdl-radio__outer-circle {
|
||||
color: rgba(0, 0, 0, 0.26);
|
||||
}
|
||||
|
||||
.mdl-radio.show-focus .mdl-radio__button:focus + .mdl-radio__circles .mdl-radio__outer-circle {
|
||||
color: #00a4dc;
|
||||
}
|
||||
|
||||
.mdl-radio__inner-circle {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
margin: 0;
|
||||
top: 8px;
|
||||
left: 4px;
|
||||
box-sizing: border-box;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
cursor: pointer;
|
||||
transition-duration: 0.28s;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-duration: 0.2s;
|
||||
transition-property: -webkit-transform;
|
||||
transition-property: transform;
|
||||
transition-property: transform, -webkit-transform;
|
||||
-webkit-transform: scale3d(0, 0, 0);
|
||||
transform: scale3d(0, 0, 0);
|
||||
-webkit-transform: scale(0);
|
||||
transform: scale(0);
|
||||
transform-origin: 50% 50%;
|
||||
}
|
||||
|
||||
.mdl-radio__button:checked + .mdl-radio__circles .mdl-radio__inner-circle {
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.mdl-radio__button:disabled + .mdl-radio__circles .mdl-radio__inner-circle {
|
||||
color: rgba(0, 0, 0, 0.26);
|
||||
}
|
||||
|
||||
.mdl-radio.show-focus .mdl-radio__button:focus + .mdl-radio__circles .mdl-radio__inner-circle {
|
||||
color: #00a4dc;
|
||||
}
|
||||
|
||||
.mdl-radio__focus-circle {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
border-radius: 50%;
|
||||
background: #00a4dc;
|
||||
opacity: 0.26;
|
||||
transition-duration: 0.2s;
|
||||
transition-property: -webkit-transform;
|
||||
transition-property: transform;
|
||||
transition-property: transform, -webkit-transform;
|
||||
-webkit-transform: scale(0);
|
||||
transform: scale(0);
|
||||
}
|
||||
|
||||
.mdl-radio__button:checked + .mdl-radio__label + .mdl-radio__outer-circle + .mdl-radio__inner-circle {
|
||||
-webkit-transform: scale3d(1, 1, 1);
|
||||
transform: scale3d(1, 1, 1);
|
||||
}
|
||||
|
||||
.mdl-radio__button:disabled + .mdl-radio__label + .mdl-radio__outer-circle + .mdl-radio__inner-circle {
|
||||
background: rgba(0, 0, 0, 0.26);
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
.mdl-radio__button:focus + .mdl-radio__label + .mdl-radio__outer-circle + .mdl-radio__inner-circle {
|
||||
box-shadow: 0 0 0 10px rgba(255, 255, 255, 0.76);
|
||||
}
|
||||
|
||||
.mdl-radio__button:checked:focus + .mdl-radio__label + .mdl-radio__outer-circle + .mdl-radio__inner-circle {
|
||||
box-shadow: 0 0 0 10px rgba(0, 164, 220, 0.26);
|
||||
.mdl-radio.show-focus .mdl-radio__button:focus + .mdl-radio__circles .mdl-radio__focus-circle {
|
||||
-webkit-transform: scale(1.75);
|
||||
transform: scale(1.75);
|
||||
}
|
||||
|
||||
.mdl-radio__label {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
define(['css!./emby-radio', 'registerElement'], function () {
|
||||
define(['layoutManager', 'css!./emby-radio', 'registerElement'], function (layoutManager) {
|
||||
'use strict';
|
||||
|
||||
var EmbyRadioPrototype = Object.create(HTMLInputElement.prototype);
|
||||
|
@ -23,6 +23,7 @@ define(['css!./emby-radio', 'registerElement'], function () {
|
|||
}
|
||||
|
||||
EmbyRadioPrototype.attachedCallback = function () {
|
||||
var showFocus = !layoutManager.mobile;
|
||||
|
||||
if (this.getAttribute('data-radio') === 'true') {
|
||||
return;
|
||||
|
@ -37,13 +38,36 @@ define(['css!./emby-radio', 'registerElement'], function () {
|
|||
labelElement.classList.add('mdl-radio');
|
||||
labelElement.classList.add('mdl-js-radio');
|
||||
labelElement.classList.add('mdl-js-ripple-effect');
|
||||
if (showFocus) {
|
||||
labelElement.classList.add('show-focus');
|
||||
}
|
||||
|
||||
var labelTextElement = labelElement.querySelector('span');
|
||||
|
||||
labelTextElement.classList.add('radioButtonLabel');
|
||||
labelTextElement.classList.add('mdl-radio__label');
|
||||
|
||||
labelElement.insertAdjacentHTML('beforeend', '<span class="mdl-radio__outer-circle"></span><span class="mdl-radio__inner-circle"></span>');
|
||||
var html = '';
|
||||
|
||||
html += '<div class="mdl-radio__circles">';
|
||||
|
||||
html += '<svg>';
|
||||
html += '<defs>';
|
||||
html += '<clipPath id="cutoff">';
|
||||
html += '<circle cx="50%" cy="50%" r="50%" />';
|
||||
html += '</clipPath>';
|
||||
html += '</defs>';
|
||||
html += '<circle class="mdl-radio__outer-circle" cx="50%" cy="50%" r="50%" fill="none" stroke="currentcolor" stroke-width="0.26em" clip-path="url(#cutoff)" />';
|
||||
html += '<circle class="mdl-radio__inner-circle" cx="50%" cy="50%" r="25%" fill="currentcolor" />';
|
||||
html += '</svg>';
|
||||
|
||||
if (showFocus) {
|
||||
html += '<div class="mdl-radio__focus-circle"></div>';
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
|
||||
this.insertAdjacentHTML('afterend', html);
|
||||
|
||||
this.addEventListener('keydown', onKeyDown);
|
||||
};
|
||||
|
|
|
@ -109,7 +109,7 @@
|
|||
}
|
||||
|
||||
.selectArrow {
|
||||
margin-top: 0.35em;
|
||||
margin-top: 1.2em;
|
||||
font-size: 1.7em;
|
||||
}
|
||||
|
||||
|
|
|
@ -144,7 +144,7 @@ define(['layoutManager', 'browser', 'actionsheet', 'css!./emby-select', 'registe
|
|||
this.parentNode.insertBefore(label, this);
|
||||
|
||||
if (this.classList.contains('emby-select-withcolor')) {
|
||||
this.parentNode.insertAdjacentHTML('beforeend', '<div class="selectArrowContainer"><div style="visibility:hidden;">0</div><i class="selectArrow material-icons keyboard_arrow_down"></i></div>');
|
||||
this.parentNode.insertAdjacentHTML('beforeend', '<div class="selectArrowContainer"><div style="visibility:hidden;display:none;">0</div><i class="selectArrow material-icons keyboard_arrow_down"></i></div>');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -402,7 +402,7 @@ define(['browser', 'dom', 'layoutManager', 'keyboardnavigation', 'css!./emby-sli
|
|||
this.addEventListener('keydown', onKeyDown);
|
||||
this.keyboardDraggingEnabled = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set steps for keyboard input.
|
||||
|
@ -413,7 +413,7 @@ define(['browser', 'dom', 'layoutManager', 'keyboardnavigation', 'css!./emby-sli
|
|||
EmbySliderPrototype.setKeyboardSteps = function (stepDown, stepUp) {
|
||||
this.keyboardStepDown = stepDown || stepUp || 1;
|
||||
this.keyboardStepUp = stepUp || stepDown || 1;
|
||||
}
|
||||
};
|
||||
|
||||
function setRange(elem, startPercent, endPercent) {
|
||||
|
||||
|
|
|
@ -136,6 +136,14 @@
|
|||
<div class="fieldDescription">${H264CrfHelp}</div>
|
||||
</div>
|
||||
|
||||
<div class="selectContainer">
|
||||
<select is="emby-select" id="selectDeinterlaceMethod" label="${LabelDeinterlaceMethod}">
|
||||
<option value="yadif">${Yadif}</option>
|
||||
<option value="yadif_bob">${YadifBob}</option>
|
||||
</select>
|
||||
<div class="fieldDescription">${DeinterlaceMethodHelp}</div>
|
||||
</div>
|
||||
|
||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||
<label>
|
||||
<input is="emby-checkbox" type="checkbox" id="chkEnableSubtitleExtraction" />
|
||||
|
|
|
@ -111,22 +111,32 @@
|
|||
<div class="detailImageContainer padded-left"></div>
|
||||
<div class="detailPageContent">
|
||||
<div class="detailPagePrimaryContent padded-left padded-right">
|
||||
<div class="detailSection" style="margin-bottom: 0;">
|
||||
<div class="detailSection">
|
||||
<div class="itemMiscInfo nativeName hide"></div>
|
||||
<div class="genres hide" style="margin: .7em 0;font-size:92%;"></div>
|
||||
<div class="directors hide" style="margin: .7em 0;font-size:92%;"></div>
|
||||
|
||||
<form class="trackSelections flex align-items-center flex-wrap-wrap hide focuscontainer-x">
|
||||
<div class="selectContainer selectContainer-inline selectSourceContainer hide trackSelectionFieldContainer flex-shrink-zero">
|
||||
<div class="itemDetailsGroup">
|
||||
<div class="detailsGroupItem genresGroup hide">
|
||||
<div class="genresLabel label"></div>
|
||||
<div class="genres content"></div>
|
||||
</div>
|
||||
|
||||
<div class="detailsGroupItem directorsGroup hide">
|
||||
<div class="directorsLabel label"></div>
|
||||
<div class="directors content"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form class="trackSelections hide focuscontainer-x">
|
||||
<div class="selectContainer selectSourceContainer hide trackSelectionFieldContainer flex-shrink-zero">
|
||||
<select is="emby-select" class="selectSource detailTrackSelect" label=""></select>
|
||||
</div>
|
||||
<div class="selectContainer selectContainer-inline selectVideoContainer hide trackSelectionFieldContainer flex-shrink-zero">
|
||||
<div class="selectContainer selectVideoContainer hide trackSelectionFieldContainer flex-shrink-zero">
|
||||
<select is="emby-select" class="selectVideo detailTrackSelect" label=""></select>
|
||||
</div>
|
||||
<div class="selectContainer selectContainer-inline selectAudioContainer hide trackSelectionFieldContainer flex-shrink-zero">
|
||||
<div class="selectContainer selectAudioContainer hide trackSelectionFieldContainer flex-shrink-zero">
|
||||
<select is="emby-select" class="selectAudio detailTrackSelect" label=""></select>
|
||||
</div>
|
||||
<div class="selectContainer selectContainer-inline selectSubtitlesContainer hide trackSelectionFieldContainer">
|
||||
<div class="selectContainer selectSubtitlesContainer hide trackSelectionFieldContainer">
|
||||
<select is="emby-select" class="selectSubtitles detailTrackSelect" label=""></select>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -176,8 +186,8 @@
|
|||
</div>
|
||||
|
||||
<div id="additionalPartsCollapsible" class="verticalSection detailVerticalSection hide">
|
||||
<h2 class="sectionTitle sectionTitle-cards padded-left">${HeaderAdditionalParts}</h2>
|
||||
<div id="additionalPartsContent" is="emby-itemscontainer" class="itemsContainer vertical-wrap"></div>
|
||||
<h2 class="sectionTitle sectionTitle-cards padded-left padded-right">${HeaderAdditionalParts}</h2>
|
||||
<div id="additionalPartsContent" is="emby-itemscontainer" class="itemsContainer vertical-wrap padded-left padded-right"></div>
|
||||
</div>
|
||||
|
||||
<div class="verticalSection itemVerticalSection moreFromSeasonSection hide">
|
||||
|
@ -203,17 +213,17 @@
|
|||
|
||||
<div id="seriesScheduleSection" class="verticalSection detailVerticalSection hide">
|
||||
<h2 class="sectionTitle padded-left padded-right">${HeaderUpcomingOnTV}</h2>
|
||||
<div id="seriesScheduleList" is="emby-itemscontainer" class="itemsContainer vertical-list"></div>
|
||||
<div id="seriesScheduleList" is="emby-itemscontainer" class="itemsContainer vertical-list padded-left padded-right"></div>
|
||||
</div>
|
||||
|
||||
<div id="specialsCollapsible" class="verticalSection detailVerticalSection hide">
|
||||
<h2 class="sectionTitle sectionTitle-cards padded-left">${HeaderSpecialFeatures}</h2>
|
||||
<div id="specialsContent" is="emby-itemscontainer" class="itemsContainer vertical-wrap"></div>
|
||||
<h2 class="sectionTitle sectionTitle-cards padded-left padded-right">${HeaderSpecialFeatures}</h2>
|
||||
<div id="specialsContent" is="emby-itemscontainer" class="itemsContainer vertical-wrap padded-left padded-right"></div>
|
||||
</div>
|
||||
|
||||
<div id="musicVideosCollapsible" class="verticalSection detailVerticalSection hide">
|
||||
<h2 class="sectionTitle sectionTitle-cards padded-left">${HeaderMusicVideos}</h2>
|
||||
<div id="musicVideosContent" is="emby-itemscontainer" class="itemsContainer vertical-wrap"></div>
|
||||
<h2 class="sectionTitle sectionTitle-cards padded-left padded-right">${HeaderMusicVideos}</h2>
|
||||
<div id="musicVideosContent" is="emby-itemscontainer" class="itemsContainer vertical-wrap padded-left padded-right"></div>
|
||||
</div>
|
||||
|
||||
<div id="scenesCollapsible" class="verticalSection itemVerticalSection verticalSection-extrabottompadding hide">
|
||||
|
|
|
@ -2,19 +2,19 @@ Dashboard.confirm = function(message, title, callback) {
|
|||
"use strict";
|
||||
require(["confirm"], function(confirm) {
|
||||
confirm(message, title).then(function() {
|
||||
callback(!0)
|
||||
callback(!0);
|
||||
}, function() {
|
||||
callback(!1)
|
||||
})
|
||||
})
|
||||
callback(!1);
|
||||
});
|
||||
});
|
||||
}, Dashboard.showLoadingMsg = function() {
|
||||
"use strict";
|
||||
require(["loading"], function(loading) {
|
||||
loading.show()
|
||||
})
|
||||
loading.show();
|
||||
});
|
||||
}, Dashboard.hideLoadingMsg = function() {
|
||||
"use strict";
|
||||
require(["loading"], function(loading) {
|
||||
loading.hide()
|
||||
})
|
||||
loading.hide();
|
||||
});
|
||||
};
|
||||
|
|
|
@ -2,9 +2,9 @@ define(["jQuery"], function($) {
|
|||
"use strict";
|
||||
$.fn.checked = function(value) {
|
||||
return !0 === value || !1 === value ? $(this).each(function() {
|
||||
this.checked = value
|
||||
}) : this.length && this[0].checked
|
||||
this.checked = value;
|
||||
}) : this.length && this[0].checked;
|
||||
}, $.fn.checkboxradio = function() {
|
||||
return this
|
||||
}
|
||||
return this;
|
||||
};
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
define(["jQuery"], function($) {
|
||||
"use strict";
|
||||
$.fn.selectmenu = function() {
|
||||
return this
|
||||
}
|
||||
return this;
|
||||
};
|
||||
});
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
</div>
|
||||
|
||||
<div class="inputContainer">
|
||||
<input is="emby-input" type="text" id="txtManualName" required="required" label="${LabelUser}" autocomplete="username" />
|
||||
<input is="emby-input" type="text" id="txtManualName" required="required" label="${LabelUser}" autocomplete="username" autocapitalize="off" />
|
||||
</div>
|
||||
|
||||
<div class="inputContainer">
|
||||
|
|
|
@ -47,6 +47,15 @@
|
|||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a is="emby-linkbutton" data-ripple="false" href="#" style="display:block;padding:0;margin:0;" class="clientSettings listItem-border">
|
||||
<div class="listItem">
|
||||
<i class="material-icons listItemIcon listItemIcon-transparent devices_other"></i>
|
||||
<div class="listItemBody">
|
||||
<div class="listItemBodyText">${ClientSettings}</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="adminSection verticalSection verticalSection-extrabottompadding">
|
||||
<h2 class="sectionTitle" style="padding-left:.25em;">${HeaderAdmin}</h2>
|
||||
|
|
|
@ -292,7 +292,7 @@ define([], function () {
|
|||
}
|
||||
|
||||
if (typeof document !== 'undefined') {
|
||||
if (('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) {
|
||||
if (('ontouchstart' in window) || (navigator.maxTouchPoints > 0)) {
|
||||
browser.touch = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -433,13 +433,9 @@ define(['browser'], function (browser) {
|
|||
|
||||
var supportsDts = browser.tizen || browser.orsay || browser.web0s || options.supportsDts;
|
||||
|
||||
if (self.tizen && self.tizen.systeminfo) {
|
||||
var v = tizen.systeminfo.getCapability('http://tizen.org/feature/platform.version');
|
||||
|
||||
// DTS audio not supported in 2018 models (Tizen 4.0)
|
||||
if (v && parseFloat(v) >= parseFloat('4.0')) {
|
||||
supportsDts = false;
|
||||
}
|
||||
// DTS audio not supported in 2018 models (Tizen 4.0)
|
||||
if (browser.tizenVersion >= 4) {
|
||||
supportsDts = false;
|
||||
}
|
||||
|
||||
if (supportsDts) {
|
||||
|
@ -766,6 +762,11 @@ define(['browser'], function (browser) {
|
|||
maxH264Level = 51;
|
||||
}
|
||||
|
||||
// Support H264 Level 52 (Tizen 5.0) - app only
|
||||
if (browser.tizenVersion >= 5 && window.NativeShell) {
|
||||
maxH264Level = 52;
|
||||
}
|
||||
|
||||
if (browser.tizen || browser.orsay ||
|
||||
videoTestElement.canPlayType('video/mp4; codecs="avc1.6e0033"').replace(/no/, '')) {
|
||||
|
||||
|
|
104
src/scripts/dfnshelper.js
Normal file
104
src/scripts/dfnshelper.js
Normal file
|
@ -0,0 +1,104 @@
|
|||
import { ar, be, bg, ca, cs, da, de, el, enGB, enUS, es, faIR, fi, fr, frCA, he, hi, hr, hu, id, it, kk, ko, lt, ms, nb, nl, pl, ptBR, pt, ro, ru, sk, sl, sv, tr, uk, vi, zhCN, zhTW } from 'date-fns/locale';
|
||||
import globalize from 'globalize';
|
||||
|
||||
export function getLocale() {
|
||||
switch (globalize.getCurrentLocale()) {
|
||||
case 'ar':
|
||||
return ar;
|
||||
case 'be-by':
|
||||
return be;
|
||||
case 'bg-bg':
|
||||
return bg;
|
||||
case 'ca':
|
||||
return ca;
|
||||
case 'cs':
|
||||
return cs;
|
||||
case 'da':
|
||||
return da;
|
||||
case 'de':
|
||||
return de;
|
||||
case 'el':
|
||||
return el;
|
||||
case 'en-gb':
|
||||
return enGB;
|
||||
case 'en-us':
|
||||
return enUS;
|
||||
case 'es':
|
||||
return es;
|
||||
case 'es-ar':
|
||||
return es;
|
||||
case 'es-mx':
|
||||
return es;
|
||||
case 'fa':
|
||||
return faIR;
|
||||
case 'fi':
|
||||
return fi;
|
||||
case 'fr':
|
||||
return fr;
|
||||
case 'fr-ca':
|
||||
return frCA;
|
||||
case 'gsw':
|
||||
return de;
|
||||
case 'he':
|
||||
return he;
|
||||
case 'hi-in':
|
||||
return hi;
|
||||
case 'hr':
|
||||
return hr;
|
||||
case 'hu':
|
||||
return hu;
|
||||
case 'id':
|
||||
return id;
|
||||
case 'it':
|
||||
return it;
|
||||
case 'kk':
|
||||
return kk;
|
||||
case 'ko':
|
||||
return ko;
|
||||
case 'lt-lt':
|
||||
return lt;
|
||||
case 'ms':
|
||||
return ms;
|
||||
case 'nb':
|
||||
return nb;
|
||||
case 'nl':
|
||||
return nl;
|
||||
case 'pl':
|
||||
return pl;
|
||||
case 'pt-br':
|
||||
return ptBR;
|
||||
case 'pt-pt':
|
||||
return pt;
|
||||
case 'ro':
|
||||
return ro;
|
||||
case 'ru':
|
||||
return ru;
|
||||
case 'sk':
|
||||
return sk;
|
||||
case 'sl-si':
|
||||
return sl;
|
||||
case 'sv':
|
||||
return sv;
|
||||
case 'tr':
|
||||
return tr;
|
||||
case 'uk':
|
||||
return uk;
|
||||
case 'vi':
|
||||
return vi;
|
||||
case 'zh-cn':
|
||||
return zhCN;
|
||||
case 'zh-hk':
|
||||
return zhCN;
|
||||
case 'zh-tw':
|
||||
return zhTW;
|
||||
default:
|
||||
return enUS;
|
||||
}
|
||||
}
|
||||
|
||||
export const localeWithSuffix = { addSuffix: true, locale: getLocale() };
|
||||
|
||||
export default {
|
||||
getLocale: getLocale,
|
||||
localeWithSuffix: localeWithSuffix
|
||||
};
|
|
@ -1,4 +1,4 @@
|
|||
define(['connectionManager', 'userSettings', 'events'], function (connectionManager, userSettings, events) {
|
||||
define(['userSettings', 'events'], function (userSettings, events) {
|
||||
'use strict';
|
||||
var fallbackCulture = 'en-us';
|
||||
|
||||
|
@ -253,7 +253,6 @@ define(['connectionManager', 'userSettings', 'events'], function (connectionMana
|
|||
|
||||
updateCurrentCulture();
|
||||
|
||||
events.on(connectionManager, 'localusersignedin', updateCurrentCulture);
|
||||
events.on(userSettings, 'change', function (e, name) {
|
||||
if (name === 'language' || name === 'datetimelocale') {
|
||||
updateCurrentCulture();
|
||||
|
@ -269,6 +268,7 @@ define(['connectionManager', 'userSettings', 'events'], function (connectionMana
|
|||
defaultModule: defaultModule,
|
||||
getCurrentLocale: getCurrentLocale,
|
||||
getCurrentDateTimeLocale: getCurrentDateTimeLocale,
|
||||
register: register
|
||||
register: register,
|
||||
updateCurrentCulture: updateCurrentCulture
|
||||
};
|
||||
});
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
define(["browser"], function (browser) {
|
||||
"use strict";
|
||||
/* eslint-disable indent */
|
||||
|
||||
function getDeviceIcon(device) {
|
||||
import browser from 'browser';
|
||||
|
||||
export function getDeviceIcon(device) {
|
||||
var baseUrl = "assets/img/devices/";
|
||||
switch (device.AppName || device.Client) {
|
||||
case "Samsung Smart TV":
|
||||
|
@ -42,7 +43,7 @@ define(["browser"], function (browser) {
|
|||
}
|
||||
}
|
||||
|
||||
function getLibraryIcon(library) {
|
||||
export function getLibraryIcon(library) {
|
||||
switch (library) {
|
||||
case "movies":
|
||||
return "video_library";
|
||||
|
@ -71,8 +72,9 @@ define(["browser"], function (browser) {
|
|||
}
|
||||
}
|
||||
|
||||
return {
|
||||
getDeviceIcon: getDeviceIcon,
|
||||
getLibraryIcon: getLibraryIcon
|
||||
};
|
||||
});
|
||||
/* eslint-enable indent */
|
||||
|
||||
export default {
|
||||
getDeviceIcon: getDeviceIcon,
|
||||
getLibraryIcon: getLibraryIcon
|
||||
};
|
||||
|
|
|
@ -83,7 +83,7 @@ define(["userSettings"], function (userSettings) {
|
|||
|
||||
if (html += '<div class="listPaging">', showControls) {
|
||||
html += '<span style="vertical-align:middle;">';
|
||||
html += (totalRecordCount ? startIndex + 1 : 0) + "-" + recordsEnd + " of " + totalRecordCount;
|
||||
html += Globalize.translate("ListPaging", (totalRecordCount ? startIndex + 1 : 0), recordsEnd, totalRecordCount);
|
||||
html += "</span>";
|
||||
}
|
||||
|
||||
|
|
|
@ -243,15 +243,20 @@ define(["dom", "layoutManager", "inputManager", "connectionManager", "events", "
|
|||
html += '<a is="emby-linkbutton" class="navMenuOption lnkMediaFolder" data-itemid="selectserver" href="selectserver.html?showuser=1"><i class="material-icons navMenuOptionIcon">wifi</i><span class="navMenuOptionText">' + globalize.translate("ButtonSelectServer") + "</span></a>";
|
||||
}
|
||||
|
||||
html += '<a is="emby-linkbutton" class="navMenuOption lnkMediaFolder btnSettings" data-itemid="settings" href="#"><i class="material-icons navMenuOptionIcon settings"></i><span class="navMenuOptionText">' + globalize.translate("ButtonSettings") + "</span></a>";
|
||||
html += '<a is="emby-linkbutton" class="navMenuOption lnkMediaFolder btnLogout" data-itemid="logout" href="#"><i class="material-icons navMenuOptionIcon exit_to_app"></i><span class="navMenuOptionText">' + globalize.translate("ButtonSignOut") + "</span></a>";
|
||||
html += "</div>";
|
||||
}
|
||||
|
||||
// add buttons to navigation drawer
|
||||
navDrawerScrollContainer.innerHTML = html;
|
||||
// bind logout button click to method
|
||||
var btnLogout = navDrawerScrollContainer.querySelector(".btnLogout");
|
||||
|
||||
var btnSettings = navDrawerScrollContainer.querySelector(".btnSettings");
|
||||
if (btnSettings) {
|
||||
btnSettings.addEventListener("click", onSettingsClick);
|
||||
}
|
||||
|
||||
var btnLogout = navDrawerScrollContainer.querySelector(".btnLogout");
|
||||
if (btnLogout) {
|
||||
btnLogout.addEventListener("click", onLogoutClick);
|
||||
}
|
||||
|
@ -598,6 +603,10 @@ define(["dom", "layoutManager", "inputManager", "connectionManager", "events", "
|
|||
}
|
||||
}
|
||||
|
||||
function onSettingsClick() {
|
||||
Dashboard.navigate("mypreferencesmenu.html");
|
||||
}
|
||||
|
||||
function onLogoutClick() {
|
||||
Dashboard.logout();
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
define(['appStorage', 'events'], function (appStorage, events) {
|
||||
'use strict';
|
||||
/* eslint-disable indent */
|
||||
|
||||
import appStorage from 'appStorage';
|
||||
import events from 'events';
|
||||
|
||||
function getKey(name, userId) {
|
||||
if (userId) {
|
||||
|
@ -9,18 +11,23 @@ define(['appStorage', 'events'], function (appStorage, events) {
|
|||
return name;
|
||||
}
|
||||
|
||||
function AppSettings() {
|
||||
}
|
||||
|
||||
AppSettings.prototype.enableAutoLogin = function (val) {
|
||||
export function enableAutoLogin(val) {
|
||||
if (val != null) {
|
||||
this.set('enableAutoLogin', val.toString());
|
||||
}
|
||||
|
||||
return this.get('enableAutoLogin') !== 'false';
|
||||
};
|
||||
}
|
||||
|
||||
AppSettings.prototype.enableAutomaticBitrateDetection = function (isInNetwork, mediaType, val) {
|
||||
export function enableSystemExternalPlayers(val) {
|
||||
if (val !== null) {
|
||||
this.set('enableSystemExternalPlayers', val.toString());
|
||||
}
|
||||
|
||||
return this.get('enableSystemExternalPlayers') === 'true';
|
||||
}
|
||||
|
||||
export function enableAutomaticBitrateDetection(isInNetwork, mediaType, val) {
|
||||
var key = 'enableautobitratebitrate-' + mediaType + '-' + isInNetwork;
|
||||
if (val != null) {
|
||||
if (isInNetwork && mediaType === 'Audio') {
|
||||
|
@ -35,9 +42,9 @@ define(['appStorage', 'events'], function (appStorage, events) {
|
|||
} else {
|
||||
return this.get(key) !== 'false';
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
AppSettings.prototype.maxStreamingBitrate = function (isInNetwork, mediaType, val) {
|
||||
export function maxStreamingBitrate(isInNetwork, mediaType, val) {
|
||||
var key = 'maxbitrate-' + mediaType + '-' + isInNetwork;
|
||||
if (val != null) {
|
||||
if (isInNetwork && mediaType === 'Audio') {
|
||||
|
@ -53,43 +60,43 @@ define(['appStorage', 'events'], function (appStorage, events) {
|
|||
} else {
|
||||
return parseInt(this.get(key) || '0') || 1500000;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
AppSettings.prototype.maxStaticMusicBitrate = function (val) {
|
||||
export function maxStaticMusicBitrate(val) {
|
||||
if (val !== undefined) {
|
||||
this.set('maxStaticMusicBitrate', val);
|
||||
}
|
||||
|
||||
var defaultValue = 320000;
|
||||
return parseInt(this.get('maxStaticMusicBitrate') || defaultValue.toString()) || defaultValue;
|
||||
};
|
||||
}
|
||||
|
||||
AppSettings.prototype.maxChromecastBitrate = function (val) {
|
||||
export function maxChromecastBitrate(val) {
|
||||
if (val != null) {
|
||||
this.set('chromecastBitrate1', val);
|
||||
}
|
||||
|
||||
val = this.get('chromecastBitrate1');
|
||||
return val ? parseInt(val) : null;
|
||||
};
|
||||
}
|
||||
|
||||
AppSettings.prototype.syncOnlyOnWifi = function (val) {
|
||||
export function syncOnlyOnWifi(val) {
|
||||
if (val != null) {
|
||||
this.set('syncOnlyOnWifi', val.toString());
|
||||
}
|
||||
|
||||
return this.get('syncOnlyOnWifi') !== 'false';
|
||||
};
|
||||
}
|
||||
|
||||
AppSettings.prototype.syncPath = function (val) {
|
||||
export function syncPath(val) {
|
||||
if (val != null) {
|
||||
this.set('syncPath', val);
|
||||
}
|
||||
|
||||
return this.get('syncPath');
|
||||
};
|
||||
}
|
||||
|
||||
AppSettings.prototype.cameraUploadServers = function (val) {
|
||||
export function cameraUploadServers(val) {
|
||||
if (val != null) {
|
||||
this.set('cameraUploadServers', val.join(','));
|
||||
}
|
||||
|
@ -100,36 +107,42 @@ define(['appStorage', 'events'], function (appStorage, events) {
|
|||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
}
|
||||
|
||||
AppSettings.prototype.runAtStartup = function (val) {
|
||||
export function runAtStartup(val) {
|
||||
if (val != null) {
|
||||
this.set('runatstartup', val.toString());
|
||||
}
|
||||
|
||||
return this.get('runatstartup') === 'true';
|
||||
};
|
||||
}
|
||||
|
||||
AppSettings.prototype.set = function (name, value, userId) {
|
||||
export function set(name, value, userId) {
|
||||
var currentValue = this.get(name, userId);
|
||||
appStorage.setItem(getKey(name, userId), value);
|
||||
|
||||
if (currentValue !== value) {
|
||||
events.trigger(this, 'change', [name]);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
AppSettings.prototype.get = function (name, userId) {
|
||||
export function get(name, userId) {
|
||||
return appStorage.getItem(getKey(name, userId));
|
||||
};
|
||||
}
|
||||
|
||||
AppSettings.prototype.enableSystemExternalPlayers = function (val) {
|
||||
if (val != null) {
|
||||
this.set('enableSystemExternalPlayers', val.toString());
|
||||
}
|
||||
/* eslint-enable indent */
|
||||
|
||||
return this.get('enableSystemExternalPlayers') === 'true';
|
||||
};
|
||||
|
||||
return new AppSettings();
|
||||
});
|
||||
export default {
|
||||
enableAutoLogin: enableAutoLogin,
|
||||
enableSystemExternalPlayers: enableSystemExternalPlayers,
|
||||
enableAutomaticBitrateDetection: enableAutomaticBitrateDetection,
|
||||
maxStreamingBitrate: maxStreamingBitrate,
|
||||
maxStaticMusicBitrate: maxStaticMusicBitrate,
|
||||
maxChromecastBitrate: maxChromecastBitrate,
|
||||
syncOnlyOnWifi: syncOnlyOnWifi,
|
||||
syncPath: syncPath,
|
||||
cameraUploadServers: cameraUploadServers,
|
||||
runAtStartup: runAtStartup,
|
||||
set: set,
|
||||
get: get
|
||||
};
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
define(['appSettings', 'events'], function (appSettings, events) {
|
||||
'use strict';
|
||||
/* eslint-disable indent */
|
||||
|
||||
import appSettings from 'appSettings';
|
||||
import events from 'events';
|
||||
|
||||
function onSaveTimeout() {
|
||||
var self = this;
|
||||
|
@ -15,10 +17,7 @@ define(['appSettings', 'events'], function (appSettings, events) {
|
|||
instance.saveTimeout = setTimeout(onSaveTimeout.bind(instance), 50);
|
||||
}
|
||||
|
||||
function UserSettings() {
|
||||
}
|
||||
|
||||
UserSettings.prototype.setUserInfo = function (userId, apiClient) {
|
||||
export function setUserInfo(userId, apiClient) {
|
||||
if (this.saveTimeout) {
|
||||
clearTimeout(this.saveTimeout);
|
||||
}
|
||||
|
@ -37,17 +36,17 @@ define(['appSettings', 'events'], function (appSettings, events) {
|
|||
result.CustomPrefs = result.CustomPrefs || {};
|
||||
self.displayPrefs = result;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
UserSettings.prototype.getData = function () {
|
||||
export function getData() {
|
||||
return this.displayPrefs;
|
||||
};
|
||||
}
|
||||
|
||||
UserSettings.prototype.importFrom = function (instance) {
|
||||
export function importFrom(instance) {
|
||||
this.displayPrefs = instance.getData();
|
||||
};
|
||||
}
|
||||
|
||||
UserSettings.prototype.set = function (name, value, enableOnServer) {
|
||||
export function set(name, value, enableOnServer) {
|
||||
var userId = this.currentUserId;
|
||||
var currentValue = this.get(name, enableOnServer);
|
||||
var result = appSettings.set(name, value, userId);
|
||||
|
@ -62,18 +61,18 @@ define(['appSettings', 'events'], function (appSettings, events) {
|
|||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
UserSettings.prototype.get = function (name, enableOnServer) {
|
||||
export function get(name, enableOnServer) {
|
||||
var userId = this.currentUserId;
|
||||
if (enableOnServer !== false && this.displayPrefs) {
|
||||
return this.displayPrefs.CustomPrefs[name];
|
||||
}
|
||||
|
||||
return appSettings.get(name, userId);
|
||||
};
|
||||
}
|
||||
|
||||
UserSettings.prototype.serverConfig = function (config) {
|
||||
export function serverConfig(config) {
|
||||
var apiClient = this.currentApiClient;
|
||||
if (config) {
|
||||
return apiClient.updateUserConfiguration(this.currentUserId, config);
|
||||
|
@ -82,135 +81,135 @@ define(['appSettings', 'events'], function (appSettings, events) {
|
|||
return apiClient.getUser(this.currentUserId).then(function (user) {
|
||||
return user.Configuration;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
UserSettings.prototype.enableCinemaMode = function (val) {
|
||||
export function enableCinemaMode(val) {
|
||||
if (val != null) {
|
||||
return this.set('enableCinemaMode', val.toString(), false);
|
||||
}
|
||||
|
||||
val = this.get('enableCinemaMode', false);
|
||||
return val !== 'false';
|
||||
};
|
||||
}
|
||||
|
||||
UserSettings.prototype.enableNextVideoInfoOverlay = function (val) {
|
||||
export function enableNextVideoInfoOverlay(val) {
|
||||
if (val != null) {
|
||||
return this.set('enableNextVideoInfoOverlay', val.toString());
|
||||
}
|
||||
|
||||
val = this.get('enableNextVideoInfoOverlay', false);
|
||||
return val !== 'false';
|
||||
};
|
||||
}
|
||||
|
||||
UserSettings.prototype.enableThemeSongs = function (val) {
|
||||
export function enableThemeSongs(val) {
|
||||
if (val != null) {
|
||||
return this.set('enableThemeSongs', val.toString(), false);
|
||||
}
|
||||
|
||||
val = this.get('enableThemeSongs', false);
|
||||
return val !== 'false';
|
||||
};
|
||||
}
|
||||
|
||||
UserSettings.prototype.enableThemeVideos = function (val) {
|
||||
export function enableThemeVideos(val) {
|
||||
if (val != null) {
|
||||
return this.set('enableThemeVideos', val.toString(), false);
|
||||
}
|
||||
|
||||
val = this.get('enableThemeVideos', false);
|
||||
return val !== 'false';
|
||||
};
|
||||
}
|
||||
|
||||
UserSettings.prototype.enableFastFadein = function (val) {
|
||||
export function enableFastFadein(val) {
|
||||
if (val != null) {
|
||||
return this.set('fastFadein', val.toString(), false);
|
||||
}
|
||||
|
||||
val = this.get('fastFadein', false);
|
||||
return val !== 'false';
|
||||
};
|
||||
}
|
||||
|
||||
UserSettings.prototype.enableBackdrops = function (val) {
|
||||
export function enableBackdrops(val) {
|
||||
if (val != null) {
|
||||
return this.set('enableBackdrops', val.toString(), false);
|
||||
}
|
||||
|
||||
val = this.get('enableBackdrops', false);
|
||||
return val !== 'false';
|
||||
};
|
||||
}
|
||||
|
||||
UserSettings.prototype.language = function (val) {
|
||||
export function language(val) {
|
||||
if (val != null) {
|
||||
return this.set('language', val.toString(), false);
|
||||
}
|
||||
|
||||
return this.get('language', false);
|
||||
};
|
||||
}
|
||||
|
||||
UserSettings.prototype.dateTimeLocale = function (val) {
|
||||
export function dateTimeLocale(val) {
|
||||
if (val != null) {
|
||||
return this.set('datetimelocale', val.toString(), false);
|
||||
}
|
||||
|
||||
return this.get('datetimelocale', false);
|
||||
};
|
||||
}
|
||||
|
||||
UserSettings.prototype.skipBackLength = function (val) {
|
||||
export function skipBackLength(val) {
|
||||
if (val != null) {
|
||||
return this.set('skipBackLength', val.toString());
|
||||
}
|
||||
|
||||
return parseInt(this.get('skipBackLength') || '10000');
|
||||
};
|
||||
}
|
||||
|
||||
UserSettings.prototype.skipForwardLength = function (val) {
|
||||
export function skipForwardLength(val) {
|
||||
if (val != null) {
|
||||
return this.set('skipForwardLength', val.toString());
|
||||
}
|
||||
|
||||
return parseInt(this.get('skipForwardLength') || '30000');
|
||||
};
|
||||
}
|
||||
|
||||
UserSettings.prototype.dashboardTheme = function (val) {
|
||||
export function dashboardTheme(val) {
|
||||
if (val != null) {
|
||||
return this.set('dashboardTheme', val);
|
||||
}
|
||||
|
||||
return this.get('dashboardTheme');
|
||||
};
|
||||
}
|
||||
|
||||
UserSettings.prototype.skin = function (val) {
|
||||
export function skin(val) {
|
||||
if (val != null) {
|
||||
return this.set('skin', val, false);
|
||||
}
|
||||
|
||||
return this.get('skin', false);
|
||||
};
|
||||
}
|
||||
|
||||
UserSettings.prototype.theme = function (val) {
|
||||
export function theme(val) {
|
||||
if (val != null) {
|
||||
return this.set('appTheme', val, false);
|
||||
}
|
||||
|
||||
return this.get('appTheme', false);
|
||||
};
|
||||
}
|
||||
|
||||
UserSettings.prototype.screensaver = function (val) {
|
||||
export function screensaver(val) {
|
||||
if (val != null) {
|
||||
return this.set('screensaver', val, false);
|
||||
}
|
||||
|
||||
return this.get('screensaver', false);
|
||||
};
|
||||
}
|
||||
|
||||
UserSettings.prototype.soundEffects = function (val) {
|
||||
export function soundEffects(val) {
|
||||
if (val != null) {
|
||||
return this.set('soundeffects', val, false);
|
||||
}
|
||||
|
||||
return this.get('soundeffects', false);
|
||||
};
|
||||
}
|
||||
|
||||
UserSettings.prototype.loadQuerySettings = function (key, query) {
|
||||
export function loadQuerySettings(key, query) {
|
||||
var values = this.get(key);
|
||||
if (values) {
|
||||
values = JSON.parse(values);
|
||||
|
@ -218,9 +217,9 @@ define(['appSettings', 'events'], function (appSettings, events) {
|
|||
}
|
||||
|
||||
return query;
|
||||
};
|
||||
}
|
||||
|
||||
UserSettings.prototype.saveQuerySettings = function (key, query) {
|
||||
export function saveQuerySettings(key, query) {
|
||||
var values = {};
|
||||
if (query.SortBy) {
|
||||
values.SortBy = query.SortBy;
|
||||
|
@ -231,25 +230,24 @@ define(['appSettings', 'events'], function (appSettings, events) {
|
|||
}
|
||||
|
||||
return this.set(key, JSON.stringify(values));
|
||||
};
|
||||
}
|
||||
|
||||
UserSettings.prototype.getSubtitleAppearanceSettings = function (key) {
|
||||
export function getSubtitleAppearanceSettings(key) {
|
||||
key = key || 'localplayersubtitleappearance3';
|
||||
return JSON.parse(this.get(key, false) || '{}');
|
||||
};
|
||||
}
|
||||
|
||||
UserSettings.prototype.setSubtitleAppearanceSettings = function (value, key) {
|
||||
export function setSubtitleAppearanceSettings(value, key) {
|
||||
key = key || 'localplayersubtitleappearance3';
|
||||
return this.set(key, JSON.stringify(value), false);
|
||||
};
|
||||
}
|
||||
|
||||
UserSettings.prototype.setFilter = function (key, value) {
|
||||
export function setFilter(key, value) {
|
||||
return this.set(key, value, true);
|
||||
};
|
||||
}
|
||||
|
||||
UserSettings.prototype.getFilter = function (key) {
|
||||
export function getFilter(key) {
|
||||
return this.get(key, true);
|
||||
};
|
||||
}
|
||||
|
||||
return UserSettings;
|
||||
});
|
||||
/* eslint-enable indent */
|
15
src/scripts/settings/webSettings.js
Normal file
15
src/scripts/settings/webSettings.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
let data;
|
||||
|
||||
function getConfig() {
|
||||
if (data) return Promise.resolve(data);
|
||||
return fetch("/config.json?nocache=" + new Date().getUTCMilliseconds()).then(function (response) {
|
||||
data = response.json();
|
||||
return data;
|
||||
});
|
||||
}
|
||||
|
||||
export function enableMultiServer() {
|
||||
return getConfig().then(config => {
|
||||
return config.multiserver;
|
||||
});
|
||||
}
|
|
@ -323,11 +323,11 @@ var AppInfo = {};
|
|||
}
|
||||
|
||||
function getElementsPath() {
|
||||
return "elements"
|
||||
return "elements";
|
||||
}
|
||||
|
||||
function getScriptsPath() {
|
||||
return "scripts"
|
||||
return "scripts";
|
||||
}
|
||||
|
||||
function getPlaybackManager(playbackManager) {
|
||||
|
@ -452,6 +452,9 @@ var AppInfo = {};
|
|||
require(["autoFocuser"], function(autoFocuser) {
|
||||
autoFocuser.enable();
|
||||
});
|
||||
require(['globalize', 'connectionManager', 'events'], function (globalize, connectionManager, events) {
|
||||
events.on(connectionManager, 'localusersignedin', globalize.updateCurrentCulture);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -574,6 +577,7 @@ var AppInfo = {};
|
|||
}
|
||||
|
||||
require(["mediaSession", "serverNotifications"]);
|
||||
require(["date-fns", "date-fns/locale"]);
|
||||
|
||||
if (!browser.tv && !browser.xboxOne) {
|
||||
require(["components/playback/playbackorientation"]);
|
||||
|
@ -647,12 +651,12 @@ var AppInfo = {};
|
|||
inputManager: "scripts/inputManager",
|
||||
datetime: "scripts/datetime",
|
||||
globalize: "scripts/globalize",
|
||||
dfnshelper: "scripts/dfnshelper",
|
||||
libraryMenu: "scripts/librarymenu",
|
||||
playlisteditor: componentsPath + "/playlisteditor/playlisteditor",
|
||||
medialibrarycreator: componentsPath + "/medialibrarycreator/medialibrarycreator",
|
||||
medialibraryeditor: componentsPath + "/medialibraryeditor/medialibraryeditor",
|
||||
imageoptionseditor: componentsPath + "/imageoptionseditor/imageoptionseditor",
|
||||
humanedate: componentsPath + "/humanedate",
|
||||
apphost: componentsPath + "/apphost",
|
||||
visibleinviewport: componentsPath + "/visibleinviewport",
|
||||
qualityoptions: componentsPath + "/qualityoptions",
|
||||
|
@ -690,12 +694,13 @@ var AppInfo = {};
|
|||
"swiper",
|
||||
"queryString",
|
||||
"sortable",
|
||||
"libjass",
|
||||
"webcomponents",
|
||||
"material-icons",
|
||||
"jellyfin-noto",
|
||||
"date-fns",
|
||||
"page",
|
||||
"polyfill"
|
||||
"polyfill",
|
||||
"classlist-polyfill"
|
||||
]
|
||||
},
|
||||
urlArgs: urlArgs,
|
||||
|
@ -704,6 +709,7 @@ var AppInfo = {};
|
|||
});
|
||||
|
||||
require(["polyfill"]);
|
||||
require(["classlist-polyfill"]);
|
||||
|
||||
// Expose jQuery globally
|
||||
require(["jQuery"], function(jQuery) {
|
||||
|
@ -767,11 +773,9 @@ var AppInfo = {};
|
|||
define("emby-textarea", [elementsPath + "/emby-textarea/emby-textarea"], returnFirstDependency);
|
||||
define("emby-toggle", [elementsPath + "/emby-toggle/emby-toggle"], returnFirstDependency);
|
||||
|
||||
define("webSettings", [scriptsPath + "/settings/webSettings"], returnFirstDependency);
|
||||
define("appSettings", [scriptsPath + "/settings/appSettings"], returnFirstDependency);
|
||||
define("userSettingsBuilder", [scriptsPath + "/settings/userSettingsBuilder"], returnFirstDependency);
|
||||
define("userSettings", ["userSettingsBuilder"], function(userSettingsBuilder) {
|
||||
return new userSettingsBuilder();
|
||||
});
|
||||
define("userSettings", [scriptsPath + "/settings/userSettings"], returnFirstDependency);
|
||||
|
||||
define("chromecastHelper", [componentsPath + "/chromecast/chromecasthelpers"], returnFirstDependency);
|
||||
define("mediaSession", [componentsPath + "/playback/mediasession"], returnFirstDependency);
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
define(["dom", "emby-button"], function (dom) {
|
||||
"use strict";
|
||||
|
||||
function onSubmit(e) {
|
||||
if (dom.parentWithClass(this, "page").querySelector(".chkAccept").checked) {
|
||||
Dashboard.navigate("wizardfinish.html");
|
||||
} else {
|
||||
Dashboard.alert({
|
||||
message: Globalize.translate("MessagePleaseAcceptTermsOfServiceBeforeContinuing"),
|
||||
title: ""
|
||||
});
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
|
||||
return function (view, params) {
|
||||
view.querySelector(".wizardAgreementForm").addEventListener("submit", onSubmit);
|
||||
view.addEventListener("viewshow", function () {
|
||||
document.querySelector(".skinHeader").classList.add("noHomeButtonHeader");
|
||||
});
|
||||
view.addEventListener("viewhide", function () {
|
||||
document.querySelector(".skinHeader").classList.remove("noHomeButtonHeader");
|
||||
});
|
||||
};
|
||||
});
|
|
@ -918,8 +918,8 @@
|
|||
"HeaderFavoriteEpisodes": "الحلقات المفضلة",
|
||||
"HeaderFavoriteArtists": "الفنانون المفضلون",
|
||||
"Shows": "الحلقات",
|
||||
"Books": "كتب",
|
||||
"ValueSpecialEpisodeName": "مميز - {0}",
|
||||
"Books": "الكتب",
|
||||
"ValueSpecialEpisodeName": "خاص - {0}",
|
||||
"HeaderFavoriteAlbums": "الألبومات المفضلة",
|
||||
"HeaderAlbumArtists": "فناني الألبومات",
|
||||
"Genres": "الأنواع",
|
||||
|
@ -952,7 +952,7 @@
|
|||
"Artists": "الفنانين",
|
||||
"Art": "فن",
|
||||
"Anytime": "اي وقت",
|
||||
"AnyLanguage": "اي لغة",
|
||||
"AnyLanguage": "أي لغة",
|
||||
"AlwaysPlaySubtitlesHelp": "الترجمة التي تطابق تفضيلات اللغة سيتم تحميلها بغض النظر عن لغة الصوت.",
|
||||
"AlwaysPlaySubtitles": "شغل الترجمة دائماً",
|
||||
"AllowedRemoteAddressesHelp": "قائمة لعناوين IP أو إدخالات IP / قناع الشبكة مفصولة بفاصلة للشبكات التي سيتم السماح لها بالاتصال عن بعد. إذا تركت فارغة ، فسيتم السماح بجميع العناوين البعيدة.",
|
||||
|
@ -1040,5 +1040,10 @@
|
|||
"DatePlayed": "تاريخ التشغيل",
|
||||
"DateAdded": "تاريخ الاضافة",
|
||||
"CriticRating": "تقييم النقاد",
|
||||
"ResumeAt": "اكمل من {0}"
|
||||
"ResumeAt": "اكمل من {0}",
|
||||
"AskAdminToCreateLibrary": "أطلب من الأدمن إنشاء مكتبة.",
|
||||
"Artist": "الفنان",
|
||||
"AllowFfmpegThrottling": "إبطاء الترميزات",
|
||||
"AlbumArtist": "المؤدي",
|
||||
"Album": "الألبوم"
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
"All": "Всички",
|
||||
"AllLibraries": "Всички библиотеки",
|
||||
"Art": "Картина",
|
||||
"Artists": "Изпълнители",
|
||||
"Artists": "Артисти",
|
||||
"AttributeNew": "Нови",
|
||||
"Audio": "Звук",
|
||||
"Auto": "Автоматично",
|
||||
|
@ -101,8 +101,7 @@
|
|||
"Desktop": "Работен плот",
|
||||
"DeviceAccessHelp": "Това се отнася само за устройства, които могат да бъдат различени и няма да попречи на достъп от мрежов четец. Филтрирането на потребителски устройства ще предотврати използването им докато не бъдат одобрени тук.",
|
||||
"Director": "Режисьор",
|
||||
"DirectorValue": "Режисьор: {0}",
|
||||
"DirectorsValue": "Режисьори: {0}",
|
||||
"Directors": "Режисьори",
|
||||
"Disc": "Диск",
|
||||
"Dislike": "Нехаресване",
|
||||
"Display": "Показване",
|
||||
|
@ -137,9 +136,8 @@
|
|||
"FormatValue": "Формат: {0}",
|
||||
"Friday": "Петък",
|
||||
"Fullscreen": "Цял екран",
|
||||
"GenreValue": "Жанр: {0}",
|
||||
"Genre": "Жанр",
|
||||
"Genres": "Жанрове",
|
||||
"GenresValue": "Жанрове: {0}",
|
||||
"GroupVersions": "Групиране на версиите",
|
||||
"GuestStar": "Гостуваща звезда",
|
||||
"Guide": "Справочник",
|
||||
|
@ -305,9 +303,9 @@
|
|||
"LabelCriticRating": "Оценка на критиците:",
|
||||
"LabelCurrentPassword": "Текуща парола:",
|
||||
"LabelCustomCertificatePath": "Път към потребителския сертификат:",
|
||||
"LabelCustomCertificatePathHelp": "Път до файл с шифровъчен стандарт №12, съдържащ сертификат и частен ключ за поддръжка на протокол TLS на собствен домейн.",
|
||||
"LabelCustomCertificatePathHelp": "Път до файл с шифровъчен стандарт №12 (PKCS #12), съдържащ сертификат и частен ключ за поддръжка на протокол TLS на собствен домейн.",
|
||||
"LabelCustomCss": "CSS по избор:",
|
||||
"LabelCustomCssHelp": "Използвайте собствен CSS към уеб интерфейса.",
|
||||
"LabelCustomCssHelp": "Добавете собствен стил за Уеб-интерфейса.",
|
||||
"LabelCustomDeviceDisplayName": "Показвано име:",
|
||||
"LabelCustomRating": "Оценка по избор:",
|
||||
"LabelDashboardTheme": "Облик на сървърното табло:",
|
||||
|
@ -435,7 +433,7 @@
|
|||
"LabelStatus": "Състояние:",
|
||||
"LabelStopWhenPossible": "Спирай, когато е възможно:",
|
||||
"LabelSubtitlePlaybackMode": "Режим на субтитрите:",
|
||||
"LabelSubtitles": "Субтитри:",
|
||||
"LabelSubtitles": "Субтитри",
|
||||
"LabelSupportedMediaTypes": "Поддържани типове медия:",
|
||||
"LabelTag": "Етикет:",
|
||||
"LabelTextColor": "Цвят на текста:",
|
||||
|
@ -788,7 +786,7 @@
|
|||
"AllowMediaConversion": "Разрешаване на медийни преобразувания",
|
||||
"AllLanguages": "Всички езици",
|
||||
"AllEpisodes": "Всички епизоди",
|
||||
"AllComplexFormats": "Всички комплексни формати (ASS, SSA, VOBSUB, PGS, SUB/IDX, и т.н.)",
|
||||
"AllComplexFormats": "Всички общи формати (ASS, SSA, VOBSUB, PGS, SUB/IDX, и др. )",
|
||||
"AllChannels": "Всички канали",
|
||||
"Alerts": "Известия",
|
||||
"AdditionalNotificationServices": "Разгледайте каталога с добавки за допълнителни услуги за известяване.",
|
||||
|
@ -812,7 +810,7 @@
|
|||
"MediaInfoLayout": "Подредба",
|
||||
"MusicVideo": "Музикален клип",
|
||||
"MediaInfoStreamTypeVideo": "Видео",
|
||||
"LabelVideo": "Видео:",
|
||||
"LabelVideo": "Видео",
|
||||
"HeaderVideoTypes": "Видове видеа",
|
||||
"HeaderVideoType": "Вид на видеото",
|
||||
"EnableExternalVideoPlayers": "Външни възпроизводители",
|
||||
|
@ -836,5 +834,43 @@
|
|||
"AllowOnTheFlySubtitleExtraction": "Позволява моментално извличане на поднадписи",
|
||||
"AllowHWTranscodingHelp": "Позволява на тунера да прекодира моментално. Това може да помогне за редуциране на прекодирането от сървъра.",
|
||||
"AddItemToCollectionHelp": "Добавяне към колекция чрез търсенето им и използване на дясно-щракване с мишката или контекстното меню.",
|
||||
"Absolute": "Aбсолютен"
|
||||
"Absolute": "Aбсолютен",
|
||||
"LabelLanNetworks": "Локални мрежи:",
|
||||
"LabelKodiMetadataSaveImagePathsHelp": "Това е препоръчително ако имате изображения, пътят към които не е съобразен с изискванията на Kodi",
|
||||
"LabelKodiMetadataSaveImagePaths": "Записване на пътеките към изображенията в nfo файловете",
|
||||
"LabelChannels": "Канали:",
|
||||
"DropShadow": "Сянка",
|
||||
"Raised": "Повишено",
|
||||
"OptionResElement": "рес. елемент",
|
||||
"ButtonChangeServer": "Смяна на сървър",
|
||||
"ButtonAddImage": "Добавяне на изображение",
|
||||
"BrowsePluginCatalogMessage": "За да видите наличните добавки, прегледайте каталога с добавките.",
|
||||
"Box": "Кутия",
|
||||
"AlwaysPlaySubtitlesHelp": "Поднадписите, съвпадащи с езика от настройките, ще се зареждат, независимо от езика на аудио то.",
|
||||
"BookLibraryHelp": "Поддържат се звукови и текстови книги. Преглед на инструкция за наименоване {1} на книга {0}.",
|
||||
"Blacklist": "Списък с блокирани",
|
||||
"BirthLocation": "Месторождение",
|
||||
"Banner": "Банер",
|
||||
"AspectRatio": "Съотношение",
|
||||
"AskAdminToCreateLibrary": "Помолете администратора за създаване на библиотека.",
|
||||
"Ascending": "Възходящо",
|
||||
"AsManyAsPossible": "Колкото е възможно повече",
|
||||
"Artist": "Артист",
|
||||
"AroundTime": "Към {0}",
|
||||
"Anytime": "По всяко време",
|
||||
"AnyLanguage": "Който и да е език",
|
||||
"AlwaysPlaySubtitles": "Постоянно изпълнение",
|
||||
"AllowRemoteAccessHelp": "Ако не е маркирано, всеки отдалечен достъп ще бъде блокиран.",
|
||||
"AllowRemoteAccess": "Позволяване на отдалечен достъп до този Jellyfin сървър.",
|
||||
"AllowFfmpegThrottling": "Подтискане на прекодирането",
|
||||
"AllowMediaConversionHelp": "Даване или отнемане на права за функциите за конвертиране на медия.",
|
||||
"AlbumArtist": "Изпълнител",
|
||||
"Album": "Албум",
|
||||
"ClientSettings": "Клиентски настройки",
|
||||
"ChannelNumber": "Номер на канала",
|
||||
"ChannelNameOnly": "Само {0} канал",
|
||||
"CancelSeries": "Откажи сериите",
|
||||
"CancelRecording": "Откажи записа",
|
||||
"ButtonSplit": "Раздели",
|
||||
"ButtonResetEasyPassword": "Нулиране на бързия ПИН код"
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
"AllEpisodes": "Všechny epizody",
|
||||
"AllLanguages": "Všechny jazyky",
|
||||
"AllowHWTranscodingHelp": "Povolit tuneru překódování v reálném čase. Může snížit zátěž překódovávání požadované Jellyfin serverem.",
|
||||
"AlwaysPlaySubtitles": "Vždy zobrazit titulky",
|
||||
"AlwaysPlaySubtitles": "Vždy zobrazovat",
|
||||
"AlwaysPlaySubtitlesHelp": "Titulky odpovídající jazykové předvolbě se načtou bez ohledu na jazyk audia.",
|
||||
"Anytime": "Kdykoliv",
|
||||
"AroundTime": "Okolo {0}",
|
||||
|
@ -158,7 +158,7 @@
|
|||
"Display": "Zobrazení",
|
||||
"DisplayMissingEpisodesWithinSeasons": "Zobrazit chybějící epizody",
|
||||
"DisplayMissingEpisodesWithinSeasonsHelp": "Toto musí být zapnuto pro knihovny TV v nastavení serveru.",
|
||||
"DisplayModeHelp": "Zvolte typ obrazovky, na které používáte Jellyfin.",
|
||||
"DisplayModeHelp": "Vyberte styl rozvržení, který chcete pro rozhraní.",
|
||||
"DoNotRecord": "Nenahrávat",
|
||||
"Down": "Dolů",
|
||||
"Download": "Stáhnout",
|
||||
|
@ -426,7 +426,7 @@
|
|||
"Images": "Obrázky",
|
||||
"ImportFavoriteChannelsHelp": "Pokud je povoleno, jen kanály označené jako oblíbené budou importována na zařízení tuneru.",
|
||||
"ImportMissingEpisodesHelp": "Pokud je povoleno, budou informace o chybějících epizodách importovány do databáze Jellyfin a zobrazí se v sezónách seriálu. To může způsobit podstatně delší skenování knihovny.",
|
||||
"InstallingPackage": "Instalace {0}",
|
||||
"InstallingPackage": "Instalace {0} (Verze {1})",
|
||||
"InstantMix": "Okamžité míchání",
|
||||
"ItemCount": "{0} položek",
|
||||
"Items": "Položky",
|
||||
|
@ -461,7 +461,7 @@
|
|||
"LabelArtist": "Umělec",
|
||||
"LabelArtists": "Umělci:",
|
||||
"LabelArtistsHelp": "Odděl pomocí ;",
|
||||
"LabelAudio": "Zvuk:",
|
||||
"LabelAudio": "Zvuk",
|
||||
"LabelAudioLanguagePreference": "Preferovaný jazyk zvuku:",
|
||||
"LabelBindToLocalNetworkAddress": "Vázat na místní síťovou adresu:",
|
||||
"LabelBindToLocalNetworkAddressHelp": "Volitelné. Přepsat lokální IP adresu vazanou na http server. Pokud je ponecháno prázdné, server se sváže ke všem dostupným adresám (aplikace bude dostupná na všech síťových zařízení, které server nabízí). Změna této hodnoty vyžaduje restartování Jellyfin Serveru.",
|
||||
|
@ -709,7 +709,7 @@
|
|||
"LabelStopping": "Zastavování",
|
||||
"LabelSubtitleFormatHelp": "Příklad: srt",
|
||||
"LabelSubtitlePlaybackMode": "Mód titulků:",
|
||||
"LabelSubtitles": "Titulky:",
|
||||
"LabelSubtitles": "Titulky",
|
||||
"LabelSupportedMediaTypes": "Podporované typy médií:",
|
||||
"LabelTagline": "Slogan:",
|
||||
"LabelTextBackgroundColor": "Barva pozadí textu:",
|
||||
|
@ -862,14 +862,14 @@
|
|||
"NoNextUpItemsMessage": "Nic nenalezeno. Začněte sledovat Vaše oblíbené seriály!",
|
||||
"NoPluginConfigurationMessage": "Tento zásuvný modul nemá žádné nastavení.",
|
||||
"NoSubtitleSearchResultsFound": "Žádné výsledky.",
|
||||
"NoSubtitles": "Žádné titulky",
|
||||
"NoSubtitles": "Žádné",
|
||||
"NoSubtitlesHelp": "Ve výchozím nastavení nebudou titulky načteny. Během přehrávání však mohou být manuálně zapnuty.",
|
||||
"None": "Žádný",
|
||||
"Normal": "Normální",
|
||||
"NumLocationsValue": "{0} složky",
|
||||
"Off": "Vypnuto",
|
||||
"OneChannel": "Jeden kanál",
|
||||
"OnlyForcedSubtitles": "Pouze vynucené titulky",
|
||||
"OnlyForcedSubtitles": "Pouze vynucené",
|
||||
"OnlyForcedSubtitlesHelp": "Jen vynucené titulky budou nahrány.",
|
||||
"OptionAdminUsers": "Administrátoři",
|
||||
"OptionAlbumArtist": "Umělec Alba",
|
||||
|
@ -1248,7 +1248,7 @@
|
|||
"Blacklist": "Černá listina",
|
||||
"BobAndWeaveWithHelp": "Bob and weave (vyšší kvalita, ale pomalejší)",
|
||||
"Browse": "Procházet",
|
||||
"BurnSubtitlesHelp": "Určuje, zda má server vypalovat titulky při převodu videa v závislosti na formátu titulků. Vynechání vypalování titulků zlepší výkon serveru. Chcete-li vypálit grafické formáty (VOBSUB, PGS, SUB / IDX atd.) a některé titulky ASS / SSA, vyberte možnost Auto.",
|
||||
"BurnSubtitlesHelp": "Určuje, zda má server vypalovat titulky při překódování videa. Vynechání tohoto zlepší výkon serveru. Chcete-li vypálit grafické formáty (VOBSUB, PGS, SUB / IDX atd.) a některé titulky ASS / SSA, vyberte možnost Auto.",
|
||||
"ButtonInfo": "Info",
|
||||
"ButtonMenu": "Menu",
|
||||
"ButtonOk": "Ok",
|
||||
|
@ -1276,8 +1276,7 @@
|
|||
"DetectingDevices": "Hledání zařízení",
|
||||
"DirectPlayError": "Chyba přímého přehrávání",
|
||||
"DirectStreamHelp2": "Přímé streamování souboru používá velmi malý výkon bez ztráty kvality videa.",
|
||||
"DirectorValue": "Režisér: {0}",
|
||||
"DirectorsValue": "Režiséři: {0}",
|
||||
"Directors": "Režiséři",
|
||||
"Disabled": "Vypnuto",
|
||||
"DisplayInMyMedia": "Zobrazit na domovské obrazovce",
|
||||
"DisplayInOtherHomeScreenSections": "Zobrazení v sekcích domovské obrazovky, jako jsou nejnovější média, a pokračování ve sledování",
|
||||
|
@ -1297,8 +1296,7 @@
|
|||
"Filters": "Filtry",
|
||||
"Folders": "Složky",
|
||||
"General": "Hlavní",
|
||||
"GenreValue": "Žánr: {0}",
|
||||
"GenresValue": "Žánry: {0}",
|
||||
"Genre": "Žánr",
|
||||
"GroupBySeries": "Seskupit podle série",
|
||||
"HandledByProxy": "Zpracováno reverzním proxy",
|
||||
"HeaderAddLocalUser": "Přidat místního uživatele",
|
||||
|
@ -1395,7 +1393,7 @@
|
|||
"LabelUrl": "URL:",
|
||||
"LabelUserAgent": "User agent:",
|
||||
"LabelUserRemoteClientBitrateLimitHelp": "Přepíše výchozí globální hodnotu nastavenou v nastavení přehrávání serveru.",
|
||||
"LabelVideo": "Video:",
|
||||
"LabelVideo": "Video",
|
||||
"LabelVideoCodec": "Video kodek:",
|
||||
"LeaveBlankToNotSetAPassword": "Můžete ponechat prázdné pro nastavení bez hesla.",
|
||||
"LetterButtonAbbreviation": "A",
|
||||
|
@ -1572,5 +1570,29 @@
|
|||
"NoCreatedLibraries": "Zdá se, že jste dosud nevytvořili žádnou knihovnu. {0}Chtěli byste nějakou vytvořit nyní?{1}",
|
||||
"AskAdminToCreateLibrary": "Požádejte administrátora o vytvoření knihovny.",
|
||||
"AllowFfmpegThrottlingHelp": "Když se překódování nebo remux dostane dostatečně daleko dopředu od aktuální pozice přehrávání, pozastaví se proces, aby spotřeboval méně zdrojů. To je nejužitečnější při sledování bez častého vyhledávání. Pokud máte problémy s přehráváním, vypněte tuto funkci.",
|
||||
"AllowFfmpegThrottling": "Omezit překódování"
|
||||
"AllowFfmpegThrottling": "Omezit překódování",
|
||||
"BoxSet": "Sbírka",
|
||||
"Track": "Stopa",
|
||||
"Season": "Sezóna",
|
||||
"ReleaseGroup": "Vydavatel",
|
||||
"PreferEmbeddedEpisodeInfosOverFileNames": "Preferovat vložené informace o epizodě před názvy souborů",
|
||||
"PreferEmbeddedEpisodeInfosOverFileNamesHelp": "Používá se informace o epizodě z vložených metadat, pokud jsou k dispozici.",
|
||||
"Person": "Osoba",
|
||||
"OtherArtist": "Ostatní interpreti",
|
||||
"Movie": "Film",
|
||||
"Episode": "Epizoda",
|
||||
"ClientSettings": "Nastavení klienta",
|
||||
"Artist": "Interpret",
|
||||
"AlbumArtist": "Interpret alba",
|
||||
"Album": "Album",
|
||||
"OnApplicationStartup": "Při zapnutí aplikace",
|
||||
"EveryXHours": "Každých {0} hodin",
|
||||
"EveryHour": "Každou hodinu",
|
||||
"EveryXMinutes": "Každých {0} minut",
|
||||
"OnWakeFromSleep": "Při probuzení",
|
||||
"DailyAt": "Denně v {0}",
|
||||
"PersonRole": "jako {0}",
|
||||
"ListPaging": "{0}-{1} ze {2}",
|
||||
"WriteAccessRequired": "Jellyfin Server potřebuje oprávnění pro zápis v této složce. Zkontrolujte oprávnění a zkuste to znovu.",
|
||||
"PathNotFound": "Cesta nebyla nalezena. Zkontrolujte, zda je platná a zkuste to znovu."
|
||||
}
|
||||
|
|
|
@ -1166,8 +1166,7 @@
|
|||
"DirectStreamHelp1": "Medie filen er kompatibel med enheden i forhold til opløsning og medie type (H.264,AC3, etc.), men er i en ikke kompatibel fil container (mkv, avi, wmv, etc). Videoen vil blive genpakket live før den streames til enheden.",
|
||||
"DirectStreamHelp2": "Direkte Streaming af en fil bruger meget lidt processor kraft uden nogen tab af video kvalitet.",
|
||||
"DirectStreaming": "Direkte streaming",
|
||||
"DirectorValue": "Instruktør: {0}",
|
||||
"DirectorsValue": "Instruktører: {0}",
|
||||
"Directors": "Instruktører",
|
||||
"Disc": "Disk",
|
||||
"Dislike": "Kan ikke lide",
|
||||
"Display": "Visning",
|
||||
|
@ -1207,8 +1206,7 @@
|
|||
"Features": "Funktioner",
|
||||
"Filters": "Filtre",
|
||||
"FormatValue": "Format: {0}",
|
||||
"GenreValue": "Genre: {0}",
|
||||
"GenresValue": "Genrer: {0}",
|
||||
"Genre": "Genre",
|
||||
"GroupBySeries": "Gruppér efter serie",
|
||||
"Guide": "Vejledning",
|
||||
"GuideProviderLogin": "Log Ind",
|
||||
|
@ -1270,7 +1268,7 @@
|
|||
"Label3DFormat": "3D format:",
|
||||
"LabelAlbum": "Album:",
|
||||
"LabelArtist": "Kunstner",
|
||||
"LabelAudio": "Lyd:",
|
||||
"LabelAudio": "Lyd",
|
||||
"LabelBitrateMbps": "Bitrate (Mbps):",
|
||||
"LabelBlockContentWithTags": "Blokér filer med mærkerne:",
|
||||
"LabelBurnSubtitles": "Brænd undertekster:",
|
||||
|
@ -1315,7 +1313,7 @@
|
|||
"LabelSortOrder": "Sorteringsorden:",
|
||||
"LabelSoundEffects": "Lydeffekter:",
|
||||
"LabelStatus": "Status:",
|
||||
"LabelSubtitles": "Undertekster:",
|
||||
"LabelSubtitles": "Undertekster",
|
||||
"LabelSyncNoTargetsHelp": "Det ser ud til at du ikke har nogen apps der understøtter offline hentning.",
|
||||
"LabelTVHomeScreen": "TV modus hjemmeskærm:",
|
||||
"LabelTag": "Mærke:",
|
||||
|
@ -1329,7 +1327,7 @@
|
|||
"LabelUrl": "Link:",
|
||||
"LabelVersion": "Version:",
|
||||
"LabelVersionNumber": "Version {0}",
|
||||
"LabelVideo": "Video:",
|
||||
"LabelVideo": "Video",
|
||||
"LabelVideoCodec": "Video codec:",
|
||||
"LabelWindowBackgroundColor": "Tekst baggrundsfarve:",
|
||||
"LabelXDlnaCap": "X-DLNA begrænsning:",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"Absolute": "Gesamt",
|
||||
"Absolute": "Absolut",
|
||||
"AccessRestrictedTryAgainLater": "Der Zugriff ist derzeit eingeschränkt. Bitte versuche es später erneut.",
|
||||
"Actor": "Darsteller(in)",
|
||||
"Add": "Hinzufügen",
|
||||
|
@ -11,13 +11,13 @@
|
|||
"AddUser": "Benutzer anlegen",
|
||||
"AddUserByManually": "Lege einen lokalen User durch manuelle Eingabe der User-Informationen an.",
|
||||
"AddedOnValue": "{0} hinzugefügt",
|
||||
"AdditionalNotificationServices": "Schau im Pluginkatalog, um weitere Benachrichtigungsdienste zu installieren.",
|
||||
"AdditionalNotificationServices": "Durchsuche den Pluginkatalog, um weitere Benachrichtigungsdienste zu installieren.",
|
||||
"AirDate": "Erstausstrahlung",
|
||||
"Aired": "Ausgestrahlt",
|
||||
"Albums": "Alben",
|
||||
"All": "Alle",
|
||||
"AllChannels": "Alle Kanäle",
|
||||
"AllComplexFormats": "Alle komplexen Formate (ASS, SSA, VOBSUB, PGS, SUB/IDX, etc.)",
|
||||
"AllComplexFormats": "Alle komplexen Formate (ASS, SSA, VOBSUB, PGS, SUB/IDX)",
|
||||
"AllEpisodes": "Alle Folgen",
|
||||
"AllLanguages": "Alle Sprachen",
|
||||
"AllLibraries": "Alle Bibliotheken",
|
||||
|
@ -26,13 +26,13 @@
|
|||
"AllowMediaConversion": "Erlaube Medienkonvertierung",
|
||||
"AllowMediaConversionHelp": "Erlaube oder unterbinde Zugriff auf die Medienkonvertierung.",
|
||||
"AllowOnTheFlySubtitleExtraction": "Erlaube Untertitelextraktion \"on-the-fly\"",
|
||||
"AllowOnTheFlySubtitleExtractionHelp": "Eingebettete Untertitel können aus Videos extrahiert und im Textformat an Clients gesendet werden um die Videotranskodierung zu vermeiden. Auf manchen Systemen kann dieser Vorgang eine lange Zeit in Anspruch nehmen und die Videowiedergabe während der Extraktion unterbrochen werden. Deaktiviere diese Option um eingebettete Untertitel während der Videotranskodierung einbrennen zu lassen, wenn sie nicht nativ vom Client unterstützt werden.",
|
||||
"AllowOnTheFlySubtitleExtractionHelp": "Eingebettete Untertitel können aus Videos extrahiert und in Reintext an Clients gesendet werden, um eine Videotranskodierung zu vermeiden. Auf manchen Systemen kann dieser Vorgang eine lange Zeit in Anspruch nehmen und deswegen währenddessen die Videowiedergabe stoppen. Deaktiviere diese Option, um eingebettete Untertitel während des Videotranskodierens einbrennen zu lassen, wenn sie nicht nativ vom Client unterstützt werden.",
|
||||
"AllowRemoteAccess": "Erlaube externe Verbindungen zu diesem Jellyfin Server.",
|
||||
"AllowRemoteAccessHelp": "Wenn deaktiviert werden alle externen Verbindungen blockiert.",
|
||||
"AllowSeasonalThemes": "Erlaube automatische Jahreszeitenmotive",
|
||||
"AllowSeasonalThemesHelp": "Wenn aktiviert, werden Jahreszeitenmotive von Zeit zu Zeit deine Motiveinstellungen überschreiben.",
|
||||
"AllowedRemoteAddressesHelp": "Kommagetrennte Liste von IP Adressen oder IP/Netzmasken für Netzwerke, für die externe Verbindungen erlaubt sind. Wenn leer, sind alle Adressen erlaubt.",
|
||||
"AlwaysPlaySubtitles": "Untertitel immer einblenden",
|
||||
"AlwaysPlaySubtitles": "Immer anzeigen",
|
||||
"AlwaysPlaySubtitlesHelp": "Untertitel die den Spracheinstellungen entsprechen werden unabhängig von der Tonspursprache geladen.",
|
||||
"AnyLanguage": "Jede Sprache",
|
||||
"Anytime": "Jederzeit",
|
||||
|
@ -60,7 +60,7 @@
|
|||
"BoxRear": "Box (Rückseite)",
|
||||
"Browse": "Blättern",
|
||||
"BrowsePluginCatalogMessage": "Durchsuche unsere Bibliothek, um alle verfügbaren Plugins anzuzeigen.",
|
||||
"BurnSubtitlesHelp": "Legt fest, ob der Server die Untertitel basierend auf deren Format während der Videokonvertierung einbrennen soll. Die Vermeidung des Einbrennen von Untertiteln verbessert die Serverperformance. Wähle Auto, um Bildfomate (z.B. VOBSUB, PGS, SUB/IDX, etc.) sowie bestimmte ASS/SSA-Untertitel einbrennen zu lassen.",
|
||||
"BurnSubtitlesHelp": "Legt fest, ob der Server die Untertitel während der Videotranskodierung einbrennen soll. Deaktivieren verbessert die Serverperformance immens. Wähle Auto, um bildbasierte Formate (z.B. VOBSUB, PGS, SUB, IDX) sowie bestimmte ASS- oder SSA-Untertitel einbrennen zu lassen.",
|
||||
"ButtonAdd": "Hinzufügen",
|
||||
"ButtonAddMediaLibrary": "Füge Medienbibliothek hinzu",
|
||||
"ButtonAddScheduledTaskTrigger": "Auslöser hinzufügen",
|
||||
|
@ -175,8 +175,7 @@
|
|||
"DirectStreamHelp2": "Direktes Streaming von Dateien benötigt sehr wenig Rechenleistung ohne Verlust der Videoqualität.",
|
||||
"DirectStreaming": "Direktes Streaming",
|
||||
"Director": "Regisseur",
|
||||
"DirectorValue": "Regisseur: {0}",
|
||||
"DirectorsValue": "Regisseure: {0}",
|
||||
"Directors": "Regisseure",
|
||||
"Disabled": "Abgeschaltet",
|
||||
"Disc": "Disk",
|
||||
"Disconnect": "Verbindung trennen",
|
||||
|
@ -186,7 +185,7 @@
|
|||
"DisplayInOtherHomeScreenSections": "Zeige auf dem Homescreen Bereiche wie 'Neueste Medien' oder 'Weiterschauen'",
|
||||
"DisplayMissingEpisodesWithinSeasons": "Zeige fehlende Episoden innerhalb von Staffeln",
|
||||
"DisplayMissingEpisodesWithinSeasonsHelp": "Dies muss auch für Serienbibliotheken in den Servereinstellungen aktiviert sein.",
|
||||
"DisplayModeHelp": "Bitte wähle den Typ des Bildschirms auf dem Du Jellyfin verwendest.",
|
||||
"DisplayModeHelp": "Wähle das Layout welches du für die Oberfläche verwenden möchtest.",
|
||||
"DoNotRecord": "Nicht aufnehmen",
|
||||
"Down": "Runter",
|
||||
"DownloadsValue": "{0} Downloads",
|
||||
|
@ -470,7 +469,7 @@
|
|||
"Images": "Bilder",
|
||||
"ImportFavoriteChannelsHelp": "Wenn aktiviert, werden nur auf dem Tuner favorisierte Kanäle importiert.",
|
||||
"ImportMissingEpisodesHelp": "Wenn aktiviert, werden Informationen über fehlende Episoden in Deine Jellyfin Datenbank importiert und innerhalb von Staffeln angezeigt. Dies kann zu deutlich längeren Bibliothek Scans führen.",
|
||||
"InstallingPackage": "Installiere {0}",
|
||||
"InstallingPackage": "Installiere {0} (Version {1})",
|
||||
"InstantMix": "Schnellmix",
|
||||
"ItemCount": "{0} Einträge",
|
||||
"Items": "Einträge",
|
||||
|
@ -759,7 +758,7 @@
|
|||
"LabelSubtitleDownloaders": "Untertitel Downloader:",
|
||||
"LabelSubtitleFormatHelp": "Beispiel: srt",
|
||||
"LabelSubtitlePlaybackMode": "Untertitelmodus:",
|
||||
"LabelSubtitles": "Untertitel:",
|
||||
"LabelSubtitles": "Untertitel",
|
||||
"LabelSupportedMediaTypes": "Unterstüzte Medientypen:",
|
||||
"LabelTVHomeScreen": "TV-Mode Startseite:",
|
||||
"LabelTextBackgroundColor": "Hintergrundfarbe des Textes:",
|
||||
|
@ -909,15 +908,15 @@
|
|||
"NoNextUpItemsMessage": "Es wurde nichts gefunden. Schau dir deine Shows an!",
|
||||
"NoPluginConfigurationMessage": "Dieses Plugin hat keine konfigurierbaren Einstellungen.",
|
||||
"NoSubtitleSearchResultsFound": "Keine Ergebnisse gefunden.",
|
||||
"NoSubtitles": "Keine Untertitel",
|
||||
"NoSubtitles": "Keine",
|
||||
"NoSubtitlesHelp": "Untertitel werden standardmäßig nicht geladen. Sie können aber während der Wiedergabe manuell aktiviert werden.",
|
||||
"None": "Keines",
|
||||
"NumLocationsValue": "{0} Verzeichnisse",
|
||||
"Off": "Aus",
|
||||
"OneChannel": "Ein Kanal",
|
||||
"OnlyForcedSubtitles": "Nur erzwungene Untertitel",
|
||||
"OnlyForcedSubtitles": "Nur Erzwungene",
|
||||
"OnlyForcedSubtitlesHelp": "Nur Untertitel, die als erzwungen markiert wurden, werden geladen.",
|
||||
"OnlyImageFormats": "Nur Bildformate (VOBSUB, PGS, SUB, etc.)",
|
||||
"OnlyImageFormats": "Nur Bildformate (VOBSUB, PGS, SUB)",
|
||||
"OptionAdminUsers": "Administratoren",
|
||||
"OptionAlbumArtist": "Album-Interpret",
|
||||
"OptionAllUsers": "Alle Benutzer",
|
||||
|
@ -1037,9 +1036,9 @@
|
|||
"OptionWeekly": "Wöchentlich",
|
||||
"OriginalAirDateValue": "Erstausstrahlung: {0}",
|
||||
"Overview": "Übersicht",
|
||||
"PackageInstallCancelled": "{0} Installation abgebrochen.",
|
||||
"PackageInstallCompleted": "{0} Installation abgeschlossen.",
|
||||
"PackageInstallFailed": "{0} Installation fehlgeschlagen.",
|
||||
"PackageInstallCancelled": "{0} (Version {1}) Installation abgebrochen.",
|
||||
"PackageInstallCompleted": "{0} (Version {1}) Installation abgeschlossen.",
|
||||
"PackageInstallFailed": "{0} (Version {1}) Installation fehlgeschlagen.",
|
||||
"ParentalRating": "Altersfreigabe",
|
||||
"PasswordMatchError": "Die Passwörter müssen übereinstimmen.",
|
||||
"PasswordResetComplete": "Das Passwort wurde zurückgesetzt.",
|
||||
|
@ -1293,9 +1292,8 @@
|
|||
"Extras": "Extras",
|
||||
"FolderTypeTvShows": "TV Serien",
|
||||
"FormatValue": "Format: {0}",
|
||||
"GenreValue": "Genre: {0}",
|
||||
"Genre": "Genre",
|
||||
"Genres": "Genres",
|
||||
"GenresValue": "Genres: {0}",
|
||||
"HeaderAdmin": "Admin",
|
||||
"HeaderApp": "App",
|
||||
"HeaderGenres": "Genres",
|
||||
|
@ -1307,7 +1305,7 @@
|
|||
"Home": "Startseite",
|
||||
"Horizontal": "Horizontal",
|
||||
"LabelAlbum": "Album:",
|
||||
"LabelAudio": "Audio:",
|
||||
"LabelAudio": "Audio",
|
||||
"LabelCache": "Cache:",
|
||||
"LabelFormat": "Format:",
|
||||
"LabelH264Crf": "H264 Encodierungs-CRF:",
|
||||
|
@ -1316,7 +1314,7 @@
|
|||
"LabelProfileCodecs": "Codecs:",
|
||||
"LabelProfileContainer": "Container:",
|
||||
"LabelSkin": "Textur:",
|
||||
"Art": "Kunst",
|
||||
"Art": "Coverkunst",
|
||||
"Name": "Name",
|
||||
"Songs": "Songs",
|
||||
"ValueSpecialEpisodeName": "Extra - {0}",
|
||||
|
@ -1326,7 +1324,7 @@
|
|||
"LabelTypeText": "Text",
|
||||
"LabelVersion": "Version:",
|
||||
"LabelVersionNumber": "Version {0}",
|
||||
"LabelVideo": "Video:",
|
||||
"LabelVideo": "Video",
|
||||
"LeaveBlankToNotSetAPassword": "Du kannst dieses Feld frei lassen um kein Passwort zu setzen.",
|
||||
"LinksValue": "Links: {0}",
|
||||
"MessageImageFileTypeAllowed": "Nur JPEG- und PNG-Dateien werden unterstützt.",
|
||||
|
@ -1402,8 +1400,8 @@
|
|||
"Thumb": "Miniaturansicht",
|
||||
"TitleSupport": "Hilfe",
|
||||
"Whitelist": "Erlaubt",
|
||||
"AuthProviderHelp": "Auswählen eines Authentifizierungsanbieter, der zur Authentifizierung des Passworts dieses Benutzes verwendet werden soll.",
|
||||
"Features": "Features",
|
||||
"AuthProviderHelp": "Authentifizierungsanbieter auswählen, der zur Authentifizierung des Benutzerpassworts verwendet werden soll.",
|
||||
"Features": "Funktionen",
|
||||
"HeaderFavoriteBooks": "Lieblingsbücher",
|
||||
"HeaderFavoriteMovies": "Lieblingsfilme",
|
||||
"HeaderFavoriteShows": "Lieblingsserien",
|
||||
|
@ -1493,5 +1491,29 @@
|
|||
"NoCreatedLibraries": "Sieht so aus als hättest du bis jetzt keine Bibliothek erstellt. {0}Möchtest du jetzt eine Bibliothek erstellen?{1}",
|
||||
"AllowFfmpegThrottling": "Transkodierung drosseln",
|
||||
"PlaybackErrorNoCompatibleStream": "Es gab ein Problem bei der Erkennung des Wiedergabeprofils des Clients und der Server sendet kein kompatibles Format.",
|
||||
"AllowFfmpegThrottlingHelp": "Wenn eine Transkodierung oder ein Remux weit genug über die aktuelle Abspielposition fortgeschritten ist, pausiere sie sodass weniger Ressourcen verbraucht werden. Dies ist am nützlichsten, wenn wenig geskippt wird. Bei Wiedergabeproblemen sollte diese Option deaktiviert werden."
|
||||
"AllowFfmpegThrottlingHelp": "Wenn eine Transkodierung oder ein Remux weit genug über die aktuelle Abspielposition fortgeschritten ist, pausiere sie sodass weniger Ressourcen verbraucht werden. Dies ist am nützlichsten, wenn wenig geskippt wird. Bei Wiedergabeproblemen sollte diese Option deaktiviert werden.",
|
||||
"ClientSettings": "Client Einstellungen",
|
||||
"OnApplicationStartup": "Beim Starten der Applikation",
|
||||
"EveryXHours": "Alle {0} Stunden",
|
||||
"EveryHour": "Jede Stunde",
|
||||
"EveryXMinutes": "Alle {0} Minuten",
|
||||
"OnWakeFromSleep": "Beim Aufwachen aus \"Energie sparen\"",
|
||||
"WeeklyAt": "{0} um {1}",
|
||||
"DailyAt": "Täglich um {0}",
|
||||
"LastSeen": "Zuletzt gesehen {0}",
|
||||
"PersonRole": "als {0}",
|
||||
"ListPaging": "{0}-{1} von {2}",
|
||||
"WriteAccessRequired": "Jellyfin Server benötigt Schreibrechte auf diesem Ordner. Bitte prüfe die Schreibrechte und versuche es erneut.",
|
||||
"PathNotFound": "Der Pfad konnte nicht gefunden werden. Bitte versichere dich dass der Pfad korrekt ist und versuche es erneut.",
|
||||
"Track": "Track",
|
||||
"Season": "Staffel",
|
||||
"ReleaseGroup": "Veröffentlichungs-Gruppe",
|
||||
"Person": "Person",
|
||||
"OtherArtist": "Andere Künstler",
|
||||
"Movie": "Film",
|
||||
"Episode": "Episode",
|
||||
"Artist": "Künstler",
|
||||
"AlbumArtist": "Album Künstler",
|
||||
"Album": "Album",
|
||||
"BoxSet": "Box Set"
|
||||
}
|
||||
|
|
|
@ -166,8 +166,7 @@
|
|||
"DirectStreamHelp2": "Η άμεση ροή ενός αρχείου χρησιμοποιεί ελάχιστη ισχύ επεξεργασίας χωρίς απώλεια της ποιότητας του βίντεο.",
|
||||
"DirectStreaming": "Απευθείας Αναμετάδοση",
|
||||
"Director": "Σκηνοθέτης",
|
||||
"DirectorValue": "Σκηνοθέτης: {0}",
|
||||
"DirectorsValue": "Σκηνοθέτες: {0}",
|
||||
"Directors": "Σκηνοθέτες",
|
||||
"Disabled": "Απενεργοποιημένο",
|
||||
"Disc": "Δίσκος",
|
||||
"Disconnect": "Αποσύνδεση",
|
||||
|
@ -233,9 +232,8 @@
|
|||
"Friday": "Παρασκευή",
|
||||
"Fullscreen": "ΠΛΗΡΗΣ ΟΘΟΝΗ",
|
||||
"General": "Γενικά",
|
||||
"GenreValue": "Είδος: {0}",
|
||||
"Genre": "Είδος",
|
||||
"Genres": "Είδη",
|
||||
"GenresValue": "Είδη: {0}",
|
||||
"GroupBySeries": "Ομαδοποίηση κατά σειρά",
|
||||
"GroupVersions": "Ομαδικές εκδόσεις",
|
||||
"GuestStar": "Φιλική Συμμετοχή",
|
||||
|
@ -452,7 +450,7 @@
|
|||
"LabelAppNameExample": "Παράδειγμα: Sickbeard, NzbDrone",
|
||||
"LabelArtists": "Καλλιτέχνες:",
|
||||
"LabelArtistsHelp": "Ξεχωρίστε πολλαπλά χρησιμοποιώντας;",
|
||||
"LabelAudio": "Ήχος:",
|
||||
"LabelAudio": "Ήχος",
|
||||
"LabelAudioLanguagePreference": "Προτιμώμενη γλώσσα ήχου:",
|
||||
"LabelAutomaticallyRefreshInternetMetadataEvery": "Αυτόματη ανανέωση μεταδεδομένων από το internet:",
|
||||
"LabelBirthDate": "Ημερομηνία Γενεθλίων:",
|
||||
|
@ -666,7 +664,7 @@
|
|||
"LabelStopWhenPossible": "Διακοπή όταν είναι δυνατόν:",
|
||||
"LabelSubtitleFormatHelp": "Παράδειγμα: srt",
|
||||
"LabelSubtitlePlaybackMode": "Λειτουργία υποτίτλων:",
|
||||
"LabelSubtitles": "Υπότιτλοι:",
|
||||
"LabelSubtitles": "Υπότιτλοι",
|
||||
"LabelSupportedMediaTypes": "Υποστηριζόμενοι τύποι μέσων:",
|
||||
"LabelTVHomeScreen": "Αρχική οθόνη λειτουργίας τηλεόρασης:",
|
||||
"LabelTag": "Ετικέτα:",
|
||||
|
@ -696,7 +694,7 @@
|
|||
"LabelVersion": "Έκδοση:",
|
||||
"LabelVersionInstalled": "{0} εγκαταστήθηκε",
|
||||
"LabelVersionNumber": "Έκδοση {0}",
|
||||
"LabelVideo": "Βίντεο:",
|
||||
"LabelVideo": "Βίντεο",
|
||||
"LabelXDlnaCapHelp": "Καθορίζει το περιεχόμενο του στοιχείου X_DLNACAP στο urn:schemas-dlna-org:device-1-0 namespace.",
|
||||
"LabelXDlnaDocHelp": "Καθορίζει το περιεχόμενο του στοιχείου X_DLNACAP στο urn:schemas-dlna-org:device-1-0 namespace.",
|
||||
"LabelYear": "Έτος:",
|
||||
|
|
|
@ -223,8 +223,7 @@
|
|||
"DirectStreamHelp2": "Direct Streaming a file uses very little processing power without any loss in video quality.",
|
||||
"DirectStreaming": "Direct streaming",
|
||||
"Director": "Director",
|
||||
"DirectorValue": "Director: {0}",
|
||||
"DirectorsValue": "Directors: {0}",
|
||||
"Directors": "Directors",
|
||||
"Disabled": "Disabled",
|
||||
"Disc": "Disc",
|
||||
"Disconnect": "Disconnect",
|
||||
|
@ -299,8 +298,7 @@
|
|||
"Friday": "Friday",
|
||||
"Fullscreen": "Full screen",
|
||||
"General": "General",
|
||||
"GenreValue": "Genre: {0}",
|
||||
"GenresValue": "Genres: {0}",
|
||||
"Genre": "Genre",
|
||||
"GroupBySeries": "Group by series",
|
||||
"GroupVersions": "Group versions",
|
||||
"GuestStar": "Guest star",
|
||||
|
@ -808,7 +806,7 @@
|
|||
"LabelTag": "Tag:",
|
||||
"LabelTVHomeScreen": "TV mode home screen:",
|
||||
"LabelSupportedMediaTypes": "Supported Media Types:",
|
||||
"LabelSubtitles": "Subtitles:",
|
||||
"LabelSubtitles": "Subtitles",
|
||||
"LabelSubtitlePlaybackMode": "Subtitle mode:",
|
||||
"LabelSubtitleFormatHelp": "Example: srt",
|
||||
"LabelSubtitleDownloaders": "Subtitle downloaders:",
|
||||
|
@ -1169,7 +1167,7 @@
|
|||
"LabelAudioChannels": "Audio channels:",
|
||||
"LabelAudioBitrate": "Audio bitrate:",
|
||||
"LabelAudioBitDepth": "Audio bit depth:",
|
||||
"LabelAudio": "Audio:",
|
||||
"LabelAudio": "Audio",
|
||||
"LabelArtistsHelp": "Separate multiple using ;",
|
||||
"LabelArtists": "Artists:",
|
||||
"LabelAppName": "App name",
|
||||
|
@ -1338,7 +1336,7 @@
|
|||
"MediaInfoSoftware": "Software",
|
||||
"ValueOneSeries": "1 series",
|
||||
"MediaInfoBitrate": "Bitrate",
|
||||
"LabelVideo": "Video:",
|
||||
"LabelVideo": "Video",
|
||||
"LabelPrevious": "Previous",
|
||||
"LabelPostProcessorArgumentsHelp": "Use {path} as the path to the recording file.",
|
||||
"LabelKodiMetadataEnableExtraThumbs": "Copy extrafanart to extrathumbs field",
|
||||
|
|
|
@ -11,11 +11,13 @@
|
|||
"AdditionalNotificationServices": "Browse the plugin catalog to install additional notification services.",
|
||||
"AirDate": "Air date",
|
||||
"Aired": "Aired",
|
||||
"Album": "Album",
|
||||
"AlbumArtist": "Album Artist",
|
||||
"Albums": "Albums",
|
||||
"Alerts": "Alerts",
|
||||
"All": "All",
|
||||
"AllChannels": "All channels",
|
||||
"AllComplexFormats": "All complex formats (ASS, SSA, VOBSUB, PGS, SUB/IDX, etc.)",
|
||||
"AllComplexFormats": "All Complex Formats (ASS, SSA, VOBSUB, PGS, SUB, IDX)",
|
||||
"AllEpisodes": "All episodes",
|
||||
"AllLanguages": "All languages",
|
||||
"AllLibraries": "All libraries",
|
||||
|
@ -29,12 +31,13 @@
|
|||
"AllowRemoteAccess": "Allow remote connections to this Jellyfin Server.",
|
||||
"AllowRemoteAccessHelp": "If unchecked, all remote connections will be blocked.",
|
||||
"AllowedRemoteAddressesHelp": "Comma separated list of IP addresses or IP/netmask entries for networks that will be allowed to connect remotely. If left blank, all remote addresses will be allowed.",
|
||||
"AlwaysPlaySubtitles": "Always play subtitles",
|
||||
"AlwaysPlaySubtitles": "Always Play",
|
||||
"AlwaysPlaySubtitlesHelp": "Subtitles matching the language preference will be loaded regardless of the audio language.",
|
||||
"AnyLanguage": "Any language",
|
||||
"AnyLanguage": "Any Language",
|
||||
"Anytime": "Anytime",
|
||||
"AroundTime": "Around {0}",
|
||||
"Art": "Art",
|
||||
"Artist": "Artist",
|
||||
"Artists": "Artists",
|
||||
"AsManyAsPossible": "As many as possible",
|
||||
"Ascending": "Ascending",
|
||||
|
@ -55,10 +58,11 @@
|
|||
"BookLibraryHelp": "Audio and text books are supported. Review the {0}book naming guide{1}.",
|
||||
"Books": "Books",
|
||||
"Box": "Box",
|
||||
"BoxSet": "Box Set",
|
||||
"BoxRear": "Box (rear)",
|
||||
"Browse": "Browse",
|
||||
"BrowsePluginCatalogMessage": "Browse our plugin catalog to view available plugins.",
|
||||
"BurnSubtitlesHelp": "Determines if the server should burn in subtitles when converting video depending on the subtitle format. Avoiding burning in subtitles will improve server performance. Select Auto to burn image based formats (VOBSUB, PGS, SUB/IDX, etc) and certain ASS/SSA subtitles.",
|
||||
"BurnSubtitlesHelp": "Determines if the server should burn in subtitles when transcoding videos. Avoiding this will greatly improve performance. Select Auto to burn image based formats (VOBSUB, PGS, SUB, IDX) and certain ASS or SSA subtitles.",
|
||||
"ButtonAdd": "Add",
|
||||
"ButtonAddImage": "Add Image",
|
||||
"ButtonAddMediaLibrary": "Add Media Library",
|
||||
|
@ -147,6 +151,7 @@
|
|||
"ChannelNumber": "Channel number",
|
||||
"Channels": "Channels",
|
||||
"CinemaModeConfigurationHelp": "Cinema mode brings the theater experience straight to your living room with the ability to play trailers and custom intros before the main feature.",
|
||||
"ClientSettings": "Client Settings",
|
||||
"Collections": "Collections",
|
||||
"ColorPrimaries": "Color primaries",
|
||||
"ColorSpace": "Color space",
|
||||
|
@ -174,6 +179,7 @@
|
|||
"DefaultErrorMessage": "There was an error processing the request. Please try again later.",
|
||||
"DefaultMetadataLangaugeDescription": "These are your defaults and can be customized on a per-library basis.",
|
||||
"DefaultSubtitlesHelp": "Subtitles are loaded based on the default and forced flags in the embedded metadata. Language preferences are considered when multiple options are available.",
|
||||
"DeinterlaceMethodHelp": "Select the deinterlacing method to use when transcoding interlaced content.",
|
||||
"Delete": "Delete",
|
||||
"DeleteDeviceConfirmation": "Are you sure you wish to delete this device? It will reappear the next time a user signs in with it.",
|
||||
"DeleteImage": "Delete Image",
|
||||
|
@ -191,8 +197,7 @@
|
|||
"DirectStreamHelp2": "Direct Streaming a file uses very little processing power without any loss in video quality.",
|
||||
"DirectStreaming": "Direct streaming",
|
||||
"Director": "Director",
|
||||
"DirectorValue": "Director: {0}",
|
||||
"DirectorsValue": "Directors: {0}",
|
||||
"Directors": "Directors",
|
||||
"Disabled": "Disabled",
|
||||
"Disc": "Disc",
|
||||
"Disconnect": "Disconnect",
|
||||
|
@ -202,7 +207,7 @@
|
|||
"DisplayInOtherHomeScreenSections": "Display in home screen sections such as latest media and continue watching",
|
||||
"DisplayMissingEpisodesWithinSeasons": "Display missing episodes within seasons",
|
||||
"DisplayMissingEpisodesWithinSeasonsHelp": "This must also be enabled for TV libraries in the server configuration.",
|
||||
"DisplayModeHelp": "Select the type of screen you're running Jellyfin on.",
|
||||
"DisplayModeHelp": "Select the layout style you want for the interface.",
|
||||
"DoNotRecord": "Do not record",
|
||||
"Down": "Down",
|
||||
"Download": "Download",
|
||||
|
@ -234,6 +239,7 @@
|
|||
"EnableThemeVideosHelp": "Play theme videos in the background while browsing the library.",
|
||||
"Ended": "Ended",
|
||||
"EndsAtValue": "Ends at {0}",
|
||||
"Episode": "Episode",
|
||||
"Episodes": "Episodes",
|
||||
"ErrorAddingListingsToSchedulesDirect": "There was an error adding the lineup to your Schedules Direct account. Schedules Direct only allows a limited number of lineups per account. You may need to log into the Schedules Direct website and remove others listings from your account before proceeding.",
|
||||
"ErrorAddingMediaPathToVirtualFolder": "There was an error adding the media path. Please ensure the path is valid and the Jellyfin Server process has access to that location.",
|
||||
|
@ -271,9 +277,8 @@
|
|||
"Friday": "Friday",
|
||||
"Fullscreen": "Full screen",
|
||||
"General": "General",
|
||||
"GenreValue": "Genre: {0}",
|
||||
"Genre": "Genre",
|
||||
"Genres": "Genres",
|
||||
"GenresValue": "Genres: {0}",
|
||||
"GroupBySeries": "Group by series",
|
||||
"GroupVersions": "Group versions",
|
||||
"GuestStar": "Guest star",
|
||||
|
@ -517,7 +522,7 @@
|
|||
"Images": "Images",
|
||||
"ImportFavoriteChannelsHelp": "If enabled, only channels that are marked as favorite on the tuner device will be imported.",
|
||||
"ImportMissingEpisodesHelp": "If enabled, information about missing episodes will be imported into your Jellyfin database and displayed within seasons and series. This may cause significantly longer library scans.",
|
||||
"InstallingPackage": "Installing {0}",
|
||||
"InstallingPackage": "Installing {0} (version {1})",
|
||||
"InstantMix": "Instant mix",
|
||||
"ItemCount": "{0} items",
|
||||
"Items": "Items",
|
||||
|
@ -550,7 +555,7 @@
|
|||
"LabelAppNameExample": "Example: Sickbeard, Sonarr",
|
||||
"LabelArtists": "Artists:",
|
||||
"LabelArtistsHelp": "Separate multiple using ;",
|
||||
"LabelAudio": "Audio:",
|
||||
"LabelAudio": "Audio",
|
||||
"LabelAudioBitDepth": "Audio bit depth:",
|
||||
"LabelAudioBitrate": "Audio bitrate:",
|
||||
"LabelAudioChannels": "Audio channels:",
|
||||
|
@ -599,6 +604,7 @@
|
|||
"LabelDefaultScreen": "Default screen:",
|
||||
"LabelDefaultUser": "Default user:",
|
||||
"LabelDefaultUserHelp": "Determines which user library should be displayed on connected devices. This can be overridden for each device using profiles.",
|
||||
"LabelDeinterlaceMethod": "Deinterlacing method:",
|
||||
"LabelDeviceDescription": "Device description",
|
||||
"LabelDidlMode": "DIDL mode:",
|
||||
"LabelDiscNumber": "Disc number:",
|
||||
|
@ -840,7 +846,7 @@
|
|||
"LabelSubtitleDownloaders": "Subtitle downloaders:",
|
||||
"LabelSubtitleFormatHelp": "Example: srt",
|
||||
"LabelSubtitlePlaybackMode": "Subtitle mode:",
|
||||
"LabelSubtitles": "Subtitles:",
|
||||
"LabelSubtitles": "Subtitles",
|
||||
"LabelSupportedMediaTypes": "Supported Media Types:",
|
||||
"LabelTVHomeScreen": "TV mode home screen:",
|
||||
"LabelTag": "Tag:",
|
||||
|
@ -886,7 +892,7 @@
|
|||
"DashboardServerName": "Server: {0}",
|
||||
"DashboardOperatingSystem": "Operating System: {0}",
|
||||
"DashboardArchitecture": "Architecture: {0}",
|
||||
"LabelVideo": "Video:",
|
||||
"LabelVideo": "Video",
|
||||
"LabelVideoBitrate": "Video bitrate:",
|
||||
"LabelVideoCodec": "Video codec:",
|
||||
"LabelVideoResolution": "Video resolution:",
|
||||
|
@ -1017,6 +1023,7 @@
|
|||
"MoveLeft": "Move left",
|
||||
"MoveRight": "Move right",
|
||||
"MovieLibraryHelp": "Review the {0}movie naming guide{1}.",
|
||||
"Movie": "Movie",
|
||||
"Movies": "Movies",
|
||||
"MusicAlbum": "Music Album",
|
||||
"MusicArtist": "Music Artist",
|
||||
|
@ -1040,16 +1047,16 @@
|
|||
"NoNextUpItemsMessage": "None found. Start watching your shows!",
|
||||
"NoPluginConfigurationMessage": "This plugin has no settings to configure.",
|
||||
"NoSubtitleSearchResultsFound": "No results found.",
|
||||
"NoSubtitles": "No subtitles",
|
||||
"NoSubtitles": "None",
|
||||
"NoSubtitlesHelp": "Subtitles will not be loaded by default. They can still be turned on manually during playback.",
|
||||
"None": "None",
|
||||
"Normal": "Normal",
|
||||
"NumLocationsValue": "{0} folders",
|
||||
"Off": "Off",
|
||||
"OneChannel": "One channel",
|
||||
"OnlyForcedSubtitles": "Only forced subtitles",
|
||||
"OnlyForcedSubtitles": "Only Forced",
|
||||
"OnlyForcedSubtitlesHelp": "Only subtitles marked as forced will be loaded.",
|
||||
"OnlyImageFormats": "Only image formats (VOBSUB, PGS, SUB, etc)",
|
||||
"OnlyImageFormats": "Only Image Formats (VOBSUB, PGS, SUB)",
|
||||
"Option3D": "3D",
|
||||
"OptionAdminUsers": "Administrators",
|
||||
"OptionAlbum": "Album",
|
||||
|
@ -1203,10 +1210,11 @@
|
|||
"OptionWeekends": "Weekends",
|
||||
"OptionWeekly": "Weekly",
|
||||
"OriginalAirDateValue": "Original air date: {0}",
|
||||
"OtherArtist": "Other Artist",
|
||||
"Overview": "Overview",
|
||||
"PackageInstallCancelled": "{0} installation cancelled.",
|
||||
"PackageInstallCompleted": "{0} installation completed.",
|
||||
"PackageInstallFailed": "{0} installation failed.",
|
||||
"PackageInstallCancelled": "{0} (version {1}) installation cancelled.",
|
||||
"PackageInstallCompleted": "{0} (version {1}) installation completed.",
|
||||
"PackageInstallFailed": "{0} (version {1}) installation failed.",
|
||||
"ParentalRating": "Parental rating",
|
||||
"PasswordMatchError": "Password and password confirmation must match.",
|
||||
"PasswordResetComplete": "The password has been reset.",
|
||||
|
@ -1216,6 +1224,7 @@
|
|||
"PasswordSaved": "Password saved.",
|
||||
"People": "People",
|
||||
"PerfectMatch": "Perfect match",
|
||||
"Person": "Person",
|
||||
"Photos": "Photos",
|
||||
"PictureInPicture": "Picture in picture",
|
||||
"PinCodeResetComplete": "The pin code has been reset.",
|
||||
|
@ -1228,7 +1237,7 @@
|
|||
"PlayFromBeginning": "Play from beginning",
|
||||
"PlayNext": "Play next",
|
||||
"PlayNextEpisodeAutomatically": "Play next episode automatically",
|
||||
"PlaybackErrorNoCompatibleStream": "There was an issue with the client profiling and the server isn't sending a compatible media format.",
|
||||
"PlaybackErrorNoCompatibleStream": "This client isn't compatible with the media and the server isn't sending a compatible media format.",
|
||||
"Played": "Played",
|
||||
"Playlists": "Playlists",
|
||||
"PleaseAddAtLeastOneFolder": "Please add at least one folder to this library by clicking the Add button.",
|
||||
|
@ -1269,6 +1278,7 @@
|
|||
"RefreshMetadata": "Refresh metadata",
|
||||
"RefreshQueued": "Refresh queued.",
|
||||
"ReleaseDate": "Release date",
|
||||
"ReleaseGroup": "Release Group",
|
||||
"RememberMe": "Remember me",
|
||||
"RemoveFromCollection": "Remove from collection",
|
||||
"RemoveFromPlaylist": "Remove from playlist",
|
||||
|
@ -1299,6 +1309,7 @@
|
|||
"SearchForMissingMetadata": "Search for missing metadata",
|
||||
"SearchForSubtitles": "Search for Subtitles",
|
||||
"SearchResults": "Search Results",
|
||||
"Season": "Season",
|
||||
"SelectAdminUsername": "Please select a username for the admin account.",
|
||||
"SendMessage": "Send message",
|
||||
"Series": "Series",
|
||||
|
@ -1414,6 +1425,7 @@
|
|||
"TitleHardwareAcceleration": "Hardware Acceleration",
|
||||
"TitleHostingSettings": "Hosting Settings",
|
||||
"TitlePlayback": "Playback",
|
||||
"Track": "Track",
|
||||
"TrackCount": "{0} tracks",
|
||||
"Trailers": "Trailers",
|
||||
"Transcoding": "Transcoding",
|
||||
|
@ -1469,6 +1481,20 @@
|
|||
"XmlTvNewsCategoriesHelp": "Programs with these categories will be displayed as news programs. Separate multiple with '|'.",
|
||||
"XmlTvPathHelp": "A path to a XMLTV file. Jellyfin will read this file and periodically check it for updates. You are responsible for creating and updating the file.",
|
||||
"XmlTvSportsCategoriesHelp": "Programs with these categories will be displayed as sports programs. Separate multiple with '|'.",
|
||||
"Yadif": "Yadif",
|
||||
"YadifBob": "Yadif Bob",
|
||||
"Yes": "Yes",
|
||||
"Yesterday": "Yesterday"
|
||||
"Yesterday": "Yesterday",
|
||||
"PathNotFound": "The path could not be found. Please ensure the path is valid and try again.",
|
||||
"WriteAccessRequired": "Jellyfin Server requires write access to this folder. Please ensure write access and try again.",
|
||||
"ListPaging": "{0}-{1} of {2}",
|
||||
"PersonRole": "as {0}",
|
||||
"LastSeen": "Last seen {0}",
|
||||
"DailyAt": "Daily at {0}",
|
||||
"WeeklyAt": "{0}s at {1}",
|
||||
"OnWakeFromSleep": "On wake from sleep",
|
||||
"EveryXMinutes": "Every {0} minutes",
|
||||
"EveryHour": "Every hour",
|
||||
"EveryXHours": "Every {0} hours",
|
||||
"OnApplicationStartup": "On application startup"
|
||||
}
|
||||
|
|
|
@ -37,9 +37,9 @@
|
|||
"Favorites": "Favoritos",
|
||||
"Folders": "Carpetas",
|
||||
"Genres": "Géneros",
|
||||
"HeaderAlbumArtists": "Artistas de álbumes",
|
||||
"HeaderAlbumArtists": "Artistas de álbum",
|
||||
"HeaderContinueWatching": "Continuar viendo",
|
||||
"HeaderNextUp": "Continuar Viendo",
|
||||
"HeaderNextUp": "A Continuación",
|
||||
"Movies": "Películas",
|
||||
"Photos": "Fotos",
|
||||
"Playlists": "Listas de reproducción",
|
||||
|
@ -61,7 +61,7 @@
|
|||
"Alerts": "Alertas",
|
||||
"All": "Todo",
|
||||
"AllChannels": "Todos los canales",
|
||||
"AllComplexFormats": "Todos los formatos complejos (ASS, SSA, VOBSUB, PGS, SUB/IDX, etc.)",
|
||||
"AllComplexFormats": "Todos los formatos complejos (ASS, SSA, VOBSUB, PGS, SUB, IDX)",
|
||||
"AllEpisodes": "Todos los capítulos",
|
||||
"AllLanguages": "Todos los idiomas",
|
||||
"AllLibraries": "Todas las bibliotecas",
|
||||
|
@ -71,7 +71,7 @@
|
|||
"AllowOnTheFlySubtitleExtractionHelp": "Los subtítulos incrustados pueden extraerse de los videos y entregarse a los reproductores en texto plano para ayudar a evitar la transcodificación de video. En algunos sistemas, esto puede tardar mucho tiempo y provocar que la reproducción de video se detenga durante el proceso de extracción. Deshabilite esta opción para que los subtítulos incrustados se graben con transcodificación de video cuando no estén soportados de forma nativa por el dispositivo cliente.",
|
||||
"AllowRemoteAccess": "Permitir conexiones remotas a este Servidor Jellyfin.",
|
||||
"AllowRemoteAccessHelp": "Si no está tildado, todas las conexiones remotas serán bloqueadas.",
|
||||
"AlwaysPlaySubtitles": "Siempre reproducir subtítulos",
|
||||
"AlwaysPlaySubtitles": "Siempre mostrar subtítulos",
|
||||
"AnyLanguage": "Cualquier idioma",
|
||||
"Anytime": "Cualquier fecha",
|
||||
"Ascending": "Ascendente",
|
||||
|
@ -107,7 +107,7 @@
|
|||
"BoxRear": "Caja (lado opuesto)",
|
||||
"Browse": "Explorar",
|
||||
"BrowsePluginCatalogMessage": "Explore nuestro catálogo de complementos para ver los complementos disponibles.",
|
||||
"BurnSubtitlesHelp": "Determina si el servidor debe grabar subtítulos al convertir videos dependiendo del formato de los subtítulos. Evitar la grabación de subtítulos mejorará el rendimiento del servidor. Seleccione Auto para grabar formatos basados en imágenes (VOBSUB, PGS, SUB/IDX, etc.) y ciertos subtítulos ASS/SSA.",
|
||||
"BurnSubtitlesHelp": "Determina si el servidor debe grabar subtítulos al transcodificar videos. Evitar esto mejorará altamente el rendimiento del servidor. Seleccione Auto para grabar formatos basados en imágenes (VOBSUB, PGS, SUB/IDX) y ciertos subtítulos ASS o SSA.",
|
||||
"ButtonAccept": "Aceptar",
|
||||
"ButtonAdd": "Agregar",
|
||||
"ButtonAddMediaLibrary": "Agregar biblioteca de medios",
|
||||
|
@ -312,8 +312,7 @@
|
|||
"DirectStreamHelp2": "Transmitir directamente un archivo usa muy poco procesamiento, esto sin perdida en la calidad de video.",
|
||||
"DirectStreaming": "Transmisión en directo",
|
||||
"Director": "Director",
|
||||
"DirectorValue": "Director: {0}",
|
||||
"DirectorsValue": "Directores: {0}",
|
||||
"Directors": "Directores",
|
||||
"Disabled": "Deshabilitado",
|
||||
"Disc": "Disco",
|
||||
"Disconnect": "Desconectar",
|
||||
|
@ -322,7 +321,7 @@
|
|||
"DisplayInOtherHomeScreenSections": "Mostrar en las secciones de la pantalla principal, como últimos medios y continuar viendo",
|
||||
"DisplayMissingEpisodesWithinSeasons": "Mostrar episodios faltantes entre temporadas",
|
||||
"DisplayMissingEpisodesWithinSeasonsHelp": "Esto también debe estar habilitado para las bibliotecas de TV en la configuración del servidor.",
|
||||
"DisplayModeHelp": "Selecciona el tipo de pantalla en el cual estas usando Jellyfin.",
|
||||
"DisplayModeHelp": "Seleccione el estilo de diseño que desea en la Interfaz.",
|
||||
"DoNotRecord": "No grabar",
|
||||
"Down": "Abajo",
|
||||
"Download": "Descargar",
|
||||
|
@ -336,32 +335,32 @@
|
|||
"DropShadow": "Sombra paralela",
|
||||
"DvrFeatureDescription": "Agendar grabaciones individuales de TV en vivo, grabaciones de series y mucho mas con Jellyfin DVR.",
|
||||
"DvrSubscriptionRequired": "Jellyfin DVR requiere una suscripción a Jellyfin Premiere.",
|
||||
"EasyPasswordHelp": "Tu numero de PIN fácil puede ser usado por las aplicaciones de Jellyfin soportadas, también pueden ser utilizadas para acceder fácilmente al ingresar dentro de tu red.",
|
||||
"EasyPasswordHelp": "Su código PIN fácil se utiliza para el acceso sin conexión en los clientes compatibles y también puede utilizarse para acceder fácilmente cuando se está en la misma red.",
|
||||
"Edit": "Editar",
|
||||
"EditImages": "Editar imagenes",
|
||||
"EditMetadata": "Editar metadata",
|
||||
"EditSubtitles": "Editar subtitulos",
|
||||
"EnableBackdrops": "Habilitar fondo",
|
||||
"EnableBackdropsHelp": "Si esta habilitado, los fondos van a ser mostrados en segundo plano de algunas paginas mientras estas en la biblioteca.",
|
||||
"EnableCinemaMode": "Habilitar modo Cine",
|
||||
"EnableColorCodedBackgrounds": "Habilitar colores en el fondo del codigo",
|
||||
"EnableBackdrops": "Imágenes de fondo",
|
||||
"EnableBackdropsHelp": "Muestra imágenes de fondo en el fondo de algunas páginas mientras se navega por la biblioteca.",
|
||||
"EnableCinemaMode": "Modo cine",
|
||||
"EnableColorCodedBackgrounds": "Habilitar colores en el fondo del código",
|
||||
"AuthProviderHelp": "Seleccione un proveedor de autenticación que se utilizará para autenticar la contraseña de este usuario.",
|
||||
"CriticRating": "Calificación de la crítica",
|
||||
"DefaultSubtitlesHelp": "Los subtítulos se cargan en base a los indicadores por defecto y los indicadores forzados en los metadatos embebidos. Las preferencias de idioma son consideradas cuando existe más de una opción.",
|
||||
"Dislike": "No me gusta",
|
||||
"EnableDebugLoggingHelp": "El registro de depuración debería activarse solo a fin de solucionar problemas. El incremento en el acceso al sistema de archivos podría prevenir que el servidor entre en modo de suspensión en algunos entornos.",
|
||||
"EnableDisplayMirroring": "Habilitar duplicación de la pantalla",
|
||||
"EnableExternalVideoPlayers": "Habilitar reproductores de vídeo externos",
|
||||
"EnableExternalVideoPlayers": "Habilitar reproductores de video externos",
|
||||
"EnableExternalVideoPlayersHelp": "Se mostrará un menú de reproductor externo al iniciar la reproducción de video",
|
||||
"EnableNextVideoInfoOverlay": "Habilitar vista de información del siguiente vídeo durante la reproducción",
|
||||
"EnableNextVideoInfoOverlay": "Mostrar la información del siguiente video durante la reproducción",
|
||||
"EnableNextVideoInfoOverlayHelp": "Al finalizar un video, mostrar información sobre el siguiente vídeo en la lista de reproducción",
|
||||
"EnablePhotos": "Habilitar fotos",
|
||||
"EnablePhotosHelp": "Las fotos serán detectadas y se mostrarán junto a otros archivos de medios",
|
||||
"EnablePhotos": "Mostrar fotografías",
|
||||
"EnablePhotosHelp": "Las imágenes serán detectadas y mostradas junto con otros archivos multimedia.",
|
||||
"EnableStreamLooping": "Repetir automáticamente transmisiones en vivo",
|
||||
"EnableStreamLoopingHelp": "Habilita esto sí las transmisiones en vivo sólo contienen unos cuantos segundos y es necesario solicitarlos continuamente. Habilitar esto cuando no es necesario puede causar problemas.",
|
||||
"EnableThemeSongs": "Habilitar canciones tema",
|
||||
"EnableThemeSongsHelp": "Al habilitarse, las canciones tema se reproducirán de fondo cuando navegas por la biblioteca.",
|
||||
"EnableThemeVideos": "Habilitar videos de tema",
|
||||
"EnableThemeSongs": "Canciones temáticas",
|
||||
"EnableThemeSongsHelp": "Reproducir canciones temáticas en el fondo mientras se navega por la biblioteca.",
|
||||
"EnableThemeVideos": "Videos temáticos",
|
||||
"EnableThemeVideosHelp": "Al habilitarse, los videos de tema se reproducirán de fondo mientras navegues por la biblioteca.",
|
||||
"Ended": "Finalizado",
|
||||
"EndsAtValue": "Termina en {0}",
|
||||
|
@ -398,8 +397,7 @@
|
|||
"Friday": "Viernes",
|
||||
"Fullscreen": "Pantalla Completa",
|
||||
"General": "General",
|
||||
"GenreValue": "Género: {0}",
|
||||
"GenresValue": "Géneros: {0}",
|
||||
"Genre": "Género",
|
||||
"GroupBySeries": "Agrupar por Serie",
|
||||
"GroupVersions": "Agrupar versiones",
|
||||
"GuestStar": "Estrella invitada",
|
||||
|
@ -417,7 +415,45 @@
|
|||
"CopyStreamURL": "Copiar la URL de la transmisión",
|
||||
"ButtonSplit": "Dividir",
|
||||
"ButtonAddImage": "Agregar imagen",
|
||||
"AskAdminToCreateLibrary": "Pregunte al administrador para crear una biblioteca.",
|
||||
"AskAdminToCreateLibrary": "Preguntar al administrador para crear una biblioteca.",
|
||||
"AllowFfmpegThrottlingHelp": "Cuando una transcodificación o conversión avanza demasiado con respecto a la posición actual de la reproducción, se pausara el proceso para consumir menos recursos. esto es mas útil cuando no se hacen búsquedas de tiempo a menudo. Desactive esta opción si experimenta problemas en la reproducción.",
|
||||
"AllowFfmpegThrottling": "Transcodificación Throttle"
|
||||
"AllowFfmpegThrottling": "Transcodificación Throttle",
|
||||
"HeaderCancelRecording": "Cancelar Grabación",
|
||||
"HeaderBranding": "Marca",
|
||||
"HeaderBooks": "Libros",
|
||||
"HeaderBlockItemsWithNoRating": "Bloquear elementos con rating de información no reconocible:",
|
||||
"HeaderAutomaticUpdates": "Actualizaciones Automáticas",
|
||||
"HeaderAudioSettings": "Configuración del Audio",
|
||||
"HeaderAudioBooks": "Audiolibros",
|
||||
"HeaderAppearsOn": "Aparece en",
|
||||
"HeaderApp": "Aplicación",
|
||||
"HeaderApiKeysHelp": "Las aplicaciones externas requieren una llave API para poder comunicarse con el servidor Jellyfin. Las llaves se emiten iniciando sesión con una cuenta Jellyfin u otorgando manualmente una clave a la aplicación.",
|
||||
"HeaderApiKeys": "Llaves API",
|
||||
"HeaderApiKey": "Llave API",
|
||||
"HeaderAllowMediaDeletionFrom": "Permitir el borrado de medios desde",
|
||||
"HeaderAlert": "Alerta",
|
||||
"HeaderAlbums": "Albumes",
|
||||
"HeaderAdmin": "Admin",
|
||||
"HeaderAdditionalParts": "Partes adicionales",
|
||||
"HeaderAddUpdateImage": "Agregar/Actualizar imagen",
|
||||
"HeaderAddToPlaylist": "Agregar a la lista de reproducción",
|
||||
"HeaderAddToCollection": "Agregar a la Colección",
|
||||
"HeaderAddScheduledTaskTrigger": "Agregar disparador",
|
||||
"HeaderActivity": "Actividad",
|
||||
"HeaderActiveRecordings": "Grabaciones activas",
|
||||
"HeaderActiveDevices": "Dispositivos activos",
|
||||
"HeaderAccessScheduleHelp": "Crear un calendario de acceso, para limitar el acceso en determinadas horas.",
|
||||
"HeaderAccessSchedule": "Acceder al Calendario",
|
||||
"HardwareAccelerationWarning": "Habilitar la aceleración de hardware puede causar inestabilidad en algunos entornos. Asegúrese de que su sistema operativo y los controladores de video estén completamente actualizados. Si tiene dificultades para reproducir el video después de habilitarlo, deberá volver a cambiar la configuración a \"Nada\".",
|
||||
"HandledByProxy": "Manejado por un proxy reverso",
|
||||
"HDPrograms": "Programas en HD",
|
||||
"EncoderPresetHelp": "Elige un valor más rápido para mejorar la performance, o elige un valor más lento para mejorar la calidad.",
|
||||
"FetchingData": "Obteniendo información adicional",
|
||||
"Episode": "Episodio",
|
||||
"Yesterday": "Ayer",
|
||||
"ClientSettings": "Configuración de cliente",
|
||||
"BoxSet": "Colección",
|
||||
"Artist": "Artista",
|
||||
"AlbumArtist": "Artista del Album",
|
||||
"Album": "Album"
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
"Alerts": "Alertas",
|
||||
"All": "Todo",
|
||||
"AllChannels": "Todos los canales",
|
||||
"AllComplexFormats": "Todos los formatos complejos (ASS, SSA, VOBSUB, PGS, SUB/IDX, etc.)",
|
||||
"AllComplexFormats": "Todos los formatos complejos (ASS, SSA, VOBSUB, PGS, SUB, IDX)",
|
||||
"AllEpisodes": "Todos los episodios",
|
||||
"AllLanguages": "Todos los idiomas",
|
||||
"AllLibraries": "Todas las bibliotecas",
|
||||
|
@ -55,7 +55,7 @@
|
|||
"BoxRear": "Reverso de caja",
|
||||
"Browse": "Navegar",
|
||||
"BrowsePluginCatalogMessage": "Explorar el catalogo de complementos para ver los complementos disponibles.",
|
||||
"BurnSubtitlesHelp": "Determina si el servidor debe grabar subtítulos al convertir videos dependiendo del formato de los subtítulos. Evitar la grabación de subtítulos mejorará el rendimiento del servidor. Seleccione Auto para grabar formatos basados en imágenes (VOBSUB, PGS, SUB/IDX, etc.) y ciertos subtítulos ASS/SSA.",
|
||||
"BurnSubtitlesHelp": "Determina si el servidor debe grabar subtítulos al transcodificar videos. Evitar esto mejorará altamente el rendimiento del servidor. Seleccione Auto para grabar formatos basados en imágenes (VOBSUB, PGS, SUB/IDX) y ciertos subtítulos ASS o SSA.",
|
||||
"ButtonAdd": "Agregar",
|
||||
"ButtonAddMediaLibrary": "Agregar Biblioteca de Medios",
|
||||
"ButtonAddScheduledTaskTrigger": "Agregar Disparador",
|
||||
|
@ -158,7 +158,7 @@
|
|||
"CustomDlnaProfilesHelp": "Crear un perfil personalizado para un nuevo dispositivo o reemplazar un perfil del sistema.",
|
||||
"DateAdded": "Fecha de adición",
|
||||
"DatePlayed": "Fecha de reproducción",
|
||||
"DeathDateValue": "Fallcimiento: {0}",
|
||||
"DeathDateValue": "Terminación: {0}",
|
||||
"Default": "Por defecto",
|
||||
"DefaultErrorMessage": "Ha ocurrido un error al procesar la solicitud. Por favor inténtelo de nuevo mas tarde.",
|
||||
"DefaultMetadataLangaugeDescription": "Estas son sus configuraciones por defecto y puedes ser personalizadas independientemente en cada biblioteca.",
|
||||
|
@ -179,7 +179,7 @@
|
|||
"DirectStreamHelp1": "El medio es compatible con el dispositivo en cuanto a la resolución y tipo de medio (H.264, AC3, etc.), pero está en un contenedor de archivo incompatible (mkv, avi, wmv, etc.). El video sera reempaquetado en el acto antes de transmitirlo al dispositivo.",
|
||||
"DirectStreamHelp2": "La Transmisión Directa de un archivo usa muy poco poder de procesamiento sin ninguna perdida en la calidad de video.",
|
||||
"DirectStreaming": "Transmisión Directa",
|
||||
"DirectorsValue": "Directores: {0}",
|
||||
"Directors": "Directores",
|
||||
"Disabled": "Desactivado",
|
||||
"Disc": "DIsco",
|
||||
"Disconnect": "Desconectar",
|
||||
|
@ -189,14 +189,14 @@
|
|||
"DisplayInOtherHomeScreenSections": "Mostrar en las secciones de la pantalla de inicio como Recientes o Continua Viendo",
|
||||
"DisplayMissingEpisodesWithinSeasons": "Mostrar episodios faltantes en las temporadas",
|
||||
"DisplayMissingEpisodesWithinSeasonsHelp": "Esto también debe estar habilitado para las bibliotecas de TV en la configuración del servidor.",
|
||||
"DisplayModeHelp": "Seleccione el tipo de pantalla en la que se encuentra ejecutando Jellyfin.",
|
||||
"DisplayModeHelp": "Seleccione el estilo de diseño que desea en la Interfaz.",
|
||||
"DoNotRecord": "No grabar",
|
||||
"Down": "Abajo",
|
||||
"Download": "Descargar",
|
||||
"DownloadsValue": "{0} descargas",
|
||||
"DrmChannelsNotImported": "Los canales con DRM no serán importados.",
|
||||
"DropShadow": "Sombra Paralela",
|
||||
"EasyPasswordHelp": "Su código PIN fácil se utiliza para el acceso sin conexión en los clientes compatibles y también puede utilizarse para acceder fácilmente cuando se está en la misma red.",
|
||||
"EasyPasswordHelp": "El código PIN fácil se utiliza para el acceso sin conexión en los clientes soportados y también puede utilizarse para acceder fácilmente cuando se está en la misma red.",
|
||||
"Edit": "Editar",
|
||||
"EditImages": "Editar imágenes",
|
||||
"EditMetadata": "Editar metadatos",
|
||||
|
@ -225,7 +225,7 @@
|
|||
"ErrorAddingListingsToSchedulesDirect": "Hubo un error agregando la programación de su cuenta de Schedules Direct. Schedules Direct solo permite un numero limitado de programaciones por cuenta. Tal vez necesite acceder al sitio web de Schedules Direct y eliminar otras programaciones de su cuenta antes de continuar.",
|
||||
"ErrorAddingMediaPathToVirtualFolder": "Hubo un error agregando la ruta de medios. Por favor asegúrese de que la ruta es valida y que el proceso del Servidor Jellyfin tenga acceso a ese destino.",
|
||||
"ErrorAddingTunerDevice": "Hubo un error al agregar el dispositivo sintonizador. Por favor asegúrese de que este disponible e intente de nuevo.",
|
||||
"ErrorAddingXmlTvFile": "Hubo un error accediendo al archivo XmlTV. Por favor asegúrese de que el archivo existe e intente de nuevo.",
|
||||
"ErrorAddingXmlTvFile": "Hubo un error accediendo al archivo XMLTV. Por favor asegúrese de que el archivo existe e intente de nuevo.",
|
||||
"ErrorDeletingItem": "Hubo un error eliminando el ítem del Servidor Jellyfin. Por favor verifique tenga permisos de escritura en la carpeta de medios e intente de nuevo.",
|
||||
"ErrorGettingTvLineups": "Hubo un error al descargar la programación de TV. Por favor, asegúrese de que su información este correcta e inténtelo de nuevo.",
|
||||
"ErrorMessageStartHourGreaterThanEnd": "El horario de fin debe ser mayor al de comienzo.",
|
||||
|
@ -255,9 +255,8 @@
|
|||
"FormatValue": "Formato: {0}",
|
||||
"Friday": "Viernes",
|
||||
"Fullscreen": "Pantalla Completa",
|
||||
"GenreValue": "Genero: {0}",
|
||||
"Genre": "Genero",
|
||||
"Genres": "Géneros",
|
||||
"GenresValue": "Géneros: {0}",
|
||||
"GroupBySeries": "Agrupar por series",
|
||||
"GroupVersions": "Agrupar versiones",
|
||||
"GuestStar": "Estrella invitada",
|
||||
|
@ -268,7 +267,7 @@
|
|||
"EncoderPresetHelp": "Elija un valor mas rápido para mejorar el rendimiento, o uno mas lento para mejorar la calidad.",
|
||||
"HDPrograms": "Programas en HD",
|
||||
"HandledByProxy": "Manejado por un proxy inverso",
|
||||
"HardwareAccelerationWarning": "Habilitar la aceleración por hardware podría causar inestabilidad en algunos entornos, Asegúrese de que su sistema operativo y controladores de video están actualizados. Si tiene dificultades reproduciendo vides después de habilitar esto, necesita cambiar las configuraciones de nuevo a Auto.",
|
||||
"HardwareAccelerationWarning": "Habilitar la aceleración por hardware podría causar inestabilidad en algunos entornos. Asegúrese de que su sistema operativo y controladores de video están actualizados. Si tiene dificultades reproduciendo vides después de habilitar esto, necesita volver a cambiar la configuración a NO.",
|
||||
"HeaderAccessSchedule": "Acceder Programación",
|
||||
"HeaderAccessScheduleHelp": "Crear programación de acceso para limitar el acceso a ciertos horarios.",
|
||||
"HeaderActiveDevices": "Dispositivos Activos",
|
||||
|
@ -479,12 +478,12 @@
|
|||
"Hide": "Ocultar",
|
||||
"HideWatchedContentFromLatestMedia": "Ocultar contenido ya visto de Agregadas Recientemente",
|
||||
"Home": "Inicio",
|
||||
"HttpsRequiresCert": "Para habilitar las conexiones seguras, es necesario proporcionar un certificado SSL de confianza, como el de \"Lets Encrypt\". Por favor proporcione un certificado, o desactive las conexiones seguras.",
|
||||
"HttpsRequiresCert": "Para habilitar las conexiones seguras, es necesario proporcionar un certificado SSL de confianza, como el de \"Let's Encrypt\". Por favor proporcione un certificado, o desactive las conexiones seguras.",
|
||||
"Identify": "Identificar",
|
||||
"Images": "Imágenes",
|
||||
"ImportFavoriteChannelsHelp": "Si se habilita, solo los canales marcados como favoritos en el dispositivo sintonizador serán importados.",
|
||||
"ImportMissingEpisodesHelp": "Si se habilita, se importara a su base de datos de Jellyfin información sobre episodios faltantes y se mostrara dentro de las temporadas y series. Esto podría ocasionar escaneos de biblioteca significativamente mas largos.",
|
||||
"InstallingPackage": "Instalando {0}",
|
||||
"InstallingPackage": "Instalando {0} (versión {1})",
|
||||
"InstantMix": "Mix instantáneo",
|
||||
"ItemCount": "{0} ítems",
|
||||
"Items": "Ítems",
|
||||
|
@ -514,7 +513,7 @@
|
|||
"LabelAllowedRemoteAddresses": "Filtrar IP remota:",
|
||||
"LabelAllowedRemoteAddressesMode": "Modo de filtrado de IP remota:",
|
||||
"LabelAppName": "Nombre del App",
|
||||
"LabelAppNameExample": "Ejemplo: Sickbeard, NzbDrone",
|
||||
"LabelAppNameExample": "Ejemplo: Sickbeard, Sonarr",
|
||||
"LabelArtists": "Artistas:",
|
||||
"LabelArtistsHelp": "Separar múltiples empleando ;",
|
||||
"LabelAudioLanguagePreference": "Idioma preferido de audio:",
|
||||
|
@ -540,7 +539,7 @@
|
|||
"LabelCountry": "País:",
|
||||
"LabelCriticRating": "Calificación de la crítica:",
|
||||
"LabelCurrentPassword": "Contraseña actual:",
|
||||
"LabelCustomCertificatePath": "Ruta personalizada del certificado ssl:",
|
||||
"LabelCustomCertificatePath": "Ruta personalizada del certificado SSL:",
|
||||
"LabelCustomCertificatePathHelp": "Trayectoria a un archivo PKCS #12 que contiene el certificado y la llave privada para habilitar soporte a TLS en un dominio personalizado.",
|
||||
"LabelCustomCss": "CSS personalizado:",
|
||||
"LabelCustomCssHelp": "Aplicar tu propio estilo personalizado a la interfaz web.",
|
||||
|
@ -576,7 +575,7 @@
|
|||
"LabelEmbedAlbumArtDidl": "Incrustar arte del álbum en DIDL",
|
||||
"LabelEmbedAlbumArtDidlHelp": "Algunos dispositivos prefieren este método para obtener arte del álbum. Otros podrían fallar al reproducir con esta opción habilitada.",
|
||||
"LabelEnableAutomaticPortMap": "Habilitar mapeo automático de puertos",
|
||||
"LabelEnableAutomaticPortMapHelp": "Intentar mapear automáticamente el puerto público con el puerto local via UPnP. Esto podría no funcionar con algunos modelos de ruteadores.",
|
||||
"LabelEnableAutomaticPortMapHelp": "Intentar mapear automáticamente el puerto público con el puerto local vía UPnP. Esto podría no funcionar con algunos modelos de ruteadores.",
|
||||
"LabelEnableBlastAliveMessages": "Bombardeo de mensajes de vida",
|
||||
"LabelEnableBlastAliveMessagesHelp": "Habilite esto si el servidor no es detectado de manera confiable por otros dispositivos UPnP en su red.",
|
||||
"LabelEnableDlnaClientDiscoveryInterval": "Intervalo de Detección de Clientes (segundos)",
|
||||
|
@ -611,7 +610,7 @@
|
|||
"LabelH264Crf": "CRF de codificación H264:",
|
||||
"LabelEncoderPreset": "Codificación H264 predefinido:",
|
||||
"LabelHardwareAccelerationType": "Aceleración por Hardware:",
|
||||
"LabelHardwareAccelerationTypeHelp": "Esta es una característica experimental que solo está disponible en los sistemas soportados.",
|
||||
"LabelHardwareAccelerationTypeHelp": "Aceleración por Hardware requiere configuración adicional.",
|
||||
"LabelHomeNetworkQuality": "Calidad en Red Local:",
|
||||
"LabelHomeScreenSectionValue": "Pagina de inicio sección {0}:",
|
||||
"LabelHttpsPort": "Número de puerto local HTTPS:",
|
||||
|
@ -648,13 +647,13 @@
|
|||
"LabelLoginDisclaimer": "Aviso legal:",
|
||||
"LabelLoginDisclaimerHelp": "Un mensaje que se mostrará en la parte inferior de la página de inicio de sesión.",
|
||||
"LabelLogs": "Bitácoras:",
|
||||
"LabelManufacturer": "Fabricante",
|
||||
"LabelManufacturer": "Fabricante:",
|
||||
"LabelManufacturerUrl": "URL del fabricante",
|
||||
"LabelMatchType": "Tipo de Coincidencia:",
|
||||
"LabelMaxBackdropsPerItem": "Número máximo de imágenes de fondo por ítem:",
|
||||
"LabelMaxChromecastBitrate": "Tasa maxima de bits para El Chromecast:",
|
||||
"LabelMaxParentalRating": "Máxima clasificación parental permitida:",
|
||||
"LabelMaxResumePercentage": "Porcentaje máximo de reanudación:",
|
||||
"LabelMaxResumePercentage": "Porcentaje máximo para la reanudación:",
|
||||
"LabelMaxResumePercentageHelp": "Los medios se consideran totalmente reproducidos si se detienen después de este tiempo.",
|
||||
"LabelMaxScreenshotsPerItem": "Número máximo de capturas de pantalla por ítem:",
|
||||
"LabelMaxStreamingBitrate": "Calidad máxima de transmisión:",
|
||||
|
@ -687,7 +686,7 @@
|
|||
"LabelMoviePrefixHelp": "Si un prefijo es aplicado al título de las películas, introdúzcalo aquí para que el servidor pueda manejarlo correctamente.",
|
||||
"LabelMovieRecordingPath": "Ruta para grabaciones de Películas (opcional):",
|
||||
"LabelMusicStreamingTranscodingBitrate": "Tasa de transcodificación de música:",
|
||||
"LabelMusicStreamingTranscodingBitrateHelp": "Especifique la tasa de bits máxima al transferir musica en tiempo real",
|
||||
"LabelMusicStreamingTranscodingBitrateHelp": "Especifique la tasa de bits máxima al transmitir música.",
|
||||
"LabelName": "Nombre:",
|
||||
"LabelNewName": "Nuevo nombre:",
|
||||
"LabelNewPassword": "Nueva contraseña:",
|
||||
|
@ -759,7 +758,7 @@
|
|||
"LabelServerHost": "Servidor:",
|
||||
"LabelServerHostHelp": "192.168.1.100:8096 o https://miservidor.com",
|
||||
"LabelSimultaneousConnectionLimit": "Limite de transmisiones simultaneas:",
|
||||
"LabelSkin": "Piel:",
|
||||
"LabelSkin": "Apariencia:",
|
||||
"LabelSkipBackLength": "Longitud de salto hacia atrás:",
|
||||
"LabelSkipForwardLength": "Longitud de salto hacia adelante:",
|
||||
"LabelSkipIfAudioTrackPresent": "Omitir si la pista de audio por defecto coincide con el lenguaje de descarga",
|
||||
|
@ -782,7 +781,7 @@
|
|||
"LabelSubtitleDownloaders": "Recolectores de Subtitulos:",
|
||||
"LabelSubtitleFormatHelp": "Ejemplo: srt",
|
||||
"LabelSubtitlePlaybackMode": "Modo de subtítulo:",
|
||||
"LabelSubtitles": "Subtítulos:",
|
||||
"LabelSubtitles": "Subtítulos",
|
||||
"LabelSupportedMediaTypes": "Tipos de Medios Soportados:",
|
||||
"LabelTVHomeScreen": "Modo de pantalla de TV:",
|
||||
"LabelTag": "Etiqueta:",
|
||||
|
@ -822,7 +821,7 @@
|
|||
"LabelVersionNumber": "Versión {0}",
|
||||
"LabelXDlnaCap": "X-DLNA cap:",
|
||||
"LabelXDlnaCapHelp": "Determina el contenido del elemento X_DLNACAP en el namespace urn:schemas-dlna-org:device-1-0.",
|
||||
"LabelXDlnaDoc": "X-DLNA doc:",
|
||||
"LabelXDlnaDoc": "Documento X-DLNA:",
|
||||
"LabelXDlnaDocHelp": "Determina el contenido del elemento X_DLNADOC en el namespace urn:schemas-dlna-org:device-1-0.",
|
||||
"LabelYear": "Año:",
|
||||
"LabelYourFirstName": "Su nombre:",
|
||||
|
@ -889,7 +888,7 @@
|
|||
"MessageDeleteTaskTrigger": "¿Está seguro de querer eliminar este disparador de tarea?",
|
||||
"MessageDirectoryPickerBSDInstruction": "Para BSD, quizás necesite configurar el almacenamiento dentro de su \"FreeNAS Jail\" de manera que permita a Jellyfin accesarlo.",
|
||||
"MessageDirectoryPickerInstruction": "Las rutas de red pueden ser introducidas manualmente en caso de que el botón de Red no pueda localizar sus dispositivos. Por ejemplo, {0} or {1}.",
|
||||
"MessageDirectoryPickerLinuxInstruction": "Para Linux en Arch Linux, CentOS, Debian, Fedora, OpenSuse o Ubuntu, debe conceder al usuario del servicio al menos permisos de lectura a sus ubicaciones de almacenamiento.",
|
||||
"MessageDirectoryPickerLinuxInstruction": "Para Linux en Arch Linux, CentOS, Debian, Fedora, openSUSE, o Ubuntu. Debe conceder al usuario del servicio al menos permisos de lectura a sus ubicaciones de almacenamiento.",
|
||||
"MessageDownloadQueued": "Descargar cola.",
|
||||
"MessageEnablingOptionLongerScans": "Habilitar esta opción podría resultar en escaneos de bibliotecas significativamente mas largos.",
|
||||
"MessageFileReadError": "Hubo un error al leer el archivo. Por favor intente de nuevo.",
|
||||
|
@ -947,15 +946,15 @@
|
|||
"NoNextUpItemsMessage": "No se encontró nada. ¡Comienza a ver tus programas!",
|
||||
"NoPluginConfigurationMessage": "El complemento no tiene configuraciones disponibles.",
|
||||
"NoSubtitleSearchResultsFound": "No se encontraron resultados.",
|
||||
"NoSubtitles": "Sin Subtitulos",
|
||||
"NoSubtitles": "Nada",
|
||||
"NoSubtitlesHelp": "Los subtítulos no serán cargados por defecto. Pero pueden ser activados manualmente durante la reproducción.",
|
||||
"None": "Ninguno",
|
||||
"NumLocationsValue": "{0} carpetas",
|
||||
"Off": "Apagar",
|
||||
"OneChannel": "Un canal",
|
||||
"OnlyForcedSubtitles": "Únicamente subtítulos forzados",
|
||||
"OnlyForcedSubtitles": "Únicamente forzados",
|
||||
"OnlyForcedSubtitlesHelp": "Se cargarán únicamente subtítulos marcados como forzados.",
|
||||
"OnlyImageFormats": "Solo formatos de imagen (VOBSUB, PGS, SUB, etc.)",
|
||||
"OnlyImageFormats": "Solo formatos de imagen (VOBSUB, PGS, SUB)",
|
||||
"OptionAdminUsers": "Administradores",
|
||||
"OptionAlbum": "Álbum",
|
||||
"OptionAlbumArtist": "Artista del Álbum",
|
||||
|
@ -1004,12 +1003,12 @@
|
|||
"OptionDisplayFolderView": "Mostrar una vista de carpetas para mostrar carpetas de medios simples",
|
||||
"OptionDisplayFolderViewHelp": "Muestra las carpetas junto con sus otras bibliotecas multimedia. Esto puede ser útil si desea tener una vista de carpeta sencilla.",
|
||||
"OptionDownloadArtImage": "Arte",
|
||||
"OptionDownloadBackImage": "Reverso",
|
||||
"OptionDownloadBannerImage": "Cartél",
|
||||
"OptionDownloadBackImage": "Atras",
|
||||
"OptionDownloadBannerImage": "Banner",
|
||||
"OptionDownloadBoxImage": "Caja",
|
||||
"OptionDownloadDiscImage": "DIsco",
|
||||
"OptionDownloadImagesInAdvance": "Descargar las imágenes desde el inicio",
|
||||
"OptionDownloadImagesInAdvanceHelp": "Por defecto, la mayoría de las imágenes son descargadas solo cuando son solicitadas por alguna aplicación Jellyfin. Habilite esta opción para descargar todas las imágenes desde por adelantado, conforme se vayan agregando mas medios. Esto podría causar escaneos de bibliotecas mas largos.",
|
||||
"OptionDownloadImagesInAdvanceHelp": "Por defecto, la mayoría de las imágenes son descargadas solo cuando son solicitadas por una aplicacion Jellyfin. Habilite esta opción para descargar todas las imágenes por adelantado, a medida que se agregen mas medios. Esto podría causar escaneos de bibliotecas significativamente largos.",
|
||||
"OptionDownloadMenuImage": "Menú",
|
||||
"OptionDownloadPrimaryImage": "Principal",
|
||||
"OptionDownloadThumbImage": "Miniatura",
|
||||
|
@ -1068,7 +1067,7 @@
|
|||
"OptionReportByteRangeSeekingWhenTranscodingHelp": "Esto es requerido para algunos dispositivos que no pueden hacer búsquedas por tiempo muy bien.",
|
||||
"OptionRequirePerfectSubtitleMatch": "Solo descargar subtitulos que corresponden perfectamente para mis archivos de video",
|
||||
"OptionRequirePerfectSubtitleMatchHelp": "Solicitar una coincidencia perfecta filtrara los subtitulos para incluir solo aquellos que han sido verificados exactamente con su archivo de video. Desmarcar esta opción incrementara las probabilidades de que los subtitulos sean descargados, a la vez que incrementara las posibilidades de obtener subtitulos mal sincronizados o con texto incorrecto.",
|
||||
"OptionResElement": "Elemento res",
|
||||
"OptionResElement": "Elemento Res",
|
||||
"OptionResumable": "Reanudable",
|
||||
"OptionRuntime": "Duración",
|
||||
"OptionSaturday": "Sábado",
|
||||
|
@ -1080,7 +1079,7 @@
|
|||
"OptionThursday": "Jueves",
|
||||
"OptionTrackName": "Nombre de la Pista",
|
||||
"OptionTuesday": "Martes",
|
||||
"OptionTvdbRating": "Calificación de Tvdb",
|
||||
"OptionTvdbRating": "Calificación de TVDB",
|
||||
"OptionUnairedEpisode": "Episodios no Emitidos",
|
||||
"OptionUnplayed": "No reproducido",
|
||||
"OptionWakeFromSleep": "Al Despertar",
|
||||
|
@ -1090,9 +1089,9 @@
|
|||
"OptionWeekly": "Semanal",
|
||||
"OriginalAirDateValue": "Fecha de emisión original: {0}",
|
||||
"Overview": "Sinopsis",
|
||||
"PackageInstallCancelled": "{0} instalación cancelada.",
|
||||
"PackageInstallCompleted": "{0} instalación completada.",
|
||||
"PackageInstallFailed": "{0} instalación fallida.",
|
||||
"PackageInstallCancelled": "{0} (versión {1}) instalación cancelada.",
|
||||
"PackageInstallCompleted": "{0} (version {1}) instalación completada.",
|
||||
"PackageInstallFailed": "{0} (versión {1}) instalación fallida.",
|
||||
"ParentalRating": "Clasificación Parental",
|
||||
"PasswordMatchError": "La Contraseña y la confirmación de la contraseña deben coincidir.",
|
||||
"PasswordResetComplete": "La contraseña ha sido restablecida.",
|
||||
|
@ -1116,7 +1115,7 @@
|
|||
"Playlists": "Listas de reproducción",
|
||||
"PleaseAddAtLeastOneFolder": "Por favor agregue al menos una carpeta a esta biblioteca dando clic al botón de Agregar.",
|
||||
"PleaseConfirmPluginInstallation": "Por favor haga clic en OK para confirmar que ha leido lo que se encuentra arriba y que desea proceder con la instalación del complemento.",
|
||||
"PleaseEnterNameOrId": "Por favor introduzca un nombre o id externo.",
|
||||
"PleaseEnterNameOrId": "Por favor introduzca un nombre o ID externo.",
|
||||
"PleaseRestartServerName": "Por favor reinicie el Servidor Jellyfin - {0}.",
|
||||
"PleaseSelectTwoItems": "Por favor selecciona al menos dos ítems.",
|
||||
"PluginInstalledMessage": "El complemento ha sido instalado exitosamente. El Servidor Jellyfin necesitará reiniciarse para que los cambios surtan efecto.",
|
||||
|
@ -1132,7 +1131,7 @@
|
|||
"Programs": "Programas",
|
||||
"Quality": "Calidad",
|
||||
"QueueAllFromHere": "Encolar todos desde aquí",
|
||||
"Raised": "Elevacion",
|
||||
"Raised": "Elevación",
|
||||
"Rate": "Calificación",
|
||||
"RecentlyWatched": "Visto recientemente",
|
||||
"RecommendationBecauseYouLike": "Porque te gustó {0}",
|
||||
|
@ -1156,7 +1155,7 @@
|
|||
"Repeat": "Repetir",
|
||||
"RepeatAll": "Repetir todas",
|
||||
"RepeatEpisodes": "Repetir episodios",
|
||||
"RepeatMode": "Modo de repeticion",
|
||||
"RepeatMode": "Modo de repetición",
|
||||
"RepeatOne": "Repetir uno",
|
||||
"ReplaceAllMetadata": "Remplazar todos los metadatos",
|
||||
"ReplaceExistingImages": "Reemplazar imágenes existentes",
|
||||
|
@ -1182,7 +1181,7 @@
|
|||
"SearchResults": "Resultados de la búsqueda",
|
||||
"SendMessage": "Enviar mensaje",
|
||||
"SeriesCancelled": "Serie cancelada.",
|
||||
"SeriesDisplayOrderHelp": "Ordenar los episodios por fecha transmisión, orden del dvd o por su numeración absoluta.",
|
||||
"SeriesDisplayOrderHelp": "Ordenar los episodios por fecha transmisión, orden del DVD o por su numeración absoluta.",
|
||||
"SeriesRecordingScheduled": "Grabación de series programadas.",
|
||||
"SeriesSettings": "Configuración de la Serie",
|
||||
"SeriesYearToPresent": "{0} - Actualidad",
|
||||
|
@ -1196,7 +1195,7 @@
|
|||
"Share": "Compartir",
|
||||
"ShowAdvancedSettings": "Mostrar configuraciones avanzadas",
|
||||
"ShowIndicatorsFor": "Mostrar indicadores para:",
|
||||
"ShowTitle": "Mostrar titulo",
|
||||
"ShowTitle": "Mostrar título",
|
||||
"ShowYear": "Mostrar año",
|
||||
"Shows": "Programas",
|
||||
"Shuffle": "Aleatorio",
|
||||
|
@ -1335,9 +1334,9 @@
|
|||
"Writer": "Escritor",
|
||||
"XmlDocumentAttributeListHelp": "Estos atributos se aplican al elemento raíz de cada respuesta XML.",
|
||||
"XmlTvKidsCategoriesHelp": "Los programas con estas categorías serán mostrados como programas infantiles. Separe varios con un \"|\".",
|
||||
"XmlTvMovieCategoriesHelp": "Los programas con estas categorías serán mostrado como películas. Separe varios con un \"|\".",
|
||||
"XmlTvNewsCategoriesHelp": "Los programas con estas categorías serán mostrado como programas noticiosos. Separe varios con un \"|\".",
|
||||
"XmlTvPathHelp": "Una ruta a un archivo XML de TV. Jellyfin leerá este archivo y lo revisará periódicamente en busca de actualizaciones. Usted es responsable de crear y actualizar el fichero.",
|
||||
"XmlTvMovieCategoriesHelp": "Los programas con estas categorías serán mostrados como películas. Separe varios con un \"|\".",
|
||||
"XmlTvNewsCategoriesHelp": "Los programas con estas categorías serán mostrados como programas noticiosos. Separe varios con un \"|\".",
|
||||
"XmlTvPathHelp": "Ruta al archivo XMLTV. Jellyfin leerá este archivo y lo revisará periódicamente en busca de actualizaciones. Usted es responsable de crear y actualizar el archivo.",
|
||||
"XmlTvSportsCategoriesHelp": "Los programas con estas categorías serán mostrados como programas deportivos. Separe varios con un \"|\".",
|
||||
"Yes": "Sí",
|
||||
"Yesterday": "Ayer",
|
||||
|
@ -1350,11 +1349,10 @@
|
|||
"ButtonTrailer": "Trailer",
|
||||
"AuthProviderHelp": "Seleccione un proveedor de autenticación que se utilizará para autenticar la contraseña de este usuario.",
|
||||
"Director": "Director",
|
||||
"DirectorValue": "Director: {0}",
|
||||
"Extras": "Extras",
|
||||
"General": "General",
|
||||
"HeaderAdmin": "Administrador",
|
||||
"HeaderApp": "App",
|
||||
"HeaderApp": "Aplicación",
|
||||
"HeaderError": "Error",
|
||||
"HeaderFavoriteMovies": "Peliculas favoritas",
|
||||
"HeaderFavoriteShows": "Programas favoritos",
|
||||
|
@ -1367,7 +1365,7 @@
|
|||
"HeaderRestartingServer": "Reiniciando servidor",
|
||||
"HeaderVideos": "Videos",
|
||||
"Horizontal": "Horizontal",
|
||||
"LabelAudio": "Audio:",
|
||||
"LabelAudio": "Audio",
|
||||
"LabelAuthProvider": "Proveedor de autenticación:",
|
||||
"LabelDynamicExternalId": "{0} Id:",
|
||||
"LabelPasswordResetProvider": "Proveedor de restablecimiento de contraseña:",
|
||||
|
@ -1380,8 +1378,8 @@
|
|||
"DashboardServerName": "Servidor: {0}",
|
||||
"DashboardOperatingSystem": "Sistema operativo: {0}",
|
||||
"DashboardArchitecture": "Arquitectura: {0}",
|
||||
"LabelVideo": "Video:",
|
||||
"LabelWeb": "Web: ",
|
||||
"LabelVideo": "Video",
|
||||
"LabelWeb": "Web:",
|
||||
"LaunchWebAppOnStartup": "Iniciar la interfaz web al iniciar el servidor",
|
||||
"LaunchWebAppOnStartupHelp": "Abre el cliente web en su navegador web predeterminado cuando se inicia el servidor. Esto no ocurrirá cuando se utilice la función de reinicio del servidor.",
|
||||
"LeaveBlankToNotSetAPassword": "Puede dejar este campo en blanco para no establecer ninguna contraseña.",
|
||||
|
@ -1402,7 +1400,7 @@
|
|||
"No": "No",
|
||||
"Normal": "Normal",
|
||||
"Option3D": "3D",
|
||||
"OptionBanner": "Banner",
|
||||
"OptionBanner": "Baner",
|
||||
"OptionBluray": "Blu-ray",
|
||||
"OptionCaptionInfoExSamsung": "CaptionInfoEx (Samsung)",
|
||||
"OptionDownloadLogoImage": "Logo",
|
||||
|
@ -1454,5 +1452,55 @@
|
|||
"LabelVideoBitrate": "Velocidad de bits:",
|
||||
"LabelTranscodingProgress": "Progreso de la transcodificación:",
|
||||
"LabelTranscodingFramerate": "Velocidad de cuadros de la transcodificación:",
|
||||
"LabelSize": "Tamaño:"
|
||||
"LabelSize": "Tamaño:",
|
||||
"SelectAdminUsername": "Por favor seleccione un nombre de usuario para la cuenta de administrador.",
|
||||
"EnableFastImageFadeInHelp": "Habilita la animación de desvanecido rápido para las imágenes cargadas",
|
||||
"LabelDroppedFrames": "Cuadros saltados:",
|
||||
"CopyStreamURLError": "Hubo un error copiando la URL.",
|
||||
"ButtonSplit": "Dividir",
|
||||
"WeeklyAt": "{0}s a las {1}",
|
||||
"OnApplicationStartup": "Cuando se inicia la aplicación",
|
||||
"EveryXHours": "Cada {0} horas",
|
||||
"EveryHour": "Cada hora",
|
||||
"EveryXMinutes": "Cada {0} minutos",
|
||||
"OnWakeFromSleep": "Activarse del Modo Suspensión",
|
||||
"DailyAt": "Diariamente a las {0}",
|
||||
"LastSeen": "Ultima vez visto {0}",
|
||||
"PersonRole": "como {0}",
|
||||
"ListPaging": "{0}-{1} de {2}",
|
||||
"WriteAccessRequired": "El servidor Jellyfin requiere permiso de escritura en esta carpeta. Por favor brinde acceso de escritura e intente de nuevo.",
|
||||
"PathNotFound": "No se pudo encontrar la ruta. Por favor asegúrese de que la ruta es valida e intente de nuevo.",
|
||||
"Track": "Pista",
|
||||
"Season": "Temporada",
|
||||
"ReleaseGroup": "Grupo de salida",
|
||||
"PreferEmbeddedEpisodeInfosOverFileNames": "Preferir información embebida en el episodio sobre el nombre de archivo",
|
||||
"PreferEmbeddedEpisodeInfosOverFileNamesHelp": "Esto utiliza la información de el episodio desde metadatos embebidos si esta disponible.",
|
||||
"PlaybackErrorNoCompatibleStream": "Este cliente no es compatible con los medios y el servidor no está enviando un formato de medios compatible.",
|
||||
"Person": "Persona",
|
||||
"OtherArtist": "Otro artista",
|
||||
"OptionRandom": "Aleatorio",
|
||||
"OptionForceRemoteSourceTranscoding": "Forzar transcodificación para fuentes remotas (como TV en Vivo)",
|
||||
"NoCreatedLibraries": "Parece que no has creado ninguna biblioteca todavía. {0}Quisieras crear una ahora?{1}",
|
||||
"Movie": "Película",
|
||||
"MessageConfirmAppExit": "Deseas salir?",
|
||||
"LabelVideoResolution": "Resolución de video:",
|
||||
"LabelStreamType": "Tipo de transmisión:",
|
||||
"EnableFastImageFadeIn": "Desvanecido de imagen rápido",
|
||||
"LabelPlayerDimensions": "Dimensiones del reproductor:",
|
||||
"LabelCorruptedFrames": "Cuadros corruptos:",
|
||||
"HeaderNavigation": "Navegación",
|
||||
"HeaderFavoritePeople": "Personas favoritas",
|
||||
"Episode": "Episodio",
|
||||
"ClientSettings": "Configuración de cliente",
|
||||
"BoxSet": "Box Set",
|
||||
"AskAdminToCreateLibrary": "Preguntar al administrador para crear una biblioteca.",
|
||||
"Artist": "Artista",
|
||||
"AllowFfmpegThrottlingHelp": "Cuando una transcodificación o remux se encuentra muy por delante de la posición de reproducción, pausar el proceso para consumir menos recursos. Esto es mas practico cuando se esta viendo sin buscar constantemente. Deshabilitar esta opción si experimentas problemas de reproducción.",
|
||||
"AllowFfmpegThrottling": "Aceleración de Transcoders",
|
||||
"AlbumArtist": "Álbum de artista",
|
||||
"Album": "Álbum",
|
||||
"YadifBob": "Yadif Bob",
|
||||
"Yadif": "Yadif",
|
||||
"LabelDeinterlaceMethod": "Metodo de Desentrelazado:",
|
||||
"DeinterlaceMethodHelp": "Seleccione el método de desentrelazado que se usará al codificar el contenido entrelazado."
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue