1
0
Fork 0
mirror of https://github.com/jellyfin/jellyfin-web synced 2025-03-30 19:56:21 +00:00

Merge remote-tracking branch 'upstream/master' into patch-10

This commit is contained in:
ferferga 2020-04-04 12:56:08 +02:00
commit a461f16332
169 changed files with 11856 additions and 5175 deletions

View file

@ -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 {

View file

@ -29,7 +29,7 @@ h3 {
}
.layout-tv {
font-size: 2.5vh;
font-size: 130%;
}
.layout-mobile {

View file

@ -21,7 +21,7 @@
}
.libraryPage {
padding-top: 7em !important;
padding-top: 7em;
}
.itemDetailPage {
@ -115,7 +115,7 @@
display: -webkit-inline-box;
display: -webkit-inline-flex;
display: inline-flex;
margin: 0.3em 0 0 0.5em;
margin: 0 0 0 0.5em;
height: 1.7em;
-webkit-box-align: center;
-webkit-align-items: center;
@ -128,6 +128,10 @@
margin-top: 0;
}
.layout-mobile .pageTitleWithDefaultLogo {
background-image: url(../img/icon-transparent.png);
}
.headerLeft,
.skinHeader {
display: -webkit-box;
@ -242,7 +246,6 @@
}
@media all and (min-width: 40em) {
.dashboardDocument .adminDrawerLogo,
.dashboardDocument .mainDrawerButton {
display: none !important;
}
@ -268,12 +271,6 @@
}
}
@media all and (max-width: 60em) {
.libraryDocument .mainDrawerButton {
display: none;
}
}
@media all and (max-width: 84em) {
.withSectionTabs .headerTop {
padding-bottom: 0.55em;
@ -316,7 +313,7 @@
}
.dashboardDocument .mainDrawer-scrollContainer {
margin-top: 6em !important;
margin-top: 4.6em !important;
}
}
@ -439,10 +436,46 @@
background-size: cover;
background-repeat: no-repeat;
background-position: center;
background-attachment: fixed;
height: 50vh;
position: relative;
}
.layout-mobile .itemBackdrop {
background-attachment: scroll;
}
.layout-desktop .itemBackdrop::after,
.layout-tv .itemBackdrop::after {
content: "";
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.65);
display: block;
}
.layout-desktop .noBackdrop .itemBackdrop,
.layout-tv .noBackdrop .itemBackdrop {
display: none;
}
.detailPageContent {
display: flex;
flex-direction: column;
padding-left: 2%;
padding-right: 2%;
}
.layout-desktop .noBackdrop .detailPageContent,
.layout-tv .noBackdrop .detailPageContent {
margin-top: 2.5em;
}
.layout-desktop .noBackdrop .detailImageContainer img,
.layout-tv .noBackdrop .detailImageContainer img {
margin-top: 0;
}
.personBackdrop {
background-size: contain;
}
@ -503,14 +536,13 @@
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
.detailPagePrimaryContainer {
display: flex;
align-items: center;
align-content: center;
position: sticky;
top: 3.85em;
z-index: 2;
}
@ -520,13 +552,21 @@
top: 0;
}
.layout-tv .detailPagePrimaryContainer {
.layout-tv #itemDetailPage:not(.noBackdrop) .detailPagePrimaryContainer,
.layout-desktop #itemDetailPage:not(.noBackdrop) .detailPagePrimaryContainer {
position: relative;
top: 0;
padding-left: 32.45vw;
}
.detailSticky {
background-color: #101010;
.layout-desktop .detailSticky,
.layout-tv .detailSticky {
margin-top: -7.2em;
}
.layout-desktop .noBackdrop .detailSticky,
.layout-tv .noBackdrop .detailSticky {
margin-top: 0;
}
.infoWrapper {
@ -548,23 +588,17 @@
margin: 1.25em 0;
}
.detailPageContent {
display: flex;
flex-direction: column;
padding-left: 2%;
padding-right: 2%;
}
.detailImageContainer {
position: sticky;
top: 25%;
position: relative;
margin-top: -25vh;
float: left;
width: 22.786458333333332vw;
width: 25vw;
z-index: 3;
}
.layout-mobile .detailImageContainer,
.layout-tv .detailImageContainer {
position: relative;
.layout-desktop .noBackdrop .detailImageContainer,
.layout-tv .noBackdrop .detailImageContainer {
margin-top: 0;
}
.detailPagePrimaryContent {
@ -572,15 +606,19 @@
}
.detailLogo {
width: 25em;
height: 9.375em;
width: 67.25vw;
height: 14.5vh;
position: absolute;
top: 14.5%;
right: 10.5%;
top: 15vh;
right: 0;
-webkit-background-size: contain;
background-size: contain;
}
.noBackdrop .detailLogo {
display: none;
}
@media all and (max-width: 87.5em) {
.detailLogo {
right: 5%;
@ -607,8 +645,8 @@
.itemDetailImage {
width: 100% !important;
box-shadow: 0 0.0725em 0.29em 0 rgba(0, 0, 0, 0.37);
-webkit-box-shadow: 0 0.0725em 0.29em 0 rgba(0, 0, 0, 0.37);
-webkit-box-shadow: 0 0.1em 0.5em 0 rgba(0, 0, 0, 0.75);
box-shadow: 0 0.1em 0.5em 0 rgba(0, 0, 0, 0.75);
}
div.itemDetailGalleryLink.defaultCardBackground {
@ -635,6 +673,16 @@ div.itemDetailGalleryLink.defaultCardBackground {
position: relative;
}
.layout-desktop .detailPageWrapperContainer,
.layout-tv .detailPageWrapperContainer {
margin-top: 7.2em;
}
.layout-tv #itemDetailPage:not(.noBackdrop) .detailPagePrimaryContainer,
.layout-desktop #itemDetailPage:not(.noBackdrop) .detailPagePrimaryContainer {
padding-left: 3.3%;
}
.btnPlaySimple {
display: none !important;
}
@ -803,7 +851,7 @@ div.itemDetailGalleryLink.defaultCardBackground {
width: auto;
align-items: center;
justify-content: center;
margin-top: -4.3em;
margin-top: -4.2em;
position: relative;
}
@ -849,6 +897,11 @@ div.itemDetailGalleryLink.defaultCardBackground {
border-collapse: collapse;
}
.layout-desktop .noBackdrop .detailPageWrapperContainer,
.layout-tv .noBackdrop .detailPageWrapperContainer {
margin-top: 3.8em;
}
.mediaInfoStream {
margin: 0 3em 0 0;
display: inline-block;
@ -1066,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;
}

View file

@ -16,6 +16,12 @@ _define("fetch", function() {
return fetch
});
// query-string
var query = require("query-string");
_define("queryString", function() {
return query;
});
// flvjs
var flvjs = require("flv.js/dist/flv").default;
_define("flvjs", function() {
@ -75,14 +81,7 @@ _define("sortable", function() {
// webcomponents
var webcomponents = require("webcomponents.js/webcomponents-lite");
_define("webcomponents", function() {
return webcomponents
});
// libjass
var libjass = require("libjass");
require("libjass/libjass.css");
_define("libjass", function() {
return libjass;
return webcomponents;
});
// libass-wasm
@ -97,7 +96,30 @@ _define("material-icons", function() {
return material_icons;
});
var jellyfin_noto = require("jellyfin-noto");
// noto font
var noto = require("jellyfin-noto");
_define("jellyfin-noto", function () {
return jellyfin_noto;
return noto;
});
// page.js
var page = require("page");
_define("page", function() {
return page;
});
var polyfill = require("@babel/polyfill/dist/polyfill");
_define("polyfill", function () {
return polyfill;
});
// 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;
});

View file

@ -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) {
@ -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 || "";

View file

@ -370,7 +370,7 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM
}
function enableNativeHistory() {
return page.enableNativeHistory();
return false;
}
function authenticate(ctx, route, callback) {
@ -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;
};
}
@ -562,7 +569,10 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM
if (!document.querySelector('.dialogContainer') && startPages.indexOf(curr.type) !== -1) {
return false;
}
return page.canGoBack();
if (enableHistory()) {
return history.length > 1;
}
return (page.len || 0) > 0;
}
function showDirect(path) {
@ -666,7 +676,8 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM
function pushState(state, title, url) {
state.navigate = false;
page.pushState(state, title, url);
history.pushState(state, title, url);
}
function setBaseRoute() {
@ -716,7 +727,7 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM
appRouter.getRoutes = getRoutes;
appRouter.pushState = pushState;
appRouter.enableNativeHistory = enableNativeHistory;
appRouter.handleAnchorClick = page.handleAnchorClick;
appRouter.handleAnchorClick = page.clickHandler;
appRouter.TransparencyLevel = {
None: 0,
Backdrop: 1,

View file

@ -2,7 +2,7 @@
position: fixed;
left: 0;
right: 0;
z-index: 1;
z-index: 10;
bottom: 0;
transition: transform 180ms linear;
contain: layout style;

View file

@ -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");
}

View file

@ -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
};

View file

@ -182,6 +182,7 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', "userSettings"
return apiClient.getScaledImageUrl(item.BackdropItemId || item.Id, Object.assign(imageOptions, {
type: "Backdrop",
tag: imgTag,
maxWidth: dom.getScreenWidth(),
index: index
}));
});
@ -192,6 +193,7 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', "userSettings"
return apiClient.getScaledImageUrl(item.ParentBackdropItemId, Object.assign(imageOptions, {
type: "Backdrop",
tag: imgTag,
maxWidth: dom.getScreenWidth(),
index: index
}));
});

View file

@ -429,6 +429,12 @@ button::-moz-focus-inner {
font-size: 1.66956521739130434em !important;
}
.cardOverlayButtonIcon.material-icons {
/* material-icons override display, so we need to
make a better matching selector to set it to flex */
display: flex;
}
.cardOverlayButton-centered {
bottom: initial;
right: initial;

File diff suppressed because it is too large Load diff

View file

@ -68,7 +68,7 @@ define(['datetime', 'imageLoader', 'connectionManager', 'layoutManager', 'browse
return apiClient.getScaledImageUrl(item.Id, {
maxWidth: maxWidth,
maxWidth: maxWidth * 2,
tag: chapter.ImageTag,
type: "Chapter",
index: index

View file

@ -0,0 +1,34 @@
define([], function() {
'use strict';
if (window.appMode === "cordova" || window.appMode === "android") {
return {
load: function () {
window.chrome = window.chrome || {};
return Promise.resolve();
}
};
} else {
var ccLoaded = false;
return {
load: function () {
if (ccLoaded) {
return Promise.resolve();
}
return new Promise(function (resolve, reject) {
var fileref = document.createElement("script");
fileref.setAttribute("type", "text/javascript");
fileref.onload = function () {
ccLoaded = true;
resolve();
};
fileref.setAttribute("src", "https://www.gstatic.com/cv/js/sender/v1/cast_sender.js");
document.querySelector("head").appendChild(fileref);
});
}
};
}
});

View file

@ -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");

View file

@ -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;

View file

@ -1,40 +1,65 @@
define(['dialog', 'globalize'], function (dialog, globalize) {
define(["browser", "dialog", "globalize"], function(browser, dialog, globalize) {
'use strict';
return function (text, title) {
function replaceAll(str, find, replace) {
return str.split(find).join(replace);
}
var options;
if (typeof text === 'string') {
options = {
title: title,
text: text
};
} else {
options = text;
}
var items = [];
items.push({
name: options.cancelText || globalize.translate('ButtonCancel'),
id: 'cancel',
type: 'cancel'
});
items.push({
name: options.confirmText || globalize.translate('ButtonOk'),
id: 'ok',
type: options.primary === 'delete' ? 'delete' : 'submit'
});
options.buttons = items;
return dialog(options).then(function (result) {
if (result === 'ok') {
return Promise.resolve();
if (browser.tv && window.confirm) {
// Use the native confirm dialog
return function (options) {
if (typeof options === 'string') {
options = {
title: '',
text: options
};
}
return Promise.reject();
});
};
var text = replaceAll(options.text || '', '<br/>', '\n');
var result = confirm(text);
if (result) {
return Promise.resolve();
} else {
return Promise.reject();
}
};
} else {
// Use our own dialog
return function (text, title) {
var options;
if (typeof text === 'string') {
options = {
title: title,
text: text
};
} else {
options = text;
}
var items = [];
items.push({
name: options.cancelText || globalize.translate('ButtonCancel'),
id: 'cancel',
type: 'cancel'
});
items.push({
name: options.confirmText || globalize.translate('ButtonOk'),
id: 'ok',
type: options.primary === 'delete' ? 'delete' : 'submit'
});
options.buttons = items;
return dialog(options).then(function (result) {
if (result === 'ok') {
return Promise.resolve();
}
return Promise.reject();
});
};
}
});

View file

@ -1,27 +0,0 @@
define([], function () {
'use strict';
function replaceAll(str, find, replace) {
return str.split(find).join(replace);
}
return function (options) {
if (typeof options === 'string') {
options = {
title: '',
text: options
};
}
var text = replaceAll(options.text || '', '<br/>', '\n');
var result = confirm(text);
if (result) {
return Promise.resolve();
} else {
return Promise.reject();
}
};
});

View file

@ -169,6 +169,15 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager',
}, {
passive: true
});
dom.addEventListener((dlg.dialogContainer || backdrop), 'contextmenu', function (e) {
if (e.target === dlg.dialogContainer) {
// Close the application dialog menu
close(dlg);
// Prevent the default browser context menu from appearing
e.preventDefault();
}
});
}
function isHistoryEnabled(dlg) {
@ -242,9 +251,15 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager',
var onAnimationFinish = function () {
focusManager.pushScope(dlg);
if (dlg.getAttribute('data-autofocus') === 'true') {
focusManager.autoFocus(dlg);
}
if (document.activeElement && !dlg.contains(document.activeElement)) {
// Blur foreign element to prevent triggering of an action from the previous scope
document.activeElement.blur();
}
};
if (enableAnimation()) {

View file

@ -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/>";
@ -163,16 +163,15 @@ 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()
}

View file

@ -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>

View file

@ -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,30 +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 _animationEvent;
function whichAnimationEvent() {
/**
* 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);
}
const closest = standardWidths.sort(function (a, b) {
return Math.abs(width - a) - Math.abs(width - b);
})[0];
return closest;
}
/**
* 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];
@ -138,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];
@ -168,15 +262,17 @@ define([], function () {
return _transitionEvent;
}
return {
parentWithAttribute: parentWithAttribute,
parentWithClass: parentWithClass,
parentWithTag: parentWithTag,
addEventListener: addEventListenerWithOptions,
removeEventListener: removeEventListenerWithOptions,
getWindowSize: getWindowSize,
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
};

View file

@ -13,11 +13,11 @@
vertical-align: middle;
flex-shrink: 0;
margin: 0;
padding: 1.5em;
padding: 1.5em 1.5em;
position: relative;
height: auto;
min-width: initial;
line-height: initial;
line-height: 1.25;
border-radius: 0;
overflow: hidden;
font-weight: 600;

View file

@ -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;
}));
}
}

View file

@ -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();
}

View file

@ -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 || "";

View file

@ -183,7 +183,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
for (var i = 0, length = items.length; i < length; i++) {
var item = items[i];
var icon = imageHelper.getLibraryIcon(item.CollectionType);
html += '<a is="emby-linkbutton" href="' + appRouter.getRouteUrl(item) + '" class="raised homeLibraryButton"><i class="material-icons homeLibraryIcon">' + icon + '</i><span class="homeLibraryText">' + item.Name + '</span></a>';
html += '<a is="emby-linkbutton" href="' + appRouter.getRouteUrl(item) + '" class="raised homeLibraryButton"><i class="material-icons homeLibraryIcon ' + icon + '"></i><span class="homeLibraryText">' + item.Name + '</span></a>';
}
html += '</div>';

View file

@ -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;
}
}
@ -1047,7 +1046,7 @@ 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 options = {
video: videoElement,
@ -1056,91 +1055,28 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa
return 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')
}
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
@ -1230,7 +1166,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;
}

View file

@ -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
};
});

View file

@ -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;

View file

@ -251,114 +251,150 @@ require(['apphost'], function (appHost) {
}
}
var inputLoopTimer;
function runInputLoop() {
// Get the latest gamepad state.
var gamepads;
if (navigator.getGamepads) {
gamepads = navigator.getGamepads();
} else if (navigator.webkitGetGamepads) {
gamepads = navigator.webkitGetGamepads();
}
gamepads = gamepads || [];
var i;
var j;
var len;
for (i = 0, len = gamepads.length; i < len; i++) {
var gamepads = navigator.getGamepads();
for (var i = 0, len = gamepads.length; i < len; i++) {
var gamepad = gamepads[i];
if (gamepad) {
// Iterate through the axes
var axes = gamepad.axes;
var leftStickX = axes[0];
var leftStickY = axes[1];
if (leftStickX > _THUMB_STICK_THRESHOLD) { // Right
_ButtonPressedState.setleftThumbstickRight(true);
} else if (leftStickX < -_THUMB_STICK_THRESHOLD) { // Left
_ButtonPressedState.setleftThumbstickLeft(true);
} else if (leftStickY < -_THUMB_STICK_THRESHOLD) { // Up
_ButtonPressedState.setleftThumbstickUp(true);
} else if (leftStickY > _THUMB_STICK_THRESHOLD) { // Down
_ButtonPressedState.setleftThumbstickDown(true);
} else {
_ButtonPressedState.setleftThumbstickLeft(false);
_ButtonPressedState.setleftThumbstickRight(false);
_ButtonPressedState.setleftThumbstickUp(false);
_ButtonPressedState.setleftThumbstickDown(false);
}
// Iterate through the buttons to see if Left thumbstick, DPad, A and B are pressed.
var buttons = gamepad.buttons;
for (j = 0, len = buttons.length; j < len; j++) {
if (ProcessedButtons.indexOf(j) !== -1) {
if (buttons[j].pressed) {
switch (j) {
case _GAMEPAD_DPAD_UP_BUTTON_INDEX:
_ButtonPressedState.setdPadUp(true);
break;
case _GAMEPAD_DPAD_DOWN_BUTTON_INDEX:
_ButtonPressedState.setdPadDown(true);
break;
case _GAMEPAD_DPAD_LEFT_BUTTON_INDEX:
_ButtonPressedState.setdPadLeft(true);
break;
case _GAMEPAD_DPAD_RIGHT_BUTTON_INDEX:
_ButtonPressedState.setdPadRight(true);
break;
case _GAMEPAD_A_BUTTON_INDEX:
_ButtonPressedState.setgamepadA(true);
break;
case _GAMEPAD_B_BUTTON_INDEX:
_ButtonPressedState.setgamepadB(true);
break;
default:
// No-op
break;
}
} else {
switch (j) {
case _GAMEPAD_DPAD_UP_BUTTON_INDEX:
if (_ButtonPressedState.getdPadUp()) {
_ButtonPressedState.setdPadUp(false);
}
break;
case _GAMEPAD_DPAD_DOWN_BUTTON_INDEX:
if (_ButtonPressedState.getdPadDown()) {
_ButtonPressedState.setdPadDown(false);
}
break;
case _GAMEPAD_DPAD_LEFT_BUTTON_INDEX:
if (_ButtonPressedState.getdPadLeft()) {
_ButtonPressedState.setdPadLeft(false);
}
break;
case _GAMEPAD_DPAD_RIGHT_BUTTON_INDEX:
if (_ButtonPressedState.getdPadRight()) {
_ButtonPressedState.setdPadRight(false);
}
break;
case _GAMEPAD_A_BUTTON_INDEX:
if (_ButtonPressedState.getgamepadA()) {
_ButtonPressedState.setgamepadA(false);
}
break;
case _GAMEPAD_B_BUTTON_INDEX:
if (_ButtonPressedState.getgamepadB()) {
_ButtonPressedState.setgamepadB(false);
}
break;
default:
// No-op
break;
}
if (!gamepad) {
continue;
}
// Iterate through the axes
var axes = gamepad.axes;
var leftStickX = axes[0];
var leftStickY = axes[1];
if (leftStickX > _THUMB_STICK_THRESHOLD) { // Right
_ButtonPressedState.setleftThumbstickRight(true);
} else if (leftStickX < -_THUMB_STICK_THRESHOLD) { // Left
_ButtonPressedState.setleftThumbstickLeft(true);
} else if (leftStickY < -_THUMB_STICK_THRESHOLD) { // Up
_ButtonPressedState.setleftThumbstickUp(true);
} else if (leftStickY > _THUMB_STICK_THRESHOLD) { // Down
_ButtonPressedState.setleftThumbstickDown(true);
} else {
_ButtonPressedState.setleftThumbstickLeft(false);
_ButtonPressedState.setleftThumbstickRight(false);
_ButtonPressedState.setleftThumbstickUp(false);
_ButtonPressedState.setleftThumbstickDown(false);
}
// Iterate through the buttons to see if Left thumbstick, DPad, A and B are pressed.
var buttons = gamepad.buttons;
for (var j = 0, len = buttons.length; j < len; j++) {
if (ProcessedButtons.indexOf(j) !== -1) {
if (buttons[j].pressed) {
switch (j) {
case _GAMEPAD_DPAD_UP_BUTTON_INDEX:
_ButtonPressedState.setdPadUp(true);
break;
case _GAMEPAD_DPAD_DOWN_BUTTON_INDEX:
_ButtonPressedState.setdPadDown(true);
break;
case _GAMEPAD_DPAD_LEFT_BUTTON_INDEX:
_ButtonPressedState.setdPadLeft(true);
break;
case _GAMEPAD_DPAD_RIGHT_BUTTON_INDEX:
_ButtonPressedState.setdPadRight(true);
break;
case _GAMEPAD_A_BUTTON_INDEX:
_ButtonPressedState.setgamepadA(true);
break;
case _GAMEPAD_B_BUTTON_INDEX:
_ButtonPressedState.setgamepadB(true);
break;
default:
// No-op
break;
}
} else {
switch (j) {
case _GAMEPAD_DPAD_UP_BUTTON_INDEX:
if (_ButtonPressedState.getdPadUp()) {
_ButtonPressedState.setdPadUp(false);
}
break;
case _GAMEPAD_DPAD_DOWN_BUTTON_INDEX:
if (_ButtonPressedState.getdPadDown()) {
_ButtonPressedState.setdPadDown(false);
}
break;
case _GAMEPAD_DPAD_LEFT_BUTTON_INDEX:
if (_ButtonPressedState.getdPadLeft()) {
_ButtonPressedState.setdPadLeft(false);
}
break;
case _GAMEPAD_DPAD_RIGHT_BUTTON_INDEX:
if (_ButtonPressedState.getdPadRight()) {
_ButtonPressedState.setdPadRight(false);
}
break;
case _GAMEPAD_A_BUTTON_INDEX:
if (_ButtonPressedState.getgamepadA()) {
_ButtonPressedState.setgamepadA(false);
}
break;
case _GAMEPAD_B_BUTTON_INDEX:
if (_ButtonPressedState.getgamepadB()) {
_ButtonPressedState.setgamepadB(false);
}
break;
default:
// No-op
break;
}
}
}
}
}
// Schedule the next one
requestAnimationFrame(runInputLoop);
inputLoopTimer = requestAnimationFrame(runInputLoop);
}
runInputLoop();
function startInputLoop() {
if (!inputLoopTimer) {
runInputLoop();
}
}
function stopInputLoop() {
cancelAnimationFrame(inputLoopTimer);
inputLoopTimer = undefined;
}
function isGamepadConnected() {
var gamepads = navigator.getGamepads();
for (var i = 0, len = gamepads.length; i < len; i++) {
var gamepad = gamepads[i];
if (gamepad && gamepad.connected) {
return true;
}
}
return false;
}
function onFocusOrGamepadAttach(e) {
if (isGamepadConnected() && document.hasFocus()) {
console.log("Gamepad connected! Starting input loop");
startInputLoop();
}
}
function onFocusOrGamepadDetach(e) {
if (!isGamepadConnected() || !document.hasFocus()) {
console.log("Gamepad disconnected! No other gamepads are connected, stopping input loop");
stopInputLoop();
} else {
console.log("Gamepad disconnected! There are gamepads still connected.");
}
}
// Event listeners for any change in gamepads' state.
window.addEventListener("gamepaddisconnected", onFocusOrGamepadDetach);
window.addEventListener("gamepadconnected", onFocusOrGamepadAttach);
window.addEventListener("blur", onFocusOrGamepadDetach);
window.addEventListener("focus", onFocusOrGamepadAttach);
onFocusOrGamepadAttach();
// The gamepadInputEmulation is a string property that exists in JavaScript UWAs and in WebViews in UWAs.
// It won't exist in Win8.1 style apps or browsers.

View file

@ -0,0 +1,168 @@
/**
* Module for performing keyboard navigation.
* @module components/input/keyboardnavigation
*/
import inputManager from "inputManager";
import layoutManager from "layoutManager";
/**
* 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"
};
/**
* 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();
}
}
/**
* 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;
}
let 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":
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:
capture = false;
}
if (capture) {
console.debug("disabling default event handling");
e.preventDefault();
}
});
}
// 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
};

View file

@ -346,11 +346,7 @@ define(["apphost", "globalize", "connectionManager", "itemHelper", "appRouter",
break;
case "copy-stream":
var downloadHref = apiClient.getItemDownloadUrl(itemId);
navigator.clipboard.writeText(downloadHref).then(function () {
require(["toast"], function (toast) {
toast(globalize.translate("CopyStreamURLSuccess"));
});
}, function () {
var textAreaCopy = function () {
var textArea = document.createElement("textarea");
textArea.value = downloadHref;
document.body.appendChild(textArea);
@ -364,7 +360,16 @@ define(["apphost", "globalize", "connectionManager", "itemHelper", "appRouter",
prompt(globalize.translate("CopyStreamURL"), downloadHref);
}
document.body.removeChild(textArea);
});
};
if (navigator.clipboard === undefined) {
textAreaCopy();
} else {
navigator.clipboard.writeText(downloadHref).then(function () {
require(["toast"], function (toast) {
toast(globalize.translate("CopyStreamURLSuccess"));
});
}, textAreaCopy);
}
getResolveFunction(resolve, id)();
break;
case "editsubtitles":

View file

@ -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 + '"/>';

View file

@ -1,154 +0,0 @@
define(["inputManager", "layoutManager"], function (inputManager, layoutManager) {
"use strict";
/**
* 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"
};
/**
* Keys used for keyboard navigation.
*/
var NavigationKeys = ["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"];
var hasFieldKey = false;
try {
hasFieldKey = "key" in new KeyboardEvent("keydown");
} catch (e) {
console.error("error checking 'key' field");
}
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} keyboard event
* @return {string} key name
*/
function getKeyName(event) {
return KeyNames[event.keyCode] || event.key;
}
/**
* 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;
}
function enable() {
document.addEventListener("keydown", function (e) {
var key = getKeyName(e);
// 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":
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:
capture = false;
}
if (capture) {
console.debug("disabling default event handling");
e.preventDefault();
}
});
}
return {
enable: enable,
getKeyName: getKeyName,
isNavigationKey: isNavigationKey
};
});

View file

@ -107,7 +107,7 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct
if (!plugins.length) return html;
html += '<div class="metadataFetcher" data-type="' + availableTypeOptions.Type + '">';
html += '<h3 class="checkboxListLabel">' + globalize.translate("LabelTypeMetadataDownloaders", availableTypeOptions.Type) + "</h3>";
html += '<h3 class="checkboxListLabel">' + globalize.translate("LabelTypeMetadataDownloaders", globalize.translate(availableTypeOptions.Type)) + "</h3>";
html += '<div class="checkboxList paperList checkboxList-paperList">';
for (var i = 0; i < plugins.length; i++) {
var plugin = plugins[i];
@ -273,14 +273,19 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct
function adjustSortableListElement(elem) {
var btnSortable = elem.querySelector(".btnSortable");
var inner = btnSortable.querySelector("i");
if (elem.previousSibling) {
btnSortable.title = globalize.translate("ButtonUp");
btnSortable.classList.add("btnSortableMoveUp");
btnSortable.classList.remove("btnSortableMoveDown");
btnSortable.querySelector("i").innerHTML = "keyboard_arrow_up";
inner.classList.remove("keyboard_arrow_down");
inner.classList.add("keyboard_arrow_up");
} else {
btnSortable.title = globalize.translate("ButtonDown");
btnSortable.classList.remove("btnSortableMoveUp");
btnSortable.classList.add("btnSortableMoveDown");
btnSortable.querySelector("i").innerHTML = "keyboard_arrow_down";
inner.classList.remove("keyboard_arrow_up");
inner.classList.add("keyboard_arrow_down");
}
}
@ -391,6 +396,12 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct
parent.querySelector(".chkEnableEmbeddedTitlesContainer").classList.remove("hide");
}
if (contentType === "tvshows") {
parent.querySelector(".chkEnableEmbeddedEpisodeInfosContainer").classList.remove("hide");
} else {
parent.querySelector(".chkEnableEmbeddedEpisodeInfosContainer").classList.add("hide");
}
return populateMetadataSettings(parent, contentType);
}
@ -488,6 +499,7 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct
SeasonZeroDisplayName: parent.querySelector("#txtSeasonZeroName").value,
AutomaticRefreshIntervalDays: parseInt(parent.querySelector("#selectAutoRefreshInterval").value),
EnableEmbeddedTitles: parent.querySelector("#chkEnableEmbeddedTitles").checked,
EnableEmbeddedEpisodeInfos: parent.querySelector("#chkEnableEmbeddedEpisodeInfos").checked,
SkipSubtitlesIfEmbeddedSubtitlesPresent: parent.querySelector("#chkSkipIfGraphicalSubsPresent").checked,
SkipSubtitlesIfAudioTrackMatches: parent.querySelector("#chkSkipIfAudioTrackPresent").checked,
SaveSubtitlesWithMedia: parent.querySelector("#chkSaveSubtitlesLocally").checked,
@ -540,6 +552,7 @@ define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], funct
parent.querySelector("#chkImportMissingEpisodes").checked = options.ImportMissingEpisodes;
parent.querySelector(".chkAutomaticallyGroupSeries").checked = options.EnableAutomaticSeriesGrouping;
parent.querySelector("#chkEnableEmbeddedTitles").checked = options.EnableEmbeddedTitles;
parent.querySelector("#chkEnableEmbeddedEpisodeInfos").checked = options.EnableEmbeddedEpisodeInfos;
parent.querySelector("#chkSkipIfGraphicalSubsPresent").checked = options.SkipSubtitlesIfEmbeddedSubtitlesPresent;
parent.querySelector("#chkSaveSubtitlesLocally").checked = options.SaveSubtitlesWithMedia;
parent.querySelector("#chkSkipIfAudioTrackPresent").checked = options.SkipSubtitlesIfAudioTrackMatches;

View file

@ -28,6 +28,13 @@
</label>
<div class="fieldDescription checkboxFieldDescription">${PreferEmbeddedTitlesOverFileNamesHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription chkEnableEmbeddedEpisodeInfosContainer hide advanced">
<label>
<input is="emby-checkbox" type="checkbox" id="chkEnableEmbeddedEpisodeInfos" />
<span>${PreferEmbeddedEpisodeInfosOverFileNames}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${PreferEmbeddedEpisodeInfosOverFileNamesHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription advanced">
<label>

View file

@ -238,13 +238,6 @@
background-color: transparent !important;
}
.listItemMediaInfo {
/* Don't display if flex not supported */
display: none;
align-items: center;
margin-right: 1em;
}
.listGroupHeader-first {
margin-top: 0;
}

View file

@ -72,7 +72,7 @@ define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutMan
var apiClient = connectionManager.getApiClient(item.ServerId);
var options = {
width: width,
maxWidth: width * 2,
type: "Primary"
};
@ -105,7 +105,7 @@ define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutMan
var apiClient = connectionManager.getApiClient(item.ServerId);
var options = {
width: width,
maxWidth: width * 2,
type: "Primary"
};

View file

@ -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">';

View file

@ -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 =
[

View file

@ -98,15 +98,22 @@ define(['events', 'playbackManager', 'dom', 'browser', 'css!./iconosd', 'materia
}
}
function setIcon(iconElement, icon) {
iconElement.classList.remove('brightness_high');
iconElement.classList.remove('brightness_medium');
iconElement.classList.remove('brightness_low');
iconElement.classList.add(icon);
}
function updateElementsFromPlayer(brightness) {
if (iconElement) {
if (brightness >= 80) {
iconElement.innerHTML = '&#xE1AC;';
setIcon(iconElement, 'brightness_high');
} else if (brightness >= 20) {
iconElement.innerHTML = '&#xE1AE;';
setIcon(iconElement, 'brightness_medium');
} else {
iconElement.innerHTML = '&#xE1AD;';
setIcon(iconElement, 'brightness_low');
}
}
if (progressElement) {

View file

@ -3162,7 +3162,8 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
// User clicked stop or content ended
var state = self.getPlayerState(player);
var streamInfo = getPlayerData(player).streamInfo;
var data = getPlayerData(player);
var streamInfo = data.streamInfo;
var nextItem = self._playNextAfterEnded ? self._playQueueManager.getNextItemInfo() : null;
@ -3210,6 +3211,9 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
showPlaybackInfoErrorMessage(self, displayErrorCode, nextItem);
} else if (nextItem) {
self.nextTrack();
} else {
// Nothing more to play - clear data
data.streamInfo = null;
}
}

View file

@ -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);
}

View file

@ -1,28 +0,0 @@
define([], function () {
'use strict';
function replaceAll(str, find, replace) {
return str.split(find).join(replace);
}
return function (options) {
if (typeof options === 'string') {
options = {
label: '',
text: options
};
}
var label = replaceAll(options.label || '', '<br/>', '\n');
var result = prompt(label, options.text || '');
if (result) {
return Promise.resolve(result);
} else {
return Promise.reject(result);
}
};
});

View file

@ -1,6 +1,10 @@
define(['dialogHelper', 'layoutManager', 'scrollHelper', 'globalize', 'dom', 'require', 'material-icons', 'emby-button', 'paper-icon-button-light', 'emby-input', 'formDialogStyle'], function (dialogHelper, layoutManager, scrollHelper, globalize, dom, require) {
define(["browser", 'dialogHelper', 'layoutManager', 'scrollHelper', 'globalize', 'dom', 'require', 'material-icons', 'emby-button', 'paper-icon-button-light', 'emby-input', 'formDialogStyle'], function(browser, dialogHelper, layoutManager, scrollHelper, globalize, dom, require) {
'use strict';
function replaceAll(str, find, replace) {
return str.split(find).join(replace);
}
function setInputProperties(dlg, options) {
var txtInput = dlg.querySelector('#txtInput');
@ -13,7 +17,6 @@ define(['dialogHelper', 'layoutManager', 'scrollHelper', 'globalize', 'dom', 're
}
function showDialog(options, template) {
var dialogOptions = {
removeOnClose: true,
scrollY: false
@ -71,34 +74,49 @@ define(['dialogHelper', 'layoutManager', 'scrollHelper', 'globalize', 'dom', 're
dlg.style.minWidth = (Math.min(400, dom.getWindowSize().innerWidth - 50)) + 'px';
return dialogHelper.open(dlg).then(function () {
if (layoutManager.tv) {
scrollHelper.centerFocus.off(dlg.querySelector('.formDialogContent'), false);
}
var value = submitValue;
if (value) {
return value;
if (submitValue) {
return submitValue;
} else {
return Promise.reject();
}
});
}
return function (options) {
if ((browser.tv || browser.xboxOne) && window.confirm) {
return function (options) {
if (typeof options === 'string') {
options = {
label: '',
text: options
};
}
return new Promise(function (resolve, reject) {
require(['text!./prompt.template.html'], function (template) {
var label = replaceAll(options.label || '', '<br/>', '\n');
var result = prompt(label, options.text || '');
if (typeof options === 'string') {
options = {
title: '',
text: options
};
}
showDialog(options, template).then(resolve, reject);
if (result) {
return Promise.resolve(result);
} else {
return Promise.reject(result);
}
};
} else {
return function (options) {
return new Promise(function (resolve, reject) {
require(['text!./prompt.template.html'], function (template) {
if (typeof options === 'string') {
options = {
title: '',
text: options
};
}
showDialog(options, template).then(resolve, reject);
});
});
});
};
};
}
});

View file

@ -21,16 +21,25 @@ define(['globalize', 'connectionManager', 'require', 'loading', 'apphost', 'dom'
}
}
function setButtonIcon(button, icon) {
var inner = button.querySelector('i');
inner.classList.remove('fiber_smart_record');
inner.classList.remove('fiber_manual_record');
inner.classList.add(icon);
}
function RecordingButton(options) {
this.options = options;
var button = options.button;
setButtonIcon(button, 'fiber_manual_record');
if (options.item) {
this.refreshItem(options.item);
} else if (options.itemId && options.serverId) {
this.refresh(options.itemId, options.serverId);
}
var button = options.button;
button.querySelector('i').innerHTML = 'fiber_manual_record';
var clickFn = onRecordingButtonClick.bind(this);
this.clickFn = clickFn;
@ -80,7 +89,7 @@ define(['globalize', 'connectionManager', 'require', 'loading', 'apphost', 'dom'
var options = this.options;
var button = options.button;
this.item = item;
button.querySelector('i').innerHTML = getIndicatorIcon(item);
setButtonIcon(button, getIndicatorIcon(item));
if (item.TimerId && (item.Status || 'Cancelled') !== 'Cancelled') {
button.classList.add('recordingIcon-active');

View file

@ -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);

View file

@ -122,9 +122,9 @@ define(["browser", "datetime", "backdrop", "libraryBrowser", "listView", "imageL
}
var url = item ? seriesImageUrl(item, {
maxHeight: 300
maxHeight: 300 * 2
}) || imageUrl(item, {
maxHeight: 300
maxHeight: 300 * 2
}) : null;
console.debug("updateNowPlayingInfo");

View file

@ -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);
}

View file

@ -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
};

View file

@ -191,36 +191,14 @@ define(['connectionManager', 'playbackManager', 'events', 'inputManager', 'focus
events.trigger(serverNotifications, msg.MessageType, [apiClient, msg.Data]);
}
}
function bindEvents(apiClient) {
events.off(apiClient, "message", onMessageReceived);
events.on(apiClient, "message", onMessageReceived);
}
function enableNativeGamepadKeyMapping() {
if (window.navigator && "string" == typeof window.navigator.gamepadInputEmulation) {
window.navigator.gamepadInputEmulation = "keyboard";
return true;
}
return false;
}
function isGamepadSupported() {
return "ongamepadconnected" in window || navigator.getGamepads || navigator.webkitGetGamepads;
}
connectionManager.getApiClients().forEach(bindEvents);
events.on(connectionManager, 'apiclientcreated', function (e, newApiClient) {
bindEvents(newApiClient);
});
if (!enableNativeGamepadKeyMapping() && isGamepadSupported()) {
require(["components/serverNotifications/gamepadtokey"]);
}
require(["components/serverNotifications/mouseManager"]);
return serverNotifications;
});

View file

@ -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();

View file

@ -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 () {

View file

@ -70,7 +70,7 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
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>';
return '<button is="paper-icon-button-light" class="autoSize ' + cssClass + '"' + tabIndex + autoFocus + '><i class="material-icons slideshowButtonIcon ' + icon + '"></i></button>';
}
function setUserScalable(scalable) {
@ -201,14 +201,16 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
function onAutoplayStart() {
var btnSlideshowPause = dlg.querySelector('.btnSlideshowPause i');
if (btnSlideshowPause) {
btnSlideshowPause.innerHTML = "pause";
btnSlideshowPause.classList.remove("play_arrow");
btnSlideshowPause.classList.add("pause");
}
}
function onAutoplayStop() {
var btnSlideshowPause = dlg.querySelector('.btnSlideshowPause i');
if (btnSlideshowPause) {
btnSlideshowPause.innerHTML = "&#xE037;";
btnSlideshowPause.classList.remove("pause");
btnSlideshowPause.classList.add("play_arrow");
}
}
@ -365,8 +367,7 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
}
function playPause() {
var paused = dlg.querySelector('.btnSlideshowPause i').innerHTML !== "pause";
var paused = !dlg.querySelector('.btnSlideshowPause i').classList.contains("pause");
if (paused) {
play();
} else {

View file

@ -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;
};

View file

@ -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>

View file

@ -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,6 +18,14 @@ 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) {
@ -87,8 +96,6 @@ define(['playbackManager', 'text!./subtitlesync.template.html', 'css!./subtitles
SubtitleSync.prototype.toggle("forceToHide");
});
document.body.appendChild(parent);
instance.element = parent;
}

View file

@ -264,17 +264,13 @@ define(["jQuery", "loading", "emby-checkbox", "listViewStyle", "emby-input", "em
self.init = function () {
options = options || {};
if (options.showCancelButton) {
page.querySelector(".btnCancel").classList.remove("hide");
} else {
page.querySelector(".btnCancel").classList.add("hide");
}
// Only hide the buttons if explicitly set to false; default to showing if undefined or null
// FIXME: rename this option to clarify logic
var hideCancelButton = options.showCancelButton === false;
page.querySelector(".btnCancel").classList.toggle("hide", hideCancelButton);
if (options.showSubmitButton) {
page.querySelector(".btnSubmitListings").classList.remove("hide");
} else {
page.querySelector(".btnSubmitListings").classList.add("hide");
}
var hideSubmitButton = options.showSubmitButton === false;
page.querySelector(".btnSubmitListings").classList.toggle("hide", hideSubmitButton);
$(".formLogin", page).on("submit", function () {
submitLoginForm();

View file

@ -163,17 +163,13 @@ define(["jQuery", "loading", "emby-checkbox", "emby-input", "listViewStyle", "pa
self.init = function () {
options = options || {};
if (false !== options.showCancelButton) {
page.querySelector(".btnCancel").classList.remove("hide");
} else {
page.querySelector(".btnCancel").classList.add("hide");
}
// Only hide the buttons if explicitly set to false; default to showing if undefined or null
// FIXME: rename this option to clarify logic
var hideCancelButton = options.showCancelButton === false;
page.querySelector(".btnCancel").classList.toggle("hide", hideCancelButton);
if (false !== options.showSubmitButton) {
page.querySelector(".btnSubmitListings").classList.remove("hide");
} else {
page.querySelector(".btnSubmitListings").classList.add("hide");
}
var hideSubmitButton = options.showSubmitButton === false;
page.querySelector(".btnSubmitListings").classList.toggle("hide", hideSubmitButton);
$("form", page).on("submit", function () {
submitListingsForm();

3
src/config.example.json Normal file
View file

@ -0,0 +1,3 @@
{
"multiserver": true
}

View file

@ -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
};
}
@ -634,8 +635,11 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa
return "<img src='" + iconUrl + "' />";
},
getNowPlayingImageUrl: function (item) {
/* Screen width is multiplied by 0.2, as the there is currently no way to get the width of
elements that aren't created yet. */
if (item && item.BackdropImageTags && item.BackdropImageTags.length) {
return ApiClient.getScaledImageUrl(item.Id, {
maxWidth: Math.round(dom.getScreenWidth() * 0.20),
type: "Backdrop",
tag: item.BackdropImageTags[0]
});
@ -643,6 +647,7 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa
if (item && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length) {
return ApiClient.getScaledImageUrl(item.ParentBackdropItemId, {
maxWidth: Math.round(dom.getScreenWidth() * 0.20),
type: "Backdrop",
tag: item.ParentBackdropImageTags[0]
});
@ -650,6 +655,7 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa
if (item && item.BackdropImageTag) {
return ApiClient.getScaledImageUrl(item.BackdropItemId, {
maxWidth: Math.round(dom.getScreenWidth() * 0.20),
type: "Backdrop",
tag: item.BackdropImageTag
});
@ -659,6 +665,7 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa
if (item && imageTags.Thumb) {
return ApiClient.getScaledImageUrl(item.Id, {
maxWidth: Math.round(dom.getScreenWidth() * 0.20),
type: "Thumb",
tag: imageTags.Thumb
});
@ -666,6 +673,7 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa
if (item && item.ParentThumbImageTag) {
return ApiClient.getScaledImageUrl(item.ParentThumbItemId, {
maxWidth: Math.round(dom.getScreenWidth() * 0.20),
type: "Thumb",
tag: item.ParentThumbImageTag
});
@ -673,6 +681,7 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa
if (item && item.ThumbImageTag) {
return ApiClient.getScaledImageUrl(item.ThumbItemId, {
maxWidth: Math.round(dom.getScreenWidth() * 0.20),
type: "Thumb",
tag: item.ThumbImageTag
});
@ -680,6 +689,7 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa
if (item && imageTags.Primary) {
return ApiClient.getScaledImageUrl(item.Id, {
maxWidth: Math.round(dom.getScreenWidth() * 0.20),
type: "Primary",
tag: imageTags.Primary
});
@ -687,6 +697,7 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa
if (item && item.PrimaryImageTag) {
return ApiClient.getScaledImageUrl(item.PrimaryImageItemId, {
maxWidth: Math.round(dom.getScreenWidth() * 0.20),
type: "Primary",
tag: item.PrimaryImageTag
});
@ -694,6 +705,7 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa
if (item && item.AlbumPrimaryImageTag) {
return ApiClient.getScaledImageUrl(item.AlbumId, {
maxWidth: Math.round(dom.getScreenWidth() * 0.20),
type: "Primary",
tag: item.AlbumPrimaryImageTag
});

View file

@ -90,10 +90,11 @@ define(["loading", "libraryMenu", "globalize", "emby-checkbox", "emby-select"],
}
function validateHttps(form) {
var remoteAccess = form.querySelector("#chkRemoteAccess").checked;
var certPath = form.querySelector("#txtCertificatePath").value || null;
var httpsMode = form.querySelector("#selectHttpsMode").value;
if ("enabled" !== httpsMode && "required" !== httpsMode || certPath) {
if (!remoteAccess || ("enabled" !== httpsMode && "required" !== httpsMode || certPath)) {
return Promise.resolve();
}

View file

@ -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("");

View file

@ -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) : "&nbsp;";
html += installedPlugin ? globalize.translate("LabelVersionInstalled", installedPlugin.Version) : "&nbsp;";
html += "</div>";
html += "</div>";
html += "</div>";

View file

@ -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({

View file

@ -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;

View file

@ -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) {
@ -49,7 +49,7 @@ define(["jQuery", "loading", "events", "globalize", "serverNotifications", "huma
html += "</a>";
html += "</div>";
if (task.State === "Running") {
html += '<button type="button" is="paper-icon-button-light" id="btnTask' + task.Id + '" class="btnStopTask" data-taskid="' + task.Id + '" title="' + globalize.translate("ButtonStop") + '"><i class="material-icons">stop</i></button>';
html += '<button type="button" is="paper-icon-button-light" id="btnTask' + task.Id + '" class="btnStopTask" data-taskid="' + task.Id + '" title="' + globalize.translate("ButtonStop") + '"><i class="material-icons stop"></i></button>';
} else if (task.State === "Idle") {
html += '<button type="button" is="paper-icon-button-light" id="btnTask' + task.Id + '" class="btnStartTask" data-taskid="' + task.Id + '" title="' + globalize.translate("ButtonStart") + '"><i class="material-icons play_arrow"></i></button>';
}
@ -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, dfnshelper.localeWithSuffix));
if (task.LastExecutionResult.Status === "Failed") {
html += " <span style='color:#FF0000;'>(" + globalize.translate("LabelFailed") + ")</span>";
} else if (task.LastExecutionResult.Status === "Cancelled") {
@ -90,16 +93,22 @@ define(["jQuery", "loading", "events", "globalize", "serverNotifications", "huma
return html;
}
function setTaskButtonIcon(button, icon) {
var inner = button.querySelector("i");
inner.classList.remove("stop", "play_arrow");
inner.classList.add(icon);
}
function updateTaskButton(elem, state) {
if (state === "Running") {
elem.classList.remove("btnStartTask");
elem.classList.add("btnStopTask");
elem.querySelector("i").innerHTML = "stop";
setTaskButtonIcon(elem, "stop");
elem.title = globalize.translate("ButtonStop");
} else if (state === "Idle") {
elem.classList.add("btnStartTask");
elem.classList.remove("btnStopTask");
elem.querySelector("i").innerHTML = "&#xE037;";
setTaskButtonIcon(elem, "play_arrow");
elem.title = globalize.translate("ButtonStart");
}
$(elem).parents(".listItem")[0].setAttribute("data-status", state);

View file

@ -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 += "&nbsp;";

View file

@ -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>";

View file

@ -15,6 +15,7 @@ define(["jQuery", "loading", "globalize", "dom", "libraryMenu"], function ($, lo
page.querySelector("#selectEncoderPreset").value = config.EncoderPreset || "";
page.querySelector("#txtH264Crf").value = config.H264Crf || "";
page.querySelector("#chkEnableSubtitleExtraction").checked = config.EnableSubtitleExtraction || false;
page.querySelector("#chkEnableThrottling").checked = config.EnableThrottling || false;
page.querySelector("#selectVideoDecoder").dispatchEvent(new CustomEvent("change", {
bubbles: true
}));
@ -58,6 +59,7 @@ define(["jQuery", "loading", "globalize", "dom", "libraryMenu"], function ($, lo
config.EncoderPreset = form.querySelector("#selectEncoderPreset").value;
config.H264Crf = parseInt(form.querySelector("#txtH264Crf").value || "0");
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) {
return c.checked;
}), function (c) {

View file

@ -1,4 +1,4 @@
define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuilder", "datetime", "mediaInfo", "backdrop", "listView", "itemContextMenu", "itemHelper", "dom", "indicators", "apphost", "imageLoader", "libraryMenu", "globalize", "browser", "events", "scrollHelper", "playbackManager", "libraryBrowser", "scrollStyles", "emby-itemscontainer", "emby-checkbox", "emby-button", "emby-playstatebutton", "emby-ratingbutton", "emby-scroller", "emby-select"], function (loading, appRouter, layoutManager, connectionManager, cardBuilder, datetime, mediaInfo, backdrop, listView, itemContextMenu, itemHelper, dom, indicators, appHost, imageLoader, libraryMenu, globalize, browser, events, scrollHelper, playbackManager, libraryBrowser) {
define(["loading", "appRouter", "layoutManager", "connectionManager", "userSettings", "cardBuilder", "datetime", "mediaInfo", "backdrop", "listView", "itemContextMenu", "itemHelper", "dom", "indicators", "imageLoader", "libraryMenu", "globalize", "browser", "events", "playbackManager", "scrollStyles", "emby-itemscontainer", "emby-checkbox", "emby-button", "emby-playstatebutton", "emby-ratingbutton", "emby-scroller", "emby-select"], function (loading, appRouter, layoutManager, connectionManager, userSettings, cardBuilder, datetime, mediaInfo, backdrop, listView, itemContextMenu, itemHelper, dom, indicators, imageLoader, libraryMenu, globalize, browser, events, playbackManager) {
"use strict";
function getPromise(apiClient, params) {
@ -60,8 +60,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild
return options;
}
function getProgramScheduleHtml(items, options) {
options = options || {};
function getProgramScheduleHtml(items) {
var html = "";
html += '<div is="emby-itemscontainer" class="itemsContainer vertical-list" data-contextmenu="false">';
html += listView.getListViewHtml({
@ -445,7 +444,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild
}
}
function renderBackdrop(page, item, apiClient) {
function renderBackdrop(item) {
if (dom.getWindowSize().innerWidth >= 1000) {
backdrop.setBackdrops([item]);
} else {
@ -455,66 +454,74 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild
function renderDetailPageBackdrop(page, item, apiClient) {
var imgUrl;
var screenWidth = screen.availWidth;
var hasbackdrop = false;
var itemBackdropElement = page.querySelector("#itemBackdrop");
var usePrimaryImage = item.MediaType === "Video" && item.Type !== "Movie" && item.Type !== "Trailer" ||
item.MediaType && item.MediaType !== "Video" ||
item.Type === "MusicAlbum" ||
item.Type === "MusicArtist" ||
item.Type === "Person";
if (!layoutManager.mobile && !userSettings.enableBackdrops()) {
return false;
}
if ("Program" === item.Type && item.ImageTags && item.ImageTags.Thumb) {
imgUrl = apiClient.getScaledImageUrl(item.Id, {
type: "Thumb",
maxWidth: dom.getScreenWidth(),
index: 0,
tag: item.ImageTags.Thumb
});
itemBackdropElement.classList.remove("noBackdrop");
page.classList.remove("noBackdrop");
imageLoader.lazyImage(itemBackdropElement, imgUrl, false);
hasbackdrop = true;
} else if (usePrimaryImage && item.ImageTags && item.ImageTags.Primary) {
imgUrl = apiClient.getScaledImageUrl(item.Id, {
type: "Primary",
maxWidth: dom.getScreenWidth(),
index: 0,
tag: item.ImageTags.Primary
});
itemBackdropElement.classList.remove("noBackdrop");
page.classList.remove("noBackdrop");
imageLoader.lazyImage(itemBackdropElement, imgUrl, false);
hasbackdrop = true;
} else if (item.BackdropImageTags && item.BackdropImageTags.length) {
imgUrl = apiClient.getScaledImageUrl(item.Id, {
type: "Backdrop",
maxWidth: dom.getScreenWidth(),
index: 0,
tag: item.BackdropImageTags[0]
});
itemBackdropElement.classList.remove("noBackdrop");
page.classList.remove("noBackdrop");
imageLoader.lazyImage(itemBackdropElement, imgUrl, false);
hasbackdrop = true;
} else if (item.ParentBackdropItemId && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length) {
imgUrl = apiClient.getScaledImageUrl(item.ParentBackdropItemId, {
type: "Backdrop",
maxWidth: dom.getScreenWidth(),
index: 0,
tag: item.ParentBackdropImageTags[0]
});
itemBackdropElement.classList.remove("noBackdrop");
page.classList.remove("noBackdrop");
imageLoader.lazyImage(itemBackdropElement, imgUrl, false);
hasbackdrop = true;
} else if (item.ImageTags && item.ImageTags.Thumb) {
imgUrl = apiClient.getScaledImageUrl(item.Id, {
type: "Thumb",
maxWidth: dom.getScreenWidth(),
index: 0,
tag: item.ImageTags.Thumb
});
itemBackdropElement.classList.remove("noBackdrop");
page.classList.remove("noBackdrop");
imageLoader.lazyImage(itemBackdropElement, imgUrl, false);
hasbackdrop = true;
} else {
itemBackdropElement.classList.add("noBackdrop");
itemBackdropElement.style.backgroundImage = "";
}
if ("Person" === item.Type) {
// FIXME: This hides the backdrop on all persons to fix a margin issue. Ideally, a proper fix should be made.
page.classList.add('noBackdrop');
itemBackdropElement.classList.add("personBackdrop");
} else {
itemBackdropElement.classList.remove("personBackdrop");
@ -526,6 +533,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild
function reloadFromItem(instance, page, params, item, user) {
var context = params.context;
page.querySelector(".detailPagePrimaryContainer").classList.add("detailSticky");
renderName(item, page.querySelector(".nameContainer"), false, context);
var apiClient = connectionManager.getApiClient(item.ServerId);
renderSeriesTimerEditor(page, item, apiClient, user);
@ -536,7 +544,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild
setInitialCollapsibleState(page, item, apiClient, context, user);
renderDetails(page, item, apiClient, context);
renderTrackSelections(page, instance, item);
renderBackdrop(page, item, apiClient);
renderBackdrop(item);
renderDetailPageBackdrop(page, item, apiClient);
var canPlay = reloadPlayButtons(page, item);
@ -583,7 +591,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild
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");
}
@ -597,7 +605,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild
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");
}
@ -610,7 +618,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild
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");
}
@ -663,7 +671,9 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild
});
var detailLogo = page.querySelector(".detailLogo");
if (url) {
if (!layoutManager.mobile && !userSettings.enableBackdrops()) {
detailLogo.classList.add("hide");
} else if (url) {
detailLogo.classList.remove("hide");
detailLogo.classList.add("lazy");
detailLogo.setAttribute("data-src", url);
@ -693,26 +703,9 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild
}
}
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) {
@ -726,7 +719,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild
}
if (links.length) {
html.push(globalize.translate("LinksValue", links.join(", ")));
html.push(links.join(", "));
}
linksElem.innerHTML = html.join(", ");
@ -756,44 +749,54 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild
var shape = "portrait";
var detectRatio = false;
/* In the following section, getScreenWidth() is multiplied by 0.5 as the posters
are 25vw and we need double the resolution to counter Skia's scaling. */
// TODO: Find a reliable way to get the poster width
if (imageTags.Primary) {
url = apiClient.getScaledImageUrl(item.Id, {
type: "Primary",
maxWidth: Math.round(dom.getScreenWidth() * 0.5),
tag: item.ImageTags.Primary
});
detectRatio = true;
} else if (item.BackdropImageTags && item.BackdropImageTags.length) {
url = apiClient.getScaledImageUrl(item.Id, {
type: "Backdrop",
maxWidth: Math.round(dom.getScreenWidth() * 0.5),
tag: item.BackdropImageTags[0]
});
shape = "thumb";
} else if (imageTags.Thumb) {
url = apiClient.getScaledImageUrl(item.Id, {
type: "Thumb",
maxWidth: Math.round(dom.getScreenWidth() * 0.5),
tag: item.ImageTags.Thumb
});
shape = "thumb";
} else if (imageTags.Disc) {
url = apiClient.getScaledImageUrl(item.Id, {
type: "Disc",
maxWidth: Math.round(dom.getScreenWidth() * 0.5),
tag: item.ImageTags.Disc
});
shape = "square";
} else if (item.AlbumId && item.AlbumPrimaryImageTag) {
url = apiClient.getScaledImageUrl(item.AlbumId, {
type: "Primary",
maxWidth: Math.round(dom.getScreenWidth() * 0.5),
tag: item.AlbumPrimaryImageTag
});
shape = "square";
} else if (item.SeriesId && item.SeriesPrimaryImageTag) {
url = apiClient.getScaledImageUrl(item.SeriesId, {
type: "Primary",
maxWidth: Math.round(dom.getScreenWidth() * 0.5),
tag: item.SeriesPrimaryImageTag
});
} else if (item.ParentPrimaryImageItemId && item.ParentPrimaryImageTag) {
url = apiClient.getScaledImageUrl(item.ParentPrimaryImageItemId, {
type: "Primary",
maxWidth: Math.round(dom.getScreenWidth() * 0.5),
tag: item.ParentPrimaryImageTag
});
}
@ -817,7 +820,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild
if (editable) {
html += "</a>";
} else if (!editable && url === undefined) {
html += "</div>"
html += "</div>";
}
var progressHtml = item.IsFolder || !item.UserData ? "" : indicators.getProgressBarHtml(item);
@ -873,7 +876,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild
elem.querySelector(".detailImageProgressContainer").innerHTML = indicators.getProgressBarHtml(item);
}
function refreshImage(page, item, user) {
function refreshImage(page, item) {
refreshDetailImageUserData(page.querySelector(".detailImageContainer"), item);
}
@ -922,10 +925,10 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild
if ("Playlist" == item.Type) {
page.querySelector("#childrenCollapsible").classList.remove("hide");
renderPlaylistItems(page, item, user);
renderPlaylistItems(page, item);
} else if ("Studio" == item.Type || "Person" == item.Type || "Genre" == item.Type || "MusicGenre" == item.Type || "MusicArtist" == item.Type) {
page.querySelector("#childrenCollapsible").classList.remove("hide");
renderItemsByName(page, item, user);
renderItemsByName(page, item);
} else if (item.IsFolder) {
if ("BoxSet" == item.Type) {
page.querySelector("#childrenCollapsible").classList.add("hide");
@ -937,7 +940,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild
}
if ("Series" == item.Type) {
renderSeriesSchedule(page, item, user);
renderSeriesSchedule(page, item);
renderNextUp(page, item, user);
} else {
page.querySelector(".nextUpSection").classList.add("hide");
@ -952,7 +955,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild
page.querySelector("#specialsCollapsible").classList.add("hide");
}
renderCast(page, item, context, enableScrollX() ? null : 12);
renderCast(page, item);
if (item.PartCount && item.PartCount > 1) {
page.querySelector("#additionalPartsCollapsible").classList.remove("hide");
@ -988,7 +991,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild
}
}
function renderGenres(page, item, apiClient, context, isStatic) {
function renderGenres(page, item, context) {
context = context || inferContext(item);
var type;
var genres = item.GenreItems || [];
@ -1012,17 +1015,21 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild
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");
}
}
function renderDirector(page, item, apiClient, context, isStatic) {
function renderDirector(page, item, context) {
var directors = (item.People || []).filter(function (p) {
return "Director" === p.Type;
});
@ -1036,13 +1043,17 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild
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");
}
}
@ -1050,8 +1061,8 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild
renderSimilarItems(page, item, context);
renderMoreFromSeason(page, item, apiClient);
renderMoreFromArtist(page, item, apiClient);
renderDirector(page, item, apiClient, context, isStatic);
renderGenres(page, item, apiClient, context, isStatic);
renderDirector(page, item, context);
renderGenres(page, item, context);
renderChannelGuide(page, apiClient, item);
var taglineElement = page.querySelector(".tagline");
@ -1100,7 +1111,6 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild
reloadUserDataButtons(page, item);
renderLinks(externalLinksElem, item);
renderUserInfo(page, item);
renderTags(page, item);
renderSeriesAirTime(page, item, isStatic);
}
@ -1125,14 +1135,6 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild
return scrollX ? "overflowSquare" : "square";
}
function getThumbShape(scrollX) {
if (null == scrollX) {
scrollX = enableScrollX();
}
return scrollX ? "overflowBackdrop" : "backdrop";
}
function renderMoreFromSeason(view, item, apiClient) {
var section = view.querySelector(".moreFromSeasonSection");
@ -1508,13 +1510,13 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild
}
}
function renderItemsByName(page, item, user) {
function renderItemsByName(page, item) {
require("scripts/itembynamedetailpage".split(","), function () {
window.ItemsByName.renderItems(page, item);
});
}
function renderPlaylistItems(page, item, user) {
function renderPlaylistItems(page, item) {
require("scripts/playlistedit".split(","), function () {
PlaylistViewer.render(page, item);
});
@ -1594,7 +1596,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild
}
}
function renderSeriesSchedule(page, item, user) {
function renderSeriesSchedule(page, item) {
var apiClient = connectionManager.getApiClient(item.ServerId);
apiClient.getLiveTvPrograms({
UserId: apiClient.getCurrentUserId(),
@ -1808,7 +1810,6 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild
require(["chaptercardbuilder"], function (chaptercardbuilder) {
chaptercardbuilder.buildChapterCards(item, chapters, {
itemsContainer: scenesContent,
width: 400,
backdropShape: "overflowBackdrop",
squareShape: "overflowSquare"
});
@ -1844,7 +1845,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild
});
}
function renderCast(page, item, context, limit, isStatic) {
function renderCast(page, item) {
var people = (item.People || []).filter(function (p) {
return "Director" !== p.Type;
});
@ -1861,7 +1862,6 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild
itemsContainer: castContent,
coverImage: true,
serverId: item.ServerId,
width: 160,
shape: "overflowPortrait"
});
});
@ -1934,7 +1934,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild
playbackManager.play(playOptions);
}
function playTrailer(page) {
function playTrailer() {
playbackManager.playTrailers(currentItem);
}
@ -1991,11 +1991,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild
}
function onPlayTrailerClick() {
playTrailer(view);
}
function onDownloadChange() {
reload(self, view, params);
playTrailer();
}
function onDownloadClick() {
@ -2050,9 +2046,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild
if (userData) {
currentItem.UserData = userData;
reloadPlayButtons(view, currentItem);
apiClient.getCurrentUser().then(function (user) {
refreshImage(view, currentItem, user);
});
refreshImage(view, currentItem);
}
}
}
@ -2083,11 +2077,9 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild
});
view.addEventListener("click", function (e) {
if (dom.parentWithClass(e.target, "moreScenes")) {
apiClient.getCurrentUser().then(function (user) {
renderScenes(view, currentItem);
});
renderScenes(view, currentItem);
} else if (dom.parentWithClass(e.target, "morePeople")) {
renderCast(view, currentItem, params.context);
renderCast(view, currentItem);
} else if (dom.parentWithClass(e.target, "moreSpecials")) {
apiClient.getCurrentUser().then(function (user) {
renderSpecials(view, currentItem, user);

View file

@ -157,6 +157,12 @@ define(["globalize", "listView", "layoutManager", "userSettings", "focusManager"
return query;
}
function setSortButtonIcon(btnSortIcon, icon) {
btnSortIcon.classList.remove("arrow_downward");
btnSortIcon.classList.remove("arrow_upward");
btnSortIcon.classList.add(icon);
}
function updateSortText(instance) {
var btnSortText = instance.btnSortText;
@ -175,7 +181,7 @@ define(["globalize", "listView", "layoutManager", "userSettings", "focusManager"
var btnSortIcon = instance.btnSortIcon;
if (btnSortIcon) {
btnSortIcon.innerHTML = "Descending" === values.sortOrder ? "&#xE5DB;" : "&#xE5D8;";
setSortButtonIcon(btnSortIcon, "Descending" === values.sortOrder ? "arrow_downward" : "arrow_upward");
}
}
}

View file

@ -2,6 +2,10 @@ define(["layoutManager", "loading", "cardBuilder", "apphost", "imageLoader", "sc
"use strict";
function renderRecordings(elem, recordings, cardOptions, scrollX) {
if (!elem) {
return;
}
if (recordings.length) {
elem.classList.remove("hide");
} else {

View file

@ -256,6 +256,7 @@ define(["jQuery", "apphost", "scripts/taskbutton", "loading", "libraryMenu", "gl
if (virtualFolder.PrimaryImageItemId) {
imgUrl = ApiClient.getScaledImageUrl(virtualFolder.PrimaryImageItemId, {
maxWidth: Math.round(dom.getScreenWidth() * 0.40),
type: "Primary"
});
}

View file

@ -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;
}

View file

@ -334,18 +334,24 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med
if (item) {
var imgUrl = seriesImageUrl(item, {
maxWidth: osdPoster.clientWidth * 2,
type: "Primary"
}) || seriesImageUrl(item, {
maxWidth: osdPoster.clientWidth * 2,
type: "Thumb"
}) || imageUrl(item, {
maxWidth: osdPoster.clientWidth * 2,
type: "Primary"
});
if (!imgUrl && secondaryItem && (imgUrl = seriesImageUrl(secondaryItem, {
maxWidth: osdPoster.clientWidth * 2,
type: "Primary"
}) || seriesImageUrl(secondaryItem, {
maxWidth: osdPoster.clientWidth * 2,
type: "Thumb"
}) || imageUrl(secondaryItem, {
maxWidth: osdPoster.clientWidth * 2,
type: "Primary"
})), imgUrl) {
return void (osdPoster.innerHTML = '<img src="' + imgUrl + '" />');
@ -376,7 +382,7 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med
function startOsdHideTimer() {
stopOsdHideTimer();
osdHideTimeout = setTimeout(hideOsd, 5e3);
osdHideTimeout = setTimeout(hideOsd, 3e3);
}
function stopOsdHideTimer() {
@ -665,7 +671,8 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med
}
function onTimeUpdate(e) {
if (isEnabled) {
// Test for 'currentItem' is required for Firefox since its player spams 'timeupdate' events even being at breakpoint
if (isEnabled && currentItem) {
var now = new Date().getTime();
if (!(now - lastUpdateTime < 700)) {

View file

@ -1,4 +1,4 @@
define(["displaySettings", "userSettings", "autoFocuser"], function (DisplaySettings, currentUserSettings, autoFocuser) {
define(["displaySettings", "userSettingsBuilder", "userSettings", "autoFocuser"], function (DisplaySettings, userSettingsBuilder, currentUserSettings, autoFocuser) {
"use strict";
return function (view, params) {
@ -11,7 +11,7 @@ define(["displaySettings", "userSettings", "autoFocuser"], function (DisplaySett
var settingsInstance;
var hasChanges;
var userId = params.userId || ApiClient.getCurrentUserId();
var userSettings = userId === ApiClient.getCurrentUserId() ? currentUserSettings : new userSettings();
var userSettings = userId === ApiClient.getCurrentUserId() ? currentUserSettings : new userSettingsBuilder();
view.addEventListener("viewshow", function () {
window.addEventListener("beforeunload", onBeforeUnload);

View file

@ -1,4 +1,4 @@
define(["homescreenSettings", "dom", "globalize", "loading", "userSettings", "autoFocuser", "listViewStyle"], function (HomescreenSettings, dom, globalize, loading, currentUserSettings, autoFocuser) {
define(["homescreenSettings", "userSettingsBuilder", "dom", "globalize", "loading", "userSettings", "autoFocuser", "listViewStyle"], function (HomescreenSettings, userSettingsBuilder, dom, globalize, loading, currentUserSettings, autoFocuser) {
"use strict";
return function (view, params) {
@ -11,7 +11,7 @@ define(["homescreenSettings", "dom", "globalize", "loading", "userSettings", "au
var homescreenSettingsInstance;
var hasChanges;
var userId = params.userId || ApiClient.getCurrentUserId();
var userSettings = userId === ApiClient.getCurrentUserId() ? currentUserSettings : new userSettings();
var userSettings = userId === ApiClient.getCurrentUserId() ? currentUserSettings : new userSettingsBuilder();
view.addEventListener("viewshow", function () {
window.addEventListener("beforeunload", onBeforeUnload);

View file

@ -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) {

View file

@ -1,4 +1,4 @@
define(["playbackSettings", "dom", "globalize", "loading", "userSettings", "autoFocuser", "listViewStyle"], function (PlaybackSettings, dom, globalize, loading, currentUserSettings, autoFocuser) {
define(["playbackSettings", "userSettingsBuilder", "dom", "globalize", "loading", "userSettings", "autoFocuser", "listViewStyle"], function (PlaybackSettings, userSettingsBuilder, dom, globalize, loading, currentUserSettings, autoFocuser) {
"use strict";
return function (view, params) {
@ -11,7 +11,7 @@ define(["playbackSettings", "dom", "globalize", "loading", "userSettings", "auto
var settingsInstance;
var hasChanges;
var userId = params.userId || ApiClient.getCurrentUserId();
var userSettings = userId === ApiClient.getCurrentUserId() ? currentUserSettings : new userSettings();
var userSettings = userId === ApiClient.getCurrentUserId() ? currentUserSettings : new userSettingsBuilder();
view.addEventListener("viewshow", function () {
window.addEventListener("beforeunload", onBeforeUnload);

View file

@ -1,4 +1,4 @@
define(["subtitleSettings", "userSettings", "autoFocuser"], function (SubtitleSettings, currentUserSettings, autoFocuser) {
define(["subtitleSettings", "userSettingsBuilder", "userSettings", "autoFocuser"], function (SubtitleSettings, userSettingsBuilder, currentUserSettings, autoFocuser) {
"use strict";
return function (view, params) {
@ -11,7 +11,7 @@ define(["subtitleSettings", "userSettings", "autoFocuser"], function (SubtitleSe
var subtitleSettingsInstance;
var hasChanges;
var userId = params.userId || ApiClient.getCurrentUserId();
var userSettings = userId === ApiClient.getCurrentUserId() ? currentUserSettings : new userSettings();
var userSettings = userId === ApiClient.getCurrentUserId() ? currentUserSettings : new userSettingsBuilder();
view.addEventListener("viewshow", function () {
window.addEventListener("beforeunload", onBeforeUnload);

View file

@ -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 "";

View file

@ -8,7 +8,7 @@
<div class="verticalSection">
<div class="sectionTitleContainer flex align-items-center">
<h2 class="sectionTitle">${TabSettings}</h2>
<a is="emby-linkbutton" class="raised button-alt headerHelpButton" target="_blank" href="https://docs.jellyfin.org/general/administration/connectivity.html#DLNA">${Help}</a>
<a is="emby-linkbutton" class="raised button-alt headerHelpButton" target="_blank" href="https://docs.jellyfin.org/general/networking/dlna.html">${Help}</a>
</div>
</div>

View file

@ -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;
@ -68,7 +68,8 @@
color: #fff;
}
.checkboxIcon-checked {
.checkboxIcon-checked,
.emby-checkbox-label .checkboxIcon-checked {
display: none;
}

View file

@ -5,7 +5,8 @@ define(['browser', 'dom', 'css!./emby-checkbox', 'registerElement'], function (b
function onKeyDown(e) {
// Don't submit form on enter
if (e.keyCode === 13) {
// Real (non-emulator) Tizen does nothing on Space
if (e.keyCode === 13 || e.keyCode === 32) {
e.preventDefault();
this.checked = !this.checked;

View file

@ -80,7 +80,7 @@ define(['browser', 'css!./emby-collapse', 'registerElement', 'emby-button'], fun
var title = this.getAttribute('title');
var html = '<button is="emby-button" type="button" on-click="toggleExpand" id="expandButton" class="emby-collapsible-button iconRight"><h3 class="emby-collapsible-title" title="' + title + '">' + title + '</h3><i class="material-icons emby-collapse-expandIcon">expand_more</i></button>';
var html = '<button is="emby-button" type="button" on-click="toggleExpand" id="expandButton" class="emby-collapsible-button iconRight"><h3 class="emby-collapsible-title" title="' + title + '">' + title + '</h3><i class="material-icons emby-collapse-expandIcon expand_more"></i></button>';
this.insertAdjacentHTML('afterbegin', html);

View file

@ -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 {

View file

@ -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);
@ -6,16 +6,24 @@ define(['css!./emby-radio', 'registerElement'], function () {
function onKeyDown(e) {
// Don't submit form on enter
if (e.keyCode === 13) {
// Real (non-emulator) Tizen does nothing on Space
if (e.keyCode === 13 || e.keyCode === 32) {
e.preventDefault();
this.checked = true;
if (!this.checked) {
this.checked = true;
this.dispatchEvent(new CustomEvent('change', {
bubbles: true
}));
}
return false;
}
}
EmbyRadioPrototype.attachedCallback = function () {
var showFocus = !layoutManager.mobile;
if (this.getAttribute('data-radio') === 'true') {
return;
@ -30,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);
};

View file

@ -80,6 +80,10 @@
flex-shrink: 0;
}
.trackSelections > .selectContainer {
margin: 0.4em 0;
}
.emby-select-withcolor {
-webkit-appearance: none;
appearance: none;
@ -105,7 +109,7 @@
}
.selectArrow {
margin-top: 0.35em;
margin-top: 1.2em;
font-size: 1.7em;
}

View file

@ -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>');
}
};

View file

@ -144,6 +144,14 @@
<div class="fieldDescription checkboxFieldDescription">${AllowOnTheFlySubtitleExtractionHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="chkEnableThrottling" />
<span>${AllowFfmpegThrottling}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${AllowFfmpegThrottlingHelp}</div>
</div>
<div>
<button is="emby-button" type="submit" class="raised button-submit block">
<span>${ButtonSave}</span>

View file

@ -105,5 +105,8 @@
<div class="skinHeader focuscontainer-x"></div>
<div class="mainAnimatedPages skinBody"></div>
<div class="mainDrawerHandle"></div>
<!-- inject:js -->
<!-- endinject -->
</body>
</html>

View file

@ -1,5 +1,5 @@
<div id="itemDetailPage" data-role="page" class="page libraryPage itemDetailPage noSecondaryNavPage selfBackdropPage" data-backbutton="true">
<div id="itemBackdrop" class="itemBackdrop noBackdrop">
<div id="itemDetailPage" data-role="page" class="page libraryPage itemDetailPage noSecondaryNavPage selfBackdropPage noBackdrop" data-backbutton="true">
<div id="itemBackdrop" class="itemBackdrop">
<button is="emby-button" type="button" class="btnPlay detailFloatingButton hide fab autoSize" title="${ButtonPlay}" data-mode="resume">
<i class="material-icons play_arrow"></i>
</button>
@ -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" style="margin: .5em 0;font-size:92%; padding: 0;">
<div class="selectContainer selectContainer-inline selectSourceContainer hide trackSelectionFieldContainer flex-shrink-zero" style="margin-right:1.5em;">
<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" style="margin-right:1.5em;">
<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" style="margin-right:1.5em;">
<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>

View file

@ -59,8 +59,8 @@ define(["libraries/apiclient/apiclientcore", "localassetmanager"], function(ApiC
})
}
function ApiClientEx(serverAddress, clientName, applicationVersion, deviceName, deviceId, devicePixelRatio) {
ApiClient.call(this, serverAddress, clientName, applicationVersion, deviceName, deviceId, devicePixelRatio)
function ApiClientEx(serverAddress, clientName, applicationVersion, deviceName, deviceId) {
ApiClient.call(this, serverAddress, clientName, applicationVersion, deviceName, deviceId)
}
var localPrefix = "local:",
localViewPrefix = "localview:";

View file

@ -58,7 +58,7 @@ define(["events", "appStorage"], function(events, appStorage) {
return request.data && ("string" == typeof request.data ? fetchRequest.body = request.data : (fetchRequest.body = paramsToString(request.data), contentType = contentType || "application/x-www-form-urlencoded; charset=UTF-8")), contentType && (headers["Content-Type"] = contentType), request.timeout ? fetchWithTimeout(request.url, fetchRequest, request.timeout) : fetch(request.url, fetchRequest)
}
function ApiClient(serverAddress, appName, appVersion, deviceName, deviceId, devicePixelRatio) {
function ApiClient(serverAddress, appName, appVersion, deviceName, deviceId) {
if (!serverAddress) {
throw new Error("Must supply a serverAddress");
}
@ -75,7 +75,6 @@ define(["events", "appStorage"], function(events, appStorage) {
this._deviceName = deviceName;
this._appName = appName;
this._appVersion = appVersion;
this._devicePixelRatio = devicePixelRatio;
}
function setSavedEndpointInfo(instance, info) {
@ -218,7 +217,7 @@ define(["events", "appStorage"], function(events, appStorage) {
}
function normalizeImageOptions(instance, options) {
var ratio = instance._devicePixelRatio || 1;
var ratio = window.devicePixelRatio;
ratio && (options.minScale && (ratio = Math.max(options.minScale, ratio)), options.width && (options.width = Math.round(options.width * ratio)), options.height && (options.height = Math.round(options.height * ratio)), options.maxWidth && (options.maxWidth = Math.round(options.maxWidth * ratio)), options.maxHeight && (options.maxHeight = Math.round(options.maxHeight * ratio))), options.quality = options.quality || instance.getDefaultImageQuality(options.type), instance.normalizeImageOptions && instance.normalizeImageOptions(options)
}

View file

@ -186,7 +186,7 @@ define(["events", "apiclient", "appStorage"], function (events, apiClientFactory
Manual: 2
};
var ConnectionManager = function (credentialProvider, appName, appVersion, deviceName, deviceId, capabilities, devicePixelRatio) {
var ConnectionManager = function (credentialProvider, appName, appVersion, deviceName, deviceId, capabilities) {
function onAuthenticated(apiClient, result, options, saveCredentials) {
var credentials = credentialProvider.credentials();
@ -540,7 +540,7 @@ define(["events", "apiclient", "appStorage"], function (events, apiClientFactory
var apiClient = self.getApiClient(server.Id);
if (!apiClient) {
apiClient = new apiClientFactory(serverUrl, appName, appVersion, deviceName, deviceId, devicePixelRatio);
apiClient = new apiClientFactory(serverUrl, appName, appVersion, deviceName, deviceId);
self._apiClients.push(apiClient);
apiClient.serverInfo(server);
apiClient.onAuthenticated = function (instance, result) {

File diff suppressed because it is too large Load diff

View file

@ -1,18 +0,0 @@
"use strict";
window.queryString = {}, window.queryString.extract = function(maybeUrl) {
return maybeUrl.split("?")[1] || ""
}, window.queryString.parse = function(str) {
return "string" != typeof str ? {} : (str = str.trim().replace(/^(\?|#|&)/, ""), str ? str.split("&").reduce(function(ret, param) {
var parts = param.replace(/\+/g, " ").split("="),
key = parts[0],
val = parts[1];
return key = decodeURIComponent(key), val = void 0 === val ? null : decodeURIComponent(val), ret.hasOwnProperty(key) ? Array.isArray(ret[key]) ? ret[key].push(val) : ret[key] = [ret[key], val] : ret[key] = val, ret
}, {}) : {})
}, window.queryString.stringify = function(obj) {
return obj ? Object.keys(obj).sort().map(function(key) {
var val = obj[key];
return Array.isArray(val) ? val.sort().map(function(val2) {
return encodeURIComponent(key) + "=" + encodeURIComponent(val2)
}).join("&") : encodeURIComponent(key) + "=" + encodeURIComponent(val)
}).join("&") : ""
};

Some files were not shown because too many files have changed in this diff Show more