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

First separation commit.

Added LICENSE, README.md, CONTRIBUTORS.md
This commit is contained in:
Erwin de Haan 2019-01-09 12:36:54 +01:00
parent 09513af31b
commit 4678528d00
657 changed files with 422 additions and 0 deletions

View file

@ -0,0 +1,158 @@
.actionSheet,
.actionSheetContent {
display: -webkit-box;
display: -webkit-flex
}
.actionSheetContent,
.actionSheetScroller {
-webkit-box-orient: vertical;
-webkit-box-direction: normal
}
.actionSheet {
display: flex;
-webkit-box-pack: center;
-webkit-justify-content: center;
justify-content: center;
padding: 0;
border: none;
max-height: 84%;
-webkit-border-radius: .1em !important;
border-radius: .1em !important
}
.actionsheet-not-fullscreen {
max-width: 90%;
max-height: 90%
}
.actionsheet-fullscreen {
max-height: none;
-webkit-border-radius: 0 !important;
border-radius: 0 !important
}
.actionSheetContent-centered {
text-align: center;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center
}
.actionSheetContent {
margin: 0 !important;
padding: .4em 0 !important;
-webkit-flex-direction: column;
flex-direction: column;
display: flex;
-webkit-box-pack: center;
-webkit-justify-content: center;
justify-content: center;
-webkit-box-flex: 1;
-webkit-flex-grow: 1;
flex-grow: 1;
overflow: hidden
}
.actionSheetMenuItem {
font-weight: inherit;
-webkit-box-shadow: none;
box-shadow: none;
-webkit-flex-shrink: 0;
flex-shrink: 0
}
.actionSheetMenuItem:focus {
-webkit-transform: none !important;
transform: none !important
}
.actionsheetListItemBody {
padding: .4em 1em .4em .6em !important
}
.actionSheetItemText {
white-space: nowrap;
overflow: hidden;
-o-text-overflow: ellipsis;
text-overflow: ellipsis;
vertical-align: middle;
-webkit-box-flex: 1;
-webkit-flex-grow: 1;
flex-grow: 1;
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
justify-content: flex-start
}
.actionSheetItemAsideText {
opacity: .7;
font-size: 90%;
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-box-pack: end;
-webkit-justify-content: flex-end;
justify-content: flex-end;
-webkit-flex-shrink: 0;
flex-shrink: 0;
margin-left: 5em;
margin-right: .5em
}
.actionSheetScroller {
margin-bottom: 0 !important;
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-flex-direction: column;
flex-direction: column;
width: 100%
}
.actionSheetScroller-tv {
max-height: 64%;
max-width: 60%;
width: auto
}
.actionsheetDivider {
height: .07em;
margin: .25em 0;
-webkit-flex-shrink: 0;
flex-shrink: 0
}
.actionSheetTitle {
margin: .6em 0 .7em !important;
padding: 0 .9em;
-webkit-box-flex: 0;
-webkit-flex-grow: 0;
flex-grow: 0
}
.actionSheetText {
padding: 0 1em;
-webkit-box-flex: 0;
-webkit-flex-grow: 0;
flex-grow: 0
}
.actionsheetMenuItemIcon {
margin: 0 .85em 0 .45em !important;
padding: 0 !important
}
.actionsheet-xlargeFont {
font-size: 112% !important
}
.btnCloseActionSheet {
position: fixed;
top: .75em;
left: .5em
}

View file

@ -0,0 +1,93 @@
define(["dialogHelper", "layoutManager", "globalize", "browser", "dom", "emby-button", "css!./actionsheet", "material-icons", "scrollStyles", "listViewStyle"], function(dialogHelper, layoutManager, globalize, browser, dom) {
"use strict";
function getOffsets(elems) {
var doc = document,
results = [];
if (!doc) return results;
for (var box, elem, i = 0, length = elems.length; i < length; i++) elem = elems[i], box = elem.getBoundingClientRect ? elem.getBoundingClientRect() : {
top: 0,
left: 0
}, results[i] = {
top: box.top,
left: box.left,
width: box.width,
height: box.height
};
return results
}
function getPosition(options, dlg) {
var windowSize = dom.getWindowSize(),
windowHeight = windowSize.innerHeight,
windowWidth = windowSize.innerWidth;
if (windowWidth < 600 || windowHeight < 600) return null;
var pos = getOffsets([options.positionTo])[0];
"top" !== options.positionY && (pos.top += (pos.height || 0) / 2), pos.left += (pos.width || 0) / 2;
var height = dlg.offsetHeight || 300,
width = dlg.offsetWidth || 160;
pos.top -= height / 2, pos.left -= width / 2;
var overflowX = pos.left + width - windowWidth,
overflowY = pos.top + height - windowHeight;
return overflowX > 0 && (pos.left -= overflowX + 20), overflowY > 0 && (pos.top -= overflowY + 20), pos.top += options.offsetTop || 0, pos.left += options.offsetLeft || 0, pos.top = Math.max(pos.top, 10), pos.left = Math.max(pos.left, 10), pos
}
function centerFocus(elem, horiz, on) {
require(["scrollHelper"], function(scrollHelper) {
var fn = on ? "on" : "off";
scrollHelper.centerFocus[fn](elem, horiz)
})
}
function show(options) {
var isFullscreen, dialogOptions = {
removeOnClose: !0,
enableHistory: options.enableHistory,
scrollY: !1
};
layoutManager.tv ? (dialogOptions.size = "fullscreen", isFullscreen = !0, !0, dialogOptions.autoFocus = !0) : (dialogOptions.modal = !1, dialogOptions.entryAnimation = options.entryAnimation, dialogOptions.exitAnimation = options.exitAnimation, dialogOptions.entryAnimationDuration = options.entryAnimationDuration || 140, dialogOptions.exitAnimationDuration = options.exitAnimationDuration || 100, dialogOptions.autoFocus = !1);
var dlg = dialogHelper.createDialog(dialogOptions);
isFullscreen ? dlg.classList.add("actionsheet-fullscreen") : dlg.classList.add("actionsheet-not-fullscreen"), dlg.classList.add("actionSheet"), options.dialogClass && dlg.classList.add(options.dialogClass);
var html = "",
scrollClassName = layoutManager.tv ? "scrollY smoothScrollY hiddenScrollY" : "scrollY",
style = "";
if (options.items.length > 20) {
style += "min-width:" + (dom.getWindowSize().innerWidth >= 300 ? 240 : 200) + "px;"
}
var i, length, option, itemIcon, renderIcon = !1,
icons = [];
for (i = 0, length = options.items.length; i < length; i++) option = options.items[i], itemIcon = option.icon || (option.selected ? "check" : null), itemIcon && (renderIcon = !0), icons.push(itemIcon || "");
layoutManager.tv && (html += '<button is="paper-icon-button-light" class="btnCloseActionSheet hide-mouse-idle-tv" tabindex="-1"><i class="md-icon">&#xE5C4;</i></button>');
var center = options.title && !renderIcon;
center || layoutManager.tv ? html += '<div class="actionSheetContent actionSheetContent-centered">' : html += '<div class="actionSheetContent">', options.title && (html += '<h1 class="actionSheetTitle">', html += options.title, html += "</h1>"), options.text && (html += '<p class="actionSheetText">', html += options.text, html += "</p>");
var scrollerClassName = "actionSheetScroller";
layoutManager.tv && (scrollerClassName += " actionSheetScroller-tv focuscontainer-x focuscontainer-y"), html += '<div class="' + scrollerClassName + " " + scrollClassName + '" style="' + style + '">';
var menuItemClass = "listItem listItem-button actionSheetMenuItem";
for ((options.border || options.shaded) && (menuItemClass += " listItem-border"), options.menuItemClass && (menuItemClass += " " + options.menuItemClass), layoutManager.tv && (menuItemClass += " listItem-focusscale"), layoutManager.mobile && (menuItemClass += " actionsheet-xlargeFont"), i = 0, length = options.items.length; i < length; i++)
if (option = options.items[i], option.divider) html += '<div class="actionsheetDivider"></div>';
else {
var autoFocus = option.selected && layoutManager.tv ? " autoFocus" : "",
optionId = null == option.id || "" === option.id ? option.value : option.id;
html += "<button" + autoFocus + ' is="emby-button" type="button" class="' + menuItemClass + '" data-id="' + optionId + '">', itemIcon = icons[i], itemIcon ? html += '<i class="actionsheetMenuItemIcon listItemIcon listItemIcon-transparent md-icon">' + itemIcon + "</i>" : renderIcon && !center && (html += '<i class="actionsheetMenuItemIcon listItemIcon listItemIcon-transparent md-icon" style="visibility:hidden;">check</i>'), html += '<div class="listItemBody actionsheetListItemBody">', html += '<div class="listItemBodyText actionSheetItemText">', html += option.name || option.textContent || option.innerText, html += "</div>", option.secondaryText && (html += '<div class="listItemBodyText secondary">', html += option.secondaryText, html += "</div>"), html += "</div>", option.asideText && (html += '<div class="listItemAside actionSheetItemAsideText">', html += option.asideText, html += "</div>"), html += "</button>"
} options.showCancel && (html += '<div class="buttons">', html += '<button is="emby-button" type="button" class="btnCloseActionSheet">' + globalize.translate("sharedcomponents#ButtonCancel") + "</button>", html += "</div>"), html += "</div>", dlg.innerHTML = html, layoutManager.tv && centerFocus(dlg.querySelector(".actionSheetScroller"), !1, !0), dlg.querySelector(".btnCloseActionSheet") && dlg.querySelector(".btnCloseActionSheet").addEventListener("click", function() {
dialogHelper.close(dlg)
});
var selectedId, timeout;
return options.timeout && (timeout = setTimeout(function() {
dialogHelper.close(dlg)
}, options.timeout)), new Promise(function(resolve, reject) {
var isResolved;
dlg.addEventListener("click", function(e) {
var actionSheetMenuItem = dom.parentWithClass(e.target, "actionSheetMenuItem");
actionSheetMenuItem && (selectedId = actionSheetMenuItem.getAttribute("data-id"), options.resolveOnClick && (options.resolveOnClick.indexOf ? -1 !== options.resolveOnClick.indexOf(selectedId) && (resolve(selectedId), isResolved = !0) : (resolve(selectedId), isResolved = !0)), dialogHelper.close(dlg))
}), dlg.addEventListener("close", function() {
layoutManager.tv && centerFocus(dlg.querySelector(".actionSheetScroller"), !1, !1), timeout && (clearTimeout(timeout), timeout = null), isResolved || (null != selectedId ? (options.callback && options.callback(selectedId), resolve(selectedId)) : reject())
}), dialogHelper.open(dlg);
var pos = options.positionTo && "fullscreen" !== dialogOptions.size ? getPosition(options, dlg) : null;
pos && (dlg.style.position = "fixed", dlg.style.margin = 0, dlg.style.left = pos.left + "px", dlg.style.top = pos.top + "px")
})
}
return {
show: show
}
});

View file

@ -0,0 +1,18 @@
define(["dialog", "globalize"], function(dialog, globalize) {
"use strict";
return function(text, title) {
var options;
options = "string" == typeof text ? {
title: title,
text: text
} : text;
var items = [];
return items.push({
name: globalize.translate("sharedcomponents#ButtonGotIt"),
id: "ok",
type: "submit"
}), options.buttons = items, dialog(options).then(function(result) {
return "ok" === result ? Promise.resolve() : Promise.reject()
})
}
});

View file

@ -0,0 +1,14 @@
define([], function() {
"use strict";
function replaceAll(str, find, replace) {
return str.split(find).join(replace)
}
return function(options) {
"string" == typeof options && (options = {
text: options
});
var text = replaceAll(options.text || "", "<br/>", "\n");
return alert(text), Promise.resolve()
}
});

View file

@ -0,0 +1,59 @@
define(["dom", "focusManager"], function(dom, focusManager) {
"use strict";
function onKeyDown(e) {
if (!e.ctrlKey && !e.shiftKey && !e.altKey) {
var key = e.key,
chr = key ? alphanumeric(key) : null;
chr && (chr = chr.toString().toUpperCase(), 1 === chr.length && (currentDisplayTextContainer = this.options.itemsContainer, onAlphanumericKeyPress(e, chr)))
}
}
function alphanumeric(value) {
var letterNumber = /^[0-9a-zA-Z]+$/;
return value.match(letterNumber)
}
function ensureInputDisplayElement() {
inputDisplayElement || (inputDisplayElement = document.createElement("div"), inputDisplayElement.classList.add("alphanumeric-shortcut"), inputDisplayElement.classList.add("hide"), document.body.appendChild(inputDisplayElement))
}
function clearAlphaNumericShortcutTimeout() {
alpanumericShortcutTimeout && (clearTimeout(alpanumericShortcutTimeout), alpanumericShortcutTimeout = null)
}
function resetAlphaNumericShortcutTimeout() {
clearAlphaNumericShortcutTimeout(), alpanumericShortcutTimeout = setTimeout(onAlphanumericShortcutTimeout, 2e3)
}
function onAlphanumericKeyPress(e, chr) {
currentDisplayText.length >= 3 || (ensureInputDisplayElement(), currentDisplayText += chr, inputDisplayElement.innerHTML = currentDisplayText, inputDisplayElement.classList.remove("hide"), resetAlphaNumericShortcutTimeout())
}
function onAlphanumericShortcutTimeout() {
var value = currentDisplayText,
container = currentDisplayTextContainer;
currentDisplayText = "", currentDisplayTextContainer = null, inputDisplayElement.innerHTML = "", inputDisplayElement.classList.add("hide"), clearAlphaNumericShortcutTimeout(), selectByShortcutValue(container, value)
}
function selectByShortcutValue(container, value) {
value = value.toUpperCase();
var focusElem;
"#" === value && (focusElem = container.querySelector("*[data-prefix]")), focusElem || (focusElem = container.querySelector("*[data-prefix^='" + value + "']")), focusElem && focusManager.focus(focusElem)
}
function AlphaNumericShortcuts(options) {
this.options = options;
var keyDownHandler = onKeyDown.bind(this);
dom.addEventListener(window, "keydown", keyDownHandler, {
passive: !0
}), this.keyDownHandler = keyDownHandler
}
var inputDisplayElement, currentDisplayTextContainer, alpanumericShortcutTimeout, currentDisplayText = "";
return AlphaNumericShortcuts.prototype.destroy = function() {
var keyDownHandler = this.keyDownHandler;
keyDownHandler && (dom.removeEventListener(window, "keydown", keyDownHandler, {
passive: !0
}), this.keyDownHandler = null), this.options = null
}, AlphaNumericShortcuts
});

View file

@ -0,0 +1,127 @@
define(["focusManager", "layoutManager", "dom", "css!./style.css", "paper-icon-button-light", "material-icons"], function(focusManager, layoutManager, dom) {
"use strict";
function focus() {
var scope = this,
selected = scope.querySelector("." + selectedButtonClass);
selected ? focusManager.focus(selected) : focusManager.autoFocus(scope, !0)
}
function getAlphaPickerButtonClassName(vertical) {
var alphaPickerButtonClassName = "alphaPickerButton";
return layoutManager.tv && (alphaPickerButtonClassName += " alphaPickerButton-tv"), vertical && (alphaPickerButtonClassName += " alphaPickerButton-vertical"), alphaPickerButtonClassName
}
function getLetterButton(l, vertical) {
return '<button data-value="' + l + '" class="' + getAlphaPickerButtonClassName(vertical) + '">' + l + "</button>"
}
function mapLetters(letters, vertical) {
return letters.map(function(l) {
return getLetterButton(l, vertical)
})
}
function render(element, options) {
element.classList.add("alphaPicker"), layoutManager.tv && element.classList.add("alphaPicker-tv");
var vertical = element.classList.contains("alphaPicker-vertical");
vertical || element.classList.add("focuscontainer-x");
var letters, html = "",
alphaPickerButtonClassName = getAlphaPickerButtonClassName(vertical),
rowClassName = "alphaPickerRow";
vertical && (rowClassName += " alphaPickerRow-vertical"), html += '<div class="' + rowClassName + '">', "keyboard" === options.mode ? html += '<button data-value=" " is="paper-icon-button-light" class="' + alphaPickerButtonClassName + '"><i class="md-icon alphaPickerButtonIcon">&#xE256;</i></button>' : (letters = ["#"], html += mapLetters(letters, vertical).join("")), letters = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"], html += mapLetters(letters, vertical).join(""), "keyboard" === options.mode ? (html += '<button data-value="backspace" is="paper-icon-button-light" class="' + alphaPickerButtonClassName + '"><i class="md-icon alphaPickerButtonIcon">&#xE14A;</i></button>', html += "</div>", letters = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"], html += '<div class="' + rowClassName + '">', html += "<br/>", html += mapLetters(letters, vertical).join(""), html += "</div>") : html += "</div>", element.innerHTML = html, element.classList.add("focusable"), element.focus = focus
}
function AlphaPicker(options) {
function onItemFocusTimeout() {
itemFocusTimeout = null, self.value(itemFocusValue)
}
function onAlphaFocusTimeout() {
if (alphaFocusTimeout = null, document.activeElement === alphaFocusedElement) {
var value = alphaFocusedElement.getAttribute("data-value");
self.value(value, !0)
}
}
function onAlphaPickerInKeyboardModeClick(e) {
var alphaPickerButton = dom.parentWithClass(e.target, "alphaPickerButton");
if (alphaPickerButton) {
var value = alphaPickerButton.getAttribute("data-value");
element.dispatchEvent(new CustomEvent("alphavalueclicked", {
cancelable: !1,
detail: {
value: value
}
}))
}
}
function onAlphaPickerClick(e) {
var alphaPickerButton = dom.parentWithClass(e.target, "alphaPickerButton");
if (alphaPickerButton) {
var value = alphaPickerButton.getAttribute("data-value");
(this._currentValue || "").toUpperCase() === value.toUpperCase() ? self.value(null, !0) : self.value(value, !0)
}
}
function onAlphaPickerFocusIn(e) {
alphaFocusTimeout && (clearTimeout(alphaFocusTimeout), alphaFocusTimeout = null);
var alphaPickerButton = dom.parentWithClass(e.target, "alphaPickerButton");
alphaPickerButton && (alphaFocusedElement = alphaPickerButton, alphaFocusTimeout = setTimeout(onAlphaFocusTimeout, 600))
}
function onItemsFocusIn(e) {
var item = dom.parentWithClass(e.target, itemClass);
if (item) {
var prefix = item.getAttribute("data-prefix");
prefix && prefix.length && (itemFocusValue = prefix[0], itemFocusTimeout && clearTimeout(itemFocusTimeout), itemFocusTimeout = setTimeout(onItemFocusTimeout, 100))
}
}
var self = this;
this.options = options;
var itemFocusValue, itemFocusTimeout, alphaFocusedElement, alphaFocusTimeout, element = options.element,
itemsContainer = options.itemsContainer,
itemClass = options.itemClass;
self.enabled = function(enabled) {
enabled ? (itemsContainer && itemsContainer.addEventListener("focus", onItemsFocusIn, !0), "keyboard" === options.mode && element.addEventListener("click", onAlphaPickerInKeyboardModeClick), "click" !== options.valueChangeEvent ? element.addEventListener("focus", onAlphaPickerFocusIn, !0) : element.addEventListener("click", onAlphaPickerClick.bind(this))) : (itemsContainer && itemsContainer.removeEventListener("focus", onItemsFocusIn, !0), element.removeEventListener("click", onAlphaPickerInKeyboardModeClick), element.removeEventListener("focus", onAlphaPickerFocusIn, !0), element.removeEventListener("click", onAlphaPickerClick.bind(this)))
}, render(element, options), this.enabled(!0), this.visible(!0)
}
var selectedButtonClass = "alphaPickerButton-selected";
return AlphaPicker.prototype.value = function(value, applyValue) {
var btn, selected, element = this.options.element;
if (void 0 !== value)
if (null != value) {
if (value = value.toUpperCase(), this._currentValue = value, "keyboard" !== this.options.mode) {
selected = element.querySelector("." + selectedButtonClass);
try {
btn = element.querySelector(".alphaPickerButton[data-value='" + value + "']")
} catch (err) {
console.log("Error in querySelector: " + err)
}
btn && btn !== selected && btn.classList.add(selectedButtonClass), selected && selected !== btn && selected.classList.remove(selectedButtonClass)
}
} else this._currentValue = value, (selected = element.querySelector("." + selectedButtonClass)) && selected.classList.remove(selectedButtonClass);
return applyValue && element.dispatchEvent(new CustomEvent("alphavaluechanged", {
cancelable: !1,
detail: {
value: value
}
})), this._currentValue
}, AlphaPicker.prototype.on = function(name, fn) {
this.options.element.addEventListener(name, fn)
}, AlphaPicker.prototype.off = function(name, fn) {
this.options.element.removeEventListener(name, fn)
}, AlphaPicker.prototype.visible = function(visible) {
this.options.element.style.visibility = visible ? "visible" : "hidden"
}, AlphaPicker.prototype.values = function() {
for (var element = this.options.element, elems = element.querySelectorAll(".alphaPickerButton"), values = [], i = 0, length = elems.length; i < length; i++) values.push(elems[i].getAttribute("data-value"));
return values
}, AlphaPicker.prototype.focus = function() {
var element = this.options.element;
focusManager.autoFocus(element, !0)
}, AlphaPicker.prototype.destroy = function() {
var element = this.options.element;
this.enabled(!1), element.classList.remove("focuscontainer-x"), this.options = null
}, AlphaPicker
});

View file

@ -0,0 +1,163 @@
.alphaPicker,
.alphaPickerRow {
display: -webkit-box;
display: -webkit-flex;
-webkit-box-direction: normal
}
.alphaPicker,
.alphaPickerRow,
.alphaPickerRow-vertical {
-webkit-box-direction: normal
}
.alphaPicker {
text-align: center;
display: flex;
-webkit-box-orient: vertical;
-webkit-flex-direction: column;
flex-direction: column;
-webkit-align-self: center;
align-self: center
}
.alphaPicker-vertical {
line-height: 1
}
.alphaPicker-fixed {
position: fixed;
bottom: 5.5em;
z-index: 999999
}
.alphaPickerRow {
display: flex;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
justify-content: center;
-webkit-box-orient: horizontal;
-webkit-flex-direction: row;
flex-direction: row
}
.alphaPickerRow-vertical {
-webkit-box-orient: vertical;
-webkit-flex-direction: column;
flex-direction: column
}
.alphaPickerButton {
border: 0 !important;
cursor: pointer;
outline: 0 !important;
vertical-align: middle;
font-family: inherit;
font-size: inherit;
min-width: initial;
margin: 0;
padding: .1em .4em;
width: auto;
-webkit-border-radius: .1em;
border-radius: .1em;
font-weight: 400;
-webkit-flex-shrink: 0;
flex-shrink: 0;
-webkit-box-flex: 1;
-webkit-flex-grow: 1;
flex-grow: 1
}
@media all and (max-height:50em) {
.alphaPicker-fixed {
bottom: 5em
}
.alphaPickerButton-vertical {
padding-top: 1px !important;
padding-bottom: 1px !important
}
}
@media all and (max-height:49em) {
.alphaPicker-vertical {
font-size: 94%
}
}
@media all and (max-height:44em) {
.alphaPicker-vertical {
font-size: 90%
}
.alphaPickerButton-vertical {
padding-top: 0 !important;
padding-bottom: 0 !important
}
}
@media all and (max-height:37em) {
.alphaPicker-vertical {
font-size: 82%
}
}
@media all and (max-height:32em) {
.alphaPicker-vertical {
font-size: 74%
}
}
.alphaPicker-vertical.alphaPicker-tv {
font-size: 86%
}
.alphaPickerButton-tv.alphaPickerButton-vertical {
padding: 0
}
.alphaPickerButton-vertical {
width: 1.5em;
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-box-pack: center;
-webkit-justify-content: center;
justify-content: center;
text-align: center
}
.alphaPickerButtonIcon {
font-size: 100% !important
}
.alphaPicker-fixed.alphaPicker-tv {
bottom: 1%
}
.alphaPicker-fixed-left {
left: .4em
}
.alphaPicker-fixed-right {
right: .4em
}
@media all and (min-width:62.5em) {
.alphaPicker-fixed-left {
left: 1em
}
.alphaPicker-fixed-right {
right: 1em
}
}
@media all and (max-height:31.25em) {
.alphaPicker-fixed {
display: none !important
}
}

View file

@ -0,0 +1,16 @@
.appfooter {
position: fixed;
left: 0;
right: 0;
z-index: 1;
bottom: 0;
-webkit-transition: -webkit-transform 180ms linear;
-o-transition: transform 180ms linear;
transition: transform 180ms linear;
contain: layout style
}
.appfooter.headroom--unpinned {
-webkit-transform: translateY(100%) !important;
transform: translateY(100%) !important
}

View file

@ -0,0 +1,20 @@
define(["browser", "css!./appfooter"], function(browser) {
"use strict";
function render(options) {
var elem = document.createElement("div");
return elem.classList.add("appfooter"), browser.chrome || elem.classList.add("appfooter-blurred"), document.body.appendChild(elem), elem
}
function appFooter(options) {
var self = this;
self.element = render(options), self.add = function(elem) {
self.element.appendChild(elem)
}, self.insert = function(elem) {
"string" == typeof elem ? self.element.insertAdjacentHTML("afterbegin", elem) : self.element.insertBefore(elem, self.element.firstChild)
}
}
return appFooter.prototype.destroy = function() {
this.element = null
}, appFooter
});

View file

@ -0,0 +1,39 @@
define(["appStorage", "events"], function(appStorage, events) {
"use strict";
function getKey(name, userId) {
return userId && (name = userId + "-" + name), name
}
function AppSettings() {}
return AppSettings.prototype.enableAutoLogin = function(val) {
return null != val && this.set("enableAutoLogin", val.toString()), "false" !== this.get("enableAutoLogin")
}, AppSettings.prototype.enableAutomaticBitrateDetection = function(isInNetwork, mediaType, val) {
var key = "enableautobitratebitrate-" + mediaType + "-" + isInNetwork;
return null != val && (isInNetwork && "Audio" === mediaType && (val = !0), this.set(key, val.toString())), !(!isInNetwork || "Audio" !== mediaType) || "false" !== this.get(key)
}, AppSettings.prototype.maxStreamingBitrate = function(isInNetwork, mediaType, val) {
var key = "maxbitrate-" + mediaType + "-" + isInNetwork;
return null != val && (isInNetwork && "Audio" === mediaType || this.set(key, val)), isInNetwork && "Audio" === mediaType ? 15e7 : parseInt(this.get(key) || "0") || 15e5
}, AppSettings.prototype.maxStaticMusicBitrate = function(val) {
void 0 !== val && this.set("maxStaticMusicBitrate", val);
var defaultValue = 32e4;
return parseInt(this.get("maxStaticMusicBitrate") || defaultValue.toString()) || defaultValue
}, AppSettings.prototype.maxChromecastBitrate = function(val) {
return null != val && this.set("chromecastBitrate1", val), val = this.get("chromecastBitrate1"), val ? parseInt(val) : null
}, AppSettings.prototype.syncOnlyOnWifi = function(val) {
return null != val && this.set("syncOnlyOnWifi", val.toString()), "false" !== this.get("syncOnlyOnWifi")
}, AppSettings.prototype.syncPath = function(val) {
return null != val && this.set("syncPath", val), this.get("syncPath")
}, AppSettings.prototype.cameraUploadServers = function(val) {
return null != val && this.set("cameraUploadServers", val.join(",")), val = this.get("cameraUploadServers"), val ? val.split(",") : []
}, AppSettings.prototype.runAtStartup = function(val) {
return null != val && this.set("runatstartup", val.toString()), "true" === this.get("runatstartup")
}, AppSettings.prototype.set = function(name, value, userId) {
var currentValue = this.get(name, userId);
appStorage.setItem(getKey(name, userId), value), currentValue !== value && events.trigger(this, "change", [name])
}, AppSettings.prototype.get = function(name, userId) {
return appStorage.getItem(getKey(name, userId))
}, AppSettings.prototype.enableSystemExternalPlayers = function(val) {
return null != val && this.set("enableSystemExternalPlayers", val.toString()), "true" === this.get("enableSystemExternalPlayers")
}, new AppSettings
});

View file

@ -0,0 +1,150 @@
define(["browser", "connectionManager", "playbackManager", "dom", "css!./style"], function(browser, connectionManager, playbackManager, dom) {
"use strict";
function enableAnimation(elem) {
return !browser.slow
}
function enableRotation() {
return !browser.tv && !browser.firefox
}
function Backdrop() {}
function getBackdropContainer() {
return backdropContainer || (backdropContainer = document.querySelector(".backdropContainer")), backdropContainer || (backdropContainer = document.createElement("div"), backdropContainer.classList.add("backdropContainer"), document.body.insertBefore(backdropContainer, document.body.firstChild)), backdropContainer
}
function clearBackdrop(clearAll) {
clearRotation(), currentLoadingBackdrop && (currentLoadingBackdrop.destroy(), currentLoadingBackdrop = null), getBackdropContainer().innerHTML = "", clearAll && (hasExternalBackdrop = !1), internalBackdrop(!1)
}
function getBackgroundContainer() {
return backgroundContainer || (backgroundContainer = document.querySelector(".backgroundContainer")), backgroundContainer
}
function setBackgroundContainerBackgroundEnabled() {
hasInternalBackdrop || hasExternalBackdrop ? getBackgroundContainer().classList.add("withBackdrop") : getBackgroundContainer().classList.remove("withBackdrop")
}
function internalBackdrop(enabled) {
hasInternalBackdrop = enabled, setBackgroundContainerBackgroundEnabled()
}
function externalBackdrop(enabled) {
hasExternalBackdrop = enabled, setBackgroundContainerBackgroundEnabled()
}
function setBackdropImage(url) {
currentLoadingBackdrop && (currentLoadingBackdrop.destroy(), currentLoadingBackdrop = null);
var elem = getBackdropContainer(),
existingBackdropImage = elem.querySelector(".displayingBackdropImage");
if (existingBackdropImage && existingBackdropImage.getAttribute("data-url") === url) {
if (existingBackdropImage.getAttribute("data-url") === url) return;
existingBackdropImage.classList.remove("displayingBackdropImage")
}
var instance = new Backdrop;
instance.load(url, elem, existingBackdropImage), currentLoadingBackdrop = instance
}
function getBackdropMaxWidth() {
var width = dom.getWindowSize().innerWidth;
if (-1 !== standardWidths.indexOf(width)) return width;
return width = 100 * Math.floor(width / 100), Math.min(width, 1920)
}
function getItemImageUrls(item, imageOptions) {
imageOptions = imageOptions || {};
var apiClient = connectionManager.getApiClient(item.ServerId);
return item.BackdropImageTags && item.BackdropImageTags.length > 0 ? item.BackdropImageTags.map(function(imgTag, index) {
return apiClient.getScaledImageUrl(item.BackdropItemId || item.Id, Object.assign(imageOptions, {
type: "Backdrop",
tag: imgTag,
maxWidth: getBackdropMaxWidth(),
index: index
}))
}) : item.ParentBackdropItemId && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length ? item.ParentBackdropImageTags.map(function(imgTag, index) {
return apiClient.getScaledImageUrl(item.ParentBackdropItemId, Object.assign(imageOptions, {
type: "Backdrop",
tag: imgTag,
maxWidth: getBackdropMaxWidth(),
index: index
}))
}) : []
}
function getImageUrls(items, imageOptions) {
for (var list = [], onImg = function(img) {
list.push(img)
}, i = 0, length = items.length; i < length; i++) {
getItemImageUrls(items[i], imageOptions).forEach(onImg)
}
return list
}
function arraysEqual(a, b) {
if (a === b) return !0;
if (null == a || null == b) return !1;
if (a.length !== b.length) return !1;
for (var i = 0; i < a.length; ++i)
if (a[i] !== b[i]) return !1;
return !0
}
function setBackdrops(items, imageOptions, enableImageRotation) {
var images = getImageUrls(items, imageOptions);
images.length ? startRotation(images, enableImageRotation) : clearBackdrop()
}
function startRotation(images, enableImageRotation) {
arraysEqual(images, currentRotatingImages) || (clearRotation(), currentRotatingImages = images, currentRotationIndex = -1, images.length > 1 && !1 !== enableImageRotation && enableRotation() && (rotationInterval = setInterval(onRotationInterval, 24e3)), onRotationInterval())
}
function onRotationInterval() {
if (!playbackManager.isPlayingLocally(["Video"])) {
var newIndex = currentRotationIndex + 1;
newIndex >= currentRotatingImages.length && (newIndex = 0), currentRotationIndex = newIndex, setBackdropImage(currentRotatingImages[newIndex])
}
}
function clearRotation() {
var interval = rotationInterval;
interval && clearInterval(interval), rotationInterval = null, currentRotatingImages = [], currentRotationIndex = -1
}
function setBackdrop(url, imageOptions) {
url && "string" != typeof url && (url = getImageUrls([url], imageOptions)[0]), url ? (clearRotation(), setBackdropImage(url)) : clearBackdrop()
}
Backdrop.prototype.load = function(url, parent, existingBackdropImage) {
var img = new Image,
self = this;
img.onload = function() {
if (!self.isDestroyed) {
var backdropImage = document.createElement("div");
if (backdropImage.classList.add("backdropImage"), backdropImage.classList.add("displayingBackdropImage"), backdropImage.style.backgroundImage = "url('" + url + "')", backdropImage.setAttribute("data-url", url), backdropImage.classList.add("backdropImageFadeIn"), parent.appendChild(backdropImage), !enableAnimation(backdropImage)) return existingBackdropImage && existingBackdropImage.parentNode && existingBackdropImage.parentNode.removeChild(existingBackdropImage), void internalBackdrop(!0);
var onAnimationComplete = function() {
dom.removeEventListener(backdropImage, dom.whichAnimationEvent(), onAnimationComplete, {
once: !0
}), backdropImage === self.currentAnimatingElement && (self.currentAnimatingElement = null), existingBackdropImage && existingBackdropImage.parentNode && existingBackdropImage.parentNode.removeChild(existingBackdropImage)
};
dom.addEventListener(backdropImage, dom.whichAnimationEvent(), onAnimationComplete, {
once: !0
}), internalBackdrop(!0)
}
}, img.src = url
}, Backdrop.prototype.cancelAnimation = function() {
var elem = this.currentAnimatingElement;
elem && (elem.classList.remove("backdropImageFadeIn"), this.currentAnimatingElement = null)
}, Backdrop.prototype.destroy = function() {
this.isDestroyed = !0, this.cancelAnimation()
};
var backdropContainer, backgroundContainer, hasInternalBackdrop, hasExternalBackdrop, currentLoadingBackdrop, rotationInterval, standardWidths = [480, 720, 1280, 1440, 1920],
currentRotatingImages = [],
currentRotationIndex = -1;
return {
setBackdrops: setBackdrops,
setBackdrop: setBackdrop,
clear: clearBackdrop,
externalBackdrop: externalBackdrop
}
});

View file

@ -0,0 +1,41 @@
.backdropContainer {
contain: layout style size
}
.backdropImage {
background-repeat: no-repeat;
background-position: center center;
-webkit-background-size: cover;
background-size: cover;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
contain: layout style
}
.backdropImageFadeIn {
-webkit-animation: backdrop-fadein .8s ease-in normal both;
animation: backdrop-fadein .8s ease-in normal both
}
@-webkit-keyframes backdrop-fadein {
from {
opacity: 0
}
to {
opacity: 1
}
}
@keyframes backdrop-fadein {
from {
opacity: 0
}
to {
opacity: 1
}
}

View file

@ -0,0 +1,69 @@
define([], function() {
"use strict";
function supportsCssAnimation(allowPrefix) {
if (allowPrefix) {
if (!0 === _supportsCssAnimationWithPrefix || !1 === _supportsCssAnimationWithPrefix) return _supportsCssAnimationWithPrefix
} else if (!0 === _supportsCssAnimation || !1 === _supportsCssAnimation) return _supportsCssAnimation;
var animation = !1,
domPrefixes = ["Webkit", "O", "Moz"],
pfx = "",
elm = document.createElement("div");
if (void 0 !== elm.style.animationName && (animation = !0), !1 === animation && allowPrefix)
for (var i = 0; i < domPrefixes.length; i++)
if (void 0 !== elm.style[domPrefixes[i] + "AnimationName"]) {
pfx = domPrefixes[i], pfx + "Animation", "-" + pfx.toLowerCase() + "-", animation = !0;
break
} return allowPrefix ? _supportsCssAnimationWithPrefix = animation : _supportsCssAnimation = animation
}
var _supportsCssAnimation, _supportsCssAnimationWithPrefix, userAgent = navigator.userAgent,
matched = function(ua) {
ua = ua.toLowerCase();
var match = /(edge)[ \/]([\w.]+)/.exec(ua) || /(opera)[ \/]([\w.]+)/.exec(ua) || /(opr)[ \/]([\w.]+)/.exec(ua) || /(chrome)[ \/]([\w.]+)/.exec(ua) || /(safari)[ \/]([\w.]+)/.exec(ua) || /(firefox)[ \/]([\w.]+)/.exec(ua) || /(msie) ([\w.]+)/.exec(ua) || ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || [],
versionMatch = /(version)[ \/]([\w.]+)/.exec(ua),
platform_match = /(ipad)/.exec(ua) || /(iphone)/.exec(ua) || /(windows)/.exec(ua) || /(android)/.exec(ua) || [],
browser = match[1] || "";
"edge" === browser ? platform_match = [""] : -1 !== ua.indexOf("windows phone") || -1 !== ua.indexOf("iemobile") ? browser = "msie" : -1 !== ua.indexOf("like gecko") && -1 === ua.indexOf("webkit") && -1 === ua.indexOf("opera") && -1 === ua.indexOf("chrome") && -1 === ua.indexOf("safari") && (browser = "msie"), "opr" === browser && (browser = "opera");
var version;
versionMatch && versionMatch.length > 2 && (version = versionMatch[2]), version = version || match[2] || "0";
var versionMajor = parseInt(version.split(".")[0]);
return isNaN(versionMajor) && (versionMajor = 0), {
browser: browser,
version: version,
platform: platform_match[0] || "",
versionMajor: versionMajor
}
}(userAgent),
browser = {};
return matched.browser && (browser[matched.browser] = !0, browser.version = matched.version, browser.versionMajor = matched.versionMajor), matched.platform && (browser[matched.platform] = !0), browser.chrome || browser.msie || browser.edge || browser.opera || -1 === userAgent.toLowerCase().indexOf("webkit") || (browser.safari = !0), -1 !== userAgent.toLowerCase().indexOf("playstation 4") && (browser.ps4 = !0, browser.tv = !0),
function(userAgent) {
for (var terms = ["mobi", "ipad", "iphone", "ipod", "silk", "gt-p1000", "nexus 7", "kindle fire", "opera mini"], lower = userAgent.toLowerCase(), i = 0, length = terms.length; i < length; i++)
if (-1 !== lower.indexOf(terms[i])) return !0;
return !1
}(userAgent) && (browser.mobile = !0), browser.xboxOne = -1 !== userAgent.toLowerCase().indexOf("xbox"), browser.animate = "undefined" != typeof document && null != document.documentElement.animate, browser.tizen = -1 !== userAgent.toLowerCase().indexOf("tizen") || null != self.tizen, browser.web0s = -1 !== userAgent.toLowerCase().indexOf("Web0S".toLowerCase()), browser.edgeUwp = browser.edge && (-1 !== userAgent.toLowerCase().indexOf("msapphost") || -1 !== userAgent.toLowerCase().indexOf("webview")), browser.tizen || (browser.orsay = -1 !== userAgent.toLowerCase().indexOf("smarthub")), browser.edgeUwp && (browser.edge = !0), browser.tv = function() {
var userAgent = navigator.userAgent.toLowerCase();
return -1 !== userAgent.indexOf("tv") || (-1 !== userAgent.indexOf("samsungbrowser") || (-1 !== userAgent.indexOf("nintendo") || (-1 !== userAgent.indexOf("viera") || -1 !== userAgent.indexOf("webos"))))
}(), browser.operaTv = browser.tv && -1 !== userAgent.toLowerCase().indexOf("opr/"),
function(prop, value) {
if ("undefined" == typeof window) return !1;
if (value = 2 === arguments.length ? value : "inherit", "CSS" in window && "supports" in window.CSS) return window.CSS.supports(prop, value);
if ("supportsCSS" in window) return window.supportsCSS(prop, value);
try {
var camel = prop.replace(/-([a-z]|[0-9])/gi, function(all, letter) {
return (letter + "").toUpperCase()
}),
support = camel in el.style,
el = document.createElement("div");
return el.style.cssText = prop + ":" + value, support && "" !== el.style[camel]
} catch (err) {
return !1
}
}("display", "flex") || (browser.noFlex = !0), (browser.mobile || browser.tv) && (browser.slow = !0), "undefined" != typeof document && ("ontouchstart" in window || window.DocumentTouch && document instanceof DocumentTouch) && (browser.touch = !0), browser.keyboard = function(browser) {
return !!browser.touch || (!!browser.xboxOne || (!!browser.ps4 || (!!browser.edgeUwp || !!browser.tv)))
}(browser), browser.supportsCssAnimation = supportsCssAnimation, browser.osx = -1 !== userAgent.toLowerCase().indexOf("os x"), browser.iOS = browser.ipad || browser.iphone || browser.ipod, browser.iOS && (browser.iOSVersion = function() {
if (/iP(hone|od|ad)/.test(navigator.platform)) {
var v = navigator.appVersion.match(/OS (\d+)_(\d+)_?(\d+)?/);
return [parseInt(v[1], 10), parseInt(v[2], 10), parseInt(v[3] || 0, 10)]
}
}(), browser.iOSVersion = browser.iOSVersion[0] + browser.iOSVersion[1] / 10), browser.chromecast = browser.chrome && -1 !== userAgent.toLowerCase().indexOf("crkey"), browser
});

View file

@ -0,0 +1,384 @@
define(["browser"], function(browser) {
"use strict";
function canPlayH264(videoTestElement) {
return !(!videoTestElement.canPlayType || !videoTestElement.canPlayType('video/mp4; codecs="avc1.42E01E, mp4a.40.2"').replace(/no/, ""))
}
function canPlayH265(videoTestElement, options) {
if (browser.tizen || browser.orsay || browser.xboxOne || browser.web0s || options.supportsHevc) return !0;
var userAgent = navigator.userAgent.toLowerCase();
if (browser.chromecast) {
if (-1 !== userAgent.indexOf("aarch64")) return !0
}
return !!(browser.iOS && (browser.iOSVersion || 0) >= 11) || !(!videoTestElement.canPlayType || !videoTestElement.canPlayType('video/hevc; codecs="hevc, aac"').replace(/no/, ""))
}
function supportsTextTracks() {
return !(!browser.tizen && !browser.orsay) || (null == _supportsTextTracks && (_supportsTextTracks = null != document.createElement("video").textTracks), _supportsTextTracks)
}
function canPlayHls(src) {
return null == _canPlayHls && (_canPlayHls = canPlayNativeHls() || canPlayHlsWithMSE()), _canPlayHls
}
function canPlayNativeHls() {
if (browser.tizen || browser.orsay) return !0;
var media = document.createElement("video");
return !(!media.canPlayType("application/x-mpegURL").replace(/no/, "") && !media.canPlayType("application/vnd.apple.mpegURL").replace(/no/, ""))
}
function canPlayHlsWithMSE() {
return null != window.MediaSource
}
function canPlayAudioFormat(format) {
var typeString;
if ("flac" === format) {
if (browser.tizen || browser.orsay || browser.web0s) return !0;
if (browser.edgeUwp) return !0
} else if ("wma" === format) {
if (browser.tizen || browser.orsay) return !0;
if (browser.edgeUwp) return !0
} else {
if ("opus" === format) return typeString = 'audio/ogg; codecs="opus"', !!document.createElement("audio").canPlayType(typeString).replace(/no/, "");
if ("mp2" === format) return !1
}
if ("webma" === format) typeString = "audio/webm";
else if ("mp2" === format) typeString = "audio/mpeg";
else if ("ogg" === format || "oga" === format) {
if (browser.chrome) return !1;
typeString = "audio/" + format
} else typeString = "audio/" + format;
return !!document.createElement("audio").canPlayType(typeString).replace(/no/, "")
}
function testCanPlayMkv(videoTestElement) {
if (browser.tizen || browser.orsay || browser.web0s) return !0;
if (videoTestElement.canPlayType("video/x-matroska").replace(/no/, "") || videoTestElement.canPlayType("video/mkv").replace(/no/, "")) return !0;
var userAgent = navigator.userAgent.toLowerCase();
return browser.chrome ? !browser.operaTv && (-1 === userAgent.indexOf("vivaldi") && -1 === userAgent.indexOf("opera")) : !!browser.edgeUwp
}
function testCanPlayTs() {
return browser.tizen || browser.orsay || browser.web0s || browser.edgeUwp
}
function supportsMpeg2Video() {
return browser.orsay || browser.tizen || browser.edgeUwp || browser.web0s
}
function supportsVc1() {
return browser.orsay || browser.tizen || browser.edgeUwp || browser.web0s
}
function getDirectPlayProfileForVideoContainer(container, videoAudioCodecs, videoTestElement, options) {
var supported = !1,
profileContainer = container,
videoCodecs = [];
switch (container) {
case "asf":
supported = browser.tizen || browser.orsay || browser.edgeUwp, videoAudioCodecs = [];
break;
case "avi":
supported = browser.tizen || browser.orsay || browser.edgeUwp;
break;
case "mpg":
case "mpeg":
supported = browser.edgeUwp || browser.tizen || browser.orsay;
break;
case "flv":
supported = browser.tizen || browser.orsay;
break;
case "3gp":
case "mts":
case "trp":
case "vob":
case "vro":
supported = browser.tizen || browser.orsay;
break;
case "mov":
supported = browser.tizen || browser.orsay || browser.chrome || browser.edgeUwp, videoCodecs.push("h264");
break;
case "m2ts":
supported = browser.tizen || browser.orsay || browser.web0s || browser.edgeUwp, videoCodecs.push("h264"), supportsVc1() && videoCodecs.push("vc1"), supportsMpeg2Video() && videoCodecs.push("mpeg2video");
break;
case "wmv":
supported = browser.tizen || browser.orsay || browser.web0s || browser.edgeUwp, videoAudioCodecs = [];
break;
case "ts":
supported = testCanPlayTs(), videoCodecs.push("h264"), canPlayH265(videoTestElement, options) && (videoCodecs.push("h265"), videoCodecs.push("hevc")), supportsVc1() && videoCodecs.push("vc1"), supportsMpeg2Video() && videoCodecs.push("mpeg2video"), profileContainer = "ts,mpegts"
}
return supported ? {
Container: profileContainer,
Type: "Video",
VideoCodec: videoCodecs.join(","),
AudioCodec: videoAudioCodecs.join(",")
} : null
}
function getGlobalMaxVideoBitrate() {
var userAgent = navigator.userAgent.toLowerCase();
if (browser.chromecast) {
return -1 !== userAgent.indexOf("aarch64") ? null : self.screen && self.screen.width >= 3800 ? null : 3e7
}
var isTizenFhd = !1;
if (browser.tizen) try {
isTizenFhd = !webapis.productinfo.isUdPanelSupported(), console.log("isTizenFhd = " + isTizenFhd)
} catch (error) {
console.log("isUdPanelSupported() error code = " + error.code)
}
return browser.ps4 ? 8e6 : browser.xboxOne ? 12e6 : browser.edgeUwp ? null : browser.tizen && isTizenFhd ? 2e7 : null
}
function supportsAc3(videoTestElement) {
return !!(browser.edgeUwp || browser.tizen || browser.orsay || browser.web0s) || videoTestElement.canPlayType('audio/mp4; codecs="ac-3"').replace(/no/, "") && !browser.osx && !browser.iOS
}
function supportsEac3(videoTestElement) {
return !!(browser.tizen || browser.orsay || browser.web0s) || videoTestElement.canPlayType('audio/mp4; codecs="ec-3"').replace(/no/, "")
}
var _supportsTextTracks, _canPlayHls;
return function(options) {
options = options || {};
var physicalAudioChannels = options.audioChannels || (browser.tv || browser.ps4 || browser.xboxOne ? 6 : 2),
videoTestElement = document.createElement("video"),
canPlayVp8 = videoTestElement.canPlayType('video/webm; codecs="vp8"').replace(/no/, ""),
canPlayVp9 = videoTestElement.canPlayType('video/webm; codecs="vp9"').replace(/no/, ""),
webmAudioCodecs = ["vorbis"],
canPlayMkv = testCanPlayMkv(videoTestElement),
profile = {};
profile.MaxStreamingBitrate = 12e7, profile.MaxStaticBitrate = 1e8, profile.MusicStreamingTranscodingBitrate = Math.min(12e7, 192e3), profile.DirectPlayProfiles = [];
var videoAudioCodecs = [],
hlsVideoAudioCodecs = [],
supportsMp3VideoAudio = videoTestElement.canPlayType('video/mp4; codecs="avc1.640029, mp4a.69"').replace(/no/, "") || videoTestElement.canPlayType('video/mp4; codecs="avc1.640029, mp4a.6B"').replace(/no/, ""),
supportsMp2VideoAudio = browser.edgeUwp || browser.tizen || browser.orsay || browser.web0s,
maxVideoWidth = browser.xboxOne && self.screen ? self.screen.width : null;
options.maxVideoWidth && (maxVideoWidth = options.maxVideoWidth);
var canPlayAacVideoAudio = videoTestElement.canPlayType('video/mp4; codecs="avc1.640029, mp4a.40.2"').replace(/no/, "");
if (canPlayAacVideoAudio && browser.chromecast && physicalAudioChannels <= 2 && videoAudioCodecs.push("aac"), supportsAc3(videoTestElement)) {
videoAudioCodecs.push("ac3");
var eAc3 = supportsEac3(videoTestElement);
eAc3 && videoAudioCodecs.push("eac3");
(!browser.edge || !browser.touch || browser.edgeUwp) && (hlsVideoAudioCodecs.push("ac3"), eAc3 && hlsVideoAudioCodecs.push("eac3"))
}
canPlayAacVideoAudio && browser.chromecast && -1 === videoAudioCodecs.indexOf("aac") && videoAudioCodecs.push("aac"), supportsMp3VideoAudio && (videoAudioCodecs.push("mp3"), browser.ps4 || physicalAudioChannels <= 2 && hlsVideoAudioCodecs.push("mp3")), canPlayAacVideoAudio && (-1 === videoAudioCodecs.indexOf("aac") && videoAudioCodecs.push("aac"), hlsVideoAudioCodecs.push("aac")), supportsMp3VideoAudio && (browser.ps4 || -1 === hlsVideoAudioCodecs.indexOf("mp3") && hlsVideoAudioCodecs.push("mp3")), supportsMp2VideoAudio && videoAudioCodecs.push("mp2");
var supportsDts = browser.tizen || browser.orsay || browser.web0s || options.supportsDts;
if (self.tizen && self.tizen.systeminfo) {
var v = tizen.systeminfo.getCapability("http://tizen.org/feature/platform.version");
v && parseFloat(v) >= parseFloat("4.0") && (supportsDts = !1)
}
supportsDts && (videoAudioCodecs.push("dca"), videoAudioCodecs.push("dts")), (browser.tizen || browser.orsay || browser.web0s) && (videoAudioCodecs.push("pcm_s16le"), videoAudioCodecs.push("pcm_s24le")), options.supportsTrueHd && videoAudioCodecs.push("truehd"), (browser.tizen || browser.orsay) && videoAudioCodecs.push("aac_latm"), canPlayAudioFormat("opus") && (videoAudioCodecs.push("opus"), hlsVideoAudioCodecs.push("opus"), webmAudioCodecs.push("opus")), canPlayAudioFormat("flac") && videoAudioCodecs.push("flac"), videoAudioCodecs = videoAudioCodecs.filter(function(c) {
return -1 === (options.disableVideoAudioCodecs || []).indexOf(c)
}), hlsVideoAudioCodecs = hlsVideoAudioCodecs.filter(function(c) {
return -1 === (options.disableHlsVideoAudioCodecs || []).indexOf(c)
});
var mp4VideoCodecs = [],
hlsVideoCodecs = [];
canPlayH264(videoTestElement) && (mp4VideoCodecs.push("h264"), hlsVideoCodecs.push("h264")), canPlayH265(videoTestElement, options) && (mp4VideoCodecs.push("h265"), mp4VideoCodecs.push("hevc"), (browser.tizen || browser.web0s) && (hlsVideoCodecs.push("h265"), hlsVideoCodecs.push("hevc"))), supportsMpeg2Video() && mp4VideoCodecs.push("mpeg2video"), supportsVc1() && mp4VideoCodecs.push("vc1"), (browser.tizen || browser.orsay) && mp4VideoCodecs.push("msmpeg4v2"), canPlayVp8 && mp4VideoCodecs.push("vp8"), canPlayVp9 && mp4VideoCodecs.push("vp9"), (canPlayVp8 || browser.tizen || browser.orsay) && videoAudioCodecs.push("vorbis"), mp4VideoCodecs.length && profile.DirectPlayProfiles.push({
Container: "mp4,m4v",
Type: "Video",
VideoCodec: mp4VideoCodecs.join(","),
AudioCodec: videoAudioCodecs.join(",")
}), canPlayMkv && mp4VideoCodecs.length && profile.DirectPlayProfiles.push({
Container: "mkv",
Type: "Video",
VideoCodec: mp4VideoCodecs.join(","),
AudioCodec: videoAudioCodecs.join(",")
}), ["m2ts", "wmv", "ts", "asf", "avi", "mpg", "mpeg", "flv", "3gp", "mts", "trp", "vob", "vro", "mov"].map(function(container) {
return getDirectPlayProfileForVideoContainer(container, videoAudioCodecs, videoTestElement, options)
}).filter(function(i) {
return null != i
}).forEach(function(i) {
profile.DirectPlayProfiles.push(i)
}), ["opus", "mp3", "mp2", "aac", "flac", "alac", "webma", "wma", "wav", "ogg", "oga"].filter(canPlayAudioFormat).forEach(function(audioFormat) {
"mp2" === audioFormat ? profile.DirectPlayProfiles.push({
Container: "mp2,mp3",
Type: "Audio",
AudioCodec: audioFormat
}) : "mp3" === audioFormat ? profile.DirectPlayProfiles.push({
Container: audioFormat,
Type: "Audio",
AudioCodec: audioFormat
}) : profile.DirectPlayProfiles.push({
Container: "webma" === audioFormat ? "webma,webm" : audioFormat,
Type: "Audio"
}), "aac" !== audioFormat && "alac" !== audioFormat || profile.DirectPlayProfiles.push({
Container: "m4a",
AudioCodec: audioFormat,
Type: "Audio"
})
}), canPlayVp8 && profile.DirectPlayProfiles.push({
Container: "webm",
Type: "Video",
AudioCodec: webmAudioCodecs.join(","),
VideoCodec: "VP8"
}), canPlayVp9 && profile.DirectPlayProfiles.push({
Container: "webm",
Type: "Video",
AudioCodec: webmAudioCodecs.join(","),
VideoCodec: "VP9"
}), profile.TranscodingProfiles = [];
var hlsBreakOnNonKeyFrames = !(!(browser.iOS || browser.osx || browser.edge) && canPlayNativeHls());
canPlayHls() && !1 !== browser.enableHlsAudio && profile.TranscodingProfiles.push({
Container: !canPlayNativeHls() || browser.edge || browser.android ? "ts" : "aac",
Type: "Audio",
AudioCodec: "aac",
Context: "Streaming",
Protocol: "hls",
MaxAudioChannels: physicalAudioChannels.toString(),
MinSegments: browser.iOS || browser.osx ? "2" : "1",
BreakOnNonKeyFrames: hlsBreakOnNonKeyFrames
}), ["aac", "mp3", "opus", "wav"].filter(canPlayAudioFormat).forEach(function(audioFormat) {
profile.TranscodingProfiles.push({
Container: audioFormat,
Type: "Audio",
AudioCodec: audioFormat,
Context: "Streaming",
Protocol: "http",
MaxAudioChannels: physicalAudioChannels.toString()
})
}), ["opus", "mp3", "aac", "wav"].filter(canPlayAudioFormat).forEach(function(audioFormat) {
profile.TranscodingProfiles.push({
Container: audioFormat,
Type: "Audio",
AudioCodec: audioFormat,
Context: "Static",
Protocol: "http",
MaxAudioChannels: physicalAudioChannels.toString()
})
}), !canPlayMkv || browser.tizen || browser.orsay || !1 === options.enableMkvProgressive || profile.TranscodingProfiles.push({
Container: "mkv",
Type: "Video",
AudioCodec: videoAudioCodecs.join(","),
VideoCodec: mp4VideoCodecs.join(","),
Context: "Streaming",
MaxAudioChannels: physicalAudioChannels.toString(),
CopyTimestamps: !0
}), canPlayMkv && profile.TranscodingProfiles.push({
Container: "mkv",
Type: "Video",
AudioCodec: videoAudioCodecs.join(","),
VideoCodec: mp4VideoCodecs.join(","),
Context: "Static",
MaxAudioChannels: physicalAudioChannels.toString(),
CopyTimestamps: !0
}), canPlayHls() && !1 !== options.enableHls && profile.TranscodingProfiles.push({
Container: "ts",
Type: "Video",
AudioCodec: hlsVideoAudioCodecs.join(","),
VideoCodec: hlsVideoCodecs.join(","),
Context: "Streaming",
Protocol: "hls",
MaxAudioChannels: physicalAudioChannels.toString(),
MinSegments: browser.iOS || browser.osx ? "2" : "1",
BreakOnNonKeyFrames: hlsBreakOnNonKeyFrames
}), canPlayVp8 && profile.TranscodingProfiles.push({
Container: "webm",
Type: "Video",
AudioCodec: "vorbis",
VideoCodec: "vpx",
Context: "Streaming",
Protocol: "http",
MaxAudioChannels: physicalAudioChannels.toString()
}), profile.TranscodingProfiles.push({
Container: "mp4",
Type: "Video",
AudioCodec: videoAudioCodecs.join(","),
VideoCodec: "h264",
Context: "Static",
Protocol: "http"
}), profile.ContainerProfiles = [], profile.CodecProfiles = [];
var supportsSecondaryAudio = browser.tizen || browser.orsay || videoTestElement.audioTracks,
aacCodecProfileConditions = [];
videoTestElement.canPlayType('video/mp4; codecs="avc1.640029, mp4a.40.5"').replace(/no/, "") || aacCodecProfileConditions.push({
Condition: "NotEquals",
Property: "AudioProfile",
Value: "HE-AAC"
}), supportsSecondaryAudio || aacCodecProfileConditions.push({
Condition: "Equals",
Property: "IsSecondaryAudio",
Value: "false",
IsRequired: "false"
}), browser.chromecast && aacCodecProfileConditions.push({
Condition: "LessThanEqual",
Property: "AudioChannels",
Value: "2",
IsRequired: !0
}), aacCodecProfileConditions.length && profile.CodecProfiles.push({
Type: "VideoAudio",
Codec: "aac",
Conditions: aacCodecProfileConditions
}), supportsSecondaryAudio || profile.CodecProfiles.push({
Type: "VideoAudio",
Conditions: [{
Condition: "Equals",
Property: "IsSecondaryAudio",
Value: "false",
IsRequired: "false"
}]
});
var maxH264Level = browser.chromecast ? 42 : 51,
h264Profiles = "high|main|baseline|constrained baseline";
maxH264Level >= 51 && browser.chrome && !browser.osx && (h264Profiles += "|high 10"), profile.CodecProfiles.push({
Type: "Video",
Codec: "h264",
Conditions: [{
Condition: "NotEquals",
Property: "IsAnamorphic",
Value: "true",
IsRequired: !1
}, {
Condition: "EqualsAny",
Property: "VideoProfile",
Value: h264Profiles
}, {
Condition: "LessThanEqual",
Property: "VideoLevel",
Value: maxH264Level.toString()
}]
}), browser.edgeUwp || browser.tizen || browser.orsay || browser.web0s, maxVideoWidth && profile.CodecProfiles[profile.CodecProfiles.length - 1].Conditions.push({
Condition: "LessThanEqual",
Property: "Width",
Value: maxVideoWidth.toString(),
IsRequired: !1
});
var globalMaxVideoBitrate = (getGlobalMaxVideoBitrate() || "").toString(),
h264MaxVideoBitrate = globalMaxVideoBitrate;
h264MaxVideoBitrate && profile.CodecProfiles[profile.CodecProfiles.length - 1].Conditions.push({
Condition: "LessThanEqual",
Property: "VideoBitrate",
Value: h264MaxVideoBitrate,
IsRequired: !0
});
var globalVideoConditions = [];
return globalMaxVideoBitrate && globalVideoConditions.push({
Condition: "LessThanEqual",
Property: "VideoBitrate",
Value: globalMaxVideoBitrate
}), maxVideoWidth && globalVideoConditions.push({
Condition: "LessThanEqual",
Property: "Width",
Value: maxVideoWidth.toString(),
IsRequired: !1
}), globalVideoConditions.length && profile.CodecProfiles.push({
Type: "Video",
Conditions: globalVideoConditions
}), browser.chromecast && profile.CodecProfiles.push({
Type: "Audio",
Codec: "flac",
Conditions: [{
Condition: "LessThanEqual",
Property: "AudioSampleRate",
Value: "96000"
}]
}), profile.SubtitleProfiles = [], supportsTextTracks() && profile.SubtitleProfiles.push({
Format: "vtt",
Method: "External"
}), profile.ResponseProfiles = [], profile.ResponseProfiles.push({
Type: "Video",
Container: "m4v",
MimeType: "video/mp4"
}), profile
}
});

View file

@ -0,0 +1,885 @@
.card,
.card:focus {
font-weight: inherit !important
}
.card,
.cardBox,
.cardContent,
.textActionButton {
-webkit-tap-highlight-color: transparent;
outline: 0 !important
}
button::-moz-focus-inner {
padding: 0;
border: 0
}
button {
-webkit-border-fit: border !important
}
.card {
border: 0;
font-size: inherit !important;
font-family: inherit !important;
text-transform: none;
background: 0 0 !important;
margin: 0;
padding: 0;
display: block;
color: inherit !important;
cursor: pointer;
contain: layout style;
-webkit-flex-shrink: 0;
flex-shrink: 0
}
.cardContent-button,
.textActionButton {
cursor: pointer;
vertical-align: middle;
font-family: inherit
}
.card-nofocustransform {
contain: layout style paint
}
.itemsContainer {
display: -webkit-box;
display: -webkit-flex;
display: flex
}
.vertical-list,
.vertical-wrap {
display: -webkit-box;
display: -webkit-flex;
-webkit-box-direction: normal
}
.vertical-list {
display: flex;
-webkit-box-orient: vertical;
-webkit-flex-direction: column;
flex-direction: column;
-webkit-flex-wrap: nowrap;
flex-wrap: nowrap
}
.vertical-wrap {
display: flex;
-webkit-box-orient: horizontal;
-webkit-flex-direction: row;
flex-direction: row;
-webkit-flex-wrap: wrap;
flex-wrap: wrap
}
.cardImageContainer,
.mediaSourceIndicator {
display: -webkit-box;
-webkit-box-align: center
}
.vertical-wrap.centered {
-webkit-box-pack: center;
-webkit-justify-content: center;
justify-content: center
}
.cardScalable {
position: relative;
contain: layout style
}
.cardPadder-backdrop,
.cardPadder-mixedBackdrop,
.cardPadder-overflowBackdrop,
.cardPadder-overflowSmallBackdrop,
.cardPadder-smallBackdrop {
padding-bottom: 56.25%;
contain: strict
}
.cardPadder-mixedSquare,
.cardPadder-overflowSquare,
.cardPadder-square,
.overflowSquareCard-textCardPadder {
padding-bottom: 100%;
contain: strict
}
.cardPadder-mixedPortrait,
.cardPadder-overflowPortrait,
.cardPadder-portrait,
.overflowPortraitCard-textCardPadder {
padding-bottom: 150%;
contain: strict
}
.cardPadder-banner {
padding-bottom: 18.5%;
contain: strict
}
.cardBox {
padding: 0 !important;
margin: .42em;
-webkit-transition: none;
-o-transition: none;
transition: none;
border: 0 solid transparent;
contain: layout style
}
@media (min-width:50em) {
.cardBox {
margin: .9em
}
}
.cardBox-withfocuscontent-large {
margin: .4em
}
.card-focuscontent-large {
border: .5em solid transparent
}
.cardBox-focustransform {
will-change: transform;
-webkit-transition: -webkit-transform .2s ease-out;
-o-transition: transform .2s ease-out;
transition: transform .2s ease-out
}
.card:focus>.cardBox-focustransform {
-webkit-transform: scale(1.18, 1.18);
transform: scale(1.18, 1.18)
}
.cardBox-bottompadded {
margin-bottom: 1.8em !important
}
@media (max-width:50em) {
.cardBox-bottompadded {
margin-bottom: 1.2em !important
}
}
.card:focus {
position: relative !important;
z-index: 10 !important
}
.btnCardOptions {
position: absolute;
bottom: .25em;
right: 0;
margin: 0 !important;
z-index: 1
}
.mediaSourceIndicator {
display: -webkit-flex;
display: flex;
position: absolute;
-webkit-align-items: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
justify-content: center;
top: .3em;
left: .3em;
text-align: center;
vertical-align: middle;
width: 1.6em;
height: 1.6em;
-webkit-border-radius: 50%;
border-radius: 50%;
color: #fff;
background: #38c
}
.cardText,
.innerCardFooter {
overflow: hidden;
text-align: left
}
.cardImageContainer {
-webkit-background-size: contain;
background-size: contain;
background-repeat: no-repeat;
background-position: center center;
-webkit-align-items: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
justify-content: center;
position: relative;
-webkit-background-clip: content-box !important;
background-clip: content-box !important;
color: inherit;
height: 100%;
contain: strict
}
.cardContent,
.cardImage {
position: absolute;
right: 0;
top: 0;
left: 0;
bottom: 0
}
.chapterCardImageContainer {
background-color: #000;
-webkit-border-radius: 0;
border-radius: 0
}
.textCardImageContainer {
background-color: #333
}
.cardContent {
overflow: hidden;
display: block;
margin: 0 !important;
height: 100%;
contain: strict
}
.cardContent-button {
border: 0 !important;
padding: 0 !important;
color: inherit;
width: 100%;
font-size: inherit
}
.cardContent-button:not(.defaultCardBackground) {
background-color: transparent
}
.visualCardBox .cardContent {
-webkit-border-bottom-left-radius: 0;
border-bottom-left-radius: 0;
-webkit-border-bottom-right-radius: 0;
border-bottom-right-radius: 0
}
.cardContent-shadow {
-webkit-box-shadow: 0 .0725em .29em 0 rgba(0, 0, 0, .37);
box-shadow: 0 .0725em .29em 0 rgba(0, 0, 0, .37)
}
.cardImageContainer {
display: -webkit-box;
display: -webkit-flex;
display: flex
}
.cardImage {
-webkit-background-size: contain;
background-size: contain;
background-repeat: no-repeat;
background-position: center bottom
}
.cardImage-img {
max-height: 100%;
max-width: 100%;
min-height: 70%;
min-width: 70%;
margin: auto
}
.coveredImage-img {
width: 100%;
height: 100%
}
.coveredImage-noscale-img {
max-height: none;
max-width: none
}
.coveredImage {
-webkit-background-size: 100% 100%;
background-size: 100% 100%;
background-position: center center
}
.coveredImage-noScale {
-webkit-background-size: cover;
background-size: cover
}
.cardFooter {
padding: .3em .3em .5em;
position: relative
}
.visualCardBox {
-webkit-box-shadow: 0 .0725em .29em 0 rgba(0, 0, 0, .37);
box-shadow: 0 .0725em .29em 0 rgba(0, 0, 0, .37);
-webkit-border-radius: .145em;
border-radius: .145em
}
.innerCardFooter {
background: rgba(0, 0, 0, .7);
position: absolute;
bottom: 0;
left: 0;
z-index: 1;
max-width: 100%;
color: #fff
}
.innerCardFooterClear {
background-color: transparent
}
.fullInnerCardFooter {
right: 0
}
.cardText {
padding: .06em .5em;
white-space: nowrap;
-o-text-overflow: ellipsis;
text-overflow: ellipsis
}
.cardDefaultText,
.cardTextCentered {
text-align: center
}
.cardText-secondary {
font-size: 86%
}
.cardText-first {
padding-top: .24em
}
.innerCardFooter>.cardText {
padding: .3em .5em
}
.cardFooter-withlogo {
padding-left: 4em;
position: relative
}
.cardFooterLogo {
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 4.5em;
-webkit-background-size: 70% auto;
background-size: 70% auto;
background-repeat: no-repeat;
background-position: center center
}
.cardText-rightmargin {
margin-right: 2em
}
.cardDefaultText {
white-space: normal
}
.textActionButton {
background: 0 0;
border: 0 !important;
padding: 0 !important;
color: inherit;
font-size: inherit
}
.textActionButton:hover {
text-decoration: underline
}
.cardImageIcon {
font-size: 5em;
color: inherit
}
.cardImageIcon-small {
font-size: 3em;
margin-bottom: .1em
}
.cardIndicators {
right: .225em;
top: .225em;
position: absolute;
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
contain: layout style
}
.cardProgramAttributeIndicators {
top: 0;
left: 0;
position: absolute;
display: -webkit-box;
display: -webkit-flex;
display: flex;
text-transform: uppercase;
font-size: 92%
}
.programAttributeIndicator {
padding: .18em .5em;
color: #fff;
font-weight: 500
}
.cardOverlayButton {
color: rgba(255, 255, 255, .76) !important;
margin: 0;
z-index: 1;
padding: .75em;
font-size: 88%
}
.cardOverlayButton-br {
position: absolute;
bottom: 0;
right: 0
}
.cardOverlayButtonIcon {
background-color: rgba(0, 0, 0, .7) !important;
-webkit-border-radius: 100em;
border-radius: 100em;
width: 1.5em !important;
height: 1.5em !important;
-webkit-box-pack: center;
-webkit-justify-content: center;
justify-content: center;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
display: -webkit-box;
display: -webkit-flex;
display: flex;
font-size: 1.66956521739130434em !important
}
.cardOverlayButton-centered {
bottom: initial;
right: initial;
position: static;
position: absolute;
display: -webkit-box;
display: -webkit-flex;
display: flex;
font-size: 112%;
margin: -1.3em 0 0 -1.3em;
width: 2.6em;
height: 2.6em;
top: 50%;
left: 50%;
background-color: rgba(0, 0, 0, .5) !important;
border: .06em solid rgba(255, 255, 255, .6);
padding: .38em !important;
color: rgba(255, 255, 255, .76);
-webkit-transition: -webkit-transform .2s ease-out;
-o-transition: transform .2s ease-out;
transition: transform .2s ease-out
}
.cardOverlayButton-centered:hover {
-webkit-transform: scale(1.2, 1.2);
transform: scale(1.2, 1.2)
}
.backdropCard,
.bannerCard {
width: 100%
}
.smallBackdropCard,
.squareCard {
width: 50%
}
.portraitCard {
width: 33.333333333333333333333333333333%
}
.mixedPortraitCard {
width: 12em
}
.mixedSquareCard {
width: 18em
}
.mixedBackdropCard {
width: 32em
}
@media (min-width:25em) {
.backdropCard {
width: 50%
}
}
@media (min-width:31.25em) {
.portraitCard,
.smallBackdropCard,
.squareCard {
width: 33.333333333333333333333333333333%
}
}
@media (min-width:43.75em) {
.portraitCard,
.squareCard {
width: 25%
}
}
@media (min-width:48.125em) {
.backdropCard {
width: 33.333333333333333333333333333333%
}
}
@media (min-width:50em) {
.bannerCard {
width: 50%
}
.portraitCard,
.squareCard {
width: 20%
}
.smallBackdropCard {
width: 25%
}
}
@media (min-width:62.5em) {
.smallBackdropCard {
width: 20%
}
}
@media (min-width:75em) {
.backdropCard {
width: 25%
}
.portraitCard,
.squareCard {
width: 16.666666666666666666666666666667%
}
.bannerCard {
width: 33.333333333333333333333333333333%
}
.smallBackdropCard {
width: 16.666666666666666666666666666667%
}
}
@media (min-width:87.5em) {
.portraitCard,
.smallBackdropCard,
.squareCard {
width: 14.285714285714285714285714285714%
}
}
@media (min-width:100em) {
.smallBackdropCard {
width: 12.5%
}
.backdropCard {
width: 20%
}
.portraitCard,
.squareCard {
width: 12.5%
}
}
@media (min-width:120em) {
.portraitCard,
.squareCard {
width: 11.111111111111111111111111111111%
}
}
@media (min-width:131.25em) {
.bannerCard {
width: 25%
}
.portraitCard,
.squareCard {
width: 10%
}
}
@media (min-width:156.25em) {
.backdropCard {
width: 16.666666666666666666666666666667%
}
}
.itemsContainer-tv>.backdropCard {
width: 25%
}
.itemsContainer-tv>.portraitCard,
.itemsContainer-tv>.squareCard {
width: 16.666666666666666666666666666667%
}
.overflowBackdropCard,
.overflowSmallBackdropCard {
width: 72vw
}
.overflowPortraitCard,
.overflowSquareCard {
width: 40vw
}
@media (min-width:25em) {
.overflowPortraitCard {
width: 31.2vw
}
}
@media (min-width:35em) {
.overflowSquareCard {
width: 31.2vw
}
.overflowBackdropCard {
width: 45.5vw
}
.overflowSmallBackdropCard {
width: 30vw
}
}
@media (min-width:43.75em) {
.overflowPortraitCard,
.overflowSquareCard {
width: 23.3vw
}
}
@media (min-width:48.125em) {
.overflowBackdropCard,
.overflowSmallBackdropCard {
width: 30vw
}
}
@media (orientation:landscape) {
.overflowBackdropCard,
.overflowSmallBackdropCard {
width: 30vw
}
.overflowPortraitCard,
.overflowSquareCard {
width: 23.3vw
}
}
@media (orientation:landscape) and (min-width:48.125em) {
.overflowBackdropCard,
.overflowSmallBackdropCard {
width: 23.3vw
}
}
@media (orientation:landscape) and (min-width:50em) {
.overflowSmallBackdropCard {
width: 15.5vw
}
}
@media (min-width:50em) {
.overflowPortraitCard,
.overflowSquareCard {
width: 18.4vw
}
}
@media (min-width:75em) {
.overflowBackdropCard,
.overflowSmallBackdropCard {
width: 23.3vw
}
.overflowPortraitCard,
.overflowSquareCard {
width: 15.5vw
}
}
@media (min-width:87.5em) {
.overflowPortraitCard,
.overflowSquareCard {
width: 13.3vw
}
}
@media (min-width:100em) {
.overflowBackdropCard,
.overflowSmallBackdropCard {
width: 18.7vw
}
.overflowPortraitCard,
.overflowSquareCard {
width: 11.6vw
}
}
@media (min-width:120em) {
.overflowPortraitCard,
.overflowSquareCard {
width: 10.3vw
}
}
@media (min-width:131.25em) {
.overflowPortraitCard,
.overflowSquareCard {
width: 9.3vw
}
}
@media (min-width:156.25em) {
.overflowBackdropCard,
.overflowSmallBackdropCard {
width: 15.6vw
}
}
.itemsContainer-tv>.overflowBackdropCard {
width: 23.5vw
}
.overflowBackdropCard-textCard {
width: 15.5vw !important
}
.overflowBackdropCard-textCardPadder {
padding-bottom: 87.75%
}
.itemsContainer-tv>.overflowPortraitCard,
.itemsContainer-tv>.overflowSquareCard {
width: 15.6vw
}
.itemsContainer-tv>.overflowSmallBackdropCard {
width: 18.8vw
}
.cardOverlayContainer {
background: -webkit-radial-gradient(50% 50%, farthest-corner, rgba(30, 30, 30, .5) 50%, #2c2c2c 100%);
background: -o-radial-gradient(50% 50%, farthest-corner, rgba(30, 30, 30, .5) 50%, #2c2c2c 100%);
background: radial-gradient(farthest-corner at 50% 50%, rgba(30, 30, 30, .5) 50%, #2c2c2c 100%);
opacity: 0;
-webkit-transition: opacity .2s;
-o-transition: opacity .2s;
transition: opacity .2s;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none
}
.card-hoverable :hover .cardOverlayContainer {
opacity: 1
}
.cardOverlayButton-hover {
opacity: 0;
-webkit-transition: opacity .2s;
-o-transition: opacity .2s;
transition: opacity .2s;
background: 0 0;
color: #fff !important;
padding: .5em
}
.cardOverlayButtonIcon-hover {
background: 0 0 !important
}
.card-hoverable:hover .cardOverlayButton-hover {
opacity: 1
}
.cardOverlayFab-primary {
font-size: 130%;
padding: 0;
width: 3em;
height: 3em;
margin-top: -1.5em;
margin-left: -1.5em;
position: absolute;
top: 50%;
left: 50%
}
.cardOverlayFab-primary i {
border: .07em solid rgba(255, 255, 255, .9);
color: #fff
}

View file

@ -0,0 +1,515 @@
define(["datetime", "imageLoader", "connectionManager", "itemHelper", "focusManager", "indicators", "globalize", "layoutManager", "apphost", "dom", "browser", "playbackManager", "itemShortcuts", "css!./card", "paper-icon-button-light", "programStyles"], function(datetime, imageLoader, connectionManager, itemHelper, focusManager, indicators, globalize, layoutManager, appHost, dom, browser, playbackManager, itemShortcuts) {
"use strict";
function getCardsHtml(items, options) {
return 1 === arguments.length && (options = arguments[0], items = options.items), buildCardsHtmlInternal(items, options)
}
function getPostersPerRow(shape, screenWidth, isOrientationLandscape) {
switch (shape) {
case "portrait":
return layoutManager.tv ? 5.9999999988 : screenWidth >= 2200 ? 10 : screenWidth >= 1920 ? 9.000000000009 : screenWidth >= 1600 ? 8 : screenWidth >= 1400 ? 7.0000000000021 : screenWidth >= 1200 ? 5.9999999988 : screenWidth >= 800 ? 5 : screenWidth >= 700 ? 4 : 3.0000000003;
case "square":
return layoutManager.tv ? 5.9999999988 : screenWidth >= 2200 ? 10 : screenWidth >= 1920 ? 9.000000000009 : screenWidth >= 1600 ? 8 : screenWidth >= 1400 ? 7.0000000000021 : screenWidth >= 1200 ? 5.9999999988 : screenWidth >= 800 ? 5 : screenWidth >= 700 ? 4 : screenWidth >= 500 ? 3.0000000003 : 2;
case "banner":
return screenWidth >= 2200 ? 4 : screenWidth >= 1200 ? 3.0000000003 : screenWidth >= 800 ? 2 : 1;
case "backdrop":
return layoutManager.tv ? 4 : screenWidth >= 2500 ? 6 : screenWidth >= 1600 ? 5 : screenWidth >= 1200 ? 4 : screenWidth >= 770 ? 3 : screenWidth >= 420 ? 2 : 1;
case "smallBackdrop":
return screenWidth >= 1600 ? 8 : screenWidth >= 1400 ? 7.000000000007001 : screenWidth >= 1200 ? 6 : screenWidth >= 1e3 ? 5 : screenWidth >= 800 ? 4 : screenWidth >= 500 ? 3.0000000003 : 2;
case "overflowSmallBackdrop":
return layoutManager.tv ? 100 / 18.9 : isOrientationLandscape ? screenWidth >= 800 ? 100 / 15.5 : 100 / 23.3 : screenWidth >= 540 ? 100 / 30 : 100 / 72;
case "overflowPortrait":
return layoutManager.tv ? 100 / 15.5 : isOrientationLandscape ? screenWidth >= 1700 ? 100 / 11.6 : 100 / 15.5 : screenWidth >= 1400 ? 100 / 15 : screenWidth >= 1200 ? 100 / 18 : screenWidth >= 760 ? 100 / 23 : screenWidth >= 400 ? 100 / 31.5 : 100 / 42;
case "overflowSquare":
return layoutManager.tv ? 100 / 15.5 : isOrientationLandscape ? screenWidth >= 1700 ? 100 / 11.6 : 100 / 15.5 : screenWidth >= 1400 ? 100 / 15 : screenWidth >= 1200 ? 100 / 18 : screenWidth >= 760 ? 100 / 23 : screenWidth >= 540 ? 100 / 31.5 : 100 / 42;
case "overflowBackdrop":
return layoutManager.tv ? 100 / 23.3 : isOrientationLandscape ? screenWidth >= 1700 ? 100 / 18.5 : 100 / 23.3 : screenWidth >= 1800 ? 100 / 23.5 : screenWidth >= 1400 ? 100 / 30 : screenWidth >= 760 ? 2.5 : screenWidth >= 640 ? 100 / 56 : 100 / 72;
default:
return 4
}
}
function isResizable(windowWidth) {
var screen = window.screen;
if (screen) {
if (screen.availWidth - windowWidth > 20) return !0
}
return !1
}
function getImageWidth(shape, screenWidth, isOrientationLandscape) {
var imagesPerRow = getPostersPerRow(shape, screenWidth, isOrientationLandscape),
shapeWidth = screenWidth / imagesPerRow;
return Math.round(shapeWidth)
}
function setCardData(items, options) {
options.shape = options.shape || "auto";
var primaryImageAspectRatio = imageLoader.getPrimaryImageAspectRatio(items);
if ("auto" === options.shape || "autohome" === options.shape || "autooverflow" === options.shape || "autoVertical" === options.shape) {
var requestedShape = options.shape;
options.shape = null, primaryImageAspectRatio && (primaryImageAspectRatio >= 3 ? (options.shape = "banner", options.coverImage = !0) : options.shape = primaryImageAspectRatio >= 1.33 ? "autooverflow" === requestedShape ? "overflowBackdrop" : "backdrop" : primaryImageAspectRatio > .71 ? "autooverflow" === requestedShape ? "overflowSquare" : "square" : "autooverflow" === requestedShape ? "overflowPortrait" : "portrait"), options.shape || (options.shape = options.defaultShape || ("autooverflow" === requestedShape ? "overflowSquare" : "square"))
}
if ("auto" === options.preferThumb && (options.preferThumb = "backdrop" === options.shape || "overflowBackdrop" === options.shape), options.uiAspect = getDesiredAspect(options.shape), options.primaryImageAspectRatio = primaryImageAspectRatio, !options.width && options.widths && (options.width = options.widths[options.shape]), options.rows && "number" != typeof options.rows && (options.rows = options.rows[options.shape]), !options.width) {
var screenWidth = dom.getWindowSize().innerWidth,
screenHeight = dom.getWindowSize().innerHeight;
if (isResizable(screenWidth)) {
screenWidth = 100 * Math.floor(screenWidth / 100)
}
options.width = getImageWidth(options.shape, screenWidth, screenWidth > 1.3 * screenHeight)
}
}
function buildCardsHtmlInternal(items, options) {
var isVertical;
"autoVertical" === options.shape && (isVertical = !0), setCardData(items, options);
var currentIndexValue, hasOpenRow, hasOpenSection, apiClient, lastServerId, i, length, html = "",
itemsInRow = 0,
sectionTitleTagName = options.sectionTitleTagName || "div";
for (i = 0, length = items.length; i < length; i++) {
var item = items[i],
serverId = item.ServerId || options.serverId;
if (serverId !== lastServerId && (lastServerId = serverId, apiClient = connectionManager.getApiClient(lastServerId)), options.indexBy) {
var newIndexValue = "";
if ("PremiereDate" === options.indexBy) {
if (item.PremiereDate) try {
newIndexValue = datetime.toLocaleDateString(datetime.parseISO8601Date(item.PremiereDate), {
weekday: "long",
month: "long",
day: "numeric"
})
} catch (err) {}
} else "ProductionYear" === options.indexBy ? newIndexValue = item.ProductionYear : "CommunityRating" === options.indexBy && (newIndexValue = item.CommunityRating ? Math.floor(item.CommunityRating) + (item.CommunityRating % 1 >= .5 ? .5 : 0) + "+" : null);
newIndexValue !== currentIndexValue && (hasOpenRow && (html += "</div>", hasOpenRow = !1, itemsInRow = 0), hasOpenSection && (html += "</div>", isVertical && (html += "</div>"), hasOpenSection = !1), html += isVertical ? '<div class="verticalSection">' : '<div class="horizontalSection">', html += "<" + sectionTitleTagName + ' class="sectionTitle">' + newIndexValue + "</" + sectionTitleTagName + ">", isVertical && (html += '<div class="itemsContainer vertical-wrap">'), currentIndexValue = newIndexValue, hasOpenSection = !0)
}
options.rows && 0 === itemsInRow && (hasOpenRow && (html += "</div>", hasOpenRow = !1), html += '<div class="cardColumn">', hasOpenRow = !0), html += buildCard(i, item, apiClient, options), itemsInRow++, options.rows && itemsInRow >= options.rows && (html += "</div>", hasOpenRow = !1, itemsInRow = 0)
}
hasOpenRow && (html += "</div>"), hasOpenSection && (html += "</div>", isVertical && (html += "</div>"));
var cardFooterHtml = "";
for (i = 0, length = options.lines || 0; i < length; i++) cardFooterHtml += 0 === i ? '<div class="cardText cardTextCentered cardText-first">&nbsp;</div>' : '<div class="cardText cardTextCentered cardText-secondary">&nbsp;</div>';
return html
}
function getDesiredAspect(shape) {
if (shape) {
if (shape = shape.toLowerCase(), -1 !== shape.indexOf("portrait")) return 2 / 3;
if (-1 !== shape.indexOf("backdrop")) return 16 / 9;
if (-1 !== shape.indexOf("square")) return 1;
if (-1 !== shape.indexOf("banner")) return 1e3 / 185
}
return null
}
function getCardImageUrl(item, apiClient, options, shape) {
item = item.ProgramInfo || item;
var width = options.width,
height = null,
primaryImageAspectRatio = item.PrimaryImageAspectRatio,
forceName = !1,
imgUrl = null,
coverImage = !1,
uiAspect = null;
return options.preferThumb && item.ImageTags && item.ImageTags.Thumb ? imgUrl = apiClient.getScaledImageUrl(item.Id, {
type: "Thumb",
maxWidth: width,
tag: item.ImageTags.Thumb
}) : (options.preferBanner || "banner" === shape) && item.ImageTags && item.ImageTags.Banner ? imgUrl = apiClient.getScaledImageUrl(item.Id, {
type: "Banner",
maxWidth: width,
tag: item.ImageTags.Banner
}) : options.preferDisc && item.ImageTags && item.ImageTags.Disc ? imgUrl = apiClient.getScaledImageUrl(item.Id, {
type: "Disc",
maxWidth: width,
tag: item.ImageTags.Disc
}) : options.preferLogo && item.ImageTags && item.ImageTags.Logo ? imgUrl = apiClient.getScaledImageUrl(item.Id, {
type: "Logo",
maxWidth: width,
tag: item.ImageTags.Logo
}) : options.preferLogo && item.ParentLogoImageTag && item.ParentLogoItemId ? imgUrl = apiClient.getScaledImageUrl(item.ParentLogoItemId, {
type: "Logo",
maxWidth: width,
tag: item.ParentLogoImageTag
}) : options.preferThumb && item.SeriesThumbImageTag && !1 !== options.inheritThumb ? imgUrl = apiClient.getScaledImageUrl(item.SeriesId, {
type: "Thumb",
maxWidth: width,
tag: item.SeriesThumbImageTag
}) : options.preferThumb && item.ParentThumbItemId && !1 !== options.inheritThumb && "Photo" !== item.MediaType ? imgUrl = apiClient.getScaledImageUrl(item.ParentThumbItemId, {
type: "Thumb",
maxWidth: width,
tag: item.ParentThumbImageTag
}) : options.preferThumb && item.BackdropImageTags && item.BackdropImageTags.length ? (imgUrl = apiClient.getScaledImageUrl(item.Id, {
type: "Backdrop",
maxWidth: width,
tag: item.BackdropImageTags[0]
}), forceName = !0) : options.preferThumb && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length && !1 !== options.inheritThumb && "Episode" === item.Type ? imgUrl = apiClient.getScaledImageUrl(item.ParentBackdropItemId, {
type: "Backdrop",
maxWidth: width,
tag: item.ParentBackdropImageTags[0]
}) : item.ImageTags && item.ImageTags.Primary ? (height = width && primaryImageAspectRatio ? Math.round(width / primaryImageAspectRatio) : null, imgUrl = apiClient.getScaledImageUrl(item.Id, {
type: "Primary",
maxHeight: height,
maxWidth: width,
tag: item.ImageTags.Primary
}), options.preferThumb && !1 !== options.showTitle && (forceName = !0), primaryImageAspectRatio && (uiAspect = getDesiredAspect(shape)) && (coverImage = Math.abs(primaryImageAspectRatio - uiAspect) / uiAspect <= .2)) : item.PrimaryImageTag ? (height = width && primaryImageAspectRatio ? Math.round(width / primaryImageAspectRatio) : null, imgUrl = apiClient.getScaledImageUrl(item.PrimaryImageItemId || item.Id || item.ItemId, {
type: "Primary",
maxHeight: height,
maxWidth: width,
tag: item.PrimaryImageTag
}), options.preferThumb && !1 !== options.showTitle && (forceName = !0), primaryImageAspectRatio && (uiAspect = getDesiredAspect(shape)) && (coverImage = Math.abs(primaryImageAspectRatio - uiAspect) / uiAspect <= .2)) : item.ParentPrimaryImageTag ? imgUrl = apiClient.getScaledImageUrl(item.ParentPrimaryImageItemId, {
type: "Primary",
maxWidth: width,
tag: item.ParentPrimaryImageTag
}) : item.SeriesPrimaryImageTag ? imgUrl = apiClient.getScaledImageUrl(item.SeriesId, {
type: "Primary",
maxWidth: width,
tag: item.SeriesPrimaryImageTag
}) : item.AlbumId && item.AlbumPrimaryImageTag ? (width = primaryImageAspectRatio ? Math.round(height * primaryImageAspectRatio) : null, imgUrl = apiClient.getScaledImageUrl(item.AlbumId, {
type: "Primary",
maxHeight: height,
maxWidth: width,
tag: item.AlbumPrimaryImageTag
}), primaryImageAspectRatio && (uiAspect = getDesiredAspect(shape)) && (coverImage = Math.abs(primaryImageAspectRatio - uiAspect) / uiAspect <= .2)) : "Season" === item.Type && item.ImageTags && item.ImageTags.Thumb ? imgUrl = apiClient.getScaledImageUrl(item.Id, {
type: "Thumb",
maxWidth: width,
tag: item.ImageTags.Thumb
}) : item.BackdropImageTags && item.BackdropImageTags.length ? imgUrl = apiClient.getScaledImageUrl(item.Id, {
type: "Backdrop",
maxWidth: width,
tag: item.BackdropImageTags[0]
}) : item.ImageTags && item.ImageTags.Thumb ? imgUrl = apiClient.getScaledImageUrl(item.Id, {
type: "Thumb",
maxWidth: width,
tag: item.ImageTags.Thumb
}) : item.SeriesThumbImageTag && !1 !== options.inheritThumb ? imgUrl = apiClient.getScaledImageUrl(item.SeriesId, {
type: "Thumb",
maxWidth: width,
tag: item.SeriesThumbImageTag
}) : item.ParentThumbItemId && !1 !== options.inheritThumb ? imgUrl = apiClient.getScaledImageUrl(item.ParentThumbItemId, {
type: "Thumb",
maxWidth: width,
tag: item.ParentThumbImageTag
}) : item.ParentBackdropImageTags && item.ParentBackdropImageTags.length && !1 !== options.inheritThumb && (imgUrl = apiClient.getScaledImageUrl(item.ParentBackdropItemId, {
type: "Backdrop",
maxWidth: width,
tag: item.ParentBackdropImageTags[0]
})), {
imgUrl: imgUrl,
forceName: forceName,
coverImage: coverImage
}
}
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min
}
function getDefaultColorIndex(str) {
if (str) {
for (var charIndex = Math.floor(str.length / 2), character = String(str.substr(charIndex, 1).charCodeAt()), sum = 0, i = 0; i < character.length; i++) sum += parseInt(character.charAt(i));
return String(sum).substr(-1) % numRandomColors + 1
}
return getRandomInt(1, numRandomColors)
}
function getCardTextLines(lines, cssClass, forceLines, isOuterFooter, cardLayout, addRightMargin, maxLines) {
var i, length, html = "",
valid = 0;
for (i = 0, length = lines.length; i < length; i++) {
var currentCssClass = cssClass,
text = lines[i];
if (valid > 0 && isOuterFooter ? currentCssClass += " cardText-secondary" : 0 === valid && isOuterFooter && (currentCssClass += " cardText-first"), addRightMargin && (currentCssClass += " cardText-rightmargin"), text && (html += "<div class='" + currentCssClass + "'>", html += text, html += "</div>", valid++, maxLines && valid >= maxLines)) break
}
if (forceLines)
for (length = maxLines || Math.min(lines.length, maxLines || lines.length); valid < length;) html += "<div class='" + cssClass + "'>&nbsp;</div>", valid++;
return html
}
function isUsingLiveTvNaming(item) {
return "Program" === item.Type || "Timer" === item.Type || "Recording" === item.Type
}
function getAirTimeText(item, showAirDateTime, showAirEndTime) {
var airTimeText = "";
if (item.StartDate) try {
var date = datetime.parseISO8601Date(item.StartDate);
showAirDateTime && (airTimeText += datetime.toLocaleDateString(date, {
weekday: "short",
month: "short",
day: "numeric"
}) + " "), airTimeText += datetime.getDisplayTime(date), item.EndDate && showAirEndTime && (date = datetime.parseISO8601Date(item.EndDate), airTimeText += " - " + datetime.getDisplayTime(date))
} catch (e) {
console.log("Error parsing date: " + item.StartDate)
}
return airTimeText
}
function getCardFooterText(item, apiClient, options, showTitle, forceName, overlayText, imgUrl, footerClass, progressHtml, logoUrl, isOuterFooter) {
var html = "";
logoUrl && (html += '<div class="lazy cardFooterLogo" data-src="' + logoUrl + '"></div>');
var showOtherText = isOuterFooter ? !overlayText : overlayText;
isOuterFooter && options.cardLayout && layoutManager.mobile && "none" !== options.cardFooterAside && (html += '<button is="paper-icon-button-light" class="itemAction btnCardOptions cardText-secondary" data-action="menu"><i class="md-icon">&#xE5D3;</i></button>');
var titleAdded, cssClass = options.centerText ? "cardText cardTextCentered" : "cardText",
lines = [],
parentTitleUnderneath = "MusicAlbum" === item.Type || "Audio" === item.Type || "MusicVideo" === item.Type;
if (showOtherText && (options.showParentTitle || options.showParentTitleOrTitle) && !parentTitleUnderneath)
if (isOuterFooter && "Episode" === item.Type && item.SeriesName) item.SeriesId ? lines.push(getTextActionButton({
Id: item.SeriesId,
ServerId: item.ServerId,
Name: item.SeriesName,
Type: "Series",
IsFolder: !0
})) : lines.push(item.SeriesName);
else if (isUsingLiveTvNaming(item)) lines.push(item.Name), item.EpisodeTitle || (titleAdded = !0);
else {
var parentTitle = item.SeriesName || item.Series || item.Album || item.AlbumArtist || item.GameSystem || "";
(parentTitle || showTitle) && lines.push(parentTitle)
}
var showMediaTitle = showTitle && !titleAdded || options.showParentTitleOrTitle && !lines.length;
if (showMediaTitle || titleAdded || !showTitle && !forceName || (showMediaTitle = !0), showMediaTitle) {
var name = "auto" !== options.showTitle || item.IsFolder || "Photo" !== item.MediaType ? itemHelper.getDisplayName(item, {
includeParentInfo: options.includeParentInfoInTitle
}) : "";
lines.push(name)
}
if (showOtherText) {
if (options.showParentTitle && parentTitleUnderneath && (isOuterFooter && item.AlbumArtists && item.AlbumArtists.length ? (item.AlbumArtists[0].Type = "MusicArtist", item.AlbumArtists[0].IsFolder = !0, lines.push(getTextActionButton(item.AlbumArtists[0], null, item.ServerId))) : lines.push(isUsingLiveTvNaming(item) ? item.Name : item.SeriesName || item.Series || item.Album || item.AlbumArtist || item.GameSystem || "")), options.showItemCounts) {
var itemCountHtml = getItemCountsHtml(options, item);
lines.push(itemCountHtml)
}
if (options.textLines)
for (var additionalLines = options.textLines(item), i = 0, length = additionalLines.length; i < length; i++) lines.push(additionalLines[i]);
if (options.showSongCount) {
var songLine = "";
item.SongCount && (songLine = 1 === item.SongCount ? globalize.translate("sharedcomponents#ValueOneSong") : globalize.translate("sharedcomponents#ValueSongCount", item.SongCount)), lines.push(songLine)
}
if (options.showPremiereDate)
if (item.PremiereDate) try {
lines.push(getPremiereDateText(item))
} catch (err) {
lines.push("")
} else lines.push("");
(options.showYear || options.showSeriesYear) && ("Series" === item.Type ? "Continuing" === item.Status ? lines.push(globalize.translate("sharedcomponents#SeriesYearToPresent", item.ProductionYear || "")) : item.EndDate && item.ProductionYear ? lines.push(item.ProductionYear + " - " + datetime.parseISO8601Date(item.EndDate).getFullYear()) : lines.push(item.ProductionYear || "") : lines.push(item.ProductionYear || "")), options.showRuntime && (item.RunTimeTicks ? lines.push(datetime.getDisplayRunningTime(item.RunTimeTicks)) : lines.push("")), options.showAirTime && lines.push(getAirTimeText(item, options.showAirDateTime, options.showAirEndTime) || ""), options.showChannelName && (item.ChannelId ? lines.push(getTextActionButton({
Id: item.ChannelId,
ServerId: item.ServerId,
Name: item.ChannelName,
Type: "TvChannel",
MediaType: item.MediaType,
IsFolder: !1
}, item.ChannelName)) : lines.push(item.ChannelName || "&nbsp;")), options.showCurrentProgram && "TvChannel" === item.Type && (item.CurrentProgram ? lines.push(item.CurrentProgram.Name) : lines.push("")), options.showCurrentProgramTime && "TvChannel" === item.Type && (item.CurrentProgram ? lines.push(getAirTimeText(item.CurrentProgram, !1, !0) || "") : lines.push("")), options.showSeriesTimerTime && (item.RecordAnyTime ? lines.push(globalize.translate("sharedcomponents#Anytime")) : lines.push(datetime.getDisplayTime(item.StartDate))), options.showSeriesTimerChannel && (item.RecordAnyChannel ? lines.push(globalize.translate("sharedcomponents#AllChannels")) : lines.push(item.ChannelName || globalize.translate("sharedcomponents#OneChannel"))), options.showPersonRoleOrType && (item.Role ? lines.push("as " + item.Role) : item.Type ? lines.push(globalize.translate("sharedcomponents#" + item.Type)) : lines.push(""))
}(showTitle || !imgUrl) && forceName && overlayText && 1 === lines.length && (lines = []);
var addRightTextMargin = isOuterFooter && options.cardLayout && !options.centerText && "none" !== options.cardFooterAside && layoutManager.mobile;
return html += getCardTextLines(lines, cssClass, !options.overlayText, isOuterFooter, options.cardLayout, addRightTextMargin, options.lines), progressHtml && (html += progressHtml), html && (!isOuterFooter || logoUrl || options.cardLayout) && (html = '<div class="' + footerClass + '">' + html, html += "</div>"), html
}
function getTextActionButton(item, text, serverId) {
if (text || (text = itemHelper.getDisplayName(item)), layoutManager.tv) return text;
var html = "<button " + itemShortcuts.getShortcutAttributesHtml(item, serverId) + ' type="button" class="itemAction textActionButton" data-action="link">';
return html += text, html += "</button>"
}
function getItemCountsHtml(options, item) {
var childText, counts = [];
if ("Playlist" === item.Type) {
if (childText = "", item.RunTimeTicks) {
var minutes = item.RunTimeTicks / 6e8;
minutes = minutes || 1, childText += globalize.translate("sharedcomponents#ValueMinutes", Math.round(minutes))
} else childText += globalize.translate("sharedcomponents#ValueMinutes", 0);
counts.push(childText)
} else "Genre" === item.Type || "Studio" === item.Type ? (item.MovieCount && (childText = 1 === item.MovieCount ? globalize.translate("sharedcomponents#ValueOneMovie") : globalize.translate("sharedcomponents#ValueMovieCount", item.MovieCount), counts.push(childText)), item.SeriesCount && (childText = 1 === item.SeriesCount ? globalize.translate("sharedcomponents#ValueOneSeries") : globalize.translate("sharedcomponents#ValueSeriesCount", item.SeriesCount), counts.push(childText)), item.EpisodeCount && (childText = 1 === item.EpisodeCount ? globalize.translate("sharedcomponents#ValueOneEpisode") : globalize.translate("sharedcomponents#ValueEpisodeCount", item.EpisodeCount), counts.push(childText)), item.GameCount && (childText = 1 === item.GameCount ? globalize.translate("sharedcomponents#ValueOneGame") : globalize.translate("sharedcomponents#ValueGameCount", item.GameCount), counts.push(childText))) : "GameGenre" === item.Type ? item.GameCount && (childText = 1 === item.GameCount ? globalize.translate("sharedcomponents#ValueOneGame") : globalize.translate("sharedcomponents#ValueGameCount", item.GameCount), counts.push(childText)) : "MusicGenre" === item.Type || "MusicArtist" === options.context ? (item.AlbumCount && (childText = 1 === item.AlbumCount ? globalize.translate("sharedcomponents#ValueOneAlbum") : globalize.translate("sharedcomponents#ValueAlbumCount", item.AlbumCount), counts.push(childText)), item.SongCount && (childText = 1 === item.SongCount ? globalize.translate("sharedcomponents#ValueOneSong") : globalize.translate("sharedcomponents#ValueSongCount", item.SongCount), counts.push(childText)), item.MusicVideoCount && (childText = 1 === item.MusicVideoCount ? globalize.translate("sharedcomponents#ValueOneMusicVideo") : globalize.translate("sharedcomponents#ValueMusicVideoCount", item.MusicVideoCount), counts.push(childText))) : "Series" === item.Type && (childText = 1 === item.RecursiveItemCount ? globalize.translate("sharedcomponents#ValueOneEpisode") : globalize.translate("sharedcomponents#ValueEpisodeCount", item.RecursiveItemCount), counts.push(childText));
return counts.join(", ")
}
function requireRefreshIndicator() {
refreshIndicatorLoaded || (refreshIndicatorLoaded = !0, require(["emby-itemrefreshindicator"]))
}
function getDefaultBackgroundClass(str) {
return "defaultCardBackground defaultCardBackground" + getDefaultColorIndex(str)
}
function buildCard(index, item, apiClient, options) {
var action = options.action || "link";
"play" === action && item.IsFolder ? action = "link" : "Photo" === item.MediaType && (action = "play");
var shape = options.shape;
if ("mixed" === shape) {
shape = null;
var primaryImageAspectRatio = item.PrimaryImageAspectRatio;
primaryImageAspectRatio && (shape = primaryImageAspectRatio >= 1.33 ? "mixedBackdrop" : primaryImageAspectRatio > .71 ? "mixedSquare" : "mixedPortrait"), shape = shape || "mixedSquare"
}
var className = "card";
shape && (className += " " + shape + "Card"), options.cardCssClass && (className += " " + options.cardCssClass), options.cardClass && (className += " " + options.cardClass), layoutManager.desktop && (className += " card-hoverable"), enableFocusTransfrom && layoutManager.tv || (className += " card-nofocustransform");
var imgInfo = getCardImageUrl(item, apiClient, options, shape),
imgUrl = imgInfo.imgUrl,
forceName = imgInfo.forceName,
showTitle = "auto" === options.showTitle || (options.showTitle || "PhotoAlbum" === item.Type || "Folder" === item.Type),
overlayText = options.overlayText;
forceName && !options.cardLayout && null == overlayText && (overlayText = !0);
var cardImageContainerClass = "cardImageContainer";
(options.coverImage || imgInfo.coverImage) && (cardImageContainerClass += " coveredImage", ("Photo" === item.MediaType || "PhotoAlbum" === item.Type || "Folder" === item.Type || item.ProgramInfo || "Program" === item.Type || "Recording" === item.Type) && (cardImageContainerClass += " coveredImage-noScale")), imgUrl || (cardImageContainerClass += " " + getDefaultBackgroundClass(item.Name));
var cardBoxClass = options.cardLayout ? "cardBox visualCardBox" : "cardBox";
layoutManager.tv && (cardBoxClass += enableFocusTransfrom ? " cardBox-focustransform cardBox-withfocuscontent" : " cardBox-withfocuscontent-large", options.cardLayout && (cardBoxClass += " card-focuscontent", enableFocusTransfrom || (cardBoxClass += " card-focuscontent-large")));
var footerCssClass, logoUrl, progressHtml = indicators.getProgressBarHtml(item),
innerCardFooter = "",
footerOverlayed = !1;
options.showChannelLogo && item.ChannelPrimaryImageTag ? logoUrl = apiClient.getScaledImageUrl(item.ChannelId, {
type: "Primary",
height: 40,
tag: item.ChannelPrimaryImageTag
}) : options.showLogo && item.ParentLogoImageTag && (logoUrl = apiClient.getScaledImageUrl(item.ParentLogoItemId, {
type: "Logo",
height: 40,
tag: item.ParentLogoImageTag
})), overlayText ? (logoUrl = null, footerCssClass = progressHtml ? "innerCardFooter fullInnerCardFooter" : "innerCardFooter", innerCardFooter += getCardFooterText(item, apiClient, options, showTitle, forceName, overlayText, imgUrl, footerCssClass, progressHtml, logoUrl, !1), footerOverlayed = !0) : progressHtml && (innerCardFooter += '<div class="innerCardFooter fullInnerCardFooter innerCardFooterClear">', innerCardFooter += progressHtml, innerCardFooter += "</div>", progressHtml = "");
var mediaSourceCount = item.MediaSourceCount || 1;
mediaSourceCount > 1 && (innerCardFooter += '<div class="mediaSourceIndicator">' + mediaSourceCount + "</div>");
var outerCardFooter = "";
overlayText || footerOverlayed || (footerCssClass = options.cardLayout ? "cardFooter" : "cardFooter cardFooter-transparent", logoUrl && (footerCssClass += " cardFooter-withlogo"), options.cardLayout || (logoUrl = null), outerCardFooter = getCardFooterText(item, apiClient, options, showTitle, forceName, overlayText, imgUrl, footerCssClass, progressHtml, logoUrl, !0)), outerCardFooter && !options.cardLayout && (cardBoxClass += " cardBox-bottompadded");
var overlayButtons = "";
if (layoutManager.mobile) {
var overlayPlayButton = options.overlayPlayButton;
null != overlayPlayButton || options.overlayMoreButton || options.overlayInfoButton || options.cardLayout || (overlayPlayButton = "Video" === item.MediaType);
var btnCssClass = "cardOverlayButton cardOverlayButton-br itemAction";
options.centerPlayButton && (overlayButtons += '<button is="paper-icon-button-light" class="' + btnCssClass + ' cardOverlayButton-centered" data-action="play"><i class="md-icon cardOverlayButtonIcon">&#xE037;</i></button>'), !overlayPlayButton || item.IsPlaceHolder || "Virtual" === item.LocationType && item.MediaType && "Program" !== item.Type || "Person" === item.Type || (overlayButtons += '<button is="paper-icon-button-light" class="' + btnCssClass + '" data-action="play"><i class="md-icon cardOverlayButtonIcon">&#xE037;</i></button>'), options.overlayMoreButton && (overlayButtons += '<button is="paper-icon-button-light" class="' + btnCssClass + '" data-action="menu"><i class="md-icon cardOverlayButtonIcon">&#xE5D3;</i></button>')
}
options.showChildCountIndicator && item.ChildCount && (className += " groupedCard");
var cardImageContainerOpen, cardImageContainerClose = "",
cardBoxClose = "",
cardScalableClose = "",
cardContentClass = "cardContent";
options.cardLayout || (cardContentClass += " cardContent-shadow"), layoutManager.tv ? (cardImageContainerOpen = imgUrl ? '<div class="' + cardImageContainerClass + " " + cardContentClass + ' lazy" data-src="' + imgUrl + '">' : '<div class="' + cardImageContainerClass + " " + cardContentClass + '">', cardImageContainerClose = "</div>") : (cardImageContainerOpen = imgUrl ? '<button data-action="' + action + '" class="cardContent-button ' + cardImageContainerClass + " " + cardContentClass + ' itemAction lazy" data-src="' + imgUrl + '">' : '<button data-action="' + action + '" class="cardContent-button ' + cardImageContainerClass + " " + cardContentClass + ' itemAction">', cardImageContainerClose = "</button>");
var cardScalableClass = "cardScalable";
layoutManager.tv && !options.cardLayout && (cardScalableClass += " card-focuscontent", enableFocusTransfrom || (cardScalableClass += " card-focuscontent-large")), cardImageContainerOpen = '<div class="' + cardBoxClass + '"><div class="' + cardScalableClass + '"><div class="cardPadder-' + shape + '"></div>' + cardImageContainerOpen, cardBoxClose = "</div>", cardScalableClose = "</div>";
var indicatorsHtml = "";
if (!1 !== options.missingIndicator && (indicatorsHtml += indicators.getMissingIndicator(item)), indicatorsHtml += indicators.getSyncIndicator(item), indicatorsHtml += indicators.getTimerIndicator(item), indicatorsHtml += indicators.getTypeIndicator(item), options.showGroupCount ? indicatorsHtml += indicators.getChildCountIndicatorHtml(item, {
minCount: 1
}) : indicatorsHtml += indicators.getPlayedIndicatorHtml(item), "CollectionFolder" === item.Type || item.CollectionType) {
indicatorsHtml += '<div is="emby-itemrefreshindicator"' + (item.RefreshProgress || item.RefreshStatus && "Idle" !== virtualFolder.item ? "" : ' class="hide"') + ' data-progress="' + (item.RefreshProgress || 0) + '" data-status="' + item.RefreshStatus + '"></div>', requireRefreshIndicator()
}
indicatorsHtml && (cardImageContainerOpen += '<div class="cardIndicators">' + indicatorsHtml + "</div>"), imgUrl || (cardImageContainerOpen += getCardDefaultText(item, options));
var tagName = layoutManager.tv && !overlayButtons ? "button" : "div",
nameWithPrefix = item.SortName || item.Name || "",
prefix = nameWithPrefix.substring(0, Math.min(3, nameWithPrefix.length));
prefix && (prefix = prefix.toUpperCase());
var timerAttributes = "";
item.TimerId && (timerAttributes += ' data-timerid="' + item.TimerId + '"'), item.SeriesTimerId && (timerAttributes += ' data-seriestimerid="' + item.SeriesTimerId + '"');
var actionAttribute;
"button" === tagName ? (className += " itemAction", actionAttribute = ' data-action="' + action + '"') : actionAttribute = "", "MusicAlbum" !== item.Type && "MusicArtist" !== item.Type && "Audio" !== item.Type && (className += " card-withuserdata");
var positionTicksData = item.UserData && item.UserData.PlaybackPositionTicks ? ' data-positionticks="' + item.UserData.PlaybackPositionTicks + '"' : "",
collectionIdData = options.collectionId ? ' data-collectionid="' + options.collectionId + '"' : "",
playlistIdData = options.playlistId ? ' data-playlistid="' + options.playlistId + '"' : "",
mediaTypeData = item.MediaType ? ' data-mediatype="' + item.MediaType + '"' : "",
collectionTypeData = item.CollectionType ? ' data-collectiontype="' + item.CollectionType + '"' : "",
channelIdData = item.ChannelId ? ' data-channelid="' + item.ChannelId + '"' : "",
contextData = options.context ? ' data-context="' + options.context + '"' : "",
parentIdData = options.parentId ? ' data-parentid="' + options.parentId + '"' : "",
additionalCardContent = "";
return layoutManager.desktop && (additionalCardContent += getHoverMenuHtml(item, action)), "<" + tagName + ' data-index="' + index + '"' + timerAttributes + actionAttribute + ' data-isfolder="' + (item.IsFolder || !1) + '" data-serverid="' + (item.ServerId || options.serverId) + '" data-id="' + (item.Id || item.ItemId) + '" data-type="' + item.Type + '"' + mediaTypeData + collectionTypeData + channelIdData + positionTicksData + collectionIdData + playlistIdData + contextData + parentIdData + ' data-prefix="' + prefix + '" class="' + className + '">' + cardImageContainerOpen + innerCardFooter + cardImageContainerClose + overlayButtons + additionalCardContent + cardScalableClose + outerCardFooter + cardBoxClose + "</" + tagName + ">"
}
function getHoverMenuHtml(item, action) {
var html = "";
html += '<div class="cardOverlayContainer itemAction" data-action="' + action + '">';
var btnCssClass = "cardOverlayButton cardOverlayButton-hover itemAction";
playbackManager.canPlay(item) && (html += '<button is="paper-icon-button-light" class="' + btnCssClass + ' cardOverlayFab-primary" data-action="resume"><i class="md-icon cardOverlayButtonIcon">&#xE037;</i></button>'), html += '<div class="cardOverlayButton-br">';
var userData = item.UserData || {};
if (itemHelper.canMarkPlayed(item) && (require(["emby-playstatebutton"]), html += '<button is="emby-playstatebutton" type="button" data-action="none" class="' + btnCssClass + '" data-id="' + item.Id + '" data-serverid="' + item.ServerId + '" data-itemtype="' + item.Type + '" data-played="' + userData.Played + '"><i class="md-icon cardOverlayButtonIcon cardOverlayButtonIcon-hover">&#xE5CA;</i></button>'), itemHelper.canRate(item)) {
var likes = null == userData.Likes ? "" : userData.Likes;
require(["emby-ratingbutton"]), html += '<button is="emby-ratingbutton" type="button" data-action="none" class="' + btnCssClass + '" data-id="' + item.Id + '" data-serverid="' + item.ServerId + '" data-itemtype="' + item.Type + '" data-likes="' + likes + '" data-isfavorite="' + userData.IsFavorite + '"><i class="md-icon cardOverlayButtonIcon cardOverlayButtonIcon-hover">&#xE87D;</i></button>'
}
return html += '<button is="paper-icon-button-light" class="' + btnCssClass + '" data-action="menu"><i class="md-icon cardOverlayButtonIcon cardOverlayButtonIcon-hover">&#xE5D3;</i></button>', html += "</div>", html += "</div>"
}
function getCardDefaultText(item, options) {
var collectionType = item.CollectionType;
return "livetv" === collectionType ? '<i class="cardImageIcon md-icon">&#xE1B2;</i>' : "homevideos" === collectionType || "photos" === collectionType ? '<i class="cardImageIcon md-icon">&#xE412;</i>' : "music" === collectionType ? '<i class="cardImageIcon md-icon">&#xE310;</i>' : "MusicAlbum" === item.Type ? '<i class="cardImageIcon md-icon">&#xE019;</i>' : "MusicArtist" === item.Type || "Person" === item.Type ? '<i class="cardImageIcon md-icon">&#xE7FD;</i>' : options.defaultCardImageIcon ? '<i class="cardImageIcon md-icon">' + options.defaultCardImageIcon + "</i>" : '<div class="cardText cardDefaultText">' + (isUsingLiveTvNaming(item) ? item.Name : itemHelper.getDisplayName(item)) + "</div>"
}
function buildCards(items, options) {
if (document.body.contains(options.itemsContainer)) {
if (options.parentContainer) {
if (!items.length) return void options.parentContainer.classList.add("hide");
options.parentContainer.classList.remove("hide")
}
var html = buildCardsHtmlInternal(items, options);
html ? (options.itemsContainer.cardBuilderHtml !== html && (options.itemsContainer.innerHTML = html, items.length < 50 ? options.itemsContainer.cardBuilderHtml = html : options.itemsContainer.cardBuilderHtml = null), imageLoader.lazyChildren(options.itemsContainer)) : (options.itemsContainer.innerHTML = html, options.itemsContainer.cardBuilderHtml = null), options.autoFocus && focusManager.autoFocus(options.itemsContainer, !0)
}
}
function ensureIndicators(card, indicatorsElem) {
if (indicatorsElem) return indicatorsElem;
if (!(indicatorsElem = card.querySelector(".cardIndicators"))) {
var cardImageContainer = card.querySelector(".cardImageContainer");
indicatorsElem = document.createElement("div"),
indicatorsElem.classList.add("cardIndicators"), cardImageContainer.appendChild(indicatorsElem)
}
return indicatorsElem
}
function updateUserData(card, userData) {
var type = card.getAttribute("data-type"),
enableCountIndicator = "Series" === type || "BoxSet" === type || "Season" === type,
indicatorsElem = null,
playedIndicator = null,
countIndicator = null,
itemProgressBar = null;
userData.Played ? (playedIndicator = card.querySelector(".playedIndicator"), playedIndicator || (playedIndicator = document.createElement("div"), playedIndicator.classList.add("playedIndicator"), playedIndicator.classList.add("indicator"), indicatorsElem = ensureIndicators(card, indicatorsElem), indicatorsElem.appendChild(playedIndicator)), playedIndicator.innerHTML = '<i class="md-icon indicatorIcon">&#xE5CA;</i>') : (playedIndicator = card.querySelector(".playedIndicator")) && playedIndicator.parentNode.removeChild(playedIndicator), userData.UnplayedItemCount ? (countIndicator = card.querySelector(".countIndicator"), countIndicator || (countIndicator = document.createElement("div"), countIndicator.classList.add("countIndicator"), indicatorsElem = ensureIndicators(card, indicatorsElem), indicatorsElem.appendChild(countIndicator)), countIndicator.innerHTML = userData.UnplayedItemCount) : enableCountIndicator && (countIndicator = card.querySelector(".countIndicator")) && countIndicator.parentNode.removeChild(countIndicator);
var progressHtml = indicators.getProgressBarHtml({
Type: type,
UserData: userData,
MediaType: "Video"
});
if (progressHtml) {
if (!(itemProgressBar = card.querySelector(".itemProgressBar"))) {
itemProgressBar = document.createElement("div"), itemProgressBar.classList.add("itemProgressBar");
var innerCardFooter = card.querySelector(".innerCardFooter");
if (!innerCardFooter) {
innerCardFooter = document.createElement("div"), innerCardFooter.classList.add("innerCardFooter");
card.querySelector(".cardImageContainer").appendChild(innerCardFooter)
}
innerCardFooter.appendChild(itemProgressBar)
}
itemProgressBar.innerHTML = progressHtml
} else(itemProgressBar = card.querySelector(".itemProgressBar")) && itemProgressBar.parentNode.removeChild(itemProgressBar)
}
function onUserDataChanged(userData, scope) {
for (var cards = (scope || document.body).querySelectorAll('.card-withuserdata[data-id="' + userData.ItemId + '"]'), i = 0, length = cards.length; i < length; i++) updateUserData(cards[i], userData)
}
function onTimerCreated(programId, newTimerId, itemsContainer) {
for (var cells = itemsContainer.querySelectorAll('.card[data-id="' + programId + '"]'), i = 0, length = cells.length; i < length; i++) {
var cell = cells[i];
if (!cell.querySelector(".timerIndicator")) {
ensureIndicators(cell).insertAdjacentHTML("beforeend", '<i class="md-icon timerIndicator indicatorIcon">&#xE061;</i>')
}
cell.setAttribute("data-timerid", newTimerId)
}
}
function onTimerCancelled(id, itemsContainer) {
for (var cells = itemsContainer.querySelectorAll('.card[data-timerid="' + id + '"]'), i = 0, length = cells.length; i < length; i++) {
var cell = cells[i],
icon = cell.querySelector(".timerIndicator");
icon && icon.parentNode.removeChild(icon), cell.removeAttribute("data-timerid")
}
}
function onSeriesTimerCancelled(id, itemsContainer) {
for (var cells = itemsContainer.querySelectorAll('.card[data-seriestimerid="' + id + '"]'), i = 0, length = cells.length; i < length; i++) {
var cell = cells[i],
icon = cell.querySelector(".timerIndicator");
icon && icon.parentNode.removeChild(icon), cell.removeAttribute("data-seriestimerid")
}
}
var refreshIndicatorLoaded, enableFocusTransfrom = (window.devicePixelRatio, !browser.slow && !browser.edge),
numRandomColors = 5;
return {
getCardsHtml: getCardsHtml,
buildCards: buildCards,
onUserDataChanged: onUserDataChanged,
onTimerCreated: onTimerCreated,
onTimerCancelled: onTimerCancelled,
onSeriesTimerCancelled: onSeriesTimerCancelled
}
});

View file

@ -0,0 +1,59 @@
define(["datetime", "imageLoader", "connectionManager", "layoutManager", "browser"], function(datetime, imageLoader, connectionManager, layoutManager, browser) {
"use strict";
function buildChapterCardsHtml(item, chapters, options) {
var className = "card itemAction chapterCard";
layoutManager.tv && (browser.animate || browser.edge) && (className += " card-focusscale");
var mediaStreams = ((item.MediaSources || [])[0] || {}).MediaStreams || [],
videoStream = mediaStreams.filter(function(i) {
return "Video" === i.Type
})[0] || {},
shape = options.backdropShape || "backdrop";
videoStream.Width && videoStream.Height && videoStream.Width / videoStream.Height <= 1.2 && (shape = options.squareShape || "square"), className += " " + shape + "Card", (options.block || options.rows) && (className += " block");
for (var html = "", itemsInRow = 0, apiClient = connectionManager.getApiClient(item.ServerId), i = 0, length = chapters.length; i < length; i++) {
options.rows && 0 === itemsInRow && (html += '<div class="cardColumn">');
html += buildChapterCard(item, apiClient, chapters[i], i, options, className, shape), itemsInRow++, options.rows && itemsInRow >= options.rows && (itemsInRow = 0, html += "</div>")
}
return html
}
function getImgUrl(item, chapter, index, maxWidth, apiClient) {
return chapter.ImageTag ? apiClient.getScaledImageUrl(item.Id, {
maxWidth: maxWidth,
tag: chapter.ImageTag,
type: "Chapter",
index: index
}) : null
}
function buildChapterCard(item, apiClient, chapter, index, options, className, shape) {
var imgUrl = getImgUrl(item, chapter, index, options.width || 400, apiClient),
cardImageContainerClass = "cardContent cardContent-shadow cardImageContainer chapterCardImageContainer";
options.coverImage && (cardImageContainerClass += " coveredImage");
var dataAttributes = ' data-action="play" data-isfolder="' + item.IsFolder + '" data-id="' + item.Id + '" data-serverid="' + item.ServerId + '" data-type="' + item.Type + '" data-mediatype="' + item.MediaType + '" data-positionticks="' + chapter.StartPositionTicks + '"',
cardImageContainer = imgUrl ? '<div class="' + cardImageContainerClass + ' lazy" data-src="' + imgUrl + '">' : '<div class="' + cardImageContainerClass + '">';
imgUrl || (cardImageContainer += '<i class="md-icon cardImageIcon">local_movies</i>');
var nameHtml = "";
nameHtml += '<div class="cardText">' + chapter.Name + "</div>", nameHtml += '<div class="cardText">' + datetime.getDisplayRunningTime(chapter.StartPositionTicks) + "</div>";
var cardBoxCssClass = "cardBox",
cardScalableClass = "cardScalable";
if (layoutManager.tv) {
var enableFocusTransfrom = !browser.slow && !browser.edge;
cardScalableClass += " card-focuscontent", enableFocusTransfrom ? cardBoxCssClass += " cardBox-focustransform cardBox-withfocuscontent" : (cardBoxCssClass += " cardBox-withfocuscontent-large", cardScalableClass += " card-focuscontent-large")
}
return '<button type="button" class="' + className + '"' + dataAttributes + '><div class="' + cardBoxCssClass + '"><div class="' + cardScalableClass + '"><div class="cardPadder-' + shape + '"></div>' + cardImageContainer + '</div><div class="innerCardFooter">' + nameHtml + "</div></div></div></button>"
}
function buildChapterCards(item, chapters, options) {
if (options.parentContainer) {
if (!document.body.contains(options.parentContainer)) return;
if (!chapters.length) return void options.parentContainer.classList.add("hide");
options.parentContainer.classList.remove("hide")
}
var html = buildChapterCardsHtml(item, chapters, options);
options.itemsContainer.innerHTML = html, imageLoader.lazyChildren(options.itemsContainer)
}
return {
buildChapterCards: buildChapterCards
}
});

View file

@ -0,0 +1,18 @@
define(["cardBuilder"], function(cardBuilder) {
"use strict";
function buildPeopleCards(items, options) {
options = Object.assign(options || {}, {
cardLayout: !1,
centerText: !0,
showTitle: !0,
cardFooterAside: "none",
showPersonRoleOrType: !0,
cardCssClass: "personCard",
defaultCardImageIcon: "&#xE7FD;"
}), cardBuilder.buildCards(items, options)
}
return {
buildPeopleCards: buildPeopleCards
}
});

View file

@ -0,0 +1,10 @@
.card-round:focus>.cardBox-focustransform {
-webkit-transform: scale(1.26, 1.26);
transform: scale(1.26, 1.26)
}
.cardImage-round,
.cardImageContainer-round {
-webkit-border-radius: 1000px;
border-radius: 1000px
}

View file

@ -0,0 +1,67 @@
define(["events"], function(events) {
"use strict";
function isValidIpAddress(address) {
return 1 == LinkParser.parse(address).length
}
function isLocalIpAddress(address) {
return address = address.toLowerCase(), -1 !== address.indexOf("127.0.0.1") || -1 !== address.indexOf("localhost")
}
function getServerAddress(apiClient) {
var serverAddress = apiClient.serverAddress();
if (isValidIpAddress(serverAddress) && !isLocalIpAddress(serverAddress)) return Promise.resolve(serverAddress);
var cachedValue = getCachedValue(serverAddress);
return cachedValue ? Promise.resolve(cachedValue) : apiClient.getEndpointInfo().then(function(endpoint) {
return endpoint.IsInNetwork ? apiClient.getPublicSystemInfo().then(function(info) {
return addToCache(serverAddress, info.LocalAddress), info.LocalAddress
}) : (addToCache(serverAddress, serverAddress), serverAddress)
})
}
function clearCache() {
cache = {}
}
function addToCache(key, value) {
cache[key] = {
value: value,
time: (new Date).getTime()
}
}
function getCachedValue(key) {
var obj = cache[key];
return obj && (new Date).getTime() - obj.time < 18e4 ? obj.value : null
}! function() {
function ensureProtocol(url) {
return url.match(protocolRegExp) || (url = "http://" + url), url
}
var protocols = "(?:(?:http|https|rtsp|ftp):\\/\\/)",
linkRegExp = RegExp("(?:(?:(?:http|https|rtsp|ftp):\\/\\/)?(?:(?:[a-z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-f0-9]{2})){1,64}(?:\\:(?:[a-z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-f0-9]{2})){1,25})?\\@)?(?:((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?|(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[0-9])))(?:\\:\\d{1,5})?(?:\\/(?:(?:[a-z0-9\\/\\@\\&\\#\\~\\*\\_\\-\\+])|(?:\\%[a-f0-9]{2})|(?:[\\;\\?\\:\\.\\!\\'\\(\\)\\,\\=]+(?=(?:[a-z0-9\\/\\@\\&\\#\\~\\*\\_\\-\\+])|(?:\\%[a-f0-9]{2}))))*|\\b|$)", "gi"),
protocolRegExp = RegExp("^" + protocols, "i"),
LinkParser = {
parse: function(text) {
for (var match, links = []; match = linkRegExp.exec(text);) {
var txt = match[0],
pos = match.index,
len = txt.length,
url = ensureProtocol(text);
links.push({
pos: pos,
text: txt,
len: len,
url: url
})
}
return links
}
};
window.LinkParser = LinkParser
}();
var cache = {};
return events.on(ConnectionManager, "localusersignedin", clearCache), events.on(ConnectionManager, "localusersignedout", clearCache), {
getServerAddress: getServerAddress
}
});

View file

@ -0,0 +1,425 @@
define(["appSettings", "userSettings", "playbackManager", "connectionManager", "globalize", "events", "require", "castSenderApiLoader"], function(appSettings, userSettings, playbackManager, connectionManager, globalize, events, require, castSenderApiLoader) {
"use strict";
function sendConnectionResult(isOk) {
var resolve = currentResolve,
reject = currentReject;
currentResolve = null, currentReject = null, isOk ? resolve && resolve() : reject ? reject() : playbackManager.removeActivePlayer(PlayerName)
}
function alertText(text, title) {
require(["alert"], function(alert) {
alert({
text: text,
title: title
})
})
}
function normalizeImages(state) {
if (state && state.NowPlayingItem) {
var item = state.NowPlayingItem;
item.ImageTags && item.ImageTags.Primary || item.PrimaryImageTag && (item.ImageTags = item.ImageTags || {}, item.ImageTags.Primary = item.PrimaryImageTag), item.BackdropImageTag && item.BackdropItemId === item.Id && (item.BackdropImageTags = [item.BackdropImageTag]), item.BackdropImageTag && item.BackdropItemId !== item.Id && (item.ParentBackdropImageTags = [item.BackdropImageTag], item.ParentBackdropItemId = item.BackdropItemId)
}
}
function getItemsForPlayback(apiClient, query) {
var userId = apiClient.getCurrentUserId();
return query.Ids && 1 === query.Ids.split(",").length ? apiClient.getItem(userId, query.Ids.split(",")).then(function(item) {
return {
Items: [item],
TotalRecordCount: 1
}
}) : (query.Limit = query.Limit || 100, query.ExcludeLocationTypes = "Virtual", query.EnableTotalRecordCount = !1, apiClient.getItems(userId, query))
}
function bindEventForRelay(instance, eventName) {
events.on(instance._castPlayer, eventName, function(e, data) {
var state = instance.getPlayerStateInternal(data);
events.trigger(instance, eventName, [state])
})
}
function initializeChromecast() {
var instance = this;
instance._castPlayer = new CastPlayer, document.dispatchEvent(new CustomEvent("chromecastloaded", {
detail: {
player: instance
}
})), events.on(instance._castPlayer, "connect", function(e) {
currentResolve ? sendConnectionResult(!0) : playbackManager.setActivePlayer(PlayerName, instance.getCurrentTargetInfo()), console.log("cc: connect"), instance.lastPlayerData = null
}), events.on(instance._castPlayer, "playbackstart", function(e, data) {
console.log("cc: playbackstart"), instance._castPlayer.initializeCastPlayer();
var state = instance.getPlayerStateInternal(data);
events.trigger(instance, "playbackstart", [state])
}), events.on(instance._castPlayer, "playbackstop", function(e, data) {
console.log("cc: playbackstop");
var state = instance.getPlayerStateInternal(data);
events.trigger(instance, "playbackstop", [state]), instance.lastPlayerData = {}
}), events.on(instance._castPlayer, "playbackprogress", function(e, data) {
var state = instance.getPlayerStateInternal(data);
events.trigger(instance, "timeupdate", [state])
}), bindEventForRelay(instance, "timeupdate"), bindEventForRelay(instance, "pause"), bindEventForRelay(instance, "unpause"), bindEventForRelay(instance, "volumechange"), bindEventForRelay(instance, "repeatmodechange"), events.on(instance._castPlayer, "playstatechange", function(e, data) {
var state = instance.getPlayerStateInternal(data);
events.trigger(instance, "pause", [state])
})
}
function ChromecastPlayer() {
this.name = PlayerName, this.type = "mediaplayer", this.id = "chromecast", this.isLocalPlayer = !1, this.lastPlayerData = {}, castSenderApiLoader.load().then(initializeChromecast.bind(this))
}
var currentResolve, currentReject, PlayerName = "Chromecast",
DEVICE_STATE = {
IDLE: 0,
ACTIVE: 1,
WARNING: 2,
ERROR: 3
},
PLAYER_STATE = {
IDLE: "IDLE",
LOADING: "LOADING",
LOADED: "LOADED",
PLAYING: "PLAYING",
PAUSED: "PAUSED",
STOPPED: "STOPPED",
SEEKING: "SEEKING",
ERROR: "ERROR"
},
CastPlayer = function() {
this.deviceState = DEVICE_STATE.IDLE, this.currentMediaSession = null, this.session = null, this.castPlayerState = PLAYER_STATE.IDLE, this.hasReceivers = !1, this.errorHandler = this.onError.bind(this), this.mediaStatusUpdateHandler = this.onMediaStatusUpdate.bind(this), this.initializeCastPlayer()
};
return CastPlayer.prototype.initializeCastPlayer = function() {
var chrome = window.chrome;
if (chrome) {
if (!chrome.cast || !chrome.cast.isAvailable) return void setTimeout(this.initializeCastPlayer.bind(this), 1e3);
var sessionRequest = new chrome.cast.SessionRequest("2D4B1DA3"),
apiConfig = new chrome.cast.ApiConfig(sessionRequest, this.sessionListener.bind(this), this.receiverListener.bind(this), "origin_scoped");
console.log("chromecast.initialize"), chrome.cast.initialize(apiConfig, this.onInitSuccess.bind(this), this.errorHandler)
}
}, CastPlayer.prototype.onInitSuccess = function() {
this.isInitialized = !0, console.log("chromecast init success")
}, CastPlayer.prototype.onError = function() {
console.log("chromecast error")
}, CastPlayer.prototype.sessionListener = function(e) {
this.session = e, this.session && (this.session.media[0] && this.onMediaDiscovered("activeSession", this.session.media[0]), this.onSessionConnected(e))
}, CastPlayer.prototype.messageListener = function(namespace, message) {
if ("string" == typeof message && (message = JSON.parse(message)), "playbackerror" === message.type) {
var errorCode = message.data;
setTimeout(function() {
alertText(globalize.translate("MessagePlaybackError" + errorCode), globalize.translate("HeaderPlaybackError"))
}, 300)
} else "connectionerror" === message.type ? setTimeout(function() {
alertText(globalize.translate("MessageChromecastConnectionError"), globalize.translate("HeaderError"))
}, 300) : message.type && events.trigger(this, message.type, [message.data])
}, CastPlayer.prototype.receiverListener = function(e) {
this.hasReceivers = "available" === e
}, CastPlayer.prototype.sessionUpdateListener = function(isAlive) {
isAlive || (this.session = null, this.deviceState = DEVICE_STATE.IDLE, this.castPlayerState = PLAYER_STATE.IDLE, this.currentMediaSession = null, sendConnectionResult(!1))
}, CastPlayer.prototype.launchApp = function() {
chrome.cast.requestSession(this.onRequestSessionSuccess.bind(this), this.onLaunchError.bind(this))
}, CastPlayer.prototype.onRequestSessionSuccess = function(e) {
this.onSessionConnected(e)
}, CastPlayer.prototype.onSessionConnected = function(session) {
this.session = session, this.deviceState = DEVICE_STATE.ACTIVE, this.session.addMessageListener("urn:x-cast:com.connectsdk", this.messageListener.bind(this)), this.session.addMediaListener(this.sessionMediaListener.bind(this)), this.session.addUpdateListener(this.sessionUpdateListener.bind(this)), events.trigger(this, "connect"), this.sendMessage({
options: {},
command: "Identify"
})
}, CastPlayer.prototype.sessionMediaListener = function(e) {
this.currentMediaSession = e, this.currentMediaSession.addUpdateListener(this.mediaStatusUpdateHandler)
}, CastPlayer.prototype.onLaunchError = function() {
this.deviceState = DEVICE_STATE.ERROR, sendConnectionResult(!1)
}, CastPlayer.prototype.stopApp = function() {
this.session && this.session.stop(this.onStopAppSuccess.bind(this, "Session stopped"), this.errorHandler)
}, CastPlayer.prototype.onStopAppSuccess = function(message) {
this.deviceState = DEVICE_STATE.IDLE, this.castPlayerState = PLAYER_STATE.IDLE, this.currentMediaSession = null
}, CastPlayer.prototype.loadMedia = function(options, command) {
return this.session ? (options.items = options.items.map(function(i) {
return {
Id: i.Id,
ServerId: i.ServerId,
Name: i.Name,
Type: i.Type,
MediaType: i.MediaType,
IsFolder: i.IsFolder
}
}), this.sendMessage({
options: options,
command: command
})) : Promise.reject()
}, CastPlayer.prototype.sendMessage = function(message) {
var player = this,
receiverName = null,
session = player.session;
session && session.receiver && session.receiver.friendlyName && (receiverName = session.receiver.friendlyName);
var apiClient;
apiClient = message.options && message.options.ServerId ? connectionManager.getApiClient(message.options.ServerId) : message.options && message.options.items && message.options.items.length ? connectionManager.getApiClient(message.options.items[0].ServerId) : connectionManager.currentApiClient(), message = Object.assign(message, {
userId: apiClient.getCurrentUserId(),
deviceId: apiClient.deviceId(),
accessToken: apiClient.accessToken(),
serverAddress: apiClient.serverAddress(),
serverId: apiClient.serverId(),
serverVersion: apiClient.serverVersion(),
receiverName: receiverName
});
var bitrateSetting = appSettings.maxChromecastBitrate();
return bitrateSetting && (message.maxBitrate = bitrateSetting), message.options && message.options.items && (message.subtitleAppearance = userSettings.getSubtitleAppearanceSettings(), message.subtitleBurnIn = appSettings.get("subtitleburnin") || ""), new Promise(function(resolve, reject) {
require(["chromecastHelper"], function(chromecastHelper) {
chromecastHelper.getServerAddress(apiClient).then(function(serverAddress) {
message.serverAddress = serverAddress, player.sendMessageInternal(message).then(resolve, reject)
}, reject)
})
})
}, CastPlayer.prototype.sendMessageInternal = function(message) {
return message = JSON.stringify(message), this.session.sendMessage("urn:x-cast:com.connectsdk", message, this.onPlayCommandSuccess.bind(this), this.errorHandler), Promise.resolve()
}, CastPlayer.prototype.onPlayCommandSuccess = function() {}, CastPlayer.prototype.onMediaDiscovered = function(how, mediaSession) {
this.currentMediaSession = mediaSession, "loadMedia" === how && (this.castPlayerState = PLAYER_STATE.PLAYING), "activeSession" === how && (this.castPlayerState = mediaSession.playerState), this.currentMediaSession.addUpdateListener(this.mediaStatusUpdateHandler)
}, CastPlayer.prototype.onMediaStatusUpdate = function(e) {
!1 === e && (this.castPlayerState = PLAYER_STATE.IDLE)
}, CastPlayer.prototype.setReceiverVolume = function(mute, vol) {
this.currentMediaSession && (mute ? this.session.setReceiverMuted(!0, this.mediaCommandSuccessCallback.bind(this), this.errorHandler) : this.session.setReceiverVolumeLevel(vol || 1, this.mediaCommandSuccessCallback.bind(this), this.errorHandler))
}, CastPlayer.prototype.mute = function() {
this.setReceiverVolume(!0)
}, CastPlayer.prototype.mediaCommandSuccessCallback = function(info, e) {}, ChromecastPlayer.prototype.tryPair = function(target) {
var castPlayer = this._castPlayer;
return castPlayer.deviceState !== DEVICE_STATE.ACTIVE && castPlayer.isInitialized ? new Promise(function(resolve, reject) {
currentResolve = resolve, currentReject = reject, castPlayer.launchApp()
}) : (currentResolve = null, currentReject = null, Promise.reject())
}, ChromecastPlayer.prototype.getTargets = function() {
var targets = [];
return this._castPlayer && this._castPlayer.hasReceivers && targets.push(this.getCurrentTargetInfo()), Promise.resolve(targets)
}, ChromecastPlayer.prototype.getCurrentTargetInfo = function() {
var appName = null,
castPlayer = this._castPlayer;
return castPlayer.session && castPlayer.session.receiver && castPlayer.session.receiver.friendlyName && (appName = castPlayer.session.receiver.friendlyName), {
name: PlayerName,
id: PlayerName,
playerName: PlayerName,
playableMediaTypes: ["Audio", "Video"],
isLocalPlayer: !1,
appName: PlayerName,
deviceName: appName,
supportedCommands: ["VolumeUp", "VolumeDown", "Mute", "Unmute", "ToggleMute", "SetVolume", "SetAudioStreamIndex", "SetSubtitleStreamIndex", "DisplayContent", "SetRepeatMode", "EndSession", "PlayMediaSource", "PlayTrailers"]
}
}, ChromecastPlayer.prototype.getPlayerStateInternal = function(data) {
var triggerStateChange = !1;
return data && !this.lastPlayerData && (triggerStateChange = !0), data = data || this.lastPlayerData, this.lastPlayerData = data, normalizeImages(data), triggerStateChange && events.trigger(this, "statechange", [data]), data
}, ChromecastPlayer.prototype.playWithCommand = function(options, command) {
if (!options.items) {
var apiClient = connectionManager.getApiClient(options.serverId),
instance = this;
return apiClient.getItem(apiClient.getCurrentUserId(), options.ids[0]).then(function(item) {
return options.items = [item], instance.playWithCommand(options, command)
})
}
return this._castPlayer.loadMedia(options, command)
}, ChromecastPlayer.prototype.seek = function(position) {
position = parseInt(position), position /= 1e7, this._castPlayer.sendMessage({
options: {
position: position
},
command: "Seek"
})
}, ChromecastPlayer.prototype.setAudioStreamIndex = function(index) {
this._castPlayer.sendMessage({
options: {
index: index
},
command: "SetAudioStreamIndex"
})
}, ChromecastPlayer.prototype.setSubtitleStreamIndex = function(index) {
this._castPlayer.sendMessage({
options: {
index: index
},
command: "SetSubtitleStreamIndex"
})
}, ChromecastPlayer.prototype.setMaxStreamingBitrate = function(options) {
this._castPlayer.sendMessage({
options: options,
command: "SetMaxStreamingBitrate"
})
}, ChromecastPlayer.prototype.isFullscreen = function() {
var state = this.lastPlayerData || {};
return state = state.PlayState || {}, state.IsFullscreen
}, ChromecastPlayer.prototype.nextTrack = function() {
this._castPlayer.sendMessage({
options: {},
command: "NextTrack"
})
}, ChromecastPlayer.prototype.previousTrack = function() {
this._castPlayer.sendMessage({
options: {},
command: "PreviousTrack"
})
}, ChromecastPlayer.prototype.volumeDown = function() {
this._castPlayer.sendMessage({
options: {},
command: "VolumeDown"
})
}, ChromecastPlayer.prototype.endSession = function() {
var instance = this;
this.stop().then(function() {
setTimeout(function() {
instance._castPlayer.stopApp()
}, 1e3)
})
}, ChromecastPlayer.prototype.volumeUp = function() {
this._castPlayer.sendMessage({
options: {},
command: "VolumeUp"
})
}, ChromecastPlayer.prototype.setVolume = function(vol) {
vol = Math.min(vol, 100), vol = Math.max(vol, 0), this._castPlayer.sendMessage({
options: {
volume: vol
},
command: "SetVolume"
})
}, ChromecastPlayer.prototype.unpause = function() {
this._castPlayer.sendMessage({
options: {},
command: "Unpause"
})
}, ChromecastPlayer.prototype.playPause = function() {
this._castPlayer.sendMessage({
options: {},
command: "PlayPause"
})
}, ChromecastPlayer.prototype.pause = function() {
this._castPlayer.sendMessage({
options: {},
command: "Pause"
})
}, ChromecastPlayer.prototype.stop = function() {
return this._castPlayer.sendMessage({
options: {},
command: "Stop"
})
}, ChromecastPlayer.prototype.displayContent = function(options) {
this._castPlayer.sendMessage({
options: options,
command: "DisplayContent"
})
}, ChromecastPlayer.prototype.setMute = function(isMuted) {
var castPlayer = this._castPlayer;
isMuted ? castPlayer.sendMessage({
options: {},
command: "Mute"
}) : castPlayer.sendMessage({
options: {},
command: "Unmute"
})
}, ChromecastPlayer.prototype.getRepeatMode = function() {
var state = this.lastPlayerData || {};
return state = state.PlayState || {}, state.RepeatMode
}, ChromecastPlayer.prototype.playTrailers = function(item) {
this._castPlayer.sendMessage({
options: {
ItemId: item.Id,
ServerId: item.ServerId
},
command: "PlayTrailers"
})
}, ChromecastPlayer.prototype.setRepeatMode = function(mode) {
this._castPlayer.sendMessage({
options: {
RepeatMode: mode
},
command: "SetRepeatMode"
})
}, ChromecastPlayer.prototype.toggleMute = function() {
this._castPlayer.sendMessage({
options: {},
command: "ToggleMute"
})
}, ChromecastPlayer.prototype.audioTracks = function() {
var state = this.lastPlayerData || {};
return state = state.NowPlayingItem || {}, (state.MediaStreams || []).filter(function(s) {
return "Audio" === s.Type
})
}, ChromecastPlayer.prototype.getAudioStreamIndex = function() {
var state = this.lastPlayerData || {};
return state = state.PlayState || {}, state.AudioStreamIndex
}, ChromecastPlayer.prototype.subtitleTracks = function() {
var state = this.lastPlayerData || {};
return state = state.NowPlayingItem || {}, (state.MediaStreams || []).filter(function(s) {
return "Subtitle" === s.Type
})
}, ChromecastPlayer.prototype.getSubtitleStreamIndex = function() {
var state = this.lastPlayerData || {};
return state = state.PlayState || {}, state.SubtitleStreamIndex
}, ChromecastPlayer.prototype.getMaxStreamingBitrate = function() {
var state = this.lastPlayerData || {};
return state = state.PlayState || {}, state.MaxStreamingBitrate
}, ChromecastPlayer.prototype.getVolume = function() {
var state = this.lastPlayerData || {};
return state = state.PlayState || {}, null == state.VolumeLevel ? 100 : state.VolumeLevel
}, ChromecastPlayer.prototype.isPlaying = function() {
return null != (this.lastPlayerData || {}).NowPlayingItem
}, ChromecastPlayer.prototype.isPlayingVideo = function() {
var state = this.lastPlayerData || {};
return state = state.NowPlayingItem || {}, "Video" === state.MediaType
}, ChromecastPlayer.prototype.isPlayingAudio = function() {
var state = this.lastPlayerData || {};
return state = state.NowPlayingItem || {}, "Audio" === state.MediaType
}, ChromecastPlayer.prototype.currentTime = function(val) {
if (null != val) return this.seek(val);
var state = this.lastPlayerData || {};
return state = state.PlayState || {}, state.PositionTicks
}, ChromecastPlayer.prototype.duration = function() {
var state = this.lastPlayerData || {};
return state = state.NowPlayingItem || {}, state.RunTimeTicks
}, ChromecastPlayer.prototype.getBufferedRanges = function() {
var state = this.lastPlayerData || {};
return state = state.PlayState || {}, state.BufferedRanges || []
}, ChromecastPlayer.prototype.paused = function() {
var state = this.lastPlayerData || {};
return state = state.PlayState || {}, state.IsPaused
}, ChromecastPlayer.prototype.isMuted = function() {
var state = this.lastPlayerData || {};
return state = state.PlayState || {}, state.IsMuted
}, ChromecastPlayer.prototype.shuffle = function(item) {
var apiClient = connectionManager.getApiClient(item.ServerId),
userId = apiClient.getCurrentUserId(),
instance = this;
apiClient.getItem(userId, item.Id).then(function(item) {
instance.playWithCommand({
items: [item]
}, "Shuffle")
})
}, ChromecastPlayer.prototype.instantMix = function(item) {
var apiClient = connectionManager.getApiClient(item.ServerId),
userId = apiClient.getCurrentUserId(),
instance = this;
apiClient.getItem(userId, item.Id).then(function(item) {
instance.playWithCommand({
items: [item]
}, "InstantMix")
})
}, ChromecastPlayer.prototype.canPlayMediaType = function(mediaType) {
return "audio" === (mediaType = (mediaType || "").toLowerCase()) || "video" === mediaType
}, ChromecastPlayer.prototype.canQueueMediaType = function(mediaType) {
return this.canPlayMediaType(mediaType)
}, ChromecastPlayer.prototype.queue = function(options) {
this.playWithCommand(options, "PlayLast")
}, ChromecastPlayer.prototype.queueNext = function(options) {
this.playWithCommand(options, "PlayNext")
}, ChromecastPlayer.prototype.play = function(options) {
if (options.items) return this.playWithCommand(options, "PlayNow");
if (!options.serverId) throw new Error("serverId required!");
var instance = this;
return getItemsForPlayback(connectionManager.getApiClient(options.serverId), {
Ids: options.ids.join(",")
}).then(function(result) {
return options.items = result.Items, instance.playWithCommand(options, "PlayNow")
})
}, ChromecastPlayer.prototype.toggleFullscreen = function() {}, ChromecastPlayer.prototype.beginPlayerUpdates = function() {}, ChromecastPlayer.prototype.endPlayerUpdates = function() {}, ChromecastPlayer.prototype.getPlaylist = function() {
return Promise.resolve([])
}, ChromecastPlayer.prototype.getCurrentPlaylistItemId = function() {}, ChromecastPlayer.prototype.setCurrentPlaylistItem = function(playlistItemId) {
return Promise.resolve()
}, ChromecastPlayer.prototype.removeFromPlaylist = function(playlistItemIds) {
return Promise.resolve()
}, ChromecastPlayer.prototype.getPlayerState = function() {
return this.getPlayerStateInternal() || {}
}, ChromecastPlayer
});

View file

@ -0,0 +1,12 @@
.clearButton {
background: 0 0;
border: 0 !important;
padding: 0 !important;
cursor: pointer;
outline: 0 !important;
color: inherit;
width: 100%;
vertical-align: middle;
font-family: inherit;
font-size: inherit
}

View file

@ -0,0 +1,119 @@
define(["dialogHelper", "loading", "apphost", "layoutManager", "connectionManager", "appRouter", "globalize", "emby-checkbox", "emby-input", "paper-icon-button-light", "emby-select", "material-icons", "css!./../formdialog", "emby-button", "emby-linkbutton", "flexStyles"], function(dialogHelper, loading, appHost, layoutManager, connectionManager, appRouter, globalize) {
"use strict";
function parentWithClass(elem, className) {
for (; !elem.classList || !elem.classList.contains(className);)
if (!(elem = elem.parentNode)) return null;
return elem
}
function onSubmit(e) {
loading.show();
var panel = parentWithClass(this, "dialog"),
collectionId = panel.querySelector("#selectCollectionToAddTo").value,
apiClient = connectionManager.getApiClient(currentServerId);
return collectionId ? addToCollection(apiClient, panel, collectionId) : createCollection(apiClient, panel), e.preventDefault(), !1
}
function createCollection(apiClient, dlg) {
var url = apiClient.getUrl("Collections", {
Name: dlg.querySelector("#txtNewCollectionName").value,
IsLocked: !dlg.querySelector("#chkEnableInternetMetadata").checked,
Ids: dlg.querySelector(".fldSelectedItemIds").value || ""
});
apiClient.ajax({
type: "POST",
url: url,
dataType: "json"
}).then(function(result) {
loading.hide();
var id = result.Id;
dlg.submitted = !0, dialogHelper.close(dlg), redirectToCollection(apiClient, id)
})
}
function redirectToCollection(apiClient, id) {
appRouter.showItem(id, apiClient.serverId())
}
function addToCollection(apiClient, dlg, id) {
var url = apiClient.getUrl("Collections/" + id + "/Items", {
Ids: dlg.querySelector(".fldSelectedItemIds").value || ""
});
apiClient.ajax({
type: "POST",
url: url
}).then(function() {
loading.hide(), dlg.submitted = !0, dialogHelper.close(dlg), require(["toast"], function(toast) {
toast(globalize.translate("sharedcomponents#MessageItemsAdded"))
})
})
}
function triggerChange(select) {
select.dispatchEvent(new CustomEvent("change", {}))
}
function populateCollections(panel) {
loading.show();
var select = panel.querySelector("#selectCollectionToAddTo");
panel.querySelector(".newCollectionInfo").classList.add("hide");
var options = {
Recursive: !0,
IncludeItemTypes: "BoxSet",
SortBy: "SortName",
EnableTotalRecordCount: !1
},
apiClient = connectionManager.getApiClient(currentServerId);
apiClient.getItems(apiClient.getCurrentUserId(), options).then(function(result) {
var html = "";
html += '<option value="">' + globalize.translate("sharedcomponents#OptionNew") + "</option>", html += result.Items.map(function(i) {
return '<option value="' + i.Id + '">' + i.Name + "</option>"
}), select.innerHTML = html, select.value = "", triggerChange(select), loading.hide()
})
}
function getEditorHtml() {
var html = "";
return html += '<div class="formDialogContent smoothScrollY" style="padding-top:2em;">', html += '<div class="dialogContentInner dialog-content-centered">', html += '<form class="newCollectionForm" style="margin:auto;">', html += "<div>", html += globalize.translate("sharedcomponents#NewCollectionHelp"), html += "</div>", html += '<div class="fldSelectCollection">', html += "<br/>", html += "<br/>", html += '<div class="selectContainer">', html += '<select is="emby-select" label="' + globalize.translate("sharedcomponents#LabelCollection") + '" id="selectCollectionToAddTo" autofocus></select>', html += "</div>", html += "</div>", html += '<div class="newCollectionInfo">', html += '<div class="inputContainer">', html += '<input is="emby-input" type="text" id="txtNewCollectionName" required="required" label="' + globalize.translate("sharedcomponents#LabelName") + '" />', html += '<div class="fieldDescription">' + globalize.translate("sharedcomponents#NewCollectionNameExample") + "</div>", html += "</div>", html += '<label class="checkboxContainer">', html += '<input is="emby-checkbox" type="checkbox" id="chkEnableInternetMetadata" />', html += "<span>" + globalize.translate("sharedcomponents#SearchForCollectionInternetMetadata") + "</span>", html += "</label>", html += "</div>", html += '<div class="formDialogFooter">', html += '<button is="emby-button" type="submit" class="raised btnSubmit block formDialogFooterItem button-submit">' + globalize.translate("sharedcomponents#ButtonOk") + "</button>", html += "</div>", html += '<input type="hidden" class="fldSelectedItemIds" />', html += "</form>", html += "</div>", html += "</div>"
}
function initEditor(content, items) {
if (content.querySelector("#selectCollectionToAddTo").addEventListener("change", function() {
this.value ? (content.querySelector(".newCollectionInfo").classList.add("hide"), content.querySelector("#txtNewCollectionName").removeAttribute("required")) : (content.querySelector(".newCollectionInfo").classList.remove("hide"), content.querySelector("#txtNewCollectionName").setAttribute("required", "required"))
}), content.querySelector("form").addEventListener("submit", onSubmit), content.querySelector(".fldSelectedItemIds", content).value = items.join(","), items.length) content.querySelector(".fldSelectCollection").classList.remove("hide"), populateCollections(content);
else {
content.querySelector(".fldSelectCollection").classList.add("hide");
var selectCollectionToAddTo = content.querySelector("#selectCollectionToAddTo");
selectCollectionToAddTo.innerHTML = "", selectCollectionToAddTo.value = "", triggerChange(selectCollectionToAddTo)
}
}
function centerFocus(elem, horiz, on) {
require(["scrollHelper"], function(scrollHelper) {
var fn = on ? "on" : "off";
scrollHelper.centerFocus[fn](elem, horiz)
})
}
function CollectionEditor() {}
var currentServerId;
return CollectionEditor.prototype.show = function(options) {
var items = options.items || {};
currentServerId = options.serverId;
var dialogOptions = {
removeOnClose: !0,
scrollY: !1
};
layoutManager.tv ? dialogOptions.size = "fullscreen" : dialogOptions.size = "small";
var dlg = dialogHelper.createDialog(dialogOptions);
dlg.classList.add("formDialog");
var html = "",
title = items.length ? globalize.translate("sharedcomponents#HeaderAddToCollection") : globalize.translate("sharedcomponents#NewCollection");
return html += '<div class="formDialogHeader">', html += '<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><i class="md-icon">&#xE5C4;</i></button>', html += '<h3 class="formDialogHeaderTitle">', html += title, html += "</h3>", appHost.supports("externallinks") && (html += '<a is="emby-linkbutton" class="button-link btnHelp flex align-items-center" href="https://github.com/MediaBrowser/Wiki/wiki/Collections" target="_blank" style="margin-left:auto;margin-right:.5em;padding:.25em;" title="' + globalize.translate("sharedcomponents#Help") + '"><i class="md-icon">&#xE88E;</i><span style="margin-left:.25em;">' + globalize.translate("sharedcomponents#Help") + "</span></a>"), html += "</div>", html += getEditorHtml(), dlg.innerHTML = html, initEditor(dlg, items), dlg.querySelector(".btnCancel").addEventListener("click", function() {
dialogHelper.close(dlg)
}), layoutManager.tv && centerFocus(dlg.querySelector(".formDialogContent"), !1, !0), dialogHelper.open(dlg).then(function() {
return layoutManager.tv && centerFocus(dlg.querySelector(".formDialogContent"), !1, !1), dlg.submitted ? Promise.resolve() : Promise.reject()
})
}, CollectionEditor
});

View file

@ -0,0 +1,22 @@
define(["dialog", "globalize"], function(dialog, globalize) {
"use strict";
return function(text, title) {
var options;
options = "string" == typeof text ? {
title: title,
text: text
} : text;
var items = [];
return items.push({
name: options.cancelText || globalize.translate("sharedcomponents#ButtonCancel"),
id: "cancel",
type: "cancel" === options.primary ? "submit" : "cancel"
}), items.push({
name: options.confirmText || globalize.translate("sharedcomponents#ButtonOk"),
id: "ok",
type: "cancel" === options.primary ? "cancel" : "submit"
}), options.buttons = items, dialog(options).then(function(result) {
return "ok" === result ? Promise.resolve() : Promise.reject()
})
}
});

View file

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

View file

@ -0,0 +1,123 @@
define(["globalize"], function(globalize) {
"use strict";
function parseISO8601Date(s, toLocal) {
var re = /(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(\.\d+)?(Z|([+-])(\d{2}):(\d{2}))?/,
d = s.match(re);
if (!d) throw "Couldn't parse ISO 8601 date string '" + s + "'";
var a = [1, 2, 3, 4, 5, 6, 10, 11];
for (var i in a) d[a[i]] = parseInt(d[a[i]], 10);
d[7] = parseFloat(d[7]);
var ms = Date.UTC(d[1], d[2] - 1, d[3], d[4], d[5], d[6]);
if (d[7] > 0 && (ms += Math.round(1e3 * d[7])), "Z" !== d[8] && d[10]) {
var offset = 60 * d[10] * 60 * 1e3;
d[11] && (offset += 60 * d[11] * 1e3), "-" === d[9] ? ms -= offset : ms += offset
} else !1 === toLocal && (ms += 6e4 * (new Date).getTimezoneOffset());
return new Date(ms)
}
function getDisplayRunningTime(ticks) {
var parts = [],
hours = ticks / 36e9;
hours = Math.floor(hours), hours && parts.push(hours), ticks -= 36e9 * hours;
var minutes = ticks / 6e8;
minutes = Math.floor(minutes), ticks -= 6e8 * minutes, minutes < 10 && hours && (minutes = "0" + minutes), parts.push(minutes);
var seconds = ticks / 1e7;
return seconds = Math.floor(seconds), seconds < 10 && (seconds = "0" + seconds), parts.push(seconds), parts.join(":")
}
function getOptionList(options) {
var list = [];
for (var i in options) list.push({
name: i,
value: options[i]
});
return list
}
function toLocaleString(date, options) {
if (!date) throw new Error("date cannot be null");
if (options = options || {}, toLocaleTimeStringSupportsLocales) {
var currentLocale = globalize.getCurrentDateTimeLocale();
if (currentLocale) return date.toLocaleString(currentLocale, options)
}
return date.toLocaleString()
}
function toLocaleDateString(date, options) {
if (!date) throw new Error("date cannot be null");
if (options = options || {}, toLocaleTimeStringSupportsLocales) {
var currentLocale = globalize.getCurrentDateTimeLocale();
if (currentLocale) return date.toLocaleDateString(currentLocale, options)
}
var optionList = getOptionList(options);
if (1 === optionList.length && "weekday" === optionList[0].name) {
var weekday = [];
return weekday[0] = "Sun", weekday[1] = "Mon", weekday[2] = "Tue", weekday[3] = "Wed", weekday[4] = "Thu", weekday[5] = "Fri", weekday[6] = "Sat", weekday[date.getDay()]
}
return date.toLocaleDateString()
}
function toLocaleTimeString(date, options) {
if (!date) throw new Error("date cannot be null");
if (options = options || {}, toLocaleTimeStringSupportsLocales) {
var currentLocale = globalize.getCurrentDateTimeLocale();
if (currentLocale) return date.toLocaleTimeString(currentLocale, options)
}
return date.toLocaleTimeString()
}
function getDisplayTime(date) {
if (!date) throw new Error("date cannot be null");
if ("string" === (typeof date).toString().toLowerCase()) try {
date = parseISO8601Date(date, !0)
} catch (err) {
return date
}
if (toLocaleTimeStringSupportsLocales) return toLocaleTimeString(date, {
hour: "numeric",
minute: "2-digit"
});
var time = toLocaleTimeString(date),
timeLower = time.toLowerCase();
if (-1 !== timeLower.indexOf("am") || -1 !== timeLower.indexOf("pm")) {
time = timeLower;
var hour = date.getHours() % 12,
suffix = date.getHours() > 11 ? "pm" : "am";
hour || (hour = 12);
var minutes = date.getMinutes();
minutes < 10 && (minutes = "0" + minutes), minutes = ":" + minutes, time = hour + minutes + suffix
} else {
var timeParts = time.split(":");
timeParts.length > 2 && (timeParts.length = 2, time = timeParts.join(":"))
}
return time
}
function isRelativeDay(date, offsetInDays) {
if (!date) throw new Error("date cannot be null");
var yesterday = new Date,
day = yesterday.getDate() + offsetInDays;
return yesterday.setDate(day), date.getFullYear() === yesterday.getFullYear() && date.getMonth() === yesterday.getMonth() && date.getDate() === day
}
var toLocaleTimeStringSupportsLocales = function() {
try {
(new Date).toLocaleTimeString("i")
} catch (e) {
return "RangeError" === e.name
}
return !1
}();
return {
parseISO8601Date: parseISO8601Date,
getDisplayRunningTime: getDisplayRunningTime,
toLocaleDateString: toLocaleDateString,
toLocaleString: toLocaleString,
getDisplayTime: getDisplayTime,
isRelativeDay: isRelativeDay,
toLocaleTimeString: toLocaleTimeString,
supportsLocalization: function() {
return toLocaleTimeStringSupportsLocales
}
}
});

View file

@ -0,0 +1,39 @@
define(["connectionManager", "confirm", "appRouter", "globalize"], function(connectionManager, confirm, appRouter, globalize) {
"use strict";
function alertText(options) {
return new Promise(function(resolve, reject) {
require(["alert"], function(alert) {
alert(options).then(resolve, resolve)
})
})
}
function deleteItem(options) {
var item = options.item,
itemId = item.Id,
parentId = item.SeasonId || item.SeriesId || item.ParentId,
serverId = item.ServerId,
msg = globalize.translate("sharedcomponents#ConfirmDeleteItem"),
title = globalize.translate("sharedcomponents#HeaderDeleteItem"),
apiClient = connectionManager.getApiClient(item.ServerId);
return confirm({
title: title,
text: msg,
confirmText: globalize.translate("sharedcomponents#Delete"),
primary: "cancel"
}).then(function() {
return apiClient.deleteItem(itemId).then(function() {
options.navigate && (parentId ? appRouter.showItem(parentId, serverId) : appRouter.goHome())
}, function(err) {
var result = function() {
return Promise.reject(err)
};
return alertText(globalize.translate("sharedcomponents#ErrorDeletingItem")).then(result, result)
})
})
}
return {
deleteItem: deleteItem
}
});

View file

@ -0,0 +1,46 @@
define(["dialogHelper", "dom", "layoutManager", "scrollHelper", "globalize", "require", "material-icons", "emby-button", "paper-icon-button-light", "emby-input", "formDialogStyle", "flexStyles"], function(dialogHelper, dom, layoutManager, scrollHelper, globalize, require) {
"use strict";
function showDialog(options, template) {
function onButtonClick() {
dialogResult = this.getAttribute("data-id"), dialogHelper.close(dlg)
}
var dialogOptions = {
removeOnClose: !0,
scrollY: !1
},
enableTvLayout = layoutManager.tv;
enableTvLayout && (dialogOptions.size = "fullscreen");
var dlg = dialogHelper.createDialog(dialogOptions);
dlg.classList.add("formDialog"), dlg.innerHTML = globalize.translateHtml(template, "sharedcomponents"), dlg.classList.add("align-items-center"), dlg.classList.add("justify-content-center");
var formDialogContent = dlg.querySelector(".formDialogContent");
formDialogContent.classList.add("no-grow"), enableTvLayout ? (formDialogContent.style["max-width"] = "50%", formDialogContent.style["max-height"] = "60%", scrollHelper.centerFocus.on(formDialogContent, !1)) : (formDialogContent.style.maxWidth = Math.min(150 * options.buttons.length + 200, dom.getWindowSize().innerWidth - 50) + "px", dlg.classList.add("dialog-fullscreen-lowres")), options.title ? dlg.querySelector(".formDialogHeaderTitle").innerHTML = options.title || "" : dlg.querySelector(".formDialogHeaderTitle").classList.add("hide");
var displayText = options.html || options.text || "";
dlg.querySelector(".text").innerHTML = displayText, displayText || dlg.querySelector(".dialogContentInner").classList.add("hide");
var i, length, html = "",
hasDescriptions = !1;
for (i = 0, length = options.buttons.length; i < length; i++) {
var item = options.buttons[i],
autoFocus = 0 === i ? " autofocus" : "",
buttonClass = "btnOption raised formDialogFooterItem formDialogFooterItem-autosize";
item.type && (buttonClass += " button-" + item.type), item.description && (hasDescriptions = !0), hasDescriptions && (buttonClass += " formDialogFooterItem-vertical formDialogFooterItem-nomarginbottom"), html += '<button is="emby-button" type="button" class="' + buttonClass + '" data-id="' + item.id + '"' + autoFocus + ">" + item.name + "</button>", item.description && (html += '<div class="formDialogFooterItem formDialogFooterItem-autosize fieldDescription" style="margin-top:.25em!important;margin-bottom:1.25em!important;">' + item.description + "</div>")
}
dlg.querySelector(".formDialogFooter").innerHTML = html, hasDescriptions && dlg.querySelector(".formDialogFooter").classList.add("formDialogFooter-vertical");
var dialogResult, buttons = dlg.querySelectorAll(".btnOption");
for (i = 0, length = buttons.length; i < length; i++) buttons[i].addEventListener("click", onButtonClick);
return dialogHelper.open(dlg).then(function() {
return enableTvLayout && scrollHelper.centerFocus.off(dlg.querySelector(".formDialogContent"), !1), dialogResult || Promise.reject()
})
}
return function(text, title) {
var options;
return options = "string" == typeof text ? {
title: title,
text: text
} : text, new Promise(function(resolve, reject) {
require(["text!./dialog.template.html"], function(template) {
showDialog(options, template).then(resolve, reject)
})
})
}
});

View file

@ -0,0 +1,15 @@
<div class="formDialogHeader formDialogHeader-clear justify-content-center">
<h1 class="formDialogHeaderTitle" style="margin-left:0;margin-top: .5em;padding: 0 1em;"></h1>
</div>
<div class="formDialogContent smoothScrollY">
<div class="dialogContentInner dialog-content-centered" style="padding-top:1em;padding-bottom: 1em; text-align: center;">
<div class="text">
</div>
</div>
</div>
<div class="formDialogFooter formDialogFooter-clear formDialogFooter-flex" style="padding-bottom: 1.5em;">
</div>

View file

@ -0,0 +1,267 @@
.dialogContainer {
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
justify-content: center;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 999999 !important;
contain: strict;
overflow: hidden;
overscroll-behavior: contain
}
.dialog {
margin: 0;
-webkit-border-radius: .2em;
border-radius: .2em;
-webkit-font-smoothing: antialiased;
border: 0;
padding: 0;
will-change: transform, opacity;
contain: style paint;
-webkit-box-shadow: 0 16px 24px 2px rgba(0, 0, 0, .14), 0 6px 30px 5px rgba(0, 0, 0, .12), 0 8px 10px -5px rgba(0, 0, 0, .4);
box-shadow: 0 16px 24px 2px rgba(0, 0, 0, .14), 0 6px 30px 5px rgba(0, 0, 0, .12), 0 8px 10px -5px rgba(0, 0, 0, .4)
}
.dialog-fixedSize {
-webkit-border-radius: 0;
border-radius: 0;
max-height: none;
max-width: none;
contain: layout style paint
}
.dialog-fullscreen {
position: fixed !important;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: 0;
-webkit-box-shadow: none;
box-shadow: none
}
@-webkit-keyframes scaledown {
from {
opacity: 1;
-webkit-transform: none;
transform: none
}
to {
opacity: 0;
-webkit-transform: scale(.5);
transform: scale(.5)
}
}
@keyframes scaledown {
from {
opacity: 1;
-webkit-transform: none;
transform: none
}
to {
opacity: 0;
-webkit-transform: scale(.5);
transform: scale(.5)
}
}
@-webkit-keyframes scaleup {
from {
-webkit-transform: scale(.5);
transform: scale(.5);
opacity: 0
}
to {
-webkit-transform: none;
transform: none;
opacity: 1
}
}
@keyframes scaleup {
from {
-webkit-transform: scale(.5);
transform: scale(.5);
opacity: 0
}
to {
-webkit-transform: none;
transform: none;
opacity: 1
}
}
@-webkit-keyframes fadein {
from {
opacity: 0
}
to {
opacity: 1
}
}
@keyframes fadein {
from {
opacity: 0
}
to {
opacity: 1
}
}
@-webkit-keyframes fadeout {
from {
opacity: 1
}
to {
opacity: 0
}
}
@keyframes fadeout {
from {
opacity: 1
}
to {
opacity: 0
}
}
@-webkit-keyframes slideup {
from {
opacity: 0;
-webkit-transform: translate3d(0, 30%, 0);
transform: translate3d(0, 30%, 0)
}
to {
opacity: 1;
-webkit-transform: none;
transform: none
}
}
@keyframes slideup {
from {
opacity: 0;
-webkit-transform: translate3d(0, 30%, 0);
transform: translate3d(0, 30%, 0)
}
to {
opacity: 1;
-webkit-transform: none;
transform: none
}
}
@-webkit-keyframes slidedown {
from {
opacity: 1;
-webkit-transform: none;
transform: none
}
to {
opacity: 0;
-webkit-transform: translate3d(0, 20%, 0);
transform: translate3d(0, 20%, 0)
}
}
@keyframes slidedown {
from {
opacity: 1;
-webkit-transform: none;
transform: none
}
to {
opacity: 0;
-webkit-transform: translate3d(0, 20%, 0);
transform: translate3d(0, 20%, 0)
}
}
@media all and (max-width:80em),
all and (max-height:45em) {
.dialog-fixedSize,
.dialog-fullscreen-lowres {
position: fixed !important;
top: 0 !important;
bottom: 0 !important;
left: 0 !important;
right: 0 !important;
margin: 0 !important;
-webkit-box-shadow: none;
box-shadow: none
}
}
@media all and (min-width:80em) and (min-height:45em) {
.dialog-medium {
width: 80%;
height: 80%
}
.dialog-medium-tall {
width: 80%;
height: 90%
}
.dialog-small {
width: 60%;
height: 80%
}
.dialog-fullscreen-border {
width: 90%;
height: 90%
}
}
.noScroll {
overflow-x: hidden !important;
overflow-y: hidden !important
}
.dialogBackdrop {
background-color: #000;
opacity: 0;
position: fixed !important;
top: 0 !important;
bottom: 0 !important;
left: 0 !important;
right: 0 !important;
margin: 0 !important;
z-index: 999999 !important;
-webkit-transition: opacity ease-out .2s;
-o-transition: opacity ease-out .2s;
transition: opacity ease-out .2s;
will-change: opacity
}
.dialogBackdropOpened {
opacity: .5
}

View file

@ -0,0 +1,225 @@
define(["appRouter", "focusManager", "browser", "layoutManager", "inputManager", "dom", "css!./dialoghelper.css", "scrollStyles"], function(appRouter, focusManager, browser, layoutManager, inputManager, dom) {
"use strict";
function enableAnimation() {
return !browser.tv && browser.supportsCssAnimation()
}
function removeCenterFocus(dlg) {
layoutManager.tv && (dlg.classList.contains("scrollX") ? centerFocus(dlg, !0, !1) : dlg.classList.contains("smoothScrollY") && centerFocus(dlg, !1, !1))
}
function tryRemoveElement(elem) {
var parentNode = elem.parentNode;
if (parentNode) try {
parentNode.removeChild(elem)
} catch (err) {
console.log("Error removing dialog element: " + err)
}
}
function DialogHashHandler(dlg, hash, resolve) {
function onHashChange(e) {
var isBack = self.originalUrl === window.location.href;
!isBack && isOpened(dlg) || window.removeEventListener("popstate", onHashChange), isBack && (self.closedByBack = !0, closeDialog(dlg))
}
function onBackCommand(e) {
"back" === e.detail.command && (self.closedByBack = !0, e.preventDefault(), e.stopPropagation(), closeDialog(dlg))
}
function onDialogClosed() {
if (isHistoryEnabled(dlg) || inputManager.off(dlg, onBackCommand), window.removeEventListener("popstate", onHashChange), removeBackdrop(dlg), dlg.classList.remove("opened"), removeScrollLockOnClose && document.body.classList.remove("noScroll"), !self.closedByBack && isHistoryEnabled(dlg)) {
(history.state || {}).dialogId === hash && history.back()
}
if (layoutManager.tv && focusManager.focus(activeElement), "false" !== dlg.getAttribute("data-removeonclose")) {
removeCenterFocus(dlg);
var dialogContainer = dlg.dialogContainer;
dialogContainer ? (tryRemoveElement(dialogContainer), dlg.dialogContainer = null) : tryRemoveElement(dlg)
}
setTimeout(function() {
resolve({
element: dlg,
closedByBack: self.closedByBack
})
}, 1)
}
var self = this;
self.originalUrl = window.location.href;
var activeElement = document.activeElement,
removeScrollLockOnClose = !1;
dlg.addEventListener("close", onDialogClosed), !dlg.classList.contains("dialog-fixedSize") && dlg.classList.add("centeredDialog"), dlg.classList.remove("hide"), addBackdropOverlay(dlg), dlg.classList.add("opened"), dlg.dispatchEvent(new CustomEvent("open", {
bubbles: !1,
cancelable: !1
})), "true" !== dlg.getAttribute("data-lockscroll") || document.body.classList.contains("noScroll") || (document.body.classList.add("noScroll"), removeScrollLockOnClose = !0), animateDialogOpen(dlg), isHistoryEnabled(dlg) ? (appRouter.pushState({
dialogId: hash
}, "Dialog", "#" + hash), window.addEventListener("popstate", onHashChange)) : inputManager.on(dlg, onBackCommand)
}
function addBackdropOverlay(dlg) {
var backdrop = document.createElement("div");
backdrop.classList.add("dialogBackdrop");
var backdropParent = dlg.dialogContainer || dlg;
backdropParent.parentNode.insertBefore(backdrop, backdropParent), dlg.backdrop = backdrop, backdrop.offsetWidth, backdrop.classList.add("dialogBackdropOpened"), dom.addEventListener(dlg.dialogContainer || backdrop, "click", function(e) {
e.target === dlg.dialogContainer && close(dlg)
}, {
passive: !0
})
}
function isHistoryEnabled(dlg) {
return "true" === dlg.getAttribute("data-history")
}
function open(dlg) {
globalOnOpenCallback && globalOnOpenCallback(dlg);
var parent = dlg.parentNode;
parent && parent.removeChild(dlg);
var dialogContainer = document.createElement("div");
return dialogContainer.classList.add("dialogContainer"), dialogContainer.appendChild(dlg), dlg.dialogContainer = dialogContainer, document.body.appendChild(dialogContainer), new Promise(function(resolve, reject) {
new DialogHashHandler(dlg, "dlg" + (new Date).getTime(), resolve)
})
}
function isOpened(dlg) {
return !dlg.classList.contains("hide")
}
function close(dlg) {
isOpened(dlg) && (isHistoryEnabled(dlg) ? history.back() : closeDialog(dlg))
}
function closeDialog(dlg) {
if (!dlg.classList.contains("hide")) {
dlg.dispatchEvent(new CustomEvent("closing", {
bubbles: !1,
cancelable: !1
}));
animateDialogClose(dlg, function() {
focusManager.popScope(dlg), dlg.classList.add("hide"), dlg.dispatchEvent(new CustomEvent("close", {
bubbles: !1,
cancelable: !1
}))
})
}
}
function animateDialogOpen(dlg) {
var onAnimationFinish = function() {
focusManager.pushScope(dlg), "true" === dlg.getAttribute("data-autofocus") && focusManager.autoFocus(dlg)
};
if (enableAnimation()) {
var onFinish = function() {
dom.removeEventListener(dlg, dom.whichAnimationEvent(), onFinish, {
once: !0
}), onAnimationFinish()
};
return void dom.addEventListener(dlg, dom.whichAnimationEvent(), onFinish, {
once: !0
})
}
onAnimationFinish()
}
function animateDialogClose(dlg, onAnimationFinish) {
if (enableAnimation()) {
var animated = !0;
switch (dlg.animationConfig.exit.name) {
case "fadeout":
dlg.style.animation = "fadeout " + dlg.animationConfig.exit.timing.duration + "ms ease-out normal both";
break;
case "scaledown":
dlg.style.animation = "scaledown " + dlg.animationConfig.exit.timing.duration + "ms ease-out normal both";
break;
case "slidedown":
dlg.style.animation = "slidedown " + dlg.animationConfig.exit.timing.duration + "ms ease-out normal both";
break;
default:
animated = !1
}
var onFinish = function() {
dom.removeEventListener(dlg, dom.whichAnimationEvent(), onFinish, {
once: !0
}), onAnimationFinish()
};
if (dom.addEventListener(dlg, dom.whichAnimationEvent(), onFinish, {
once: !0
}), animated) return
}
onAnimationFinish()
}
function shouldLockDocumentScroll(options) {
return !(supportsOverscrollBehavior && (options.size || !browser.touch)) && (null != options.lockScroll ? options.lockScroll : "fullscreen" === options.size || (!!options.size || browser.touch))
}
function removeBackdrop(dlg) {
var backdrop = dlg.backdrop;
if (backdrop) {
dlg.backdrop = null;
var onAnimationFinish = function() {
tryRemoveElement(backdrop)
};
if (enableAnimation()) return backdrop.classList.remove("dialogBackdropOpened"), void setTimeout(onAnimationFinish, 300);
onAnimationFinish()
}
}
function centerFocus(elem, horiz, on) {
require(["scrollHelper"], function(scrollHelper) {
var fn = on ? "on" : "off";
scrollHelper.centerFocus[fn](elem, horiz)
})
}
function createDialog(options) {
options = options || {};
var dlg = document.createElement("div");
dlg.classList.add("focuscontainer"), dlg.classList.add("hide"), shouldLockDocumentScroll(options) && dlg.setAttribute("data-lockscroll", "true"), !1 !== options.enableHistory && appRouter.enableNativeHistory() && dlg.setAttribute("data-history", "true"), !1 !== options.modal && dlg.setAttribute("modal", "modal"), !1 !== options.autoFocus && dlg.setAttribute("data-autofocus", "true");
var defaultEntryAnimation, defaultExitAnimation;
defaultEntryAnimation = "scaleup", defaultExitAnimation = "scaledown";
var entryAnimation = options.entryAnimation || defaultEntryAnimation,
exitAnimation = options.exitAnimation || defaultExitAnimation,
entryAnimationDuration = options.entryAnimationDuration || ("fullscreen" !== options.size ? 180 : 280),
exitAnimationDuration = options.exitAnimationDuration || ("fullscreen" !== options.size ? 120 : 220);
if (dlg.animationConfig = {
entry: {
name: entryAnimation,
timing: {
duration: entryAnimationDuration,
easing: "ease-out"
}
},
exit: {
name: exitAnimation,
timing: {
duration: exitAnimationDuration,
easing: "ease-out",
fill: "both"
}
}
}, dlg.classList.add("dialog"), options.scrollX ? (dlg.classList.add("scrollX"), dlg.classList.add("smoothScrollX"), layoutManager.tv && centerFocus(dlg, !0, !0)) : !1 !== options.scrollY && (dlg.classList.add("smoothScrollY"), layoutManager.tv && centerFocus(dlg, !1, !0)), options.removeOnClose && dlg.setAttribute("data-removeonclose", "true"), options.size && (dlg.classList.add("dialog-fixedSize"), dlg.classList.add("dialog-" + options.size)), enableAnimation()) switch (dlg.animationConfig.entry.name) {
case "fadein":
dlg.style.animation = "fadein " + entryAnimationDuration + "ms ease-out normal";
break;
case "scaleup":
dlg.style.animation = "scaleup " + entryAnimationDuration + "ms ease-out normal both";
break;
case "slideup":
dlg.style.animation = "slideup " + entryAnimationDuration + "ms ease-out normal";
break;
case "slidedown":
dlg.style.animation = "slidedown " + entryAnimationDuration + "ms ease-out normal"
}
return dlg
}
var globalOnOpenCallback, supportsOverscrollBehavior = "overscroll-behavior-y" in document.body.style;
return {
open: open,
close: close,
createDialog: createDialog,
setOnOpen: function(val) {
globalOnOpenCallback = val
}
}
});

View file

@ -0,0 +1,122 @@
define(["require", "browser", "layoutManager", "appSettings", "pluginManager", "apphost", "focusManager", "datetime", "globalize", "loading", "connectionManager", "skinManager", "dom", "events", "emby-select", "emby-checkbox", "emby-linkbutton"], function(require, browser, layoutManager, appSettings, pluginManager, appHost, focusManager, datetime, globalize, loading, connectionManager, skinManager, dom, events) {
"use strict";
function fillThemes(select, isDashboard) {
select.innerHTML = skinManager.getThemes().map(function(t) {
var value = t.id;
return t.isDefault && !isDashboard ? value = "" : t.isDefaultServerDashboard && isDashboard && (value = ""), '<option value="' + value + '">' + t.name + "</option>"
}).join("")
}
function loadScreensavers(context, userSettings) {
var selectScreensaver = context.querySelector(".selectScreensaver"),
options = pluginManager.ofType("screensaver").map(function(plugin) {
return {
name: plugin.name,
value: plugin.id
}
});
options.unshift({
name: globalize.translate("sharedcomponents#None"),
value: "none"
}), selectScreensaver.innerHTML = options.map(function(o) {
return '<option value="' + o.value + '">' + o.name + "</option>"
}).join(""), selectScreensaver.value = userSettings.screensaver(), selectScreensaver.value || (selectScreensaver.value = "none")
}
function loadSoundEffects(context, userSettings) {
var selectSoundEffects = context.querySelector(".selectSoundEffects"),
options = pluginManager.ofType("soundeffects").map(function(plugin) {
return {
name: plugin.name,
value: plugin.id
}
});
options.unshift({
name: globalize.translate("sharedcomponents#None"),
value: "none"
}), selectSoundEffects.innerHTML = options.map(function(o) {
return '<option value="' + o.value + '">' + o.name + "</option>"
}).join(""), selectSoundEffects.value = userSettings.soundEffects(), selectSoundEffects.value || (selectSoundEffects.value = "none")
}
function loadSkins(context, userSettings) {
var selectSkin = context.querySelector(".selectSkin"),
options = pluginManager.ofType("skin").map(function(plugin) {
return {
name: plugin.name,
value: plugin.id
}
});
selectSkin.innerHTML = options.map(function(o) {
return '<option value="' + o.value + '">' + o.name + "</option>"
}).join(""), selectSkin.value = userSettings.skin(), !selectSkin.value && options.length && (selectSkin.value = options[0].value), options.length > 1 && appHost.supports("skins") ? context.querySelector(".selectSkinContainer").classList.remove("hide") : context.querySelector(".selectSkinContainer").classList.add("hide")
}
function showOrHideMissingEpisodesField(context, user, apiClient) {
if (browser.tizen || browser.web0s) return void context.querySelector(".fldDisplayMissingEpisodes").classList.add("hide");
context.querySelector(".fldDisplayMissingEpisodes").classList.remove("hide")
}
function loadForm(context, user, userSettings, apiClient) {
apiClient.getCurrentUserId(), user.Id;
user.Policy.IsAdministrator ? context.querySelector(".selectDashboardThemeContainer").classList.remove("hide") : context.querySelector(".selectDashboardThemeContainer").classList.add("hide"), appHost.supports("displaylanguage") ? context.querySelector(".languageSection").classList.remove("hide") : context.querySelector(".languageSection").classList.add("hide"), appHost.supports("displaymode") ? context.querySelector(".fldDisplayMode").classList.remove("hide") : context.querySelector(".fldDisplayMode").classList.add("hide"), appHost.supports("externallinks") ? context.querySelector(".learnHowToContributeContainer").classList.remove("hide") : context.querySelector(".learnHowToContributeContainer").classList.add("hide"), appHost.supports("runatstartup") ? context.querySelector(".fldAutorun").classList.remove("hide") : context.querySelector(".fldAutorun").classList.add("hide"), appHost.supports("soundeffects") ? context.querySelector(".fldSoundEffects").classList.remove("hide") : context.querySelector(".fldSoundEffects").classList.add("hide"), appHost.supports("screensaver") ? context.querySelector(".selectScreensaverContainer").classList.remove("hide") : context.querySelector(".selectScreensaverContainer").classList.add("hide"), datetime.supportsLocalization() ? context.querySelector(".fldDateTimeLocale").classList.remove("hide") : context.querySelector(".fldDateTimeLocale").classList.add("hide"), browser.tizen || browser.web0s ? (context.querySelector(".fldSeasonalThemes").classList.add("hide"), context.querySelector(".fldBackdrops").classList.add("hide"), context.querySelector(".fldThemeSong").classList.add("hide"), context.querySelector(".fldThemeVideo").classList.add("hide")) : (context.querySelector(".fldSeasonalThemes").classList.remove("hide"), context.querySelector(".fldBackdrops").classList.remove("hide"), context.querySelector(".fldThemeSong").classList.remove("hide"), context.querySelector(".fldThemeVideo").classList.remove("hide")), context.querySelector(".chkRunAtStartup").checked = appSettings.runAtStartup();
var selectTheme = context.querySelector("#selectTheme"),
selectDashboardTheme = context.querySelector("#selectDashboardTheme");
fillThemes(selectTheme), fillThemes(selectDashboardTheme, !0), loadScreensavers(context, userSettings), loadSoundEffects(context, userSettings), loadSkins(context, userSettings), context.querySelector(".chkDisplayMissingEpisodes").checked = user.Configuration.DisplayMissingEpisodes || !1, context.querySelector("#chkThemeSong").checked = userSettings.enableThemeSongs(), context.querySelector("#chkThemeVideo").checked = userSettings.enableThemeVideos(), context.querySelector("#chkBackdrops").checked = userSettings.enableBackdrops(), context.querySelector("#chkSeasonalThemes").checked = userSettings.enableSeasonalThemes(), context.querySelector("#selectLanguage").value = userSettings.language() || "", context.querySelector(".selectDateTimeLocale").value = userSettings.dateTimeLocale() || "", selectDashboardTheme.value = userSettings.dashboardTheme() || "", selectTheme.value = userSettings.theme() || "", context.querySelector(".selectLayout").value = layoutManager.getSavedLayout() || "", showOrHideMissingEpisodesField(context, user, apiClient), loading.hide()
}
function saveUser(context, user, userSettingsInstance, apiClient) {
return appSettings.runAtStartup(context.querySelector(".chkRunAtStartup").checked), user.Configuration.DisplayMissingEpisodes = context.querySelector(".chkDisplayMissingEpisodes").checked, appHost.supports("displaylanguage") && userSettingsInstance.language(context.querySelector("#selectLanguage").value), userSettingsInstance.dateTimeLocale(context.querySelector(".selectDateTimeLocale").value), userSettingsInstance.enableThemeSongs(context.querySelector("#chkThemeSong").checked), userSettingsInstance.enableThemeVideos(context.querySelector("#chkThemeVideo").checked), userSettingsInstance.dashboardTheme(context.querySelector("#selectDashboardTheme").value), userSettingsInstance.theme(context.querySelector("#selectTheme").value), userSettingsInstance.soundEffects(context.querySelector(".selectSoundEffects").value), userSettingsInstance.screensaver(context.querySelector(".selectScreensaver").value), userSettingsInstance.skin(context.querySelector(".selectSkin").value), userSettingsInstance.enableBackdrops(context.querySelector("#chkBackdrops").checked), userSettingsInstance.enableSeasonalThemes(context.querySelector("#chkSeasonalThemes").checked), user.Id === apiClient.getCurrentUserId() && skinManager.setTheme(userSettingsInstance.theme()), layoutManager.setLayout(context.querySelector(".selectLayout").value), apiClient.updateUserConfiguration(user.Id, user.Configuration)
}
function save(instance, context, userId, userSettings, apiClient, enableSaveConfirmation) {
loading.show(), apiClient.getUser(userId).then(function(user) {
saveUser(context, user, userSettings, apiClient).then(function() {
loading.hide(), enableSaveConfirmation && require(["toast"], function(toast) {
toast(globalize.translate("sharedcomponents#SettingsSaved"))
}), events.trigger(instance, "saved")
}, function() {
loading.hide()
})
})
}
function onSubmit(e) {
var self = this,
apiClient = connectionManager.getApiClient(self.options.serverId),
userId = self.options.userId,
userSettings = self.options.userSettings;
return userSettings.setUserInfo(userId, apiClient).then(function() {
var enableSaveConfirmation = self.options.enableSaveConfirmation;
save(self, self.options.element, userId, userSettings, apiClient, enableSaveConfirmation)
}), e && e.preventDefault(), !1
}
function embed(options, self) {
require(["text!./displaysettings.template.html"], function(template) {
options.element.innerHTML = globalize.translateDocument(template, "sharedcomponents"), options.element.querySelector("form").addEventListener("submit", onSubmit.bind(self)), options.enableSaveButton && options.element.querySelector(".btnSave").classList.remove("hide"), self.loadData(options.autoFocus)
})
}
function DisplaySettings(options) {
this.options = options, embed(options, this)
}
return DisplaySettings.prototype.loadData = function(autoFocus) {
var self = this,
context = self.options.element;
loading.show();
var userId = self.options.userId,
apiClient = connectionManager.getApiClient(self.options.serverId),
userSettings = self.options.userSettings;
return apiClient.getUser(userId).then(function(user) {
return userSettings.setUserInfo(userId, apiClient).then(function() {
self.dataLoaded = !0, loadForm(context, user, userSettings, apiClient), autoFocus && focusManager.autoFocus(context)
})
})
}, DisplaySettings.prototype.submit = function() {
onSubmit.call(this)
}, DisplaySettings.prototype.destroy = function() {
this.options = null
}, DisplaySettings
});

View file

@ -0,0 +1,191 @@
<form style="margin: 0 auto;">
<h2 class="sectionTitle">
${Display}
</h2>
<div class="selectContainer languageSection hide">
<select id="selectLanguage" is="emby-select" label="${LabelDisplayLanguage}">
<option value="">${Auto}</option>
<option value="ar">Arabic</option>
<option value="be-BY">Belarusian (Belarus)</option>
<option value="bg-BG">Bulgarian (Bulgaria)</option>
<option value="ca">Catalan</option>
<option value="zh-CN">Chinese Simplified</option>
<option value="zh-TW">Chinese Traditional</option>
<option value="zh-HK">Chinese Traditional (Hong Kong)</option>
<option value="hr">Croatian</option>
<option value="cs">Czech</option>
<option value="da">Danish</option>
<option value="nl">Dutch</option>
<option value="en-GB">English (United Kingdom)</option>
<option value="en-US">English (United States)</option>
<option value="fi">Finnish</option>
<option value="fr">French</option>
<option value="fr-CA">French (Canada)</option>
<option value="de">German</option>
<option value="el">Greek</option>
<option value="he">Hebrew</option>
<option value="hi-IN">Hindi (India)</option>
<option value="hu">Hungarian</option>
<option value="id">Indonesian</option>
<option value="it">Italian</option>
<option value="ja">Japanese</option>
<option value="kk">Kazakh</option>
<option value="ko">Korean</option>
<option value="lt-LT">Lithuanian</option>
<option value="ms">Malay</option>
<option value="nb">Norwegian Bokmål</option>
<option value="fa">Persian</option>
<option value="pl">Polish</option>
<option value="pt-BR">Portuguese (Brazil)</option>
<option value="pt-PT">Portuguese (Portugal)</option>
<option value="ro">Romanian</option>
<option value="ru">Russian</option>
<option value="sk">Slovak</option>
<option value="sl-SI">Slovenian (Slovenia)</option>
<option value="es">Spanish</option>
<option value="es-419">Spanish (Latin America)</option>
<option value="es-MX">Spanish (Mexico)</option>
<option value="sv">Swedish</option>
<option value="gsw">Swiss German</option>
<option value="tr">Turkish</option>
<option value="uk">Ukrainian</option>
<option value="vi">Vietnamese</option>
</select>
<div class="fieldDescription">
<div>${LabelDisplayLanguageHelp}</div>
<div class="learnHowToContributeContainer hide" style="margin-top: .25em;">
<a is="emby-linkbutton" class="button-link" href="https://github.com/jellyfin/jellyfin" target="_blank">${LearnHowYouCanContribute}</a>
</div>
</div>
</div>
<div class="selectContainer fldDateTimeLocale hide">
<select is="emby-select" class="selectDateTimeLocale" label="${LabelDateTimeLocale}">
<option value="">${AutoBasedOnLanguageSetting}</option>
<option value="ar">Arabic</option>
<option value="be-BY">Belarusian (Belarus)</option>
<option value="bg-BG">Bulgarian (Bulgaria)</option>
<option value="ca">Catalan</option>
<option value="zh-CN">Chinese Simplified</option>
<option value="zh-TW">Chinese Traditional</option>
<option value="zh-HK">Chinese Traditional (Hong Kong)</option>
<option value="hr">Croatian</option>
<option value="cs">Czech</option>
<option value="da">Danish</option>
<option value="nl">Dutch</option>
<option value="en-GB">English (United Kingdom)</option>
<option value="en-US">English (United States)</option>
<option value="fi">Finnish</option>
<option value="fr">French</option>
<option value="fr-CA">French (Canada)</option>
<option value="de">German</option>
<option value="el">Greek</option>
<option value="he">Hebrew</option>
<option value="hi-IN">Hindi (India)</option>
<option value="hu">Hungarian</option>
<option value="id">Indonesian</option>
<option value="it">Italian</option>
<option value="ja">Japanese</option>
<option value="kk">Kazakh</option>
<option value="ko">Korean</option>
<option value="lt-LT">Lithuanian</option>
<option value="ms">Malay</option>
<option value="nb">Norwegian Bokmål</option>
<option value="fa">Persian</option>
<option value="pl">Polish</option>
<option value="pt-BR">Portuguese (Brazil)</option>
<option value="pt-PT">Portuguese (Portugal)</option>
<option value="ro">Romanian</option>
<option value="ru">Russian</option>
<option value="sk">Slovak</option>
<option value="sl-SI">Slovenian (Slovenia)</option>
<option value="es">Spanish</option>
<option value="es-419">Spanish (Latin America)</option>
<option value="es-MX">Spanish (Mexico)</option>
<option value="sv">Swedish</option>
<option value="gsw">Swiss German</option>
<option value="tr">Turkish</option>
<option value="uk">Ukrainian</option>
<option value="vi">Vietnamese</option>
</select>
</div>
<div class="selectContainer fldDisplayMode hide">
<select is="emby-select" class="selectLayout" label="${LabelDisplayMode}">
<option value="">${Auto}</option>
<option value="desktop">${Desktop}</option>
<option value="mobile">${Mobile}</option>
<option value="tv">${TV}</option>
</select>
<div class="fieldDescription">${DisplayModeHelp}</div>
</div>
<div class="selectContainer hide selectSkinContainer">
<select is="emby-select" class="selectSkin" label="${LabelSkin}"></select>
</div>
<div class="selectContainer">
<select id="selectTheme" is="emby-select" label="${LabelTheme}"></select>
</div>
<div class="checkboxContainer checkboxContainer-withDescription fldSeasonalThemes hide">
<label>
<input type="checkbox" is="emby-checkbox" id="chkSeasonalThemes" />
<span>${AllowSeasonalThemes}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${AllowSeasonalThemesHelp}</div>
</div>
<div class="selectContainer selectDashboardThemeContainer hide">
<select id="selectDashboardTheme" is="emby-select" label="${LabelDashboardTheme}"></select>
</div>
<div class="selectContainer hide selectScreensaverContainer">
<select is="emby-select" class="selectScreensaver" label="${LabelScreensaver}"></select>
</div>
<div class="selectContainer fldSoundEffects hide">
<select is="emby-select" class="selectSoundEffects" label="${LabelSoundEffects}"></select>
</div>
<div class="checkboxContainer checkboxContainer-withDescription fldBackdrops hide">
<label>
<input type="checkbox" is="emby-checkbox" id="chkBackdrops" />
<span>${EnableBackdrops}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${EnableBackdropsHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription fldThemeSong hide">
<label>
<input type="checkbox" is="emby-checkbox" id="chkThemeSong" />
<span>${EnableThemeSongs}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${EnableThemeSongsHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription fldThemeVideo hide">
<label>
<input type="checkbox" is="emby-checkbox" id="chkThemeVideo" />
<span>${EnableThemeVideos}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${EnableThemeVideosHelp}</div>
</div>
<div class="checkboxContainer hide fldAutorun">
<label>
<input type="checkbox" is="emby-checkbox" class="chkRunAtStartup" />
<span>${RunAtStartup}</span>
</label>
</div>
<div class="checkboxContainer checkboxContainer-withDescription fldDisplayMissingEpisodes hide">
<label>
<input type="checkbox" is="emby-checkbox" class="chkDisplayMissingEpisodes" />
<span>${DisplayMissingEpisodesWithinSeasons}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${DisplayMissingEpisodesWithinSeasonsHelp}</div>
</div>
<button is="emby-button" type="submit" class="raised button-submit block btnSave hide">
<span>${Save}</span>
</button>
</form>

View file

@ -0,0 +1,105 @@
define([], function() {
"use strict";
function parentWithAttribute(elem, name, value) {
for (; value ? elem.getAttribute(name) !== value : !elem.getAttribute(name);)
if (!(elem = elem.parentNode) || !elem.getAttribute) return null;
return elem
}
function parentWithTag(elem, tagNames) {
for (Array.isArray(tagNames) || (tagNames = [tagNames]); - 1 === tagNames.indexOf(elem.tagName || "");)
if (!(elem = elem.parentNode)) return null;
return elem
}
function containsAnyClass(classList, classNames) {
for (var i = 0, length = classNames.length; i < length; i++)
if (classList.contains(classNames[i])) return !0;
return !1
}
function parentWithClass(elem, classNames) {
for (Array.isArray(classNames) || (classNames = [classNames]); !elem.classList || !containsAnyClass(elem.classList, classNames);)
if (!(elem = elem.parentNode)) return null;
return elem
}
function addEventListenerWithOptions(target, type, handler, options) {
var optionsOrCapture = options;
supportsCaptureOption || (optionsOrCapture = options.capture), target.addEventListener(type, handler, optionsOrCapture)
}
function removeEventListenerWithOptions(target, type, handler, options) {
var optionsOrCapture = options;
supportsCaptureOption || (optionsOrCapture = options.capture), target.removeEventListener(type, handler, optionsOrCapture)
}
function clearWindowSize() {
windowSize = null
}
function getWindowSize() {
return windowSize || (windowSize = {
innerHeight: window.innerHeight,
innerWidth: window.innerWidth
}, windowSizeEventsBound || (windowSizeEventsBound = !0, addEventListenerWithOptions(window, "orientationchange", clearWindowSize, {
passive: !0
}), addEventListenerWithOptions(window, "resize", clearWindowSize, {
passive: !0
}))), windowSize
}
function whichAnimationEvent() {
if (_animationEvent) return _animationEvent;
var t, el = document.createElement("div"),
animations = {
animation: "animationend",
OAnimation: "oAnimationEnd",
MozAnimation: "animationend",
WebkitAnimation: "webkitAnimationEnd"
};
for (t in animations)
if (void 0 !== el.style[t]) return _animationEvent = animations[t], animations[t];
return _animationEvent = "animationend"
}
function whichAnimationCancelEvent() {
return whichAnimationEvent().replace("animationend", "animationcancel").replace("AnimationEnd", "AnimationCancel")
}
function whichTransitionEvent() {
if (_transitionEvent) return _transitionEvent;
var t, el = document.createElement("div"),
transitions = {
transition: "transitionend",
OTransition: "oTransitionEnd",
MozTransition: "transitionend",
WebkitTransition: "webkitTransitionEnd"
};
for (t in transitions)
if (void 0 !== el.style[t]) return _transitionEvent = transitions[t], transitions[t];
return _transitionEvent = "transitionend"
}
var supportsCaptureOption = !1;
try {
var opts = Object.defineProperty({}, "capture", {
get: function() {
supportsCaptureOption = !0
}
});
window.addEventListener("test", null, opts)
} catch (e) {}
var windowSize, windowSizeEventsBound, _animationEvent, _transitionEvent;
return {
parentWithAttribute: parentWithAttribute,
parentWithClass: parentWithClass,
parentWithTag: parentWithTag,
addEventListener: addEventListenerWithOptions,
removeEventListener: removeEventListenerWithOptions,
getWindowSize: getWindowSize,
whichTransitionEvent: whichTransitionEvent,
whichAnimationEvent: whichAnimationEvent,
whichAnimationCancelEvent: whichAnimationCancelEvent
}
});

View file

@ -0,0 +1,222 @@
.emby-button,
.fab {
-webkit-box-sizing: border-box;
-webkit-box-align: center
}
.button-flat,
.button-link {
background: 0 0
}
.emby-button,
.paper-icon-button-light {
text-align: center;
font-family: inherit;
color: inherit;
outline: 0 !important;
-webkit-tap-highlight-color: transparent;
position: relative
}
.emby-button {
display: -webkit-inline-box;
display: -webkit-inline-flex;
display: inline-flex;
-webkit-align-items: center;
align-items: center;
box-sizing: border-box;
margin: 0 .29em;
font-size: inherit;
-moz-user-select: none;
-ms-user-select: none;
-webkit-user-select: none;
user-select: none;
cursor: pointer;
z-index: 0;
padding: .86em 1em;
border: 0;
vertical-align: middle;
-webkit-border-radius: .2em;
border-radius: .2em;
font-weight: 600;
text-decoration: none;
line-height: 1.35
}
.emby-button::-moz-focus-inner {
border: 0
}
.button-flat:hover {
opacity: .5
}
.button-link {
margin: 0;
padding: 0;
vertical-align: initial
}
.button-link-inline {
display: inline
}
.button-link:hover {
text-decoration: underline
}
.emby-button-focusscale {
-webkit-transition: -webkit-transform 180ms ease-out !important;
-o-transition: transform 180ms ease-out !important;
transition: transform 180ms ease-out !important;
-webkit-transform-origin: center center;
transform-origin: center center
}
.emby-button-focusscale:focus {
-webkit-transform: scale(1.16);
transform: scale(1.16);
z-index: 1
}
.emby-button>i {
font-size: 1.36em
}
.button-link>i {
font-size: 1em
}
.fab {
display: -webkit-inline-box;
display: -webkit-inline-flex;
display: inline-flex;
-webkit-border-radius: 50%;
border-radius: 50%;
padding: .6em;
box-sizing: border-box;
-webkit-align-items: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
justify-content: center;
text-align: center
}
.emby-button.block {
display: block;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
justify-content: center;
margin: .25em 0;
width: 100%
}
.paper-icon-button-light {
display: -webkit-inline-box;
display: -webkit-inline-flex;
display: inline-flex;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
-webkit-box-sizing: border-box;
box-sizing: border-box;
margin: 0 .29em;
background: 0 0;
font-size: inherit;
-moz-user-select: none;
-ms-user-select: none;
-webkit-user-select: none;
user-select: none;
cursor: pointer;
z-index: 0;
min-width: initial;
min-height: initial;
width: auto;
height: auto;
padding: .556em;
border: 0;
vertical-align: middle;
overflow: hidden;
-webkit-border-radius: 50%;
border-radius: 50%;
-webkit-box-pack: center;
-webkit-justify-content: center;
justify-content: center
}
.paper-icon-button-light::-moz-focus-inner {
border: 0
}
.paper-icon-button-light[disabled] {
opacity: .3
}
.paper-icon-button-light>i {
font-size: 1.66956521739130434em;
position: relative;
z-index: 1;
vertical-align: middle
}
.paper-icon-button-light>img {
width: 1.72em;
max-height: 100%;
position: relative;
z-index: 1;
vertical-align: middle
}
.emby-button-foreground {
position: relative;
z-index: 1
}
.icon-button-focusscale {
-webkit-transition: -webkit-transform 180ms ease-out !important;
-o-transition: transform 180ms ease-out !important;
transition: transform 180ms ease-out !important;
-webkit-transform-origin: center center;
transform-origin: center center
}
.icon-button-focusscale:focus {
-webkit-transform: scale(1.3);
transform: scale(1.3);
z-index: 1
}
.btnFilterWithBubble {
position: relative
}
.filterButtonBubble {
color: #fff;
position: absolute;
top: 0;
right: 0;
width: 1.6em;
height: 1.6em;
z-index: 100000000;
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
justify-content: center;
font-size: 82%;
-webkit-border-radius: 100em;
border-radius: 100em;
-webkit-box-shadow: 0 4px 5px 0 rgba(0, 0, 0, .14), 0 1px 10px 0 rgba(0, 0, 0, .12), 0 2px 4px -1px rgba(0, 0, 0, .2);
box-shadow: 0 4px 5px 0 rgba(0, 0, 0, .14), 0 1px 10px 0 rgba(0, 0, 0, .12), 0 2px 4px -1px rgba(0, 0, 0, .2);
background: #03A9F4;
font-weight: 700
}

View file

@ -0,0 +1,29 @@
define(["browser", "dom", "layoutManager", "shell", "appRouter", "apphost", "css!./emby-button", "registerElement"], function(browser, dom, layoutManager, shell, appRouter, appHost) {
"use strict";
function openPremiumInfo() {
require(["registrationServices"], function(registrationServices) {
registrationServices.showPremiereInfo()
})
}
function onAnchorClick(e) {
var href = this.getAttribute("href") || "";
"#" !== href ? this.getAttribute("target") ? -1 === href.indexOf("emby.media/premiere") || appHost.supports("externalpremium") ? appHost.supports("targetblank") || (e.preventDefault(), shell.openUrl(href)) : (e.preventDefault(), openPremiumInfo()) : appRouter.handleAnchorClick(e) : e.preventDefault()
}
var EmbyButtonPrototype = Object.create(HTMLButtonElement.prototype),
EmbyLinkButtonPrototype = Object.create(HTMLAnchorElement.prototype);
return EmbyButtonPrototype.createdCallback = function() {
this.classList.contains("emby-button") || (this.classList.add("emby-button"), browser.firefox && this.classList.add("button-link-inline"), layoutManager.tv && ("false" !== this.getAttribute("data-focusscale") && this.classList.add("emby-button-focusscale"), this.classList.add("emby-button-tv")))
}, EmbyButtonPrototype.attachedCallback = function() {
"A" === this.tagName && (dom.removeEventListener(this, "click", onAnchorClick, {}), dom.addEventListener(this, "click", onAnchorClick, {}), "true" === this.getAttribute("data-autohide") && (appHost.supports("externallinks") ? this.classList.remove("hide") : this.classList.add("hide")))
}, EmbyButtonPrototype.detachedCallback = function() {
dom.removeEventListener(this, "click", onAnchorClick, {})
}, EmbyLinkButtonPrototype.createdCallback = EmbyButtonPrototype.createdCallback, EmbyLinkButtonPrototype.attachedCallback = EmbyButtonPrototype.attachedCallback, document.registerElement("emby-button", {
prototype: EmbyButtonPrototype,
extends: "button"
}), document.registerElement("emby-linkbutton", {
prototype: EmbyLinkButtonPrototype,
extends: "a"
}), EmbyButtonPrototype
});

View file

@ -0,0 +1,10 @@
define(["layoutManager", "css!./emby-button", "registerElement"], function(layoutManager) {
"use strict";
var EmbyButtonPrototype = Object.create(HTMLButtonElement.prototype);
EmbyButtonPrototype.createdCallback = function() {
this.classList.add("paper-icon-button-light"), layoutManager.tv && this.classList.add("icon-button-focusscale")
}, document.registerElement("paper-icon-button-light", {
prototype: EmbyButtonPrototype,
extends: "button"
})
});

View file

@ -0,0 +1,161 @@
.emby-checkbox-label {
position: relative;
z-index: 1;
vertical-align: middle;
display: -webkit-inline-box;
display: -webkit-inline-flex;
display: inline-flex;
-webkit-box-sizing: border-box;
box-sizing: border-box;
width: 100%;
margin: 0;
padding: 0 0 0 2.4em;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
height: 2.35em;
cursor: pointer
}
.checkboxContainer,
.checkboxListContainer {
margin-bottom: 1.8em
}
.checkboxFieldDescription {
padding-left: 2.4em
}
.checkboxContainer {
display: -webkit-box;
display: -webkit-flex;
display: flex
}
.checkboxContainer-withDescription {
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-webkit-flex-direction: column;
flex-direction: column
}
.emby-checkbox {
position: absolute;
width: 1px;
height: 1px;
margin: 0;
padding: 0;
opacity: 0;
-ms-appearance: none;
-moz-appearance: none;
-webkit-appearance: none;
appearance: none;
border: none
}
.checkboxOutline {
position: absolute;
top: 3px;
left: 0;
-webkit-box-sizing: border-box;
box-sizing: border-box;
width: 1.83em;
height: 1.83em;
margin: 0;
overflow: hidden;
border: 2px solid currentcolor;
-webkit-border-radius: .14em;
border-radius: .14em;
z-index: 2;
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
justify-content: center
}
.emby-checkbox-focushelper {
position: absolute;
top: -.915em;
left: -.915em;
width: 3.66em;
height: 3.66em;
display: inline-block;
-webkit-box-sizing: border-box;
box-sizing: border-box;
margin: 3px 0 0;
-webkit-border-radius: 50%;
border-radius: 50%;
background-color: transparent
}
.checkboxIcon {
font-size: 1.6em;
color: #fff
}
.checkboxIcon-checked {
display: none
}
.emby-checkbox:checked+span+span+.checkboxOutline>.checkboxIcon-checked {
display: -webkit-box !important;
display: -webkit-flex !important;
display: flex !important
}
.emby-checkbox:checked+span+span+.checkboxOutline>.checkboxIcon-unchecked {
display: none !important
}
.emby-checkbox:checked[disabled]+span+span+.checkboxOutline>.checkboxIcon {
background-color: rgba(0, 0, 0, .26)
}
.checkboxLabel {
position: relative;
margin: 0
}
.checkboxList>.emby-checkbox-label {
display: -webkit-box;
display: -webkit-flex;
display: flex;
margin: .5em 0
}
.checkboxList-verticalwrap {
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-flex-wrap: wrap;
flex-wrap: wrap
}
.checkboxList-verticalwrap>.emby-checkbox-label {
display: -webkit-inline-box;
display: -webkit-inline-flex;
display: inline-flex;
margin: .3em 0;
width: 12em
}
.checkboxList-paperList {
padding: 1em !important
}
.checkboxListLabel {
margin-bottom: .25em
}
@-webkit-keyframes repaintChrome {
from,
to {
padding: 0
}
}

View file

@ -0,0 +1,55 @@
define(["browser", "dom", "css!./emby-checkbox", "registerElement"], function(browser, dom) {
"use strict";
function onKeyDown(e) {
if (13 === e.keyCode) return e.preventDefault(), this.checked = !this.checked, this.dispatchEvent(new CustomEvent("change", {
bubbles: !0
})), !1
}
function forceRefresh(loading) {
var elem = this.parentNode;
elem.style.webkitAnimationName = "repaintChrome", elem.style.webkitAnimationDelay = !0 === loading ? "500ms" : "", elem.style.webkitAnimationDuration = "10ms", elem.style.webkitAnimationIterationCount = "1", setTimeout(function() {
elem.style.webkitAnimationName = ""
}, !0 === loading ? 520 : 20)
}
var EmbyCheckboxPrototype = Object.create(HTMLInputElement.prototype),
enableRefreshHack = !!(browser.tizen || browser.orsay || browser.operaTv || browser.web0s);
EmbyCheckboxPrototype.attachedCallback = function() {
if ("true" !== this.getAttribute("data-embycheckbox")) {
this.setAttribute("data-embycheckbox", "true"), this.classList.add("emby-checkbox");
var labelElement = this.parentNode;
labelElement.classList.add("emby-checkbox-label");
var labelTextElement = labelElement.querySelector("span"),
outlineClass = "checkboxOutline",
customClass = this.getAttribute("data-outlineclass");
customClass && (outlineClass += " " + customClass);
var checkedIcon = this.getAttribute("data-checkedicon") || "&#xE5CA;",
uncheckedIcon = this.getAttribute("data-uncheckedicon") || "",
checkHtml = '<i class="md-icon checkboxIcon checkboxIcon-checked">' + checkedIcon + "</i>",
uncheckedHtml = '<i class="md-icon checkboxIcon checkboxIcon-unchecked">' + uncheckedIcon + "</i>";
labelElement.insertAdjacentHTML("beforeend", '<span class="emby-checkbox-focushelper"></span><span class="' + outlineClass + '">' + checkHtml + uncheckedHtml + "</span>"), labelTextElement.classList.add("checkboxLabel"), this.addEventListener("keydown", onKeyDown), enableRefreshHack && (forceRefresh.call(this, !0), dom.addEventListener(this, "click", forceRefresh, {
passive: !0
}), dom.addEventListener(this, "blur", forceRefresh, {
passive: !0
}), dom.addEventListener(this, "focus", forceRefresh, {
passive: !0
}), dom.addEventListener(this, "change", forceRefresh, {
passive: !0
}))
}
}, EmbyCheckboxPrototype.detachedCallback = function() {
this.removeEventListener("keydown", onKeyDown), dom.removeEventListener(this, "click", forceRefresh, {
passive: !0
}), dom.removeEventListener(this, "blur", forceRefresh, {
passive: !0
}), dom.removeEventListener(this, "focus", forceRefresh, {
passive: !0
}), dom.removeEventListener(this, "change", forceRefresh, {
passive: !0
})
}, document.registerElement("emby-checkbox", {
prototype: EmbyCheckboxPrototype,
extends: "input"
})
});

View file

@ -0,0 +1,56 @@
.emby-collapse {
margin: .5em 0
}
.collapseContent {
border-width: 0;
padding: 1.25em;
height: 0;
-webkit-transition-property: height;
-o-transition-property: height;
transition-property: height;
-webkit-transition-duration: .3s;
-o-transition-duration: .3s;
transition-duration: .3s;
overflow: hidden
}
.emby-collapsible-button {
margin: 0;
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
width: 100%;
text-align: left;
text-transform: none;
border-width: 0 0 .1em;
border-style: solid;
padding-left: .1em;
background: 0 0;
-webkit-box-shadow: none;
box-shadow: none
}
.emby-collapse-expandIcon {
-webkit-transform-origin: 50% 50%;
transform-origin: 50% 50%;
-webkit-transition: -webkit-transform 180ms ease-out;
-o-transition: transform 180ms ease-out;
transition: transform 180ms ease-out;
position: absolute;
right: .5em;
font-size: 1.5em
}
.emby-collapse-expandIconExpanded {
-webkit-transform: rotate(180deg);
transform: rotate(180deg)
}
.emby-collapsible-title {
margin: 0;
padding: 0
}

View file

@ -0,0 +1,43 @@
define(["browser", "css!./emby-collapse", "registerElement", "emby-button"], function(browser) {
"use strict";
function slideDownToShow(button, elem) {
elem.classList.remove("hide"), elem.classList.add("expanded"), elem.style.height = "auto";
var height = elem.offsetHeight + "px";
elem.style.height = "0";
elem.offsetHeight;
elem.style.height = height, setTimeout(function() {
elem.classList.contains("expanded") ? elem.classList.remove("hide") : elem.classList.add("hide"), elem.style.height = "auto"
}, 300), button.querySelector("i").classList.add("emby-collapse-expandIconExpanded")
}
function slideUpToHide(button, elem) {
elem.style.height = elem.offsetHeight + "px";
elem.offsetHeight;
elem.classList.remove("expanded"), elem.style.height = "0", setTimeout(function() {
elem.classList.contains("expanded") ? elem.classList.remove("hide") : elem.classList.add("hide")
}, 300), button.querySelector("i").classList.remove("emby-collapse-expandIconExpanded")
}
function onButtonClick(e) {
var button = this,
collapseContent = button.parentNode.querySelector(".collapseContent");
collapseContent.expanded ? (collapseContent.expanded = !1, slideUpToHide(button, collapseContent)) : (collapseContent.expanded = !0, slideDownToShow(button, collapseContent))
}
var EmbyButtonPrototype = Object.create(HTMLDivElement.prototype);
EmbyButtonPrototype.attachedCallback = function() {
if (!this.classList.contains("emby-collapse")) {
this.classList.add("emby-collapse");
var collapseContent = this.querySelector(".collapseContent");
collapseContent && collapseContent.classList.add("hide");
var title = this.getAttribute("title"),
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="md-icon emby-collapse-expandIcon">expand_more</i></button>';
this.insertAdjacentHTML("afterbegin", html);
var button = this.querySelector(".emby-collapsible-button");
button.addEventListener("click", onButtonClick), "true" === this.getAttribute("data-expanded") && onButtonClick.call(button)
}
}, document.registerElement("emby-collapse", {
prototype: EmbyButtonPrototype,
extends: "div"
})
});

View file

@ -0,0 +1,106 @@
define(["globalize", "apphost", "loading", "alert", "emby-linkbutton"], function(globalize, appHost, loading, alert) {
"use strict";
function resolvePromise() {
return Promise.resolve()
}
function rejectPromise() {
return Promise.reject()
}
function showNewUserInviteMessage(result) {
if (!result.IsNewUserInvitation && !result.IsPending) return Promise.resolve();
var message = result.IsNewUserInvitation ? globalize.translate("sharedcomponents#MessageInvitationSentToNewUser", result.GuestDisplayName) : globalize.translate("sharedcomponents#MessageInvitationSentToUser", result.GuestDisplayName);
return alert({
text: message,
title: globalize.translate("sharedcomponents#HeaderInvitationSent")
}).then(resolvePromise, resolvePromise)
}
function inviteGuest(options) {
var apiClient = options.apiClient;
return loading.show(), apiClient.ajax({
type: "POST",
url: apiClient.getUrl("Connect/Invite"),
dataType: "json",
data: options.guestOptions || {}
}).then(function(result) {
return loading.hide(), showNewUserInviteMessage(result)
}, function(response) {
loading.hide();
var statusCode = response ? response.status : 0;
return 502 === statusCode ? showConnectServerUnreachableErrorMessage().then(rejectPromise, rejectPromise) : 404 === statusCode ? alert({
text: globalize.translate("sharedcomponents#GuestUserNotFound")
}).then(rejectPromise, rejectPromise) : (statusCode || 0) >= 500 ? alert({
text: globalize.translate("sharedcomponents#ErrorReachingEmbyConnect")
}).then(rejectPromise, rejectPromise) : showGuestGeneralErrorMessage().then(rejectPromise, rejectPromise)
})
}
function showGuestGeneralErrorMessage() {
var html;
appHost.supports("externallinks") && (html = globalize.translate("sharedcomponents#ErrorAddingGuestAccount1", '<a is="emby-linkbutton" class="button-link" href="https://github.com/jellyfin/jellyfin" target="_blank">https://github.com/jellyfin/jellyfin</a>'), html += "<br/><br/>" + globalize.translate("sharedcomponents#ErrorAddingGuestAccount2", "apps@emby.media"));
var text = globalize.translate("sharedcomponents#ErrorAddingGuestAccount1", "https://github.com/jellyfin/jellyfin");
return text += "\n\n" + globalize.translate("sharedcomponents#ErrorAddingGuestAccount2", "apps@emby.media"), alert({
text: text,
html: html
})
}
function showConnectServerUnreachableErrorMessage() {
var text = globalize.translate("sharedcomponents#ErrorConnectServerUnreachable", "https://connect.emby.media");
return alert({
text: text
})
}
function showLinkUserErrorMessage(username, statusCode) {
var html, text;
return 502 === statusCode ? showConnectServerUnreachableErrorMessage() : (username ? (appHost.supports("externallinks") && (html = globalize.translate("sharedcomponents#ErrorAddingEmbyConnectAccount1", '<a is="emby-linkbutton" class="button-link" href="https://github.com/jellyfin/jellyfin" target="_blank">https://github.com/jellyfin/jellyfin</a>'), html += "<br/><br/>" + globalize.translate("sharedcomponents#ErrorAddingEmbyConnectAccount2", "apps@emby.media")), text = globalize.translate("sharedcomponents#ErrorAddingEmbyConnectAccount1", "https://github.com/jellyfin/jellyfin"), text += "\n\n" + globalize.translate("sharedcomponents#ErrorAddingEmbyConnectAccount2", "apps@emby.media")) : html = text = globalize.translate("sharedcomponents#DefaultErrorMessage"), alert({
text: text,
html: html
}))
}
function updateUserLink(apiClient, user, newConnectUsername) {
var currentConnectUsername = user.ConnectUserName || "",
enteredConnectUsername = newConnectUsername,
linkUrl = apiClient.getUrl("Users/" + user.Id + "/Connect/Link");
return currentConnectUsername && !enteredConnectUsername ? apiClient.ajax({
type: "DELETE",
url: linkUrl
}).then(function() {
return alert({
text: globalize.translate("sharedcomponents#MessageEmbyAccontRemoved"),
title: globalize.translate("sharedcomponents#HeaderEmbyAccountRemoved")
}).catch(resolvePromise)
}, function(response) {
return 502 === (response ? response.status : 0) ? showConnectServerUnreachableErrorMessage().then(rejectPromise) : alert({
text: globalize.translate("sharedcomponents#ErrorRemovingEmbyConnectAccount")
}).then(rejectPromise)
}) : currentConnectUsername !== enteredConnectUsername ? apiClient.ajax({
type: "POST",
url: linkUrl,
data: {
ConnectUsername: enteredConnectUsername
},
dataType: "json"
}).then(function(result) {
var msgKey = result.IsPending ? "sharedcomponents#MessagePendingEmbyAccountAdded" : "sharedcomponents#MessageEmbyAccountAdded";
return alert({
text: globalize.translate(msgKey),
title: globalize.translate("sharedcomponents#HeaderEmbyAccountAdded")
}).catch(resolvePromise)
}, function(response) {
var statusCode = response ? response.status : 0;
return 502 === statusCode ? showConnectServerUnreachableErrorMessage().then(rejectPromise) : showLinkUserErrorMessage(".", statusCode).then(rejectPromise)
}) : Promise.reject()
}
return {
inviteGuest: inviteGuest,
updateUserLink: updateUserLink,
showLinkUserErrorMessage: showLinkUserErrorMessage,
showConnectServerUnreachableErrorMessage: showConnectServerUnreachableErrorMessage
}
});

View file

@ -0,0 +1,36 @@
.emby-input {
display: block;
margin: 0;
margin-bottom: 0 !important;
font-size: 110%;
font-family: inherit;
font-weight: inherit;
padding: .4em .25em;
-webkit-box-sizing: border-box;
box-sizing: border-box;
outline: 0 !important;
-webkit-tap-highlight-color: transparent;
width: 100%
}
.emby-input::-moz-focus-inner {
border: 0
}
.inputContainer {
margin-bottom: 1.8em
}
.inputLabel {
display: inline-block;
margin-bottom: .25em
}
.emby-input+.fieldDescription {
margin-top: .25em
}
.emby-input-iconbutton {
-webkit-align-self: flex-end;
align-self: flex-end
}

View file

@ -0,0 +1,56 @@
define(["layoutManager", "browser", "dom", "css!./emby-input", "registerElement"], function(layoutManager, browser, dom) {
"use strict";
function onChange() {
var label = this.labelElement;
if (this.value) label.classList.remove("inputLabel-float");
else {
supportsFloatingLabel && "date" !== this.type && "time" !== this.type && label.classList.add("inputLabel-float")
}
}
var EmbyInputPrototype = Object.create(HTMLInputElement.prototype),
inputId = 0,
supportsFloatingLabel = !1;
if (Object.getOwnPropertyDescriptor && Object.defineProperty) {
var descriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value");
if (descriptor && descriptor.configurable) {
var baseSetMethod = descriptor.set;
descriptor.set = function(value) {
baseSetMethod.call(this, value), this.dispatchEvent(new CustomEvent("valueset", {
bubbles: !1,
cancelable: !1
}))
}, Object.defineProperty(HTMLInputElement.prototype, "value", descriptor), supportsFloatingLabel = !0
}
}
EmbyInputPrototype.createdCallback = function() {
if (this.id || (this.id = "embyinput" + inputId, inputId++), !this.classList.contains("emby-input")) {
this.classList.add("emby-input");
var parentNode = this.parentNode,
document = this.ownerDocument,
label = document.createElement("label");
label.innerHTML = this.getAttribute("label") || "", label.classList.add("inputLabel"), label.classList.add("inputLabelUnfocused"), label.htmlFor = this.id, parentNode.insertBefore(label, this), this.labelElement = label, dom.addEventListener(this, "focus", function() {
onChange.call(this), document.attachIME && document.attachIME(this), label.classList.add("inputLabelFocused"), label.classList.remove("inputLabelUnfocused")
}, {
passive: !0
}), dom.addEventListener(this, "blur", function() {
onChange.call(this), label.classList.remove("inputLabelFocused"), label.classList.add("inputLabelUnfocused")
}, {
passive: !0
}), dom.addEventListener(this, "change", onChange, {
passive: !0
}), dom.addEventListener(this, "input", onChange, {
passive: !0
}), dom.addEventListener(this, "valueset", onChange, {
passive: !0
}), browser.orsay && this === document.activeElement && document.attachIME && document.attachIME(this)
}
}, EmbyInputPrototype.attachedCallback = function() {
this.labelElement.htmlFor = this.id, onChange.call(this)
}, EmbyInputPrototype.label = function(text) {
this.labelElement.innerHTML = text
}, document.registerElement("emby-input", {
prototype: EmbyInputPrototype,
extends: "input"
})
});

View file

@ -0,0 +1,32 @@
define(["emby-progressring", "dom", "serverNotifications", "events", "registerElement"], function(EmbyProgressRing, dom, serverNotifications, events) {
"use strict";
function addNotificationEvent(instance, name, handler) {
var localHandler = handler.bind(instance);
events.on(serverNotifications, name, localHandler), instance[name] = localHandler
}
function removeNotificationEvent(instance, name) {
var handler = instance[name];
handler && (events.off(serverNotifications, name, handler), instance[name] = null)
}
function onRefreshProgress(e, apiClient, info) {
var indicator = this;
if (indicator.itemId || (indicator.itemId = dom.parentWithAttribute(indicator, "data-id").getAttribute("data-id")), info.ItemId === indicator.itemId) {
var progress = parseFloat(info.Progress);
progress && progress < 100 ? this.classList.remove("hide") : this.classList.add("hide"), this.setProgress(progress)
}
}
var EmbyItemRefreshIndicatorPrototype = Object.create(EmbyProgressRing);
EmbyItemRefreshIndicatorPrototype.createdCallback = function() {
EmbyProgressRing.createdCallback && EmbyProgressRing.createdCallback.call(this), addNotificationEvent(this, "RefreshProgress", onRefreshProgress)
}, EmbyItemRefreshIndicatorPrototype.attachedCallback = function() {
EmbyProgressRing.attachedCallback && EmbyProgressRing.attachedCallback.call(this)
}, EmbyItemRefreshIndicatorPrototype.detachedCallback = function() {
EmbyProgressRing.detachedCallback && EmbyProgressRing.detachedCallback.call(this), removeNotificationEvent(this, "RefreshProgress"), this.itemId = null
}, document.registerElement("emby-itemrefreshindicator", {
prototype: EmbyItemRefreshIndicatorPrototype,
extends: "div"
})
});

View file

@ -0,0 +1,220 @@
define(["itemShortcuts", "inputManager", "connectionManager", "playbackManager", "imageLoader", "layoutManager", "browser", "dom", "loading", "focusManager", "serverNotifications", "events", "registerElement"], function(itemShortcuts, inputManager, connectionManager, playbackManager, imageLoader, layoutManager, browser, dom, loading, focusManager, serverNotifications, events) {
"use strict";
function onClick(e) {
var itemsContainer = this,
multiSelect = (e.target, itemsContainer.multiSelect);
multiSelect && !1 === multiSelect.onContainerClick.call(itemsContainer, e) || itemShortcuts.onClick.call(itemsContainer, e)
}
function disableEvent(e) {
return e.preventDefault(), e.stopPropagation(), !1
}
function onContextMenu(e) {
var target = e.target,
card = dom.parentWithAttribute(target, "data-id");
if (card && card.getAttribute("data-serverid")) return inputManager.trigger("menu", {
sourceElement: card
}), e.preventDefault(), e.stopPropagation(), !1
}
function getShortcutOptions() {
return {
click: !1
}
}
function onDrop(evt, itemsContainer) {
var el = evt.item,
newIndex = evt.newIndex,
itemId = el.getAttribute("data-playlistitemid"),
playlistId = el.getAttribute("data-playlistid");
if (!playlistId) {
var oldIndex = evt.oldIndex;
return void el.dispatchEvent(new CustomEvent("itemdrop", {
detail: {
oldIndex: oldIndex,
newIndex: newIndex,
playlistItemId: itemId
},
bubbles: !0,
cancelable: !1
}))
}
var serverId = el.getAttribute("data-serverid"),
apiClient = connectionManager.getApiClient(serverId);
loading.show(), apiClient.ajax({
url: apiClient.getUrl("Playlists/" + playlistId + "/Items/" + itemId + "/Move/" + newIndex),
type: "POST"
}).then(function() {
loading.hide()
}, function() {
loading.hide(), itemsContainer.refreshItems()
})
}
function onUserDataChanged(e, apiClient, userData) {
var itemsContainer = this;
require(["cardBuilder"], function(cardBuilder) {
cardBuilder.onUserDataChanged(userData, itemsContainer)
});
var eventsToMonitor = getEventsToMonitor(itemsContainer); - 1 !== eventsToMonitor.indexOf("markfavorite") ? itemsContainer.notifyRefreshNeeded() : -1 !== eventsToMonitor.indexOf("markplayed") && itemsContainer.notifyRefreshNeeded()
}
function getEventsToMonitor(itemsContainer) {
var monitor = itemsContainer.getAttribute("data-monitor");
return monitor ? monitor.split(",") : []
}
function onTimerCreated(e, apiClient, data) {
var itemsContainer = this;
if (-1 !== getEventsToMonitor(itemsContainer).indexOf("timers")) return void itemsContainer.notifyRefreshNeeded();
var programId = data.ProgramId,
newTimerId = data.Id;
require(["cardBuilder"], function(cardBuilder) {
cardBuilder.onTimerCreated(programId, newTimerId, itemsContainer)
})
}
function onSeriesTimerCreated(e, apiClient, data) {
var itemsContainer = this;
if (-1 !== getEventsToMonitor(itemsContainer).indexOf("seriestimers")) return void itemsContainer.notifyRefreshNeeded()
}
function onTimerCancelled(e, apiClient, data) {
var itemsContainer = this;
if (-1 !== getEventsToMonitor(itemsContainer).indexOf("timers")) return void itemsContainer.notifyRefreshNeeded();
var id = data.Id;
require(["cardBuilder"], function(cardBuilder) {
cardBuilder.onTimerCancelled(id, itemsContainer)
})
}
function onSeriesTimerCancelled(e, apiClient, data) {
var itemsContainer = this;
if (-1 !== getEventsToMonitor(itemsContainer).indexOf("seriestimers")) return void itemsContainer.notifyRefreshNeeded();
var id = data.Id;
require(["cardBuilder"], function(cardBuilder) {
cardBuilder.onSeriesTimerCancelled(id, itemsContainer)
})
}
function onLibraryChanged(e, apiClient, data) {
var itemsContainer = this,
eventsToMonitor = getEventsToMonitor(itemsContainer);
if (-1 === eventsToMonitor.indexOf("seriestimers") && -1 === eventsToMonitor.indexOf("timers")) {
var itemsAdded = data.ItemsAdded || [],
itemsRemoved = data.ItemsRemoved || [];
if (itemsAdded.length || itemsRemoved.length) {
var parentId = itemsContainer.getAttribute("data-parentid");
if (parentId) {
var foldersAddedTo = data.FoldersAddedTo || [],
foldersRemovedFrom = data.FoldersRemovedFrom || [],
collectionFolders = data.CollectionFolders || [];
if (-1 === foldersAddedTo.indexOf(parentId) && -1 === foldersRemovedFrom.indexOf(parentId) && -1 === collectionFolders.indexOf(parentId)) return
}
itemsContainer.notifyRefreshNeeded()
}
}
}
function onPlaybackStopped(e, stopInfo) {
var itemsContainer = this,
state = stopInfo.state,
eventsToMonitor = getEventsToMonitor(itemsContainer);
if (state.NowPlayingItem && "Video" === state.NowPlayingItem.MediaType) {
if (-1 !== eventsToMonitor.indexOf("videoplayback")) return void itemsContainer.notifyRefreshNeeded(!0)
} else if (state.NowPlayingItem && "Audio" === state.NowPlayingItem.MediaType && -1 !== eventsToMonitor.indexOf("audioplayback")) return void itemsContainer.notifyRefreshNeeded(!0)
}
function addNotificationEvent(instance, name, handler, owner) {
var localHandler = handler.bind(instance);
owner = owner || serverNotifications, events.on(owner, name, localHandler), instance["event_" + name] = localHandler
}
function removeNotificationEvent(instance, name, owner) {
var handler = instance["event_" + name];
handler && (owner = owner || serverNotifications, events.off(owner, name, handler), instance["event_" + name] = null)
}
function clearRefreshInterval(itemsContainer, isPausing) {
itemsContainer.refreshInterval && (clearInterval(itemsContainer.refreshInterval), itemsContainer.refreshInterval = null, isPausing || (itemsContainer.refreshIntervalEndTime = null))
}
function resetRefreshInterval(itemsContainer, intervalMs) {
clearRefreshInterval(itemsContainer), intervalMs || (intervalMs = parseInt(itemsContainer.getAttribute("data-refreshinterval") || "0")), intervalMs && (itemsContainer.refreshInterval = setInterval(itemsContainer.notifyRefreshNeeded.bind(itemsContainer), intervalMs), itemsContainer.refreshIntervalEndTime = (new Date).getTime() + intervalMs)
}
function onDataFetched(result) {
var items = result.Items || result,
parentContainer = this.parentContainer;
parentContainer && (items.length ? parentContainer.classList.remove("hide") : parentContainer.classList.add("hide"));
var focusId, hasActiveElement, activeElement = document.activeElement;
this.contains(activeElement) && (hasActiveElement = !0, focusId = activeElement.getAttribute("data-id")), this.innerHTML = this.getItemsHtml(items), imageLoader.lazyChildren(this), hasActiveElement && setFocus(this, focusId), resetRefreshInterval(this), this.afterRefresh && this.afterRefresh(result)
}
function setFocus(itemsContainer, focusId) {
if (focusId) {
var newElement = itemsContainer.querySelector('[data-id="' + focusId + '"]');
if (newElement) try {
return void focusManager.focus(newElement)
} catch (err) {}
}
focusManager.autoFocus(itemsContainer)
}
var ItemsContainerProtoType = Object.create(HTMLDivElement.prototype);
ItemsContainerProtoType.enableMultiSelect = function(enabled) {
var current = this.multiSelect;
if (!enabled) return void(current && (current.destroy(), this.multiSelect = null));
if (!current) {
var self = this;
require(["multiSelect"], function(MultiSelect) {
self.multiSelect = new MultiSelect({
container: self,
bindOnClick: !1
})
})
}
}, ItemsContainerProtoType.enableDragReordering = function(enabled) {
var current = this.sortable;
if (!enabled) return void(current && (current.destroy(), this.sortable = null));
if (!current) {
var self = this;
require(["sortable"], function(Sortable) {
self.sortable = new Sortable(self, {
draggable: ".listItem",
handle: ".listViewDragHandle",
onEnd: function(evt) {
return onDrop(evt, self)
}
})
})
}
}, ItemsContainerProtoType.createdCallback = function() {
this.classList.add("itemsContainer")
}, ItemsContainerProtoType.attachedCallback = function() {
this.addEventListener("click", onClick), browser.touch ? this.addEventListener("contextmenu", disableEvent) : "false" !== this.getAttribute("data-contextmenu") && this.addEventListener("contextmenu", onContextMenu), (layoutManager.desktop || layoutManager.mobile) && "false" !== this.getAttribute("data-multiselect") && this.enableMultiSelect(!0), layoutManager.tv && this.classList.add("itemsContainer-tv"), itemShortcuts.on(this, getShortcutOptions()), addNotificationEvent(this, "UserDataChanged", onUserDataChanged), addNotificationEvent(this, "TimerCreated", onTimerCreated), addNotificationEvent(this, "SeriesTimerCreated", onSeriesTimerCreated), addNotificationEvent(this, "TimerCancelled", onTimerCancelled), addNotificationEvent(this, "SeriesTimerCancelled", onSeriesTimerCancelled), addNotificationEvent(this, "LibraryChanged", onLibraryChanged), addNotificationEvent(this, "playbackstop", onPlaybackStopped, playbackManager), "true" === this.getAttribute("data-dragreorder") && this.enableDragReordering(!0)
}, ItemsContainerProtoType.detachedCallback = function() {
clearRefreshInterval(this), this.enableMultiSelect(!1), this.enableDragReordering(!1), this.removeEventListener("click", onClick), this.removeEventListener("contextmenu", onContextMenu), this.removeEventListener("contextmenu", disableEvent), itemShortcuts.off(this, getShortcutOptions()), removeNotificationEvent(this, "UserDataChanged"), removeNotificationEvent(this, "TimerCreated"), removeNotificationEvent(this, "SeriesTimerCreated"), removeNotificationEvent(this, "TimerCancelled"), removeNotificationEvent(this, "SeriesTimerCancelled"), removeNotificationEvent(this, "LibraryChanged"), removeNotificationEvent(this, "playbackstop", playbackManager), this.fetchData = null, this.getItemsHtml = null, this.parentContainer = null
}, ItemsContainerProtoType.pause = function() {
clearRefreshInterval(this, !0), this.paused = !0
}, ItemsContainerProtoType.resume = function(options) {
this.paused = !1;
var refreshIntervalEndTime = this.refreshIntervalEndTime;
if (refreshIntervalEndTime) {
var remainingMs = refreshIntervalEndTime - (new Date).getTime();
remainingMs > 0 && !this.needsRefresh ? resetRefreshInterval(this, remainingMs) : (this.needsRefresh = !0, this.refreshIntervalEndTime = null)
}
return this.needsRefresh || options && options.refresh ? this.refreshItems() : Promise.resolve()
}, ItemsContainerProtoType.refreshItems = function() {
return this.fetchData ? this.paused ? (this.needsRefresh = !0, Promise.resolve()) : (this.needsRefresh = !1, this.fetchData().then(onDataFetched.bind(this))) : Promise.resolve()
}, ItemsContainerProtoType.notifyRefreshNeeded = function(isInForeground) {
if (this.paused) return void(this.needsRefresh = !0);
var timeout = this.refreshTimeout;
timeout && clearTimeout(timeout), !0 === isInForeground ? this.refreshItems() : this.refreshTimeout = setTimeout(this.refreshItems.bind(this), 1e4)
}, document.registerElement("emby-itemscontainer", {
prototype: ItemsContainerProtoType,
extends: "div"
})
});

View file

@ -0,0 +1,157 @@
.progressring {
position: relative;
width: 2.6em;
height: 2.6em;
float: left;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-box-sizing: border-box;
box-sizing: border-box
}
.progressring-bg {
width: 100%;
height: 100%;
-webkit-border-radius: 50%;
border-radius: 50%;
border: .25em solid rgba(0, 0, 0, 1);
-webkit-box-sizing: border-box;
box-sizing: border-box;
background: rgba(0, 0, 0, .9);
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
justify-content: center
}
.spiner-holder-one,
.spiner-holder-two {
position: absolute;
top: 0;
left: 0;
overflow: hidden;
background: 0 0;
-webkit-box-sizing: border-box
}
.progressring-text {
text-align: center;
color: #ddd;
font-size: 90%
}
.spiner-holder-one {
width: 51%;
height: 51%;
box-sizing: border-box
}
.spiner-holder-two {
width: 100%;
height: 100%;
box-sizing: border-box
}
.progressring-spiner {
width: 200%;
height: 200%;
-webkit-border-radius: 50%;
border-radius: 50%;
border-width: .25em;
border-style: solid;
-webkit-box-sizing: border-box;
box-sizing: border-box
}
.animate-0-25-a {
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
-webkit-transform-origin: 100% 100%;
transform-origin: 100% 100%;
-webkit-transition: -webkit-transform 180ms ease-out;
-o-transition: transform 180ms ease-out;
transition: transform 180ms ease-out
}
.animate-0-25-b,
.animate-25-50-a {
-webkit-transition: -webkit-transform 180ms ease-out;
-o-transition: transform 180ms ease-out
}
.animate-0-25-b {
-webkit-transform: rotate(-90deg);
transform: rotate(-90deg);
-webkit-transform-origin: 100% 100%;
transform-origin: 100% 100%;
transition: transform 180ms ease-out
}
.animate-25-50-a {
-webkit-transform: rotate(180deg);
transform: rotate(180deg);
-webkit-transform-origin: 100% 100%;
transform-origin: 100% 100%;
transition: transform 180ms ease-out
}
.animate-25-50-b,
.animate-50-75-a {
-webkit-transition: -webkit-transform 180ms ease-out;
-o-transition: transform 180ms ease-out
}
.animate-25-50-b {
-webkit-transform: rotate(-90deg);
transform: rotate(-90deg);
-webkit-transform-origin: 100% 100%;
transform-origin: 100% 100%;
transition: transform 180ms ease-out
}
.animate-50-75-a {
-webkit-transform: rotate(270deg);
transform: rotate(270deg);
-webkit-transform-origin: 100% 100%;
transform-origin: 100% 100%;
transition: transform 180ms ease-out
}
.animate-50-75-b,
.animate-75-100-a {
-webkit-transition: -webkit-transform 180ms ease-out;
-o-transition: transform 180ms ease-out
}
.animate-50-75-b {
-webkit-transform: rotate(-90deg);
transform: rotate(-90deg);
-webkit-transform-origin: 100% 100%;
transform-origin: 100% 100%;
transition: transform 180ms ease-out
}
.animate-75-100-a {
-webkit-transform: rotate(0);
transform: rotate(0);
-webkit-transform-origin: 100% 100%;
transform-origin: 100% 100%;
transition: transform 180ms ease-out
}
.animate-75-100-b {
-webkit-transform: rotate(-90deg);
transform: rotate(-90deg);
-webkit-transform-origin: 100% 100%;
transform-origin: 100% 100%;
-webkit-transition: -webkit-transform 180ms ease-out;
-o-transition: transform 180ms ease-out;
transition: transform 180ms ease-out
}

View file

@ -0,0 +1,21 @@
define(["require", "css!./emby-progressring", "registerElement"], function(require) {
"use strict";
var EmbyProgressRing = Object.create(HTMLDivElement.prototype);
return EmbyProgressRing.createdCallback = function() {
this.classList.add("progressring");
var instance = this;
require(["text!./emby-progressring.template.html"], function(template) {
instance.innerHTML = template, instance.setProgress(parseFloat(instance.getAttribute("data-progress") || "0"))
})
}, EmbyProgressRing.setProgress = function(progress) {
progress = Math.floor(progress);
var angle;
progress < 25 ? (angle = progress / 100 * 360 - 90, this.querySelector(".animate-0-25-b").style.transform = "rotate(" + angle + "deg)", this.querySelector(".animate-25-50-b").style.transform = "rotate(-90deg)", this.querySelector(".animate-50-75-b").style.transform = "rotate(-90deg)", this.querySelector(".animate-75-100-b").style.transform = "rotate(-90deg)") : progress >= 25 && progress < 50 ? (angle = (progress - 25) / 100 * 360 - 90, this.querySelector(".animate-0-25-b").style.transform = "none", this.querySelector(".animate-25-50-b").style.transform = "rotate(" + angle + "deg)", this.querySelector(".animate-50-75-b").style.transform = "rotate(-90deg)", this.querySelector(".animate-75-100-b").style.transform = "rotate(-90deg)") : progress >= 50 && progress < 75 ? (angle = (progress - 50) / 100 * 360 - 90, this.querySelector(".animate-0-25-b").style.transform = "none", this.querySelector(".animate-25-50-b").style.transform = "none", this.querySelector(".animate-50-75-b").style.transform = "rotate(" + angle + "deg)", this.querySelector(".animate-75-100-b").style.transform = "rotate(-90deg)") : progress >= 75 && progress <= 100 && (angle = (progress - 75) / 100 * 360 - 90, this.querySelector(".animate-0-25-b").style.transform = "none", this.querySelector(".animate-25-50-b").style.transform = "none", this.querySelector(".animate-50-75-b").style.transform = "none", this.querySelector(".animate-75-100-b").style.transform = "rotate(" + angle + "deg)"), this.querySelector(".progressring-text").innerHTML = progress + "%"
}, EmbyProgressRing.attachedCallback = function() {}, EmbyProgressRing.detachedCallback = function() {
var observer = this.observer;
observer && (observer.disconnect(), this.observer = null)
}, document.registerElement("emby-progressring", {
prototype: EmbyProgressRing,
extends: "div"
}), EmbyProgressRing
});

View file

@ -0,0 +1,23 @@
<div class="progressring-bg">
<div class="progressring-text"></div>
</div>
<div class="spiner-holder-one animate-0-25-a">
<div class="spiner-holder-two animate-0-25-b">
<div class="progressring-spiner"></div>
</div>
</div>
<div class="spiner-holder-one animate-25-50-a">
<div class="spiner-holder-two animate-25-50-b">
<div class="progressring-spiner"></div>
</div>
</div>
<div class="spiner-holder-one animate-50-75-a">
<div class="spiner-holder-two animate-50-75-b">
<div class="progressring-spiner"></div>
</div>
</div>
<div class="spiner-holder-one animate-75-100-a">
<div class="spiner-holder-two animate-75-100-b">
<div class="progressring-spiner"></div>
</div>
</div>

View file

@ -0,0 +1,117 @@
.mdl-radio {
position: relative;
line-height: 24px;
display: inline-block;
-webkit-box-sizing: border-box;
box-sizing: border-box;
margin: 0;
padding-left: 24px
}
.radio-label-block {
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
margin-top: .5em;
margin-bottom: .5em
}
.mdl-radio__button {
line-height: 24px;
position: absolute;
width: 1px;
height: 1px;
margin: 0;
padding: 0;
opacity: 0;
-ms-appearance: none;
-moz-appearance: none;
-webkit-appearance: none;
appearance: none;
border: none
}
.mdl-radio__outer-circle {
position: absolute;
top: 4px;
left: 0;
display: inline-block;
-webkit-box-sizing: border-box;
box-sizing: border-box;
width: 16px;
height: 16px;
margin: 0;
cursor: pointer;
border: 2px solid currentcolor;
-webkit-border-radius: 50%;
border-radius: 50%;
z-index: 2
}
.mdl-radio__button:checked+.mdl-radio__label+.mdl-radio__outer-circle {
border: 2px solid #00a4dc
}
.mdl-radio__button:disabled+.mdl-radio__label+.mdl-radio__outer-circle {
border: 2px solid rgba(0, 0, 0, .26);
cursor: auto
}
.mdl-radio__inner-circle {
position: absolute;
z-index: 1;
margin: 0;
top: 8px;
left: 4px;
-webkit-box-sizing: border-box;
box-sizing: border-box;
width: 8px;
height: 8px;
cursor: pointer;
-webkit-transition-duration: .28s;
-o-transition-duration: .28s;
transition-duration: .28s;
-webkit-transition-timing-function: cubic-bezier(.4, 0, .2, 1);
-o-transition-timing-function: cubic-bezier(.4, 0, .2, 1);
transition-timing-function: cubic-bezier(.4, 0, .2, 1);
-o-transition-property: transform;
-webkit-transition-property: -webkit-transform, -webkit-transform;
transition-property: transform, -webkit-transform;
-webkit-transform: scale3d(0, 0, 0);
transform: scale3d(0, 0, 0);
-webkit-border-radius: 50%;
border-radius: 50%;
background: #00a4dc
}
.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, .26);
cursor: auto
}
.mdl-radio__button:focus+.mdl-radio__label+.mdl-radio__outer-circle+.mdl-radio__inner-circle {
-webkit-box-shadow: 0 0 0 10px rgba(255, 255, 255, .76);
box-shadow: 0 0 0 10px rgba(255, 255, 255, .76)
}
.mdl-radio__button:checked:focus+.mdl-radio__label+.mdl-radio__outer-circle+.mdl-radio__inner-circle {
-webkit-box-shadow: 0 0 0 10px rgba(0,164,220, .26);
box-shadow: 0 0 0 10px rgba(0,164,220, .26)
}
.mdl-radio__label {
cursor: pointer
}
.mdl-radio__button:disabled+.mdl-radio__label {
color: rgba(0, 0, 0, .26);
cursor: auto
}

View file

@ -0,0 +1,20 @@
define(["css!./emby-radio", "registerElement"], function() {
"use strict";
function onKeyDown(e) {
if (13 === e.keyCode) return e.preventDefault(), this.checked = !0, !1
}
var EmbyRadioPrototype = Object.create(HTMLInputElement.prototype);
EmbyRadioPrototype.attachedCallback = function() {
if ("true" !== this.getAttribute("data-radio")) {
this.setAttribute("data-radio", "true"), this.classList.add("mdl-radio__button");
var labelElement = this.parentNode;
labelElement.classList.add("mdl-radio"), labelElement.classList.add("mdl-js-radio"), labelElement.classList.add("mdl-js-ripple-effect");
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>'), this.addEventListener("keydown", onKeyDown)
}
}, document.registerElement("emby-radio", {
prototype: EmbyRadioPrototype,
extends: "input"
})
});

View file

@ -0,0 +1,67 @@
.emby-scrollbuttons-scroller {
position: relative
}
.scrollbuttoncontainer {
position: absolute;
top: 0;
bottom: 0;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
justify-content: center;
z-index: 1;
font-size: 3em;
color: #fff;
display: none;
overflow: hidden
}
.scrollbuttoncontainer-left {
background: rgba(20, 20, 20, .5);
background: -webkit-linear-gradient(left, #000 0, rgba(0, 0, 0, 0) 100%);
background: -webkit-gradient(linear, left top, right top, from(#000), to(rgba(0, 0, 0, 0)));
background: -webkit-linear-gradient(left, #000, rgba(0, 0, 0, 0));
background: -o-linear-gradient(left, #000, rgba(0, 0, 0, 0));
background: linear-gradient(to right, #000, rgba(0, 0, 0, 0));
left: 0
}
.scrollbuttoncontainer-right {
background: rgba(20, 20, 20, .5);
background: -webkit-linear-gradient(right, #000 0, rgba(0, 0, 0, 0) 100%);
background: -webkit-gradient(linear, right top, left top, from(#000), to(rgba(0, 0, 0, 0)));
background: -webkit-linear-gradient(right, #000, rgba(0, 0, 0, 0));
background: -o-linear-gradient(right, #000, rgba(0, 0, 0, 0));
background: linear-gradient(to left, #000, rgba(0, 0, 0, 0));
right: 0
}
.emby-scrollbuttons-scroller:hover .scrollbuttoncontainer {
display: -webkit-box;
display: -webkit-flex;
display: flex
}
.emby-scrollbuttons-scrollbutton {
margin: 0 -.2em;
-webkit-transition: -webkit-transform 160ms ease-out;
-o-transition: transform 160ms ease-out;
transition: transform 160ms ease-out
}
.scrollbuttoncontainer:hover>.emby-scrollbuttons-scrollbutton {
-webkit-transform: scale(1.3, 1.3);
transform: scale(1.3, 1.3)
}
.emby-scrollbuttons-scrollbutton:after {
content: '';
display: none !important
}
.emby-scrollbuttons-scrollbutton:focus {
color: inherit !important
}

View file

@ -0,0 +1,75 @@
define(["layoutManager", "dom", "css!./emby-scrollbuttons", "registerElement", "paper-icon-button-light"], function(layoutManager, dom) {
"use strict";
function getScrollButtonContainerHtml(direction) {
var html = "";
html += '<div class="scrollbuttoncontainer scrollbuttoncontainer-' + direction + ("left" === direction ? " hide" : "") + '">';
var icon = "left" === direction ? "&#xE5CB;" : "&#xE5CC;";
return html += '<button type="button" is="paper-icon-button-light" data-ripple="false" data-direction="' + direction + '" class="emby-scrollbuttons-scrollbutton">', html += '<i class="md-icon">' + icon + "</i>", html += "</button>", html += "</div>"
}
function getScrollPosition(parent) {
return parent.getScrollPosition ? parent.getScrollPosition() : 0
}
function getScrollWidth(parent) {
return parent.getScrollSize ? parent.getScrollSize() : 0
}
function onScrolledToPosition(scrollButtons, pos, scrollWidth) {
pos > 0 ? scrollButtons.scrollButtonsLeft.classList.remove("hide") : scrollButtons.scrollButtonsLeft.classList.add("hide"), scrollWidth > 0 && (pos += scrollButtons.offsetWidth, pos >= scrollWidth ? scrollButtons.scrollButtonsRight.classList.add("hide") : scrollButtons.scrollButtonsRight.classList.remove("hide"))
}
function onScroll(e) {
var scrollButtons = this,
scroller = this.scroller;
onScrolledToPosition(scrollButtons, getScrollPosition(scroller), getScrollWidth(scroller))
}
function getStyleValue(style, name) {
var value = style.getPropertyValue(name);
return value && (value = value.replace("px", "")) ? (value = parseInt(value), isNaN(value) ? 0 : value) : 0
}
function getScrollSize(elem) {
var scrollSize = elem.offsetWidth,
style = window.getComputedStyle(elem, null),
paddingLeft = getStyleValue(style, "padding-left");
paddingLeft && (scrollSize -= paddingLeft);
var paddingRight = getStyleValue(style, "padding-right");
paddingRight && (scrollSize -= paddingRight);
var slider = elem.getScrollSlider();
return style = window.getComputedStyle(slider, null), paddingLeft = getStyleValue(style, "padding-left"), paddingLeft && (scrollSize -= paddingLeft), paddingRight = getStyleValue(style, "padding-right"), paddingRight && (scrollSize -= paddingRight), scrollSize
}
function onScrollButtonClick(e) {
var newPos, parent = dom.parentWithAttribute(this, "is", "emby-scroller"),
direction = this.getAttribute("data-direction"),
scrollSize = getScrollSize(parent),
pos = getScrollPosition(parent);
newPos = "left" === direction ? Math.max(0, pos - scrollSize) : pos + scrollSize, parent.scrollToPosition(newPos, !1)
}
var EmbyScrollButtonsPrototype = Object.create(HTMLDivElement.prototype);
EmbyScrollButtonsPrototype.createdCallback = function() {}, EmbyScrollButtonsPrototype.attachedCallback = function() {
var parent = dom.parentWithAttribute(this, "is", "emby-scroller");
this.scroller = parent, parent.classList.add("emby-scrollbuttons-scroller"), this.innerHTML = getScrollButtonContainerHtml("left") + getScrollButtonContainerHtml("right");
var scrollHandler = onScroll.bind(this);
this.scrollHandler = scrollHandler;
var buttons = this.querySelectorAll(".emby-scrollbuttons-scrollbutton");
buttons[0].addEventListener("click", onScrollButtonClick), buttons[1].addEventListener("click", onScrollButtonClick), buttons = this.querySelectorAll(".scrollbuttoncontainer"), this.scrollButtonsLeft = buttons[0], this.scrollButtonsRight = buttons[1], parent.addScrollEventListener(scrollHandler, {
capture: !1,
passive: !0
})
}, EmbyScrollButtonsPrototype.detachedCallback = function() {
var parent = this.scroller;
this.scroller = null;
var scrollHandler = this.scrollHandler;
parent && scrollHandler && parent.removeScrollEventListener(scrollHandler, {
capture: !1,
passive: !0
}), this.scrollHandler = null, this.scrollButtonsLeft = null, this.scrollButtonsRight = null
}, document.registerElement("emby-scrollbuttons", {
prototype: EmbyScrollButtonsPrototype,
extends: "div"
})
});

View file

@ -0,0 +1,101 @@
define(["scroller", "dom", "layoutManager", "inputManager", "focusManager", "browser", "registerElement"], function(scroller, dom, layoutManager, inputManager, focusManager, browser) {
"use strict";
function initCenterFocus(elem, scrollerInstance) {
dom.addEventListener(elem, "focus", function(e) {
var focused = focusManager.focusableParent(e.target);
focused && scrollerInstance.toCenter(focused)
}, {
capture: !0,
passive: !0
})
}
function onInputCommand(e) {
var cmd = e.detail.command;
"end" === cmd ? (focusManager.focusLast(this, "." + this.getAttribute("data-navcommands")), e.preventDefault(), e.stopPropagation()) : "pageup" === cmd ? (focusManager.moveFocus(e.target, this, "." + this.getAttribute("data-navcommands"), -12), e.preventDefault(), e.stopPropagation()) : "pagedown" === cmd && (focusManager.moveFocus(e.target, this, "." + this.getAttribute("data-navcommands"), 12), e.preventDefault(), e.stopPropagation())
}
function initHeadroom(elem) {
require(["headroom"], function(Headroom) {
var headroom = new Headroom([], {
scroller: elem
});
headroom.init(), headroom.add(document.querySelector(".skinHeader")), elem.headroom = headroom
})
}
function loadScrollButtons(scroller) {
require(["emby-scrollbuttons"], function() {
scroller.insertAdjacentHTML("beforeend", '<div is="emby-scrollbuttons"></div>')
})
}
var ScrollerProtoType = Object.create(HTMLDivElement.prototype);
ScrollerProtoType.createdCallback = function() {
this.classList.add("emby-scroller")
}, ScrollerProtoType.scrollToBeginning = function() {
this.scroller && this.scroller.slideTo(0, !0)
}, ScrollerProtoType.toStart = function(elem, immediate) {
this.scroller && this.scroller.toStart(elem, immediate)
}, ScrollerProtoType.toCenter = function(elem, immediate) {
this.scroller && this.scroller.toCenter(elem, immediate)
}, ScrollerProtoType.scrollToPosition = function(pos, immediate) {
this.scroller && this.scroller.slideTo(pos, immediate)
}, ScrollerProtoType.getScrollPosition = function() {
if (this.scroller) return this.scroller.getScrollPosition()
}, ScrollerProtoType.getScrollSize = function() {
if (this.scroller) return this.scroller.getScrollSize()
}, ScrollerProtoType.getScrollEventName = function() {
if (this.scroller) return this.scroller.getScrollEventName()
}, ScrollerProtoType.getScrollSlider = function() {
if (this.scroller) return this.scroller.getScrollSlider()
}, ScrollerProtoType.addScrollEventListener = function(fn, options) {
this.scroller && dom.addEventListener(this.scroller.getScrollFrame(), this.scroller.getScrollEventName(), fn, options)
}, ScrollerProtoType.removeScrollEventListener = function(fn, options) {
this.scroller && dom.removeEventListener(this.scroller.getScrollFrame(), this.scroller.getScrollEventName(), fn, options)
}, ScrollerProtoType.attachedCallback = function() {
this.getAttribute("data-navcommands") && inputManager.on(this, onInputCommand);
var horizontal = "false" !== this.getAttribute("data-horizontal"),
slider = this.querySelector(".scrollSlider");
horizontal && (slider.style["white-space"] = "nowrap");
var bindHeader = "true" === this.getAttribute("data-bindheader"),
scrollFrame = this,
enableScrollButtons = layoutManager.desktop && horizontal && "false" !== this.getAttribute("data-scrollbuttons"),
options = {
horizontal: horizontal,
mouseDragging: 1,
mouseWheel: "false" !== this.getAttribute("data-mousewheel"),
touchDragging: 1,
slidee: slider,
scrollBy: 200,
speed: horizontal ? 270 : 240,
elasticBounds: 1,
dragHandle: 1,
scrollWidth: "auto" === this.getAttribute("data-scrollsize") ? null : 5e6,
autoImmediate: !0,
skipSlideToWhenVisible: "true" === this.getAttribute("data-skipfocuswhenvisible"),
dispatchScrollEvent: enableScrollButtons || bindHeader || "true" === this.getAttribute("data-scrollevent"),
hideScrollbar: enableScrollButtons || "true" === this.getAttribute("data-hidescrollbar"),
allowNativeSmoothScroll: "true" === this.getAttribute("data-allownativesmoothscroll") && !enableScrollButtons,
allowNativeScroll: !enableScrollButtons,
forceHideScrollbars: enableScrollButtons,
requireAnimation: enableScrollButtons && browser.edge
};
this.scroller = new scroller(scrollFrame, options), this.scroller.init(), layoutManager.tv && this.getAttribute("data-centerfocus") && initCenterFocus(this, this.scroller), bindHeader && initHeadroom(this), enableScrollButtons && loadScrollButtons(this)
}, ScrollerProtoType.pause = function() {
var headroom = this.headroom;
headroom && headroom.pause()
}, ScrollerProtoType.resume = function() {
var headroom = this.headroom;
headroom && headroom.resume()
}, ScrollerProtoType.detachedCallback = function() {
this.getAttribute("data-navcommands") && inputManager.off(this, onInputCommand);
var headroom = this.headroom;
headroom && (headroom.destroy(), this.headroom = null);
var scrollerInstance = this.scroller;
scrollerInstance && (scrollerInstance.destroy(), this.scroller = null)
}, document.registerElement("emby-scroller", {
prototype: ScrollerProtoType,
extends: "div"
})
});

View file

@ -0,0 +1,118 @@
.emby-select {
display: block;
margin: 0;
margin-bottom: 0 !important;
font-size: 110%;
font-family: inherit;
font-weight: inherit;
padding: .5em 1.9em .5em .5em;
-webkit-box-sizing: border-box;
box-sizing: border-box;
outline: 0 !important;
-webkit-tap-highlight-color: transparent;
width: 100%
}
.emby-select[disabled] {
background: 0 0 !important;
border-color: transparent !important;
color: inherit !important;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none
}
.selectContainer-inline>.emby-select {
padding: .3em 1.9em .3em .5em;
font-size: inherit
}
.selectContainer-inline>.emby-select[disabled] {
padding-left: 0;
padding-right: 0
}
.emby-select::-moz-focus-inner {
border: 0
}
.emby-select-focusscale {
-webkit-transition: -webkit-transform 180ms ease-out !important;
-o-transition: transform 180ms ease-out !important;
transition: transform 180ms ease-out !important;
-webkit-transform-origin: center center;
transform-origin: center center
}
.emby-select-focusscale:focus {
-webkit-transform: scale(1.04);
transform: scale(1.04);
z-index: 1
}
.emby-select+.fieldDescription {
margin-top: .25em
}
.selectContainer {
margin-bottom: 1.8em;
position: relative
}
.selectContainer-inline {
display: -webkit-inline-box;
display: -webkit-inline-flex;
display: inline-flex;
margin-bottom: 0;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center
}
.selectLabel {
display: block;
margin-bottom: .25em
}
.selectContainer-inline>.selectLabel {
margin-bottom: 0;
margin-right: .5em;
-webkit-flex-shrink: 0;
flex-shrink: 0
}
.emby-select-withcolor {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
-webkit-border-radius: .2em;
border-radius: .2em
}
.selectArrowContainer {
position: absolute;
right: .3em;
top: .2em;
color: inherit;
pointer-events: none
}
.selectContainer-inline>.selectArrowContainer {
top: initial;
bottom: .24em;
font-size: 90%
}
.emby-select[disabled]+.selectArrowContainer {
display: none
}
.selectArrow {
margin-top: .35em;
font-size: 1.7em
}
.emby-select-iconbutton {
-webkit-align-self: flex-end;
align-self: flex-end
}

View file

@ -0,0 +1,75 @@
define(["layoutManager", "browser", "actionsheet", "css!./emby-select", "registerElement"], function(layoutManager, browser, actionsheet) {
"use strict";
function enableNativeMenu() {
return !(!browser.edgeUwp && !browser.xboxOne) || !(browser.tizen || browser.orsay || browser.web0s) && (!!browser.tv || !layoutManager.tv)
}
function triggerChange(select) {
var evt = document.createEvent("HTMLEvents");
evt.initEvent("change", !1, !0), select.dispatchEvent(evt)
}
function setValue(select, value) {
select.value = value
}
function showActionSheet(select) {
var labelElem = getLabel(select),
title = labelElem ? labelElem.textContent || labelElem.innerText : null;
actionsheet.show({
items: select.options,
positionTo: select,
title: title
}).then(function(value) {
setValue(select, value), triggerChange(select)
})
}
function getLabel(select) {
for (var elem = select.previousSibling; elem && "LABEL" !== elem.tagName;) elem = elem.previousSibling;
return elem
}
function onFocus(e) {
var label = getLabel(this);
label && label.classList.add("selectLabelFocused")
}
function onBlur(e) {
var label = getLabel(this);
label && label.classList.remove("selectLabelFocused")
}
function onMouseDown(e) {
e.button || enableNativeMenu() || (e.preventDefault(), showActionSheet(this))
}
function onKeyDown(e) {
switch (e.keyCode) {
case 13:
return void(enableNativeMenu() || (e.preventDefault(), showActionSheet(this)));
case 37:
case 38:
case 39:
case 40:
return void(layoutManager.tv && e.preventDefault())
}
}
var EmbySelectPrototype = Object.create(HTMLSelectElement.prototype),
inputId = 0;
EmbySelectPrototype.createdCallback = function() {
this.id || (this.id = "embyselect" + inputId, inputId++), browser.firefox || (this.classList.add("emby-select-withcolor"), layoutManager.tv && this.classList.add("emby-select-tv-withcolor")), layoutManager.tv && this.classList.add("emby-select-focusscale"), this.addEventListener("mousedown", onMouseDown), this.addEventListener("keydown", onKeyDown), this.addEventListener("focus", onFocus), this.addEventListener("blur", onBlur)
}, EmbySelectPrototype.attachedCallback = function() {
if (!this.classList.contains("emby-select")) {
this.classList.add("emby-select");
var label = this.ownerDocument.createElement("label");
label.innerHTML = this.getAttribute("label") || "", label.classList.add("selectLabel"), label.htmlFor = this.id, this.parentNode.insertBefore(label, this), this.classList.contains("emby-select-withcolor") && this.parentNode.insertAdjacentHTML("beforeend", '<div class="selectArrowContainer"><div style="visibility:hidden;">0</div><i class="selectArrow md-icon">&#xE313;</i></div>')
}
}, EmbySelectPrototype.setLabel = function(text) {
this.parentNode.querySelector("label").innerHTML = text
}, document.registerElement("emby-select", {
prototype: EmbySelectPrototype,
extends: "select"
})
});

View file

@ -0,0 +1,234 @@
_:-ms-input-placeholder {
-ms-appearance: none;
height: 2.223em;
margin: 0
}
.mdl-slider {
width: 100%;
-webkit-appearance: none;
-moz-appearance: none;
-ms-appearance: none;
appearance: none;
height: .2em;
background: 0 0;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
outline: 0;
padding: 1em 0;
color: #00a4dc;
-webkit-align-self: center;
align-self: center;
z-index: 1;
cursor: pointer;
margin: 0;
-webkit-tap-highlight-color: transparent;
display: block
}
.mdl-slider::-moz-focus-outer {
border: 0
}
.mdl-slider::-ms-tooltip {
display: none
}
.mdl-slider::-webkit-slider-runnable-track {
background: 0 0
}
.mdl-slider::-moz-range-track {
background: #444;
border: none
}
.mdl-slider::-moz-range-progress {
background: #00a4dc
}
.mdl-slider::-ms-track {
background: 0 0;
color: transparent;
height: .2em;
width: 100%;
border: none
}
.mdl-slider::-ms-fill-lower {
display: none
}
.mdl-slider::-ms-fill-upper {
display: none
}
.mdl-slider::-webkit-slider-thumb {
-webkit-appearance: none;
width: 1.8em;
height: 1.8em;
-webkit-box-sizing: border-box;
box-sizing: border-box;
-webkit-border-radius: 50%;
border-radius: 50%;
background: #00a4dc;
border: none;
-webkit-transition: -webkit-transform .3s cubic-bezier(.4, 0, .2, 1), border .18s cubic-bezier(.4, 0, .2, 1), -webkit-box-shadow .18s cubic-bezier(.4, 0, .2, 1), background .28s cubic-bezier(.4, 0, .2, 1);
transition: transform .3s cubic-bezier(.4, 0, .2, 1), border .18s cubic-bezier(.4, 0, .2, 1), box-shadow .18s cubic-bezier(.4, 0, .2, 1), background .28s cubic-bezier(.4, 0, .2, 1)
}
.mdl-slider-hoverthumb::-webkit-slider-thumb {
margin-left: -.12em;
-webkit-transform: scale(.7, .7);
transform: scale(.7, .7)
}
.mdl-slider:hover::-webkit-slider-thumb {
-webkit-transform: none;
transform: none
}
.slider-no-webkit-thumb::-webkit-slider-thumb {
opacity: 0 !important
}
.mdl-slider::-moz-range-thumb {
-moz-appearance: none;
width: 1.8em;
height: 1.8em;
box-sizing: border-box;
border-radius: 50%;
background: #00a4dc;
border: none
}
.mdl-slider::-ms-thumb {
-webkit-appearance: none;
width: 1.8em;
height: 1.8em;
box-sizing: border-box;
border-radius: 50%;
background: #00a4dc;
border: none;
transition: transform .3s cubic-bezier(.4, 0, .2, 1), border .18s cubic-bezier(.4, 0, .2, 1), box-shadow .18s cubic-bezier(.4, 0, .2, 1), background .28s cubic-bezier(.4, 0, .2, 1)
}
.mdl-slider-hoverthumb::-ms-thumb {
margin-left: -.4em;
transform: scale(.5, .5)
}
.mdl-slider:hover::-ms-thumb {
transform: none
}
.mdl-slider[disabled]::-webkit-slider-thumb {
display: none
}
.mdl-slider[disabled]::-moz-range-thumb {
display: none
}
.mdl-slider[disabled]::-ms-thumb {
display: none
}
.mdl-slider-ie-container {
height: 1.25em;
overflow: visible;
border: none;
margin: 0;
padding: 0
}
.mdl-slider-container {
height: 1.25em;
position: relative;
background: 0 0;
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-box-orient: horizontal;
-webkit-box-direction: normal;
-webkit-flex-direction: row;
flex-direction: row
}
.mdl-slider-background-flex {
background: #333;
position: absolute;
height: .2em;
margin-top: -.1em;
width: 100%;
top: 50%;
left: 0;
display: -webkit-box;
display: -webkit-flex;
display: flex;
overflow: hidden;
border: 0;
padding: 0
}
.mdl-slider-background-flex-inner {
position: relative;
width: 100%
}
.mdl-slider-background-lower {
position: absolute;
left: 0;
width: 0;
top: 0;
bottom: 0;
background-color: #00a4dc
}
.mdl-slider-background-lower-clear {
background-color: transparent
}
.mdl-slider-background-lower-withtransform {
width: 100%;
-webkit-transform-origin: left center;
transform-origin: left center;
-webkit-transform: scaleX(0);
transform: scaleX(0)
}
.mdl-slider-background-upper {
background: #666;
background: rgba(255, 255, 255, .4);
position: absolute;
left: 0;
width: 0;
top: 0;
bottom: 0
}
.sliderBubble {
position: absolute;
top: 0;
left: 0;
-webkit-transform: translate3d(-48%, -120%, 0);
transform: translate3d(-48%, -120%, 0);
background: #282828;
color: #fff;
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
justify-content: center
}
.sliderBubbleText {
margin: 0;
padding: .5em .75em
}

View file

@ -0,0 +1,101 @@
define(["browser", "dom", "layoutManager", "css!./emby-slider", "registerElement", "emby-input"], function(browser, dom, layoutManager) {
"use strict";
function updateValues() {
var range = this,
value = range.value;
requestAnimationFrame(function() {
var backgroundLower = range.backgroundLower;
if (backgroundLower) {
var fraction = (value - range.min) / (range.max - range.min);
enableWidthWithTransform ? backgroundLower.style.transform = "scaleX(" + fraction + ")" : (fraction *= 100, backgroundLower.style.width = fraction + "%")
}
})
}
function updateBubble(range, value, bubble, bubbleText) {
requestAnimationFrame(function() {
bubble.style.left = value + "%", range.getBubbleHtml ? value = range.getBubbleHtml(value) : (value = range.getBubbleText ? range.getBubbleText(value) : Math.round(value), value = '<h1 class="sliderBubbleText">' + value + "</h1>"), bubble.innerHTML = value
})
}
function setRange(elem, startPercent, endPercent) {
var style = elem.style;
style.left = Math.max(startPercent, 0) + "%";
var widthPercent = endPercent - startPercent;
style.width = Math.max(Math.min(widthPercent, 100), 0) + "%"
}
function mapRangesFromRuntimeToPercent(ranges, runtime) {
return runtime ? ranges.map(function(r) {
return {
start: r.start / runtime * 100,
end: r.end / runtime * 100
}
}) : []
}
function startInterval(range) {
var interval = range.interval;
interval && clearInterval(interval), range.interval = setInterval(updateValues.bind(range), 100)
}
var enableWidthWithTransform, EmbySliderPrototype = Object.create(HTMLInputElement.prototype),
supportsNativeProgressStyle = browser.firefox,
supportsValueSetOverride = !1;
if (Object.getOwnPropertyDescriptor && Object.defineProperty) {
var descriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value");
descriptor && descriptor.configurable && (supportsValueSetOverride = !0)
}
EmbySliderPrototype.attachedCallback = function() {
if ("true" !== this.getAttribute("data-embyslider")) {
this.setAttribute("data-embyslider", "true"), this.classList.add("mdl-slider"), this.classList.add("mdl-js-slider"), browser.noFlex && this.classList.add("slider-no-webkit-thumb"), layoutManager.mobile || this.classList.add("mdl-slider-hoverthumb");
var containerElement = this.parentNode;
containerElement.classList.add("mdl-slider-container");
var htmlToInsert = "";
supportsNativeProgressStyle || (htmlToInsert += '<div class="mdl-slider-background-flex">', htmlToInsert += '<div class="mdl-slider-background-flex-inner">', htmlToInsert += '<div class="mdl-slider-background-upper"></div>', htmlToInsert += enableWidthWithTransform ? '<div class="mdl-slider-background-lower mdl-slider-background-lower-withtransform"></div>' : '<div class="mdl-slider-background-lower"></div>', htmlToInsert += "</div>", htmlToInsert += "</div>"), htmlToInsert += '<div class="sliderBubble hide"></div>', containerElement.insertAdjacentHTML("beforeend", htmlToInsert), this.backgroundLower = containerElement.querySelector(".mdl-slider-background-lower"), this.backgroundUpper = containerElement.querySelector(".mdl-slider-background-upper");
var sliderBubble = containerElement.querySelector(".sliderBubble"),
hasHideClass = sliderBubble.classList.contains("hide");
dom.addEventListener(this, "input", function(e) {
this.dragging = !0, updateBubble(this, this.value, sliderBubble), hasHideClass && (sliderBubble.classList.remove("hide"), hasHideClass = !1)
}, {
passive: !0
}), dom.addEventListener(this, "change", function() {
this.dragging = !1, updateValues.call(this), sliderBubble.classList.add("hide"), hasHideClass = !0
}, {
passive: !0
}), browser.firefox || (dom.addEventListener(this, window.PointerEvent ? "pointermove" : "mousemove", function(e) {
if (!this.dragging) {
var rect = this.getBoundingClientRect(),
clientX = e.clientX,
bubbleValue = (clientX - rect.left) / rect.width;
bubbleValue *= 100, updateBubble(this, bubbleValue, sliderBubble), hasHideClass && (sliderBubble.classList.remove("hide"), hasHideClass = !1)
}
}, {
passive: !0
}), dom.addEventListener(this, window.PointerEvent ? "pointerleave" : "mouseleave", function() {
sliderBubble.classList.add("hide"), hasHideClass = !0
}, {
passive: !0
})), supportsNativeProgressStyle || (supportsValueSetOverride ? this.addEventListener("valueset", updateValues) : startInterval(this))
}
}, EmbySliderPrototype.setBufferedRanges = function(ranges, runtime, position) {
var elem = this.backgroundUpper;
if (elem) {
null != runtime && (ranges = mapRangesFromRuntimeToPercent(ranges, runtime), position = position / runtime * 100);
for (var i = 0, length = ranges.length; i < length; i++) {
var range = ranges[i];
if (!(null != position && position >= range.end)) return void setRange(elem, range.start, range.end)
}
setRange(elem, 0, 0)
}
}, EmbySliderPrototype.setIsClear = function(isClear) {
var backgroundLower = this.backgroundLower;
backgroundLower && (isClear ? backgroundLower.classList.add("mdl-slider-background-lower-clear") : backgroundLower.classList.remove("mdl-slider-background-lower-clear"))
}, EmbySliderPrototype.detachedCallback = function() {
var interval = this.interval;
interval && clearInterval(interval), this.interval = null, this.backgroundUpper = null, this.backgroundLower = null
}, document.registerElement("emby-slider", {
prototype: EmbySliderPrototype,
extends: "input"
})
});

View file

@ -0,0 +1,43 @@
.emby-tab-button,
.emby-tabs-slider {
position: relative
}
.emby-tab-button {
background: 0 0;
-webkit-box-shadow: none;
box-shadow: none;
cursor: pointer;
outline: 0 !important;
width: auto;
font-family: inherit;
font-size: inherit;
display: inline-block;
vertical-align: middle;
-webkit-flex-shrink: 0;
flex-shrink: 0;
margin: 0;
padding: 1em .9em;
height: auto;
min-width: initial;
line-height: initial;
-webkit-border-radius: 0 !important;
border-radius: 0 !important;
overflow: hidden;
font-weight: 600
}
.emby-tab-button.emby-button-tv:focus {
-webkit-transform: scale(1.32);
transform: scale(1.32);
-webkit-transform-origin: center center;
transform-origin: center center
}
.emby-tab-button-ripple-effect {
background: rgba(0, 0, 0, .7) !important
}
.tabContent:not(.is-active) {
display: none
}

View file

@ -0,0 +1,191 @@
define(["dom", "scroller", "browser", "layoutManager", "focusManager", "registerElement", "css!./emby-tabs", "scrollStyles"], function(dom, scroller, browser, layoutManager, focusManager) {
"use strict";
function setActiveTabButton(tabs, newButton, oldButton, animate) {
newButton.classList.add(activeButtonClass)
}
function getFocusCallback(tabs, e) {
return function() {
onClick.call(tabs, e)
}
}
function onFocus(e) {
layoutManager.tv && (this.focusTimeout && clearTimeout(this.focusTimeout), this.focusTimeout = setTimeout(getFocusCallback(this, e), 700))
}
function getTabPanel(tabs, index) {
return null
}
function removeActivePanelClass(tabs, index) {
var tabPanel = getTabPanel(tabs, index);
tabPanel && tabPanel.classList.remove("is-active")
}
function fadeInRight(elem) {
var pct = browser.mobile ? "4%" : "0.5%",
keyframes = [{
opacity: "0",
transform: "translate3d(" + pct + ", 0, 0)",
offset: 0
}, {
opacity: "1",
transform: "none",
offset: 1
}];
elem.animate(keyframes, {
duration: 160,
iterations: 1,
easing: "ease-out"
})
}
function triggerBeforeTabChange(tabs, index, previousIndex) {
tabs.dispatchEvent(new CustomEvent("beforetabchange", {
detail: {
selectedTabIndex: index,
previousIndex: previousIndex
}
})), null != previousIndex && previousIndex !== index && removeActivePanelClass(tabs, previousIndex);
var newPanel = getTabPanel(tabs, index);
newPanel && (newPanel.animate && fadeInRight(newPanel), newPanel.classList.add("is-active"))
}
function onClick(e) {
this.focusTimeout && clearTimeout(this.focusTimeout);
var tabs = this,
current = tabs.querySelector("." + activeButtonClass),
tabButton = dom.parentWithClass(e.target, buttonClass);
if (tabButton && tabButton !== current) {
current && current.classList.remove(activeButtonClass);
var previousIndex = current ? parseInt(current.getAttribute("data-index")) : null;
setActiveTabButton(tabs, tabButton, current, !0);
var index = parseInt(tabButton.getAttribute("data-index"));
triggerBeforeTabChange(tabs, index, previousIndex), setTimeout(function() {
tabs.selectedTabIndex = index, tabs.dispatchEvent(new CustomEvent("tabchange", {
detail: {
selectedTabIndex: index,
previousIndex: previousIndex
}
}))
}, 120), tabs.scroller && tabs.scroller.toCenter(tabButton, !1)
}
}
function initScroller(tabs) {
if (!tabs.scroller) {
var contentScrollSlider = tabs.querySelector(".emby-tabs-slider");
contentScrollSlider ? (tabs.scroller = new scroller(tabs, {
horizontal: 1,
itemNav: 0,
mouseDragging: 1,
touchDragging: 1,
slidee: contentScrollSlider,
smart: !0,
releaseSwing: !0,
scrollBy: 200,
speed: 120,
elasticBounds: 1,
dragHandle: 1,
dynamicHandle: 1,
clickBar: 1,
hiddenScroll: !0,
requireAnimation: !browser.safari,
allowNativeSmoothScroll: !0
}), tabs.scroller.init()) : (tabs.classList.add("scrollX"), tabs.classList.add("hiddenScrollX"), tabs.classList.add("smoothScrollX"))
}
}
function getSelectedTabButton(elem) {
return elem.querySelector("." + activeButtonClass)
}
function getSibling(elem, method) {
for (var sibling = elem[method]; sibling;) {
if (sibling.classList.contains(buttonClass) && !sibling.classList.contains("hide")) return sibling;
sibling = sibling[method]
}
return null
}
var EmbyTabs = Object.create(HTMLDivElement.prototype),
buttonClass = "emby-tab-button",
activeButtonClass = buttonClass + "-active";
EmbyTabs.createdCallback = function() {
this.classList.contains("emby-tabs") || (this.classList.add("emby-tabs"), this.classList.add("focusable"), dom.addEventListener(this, "click", onClick, {
passive: !0
}), dom.addEventListener(this, "focus", onFocus, {
passive: !0,
capture: !0
}))
}, EmbyTabs.focus = function() {
var selected = this.querySelector("." + activeButtonClass);
selected ? focusManager.focus(selected) : focusManager.autoFocus(this)
}, EmbyTabs.refresh = function() {
this.scroller && this.scroller.reload()
}, EmbyTabs.attachedCallback = function() {
initScroller(this);
var current = this.querySelector("." + activeButtonClass),
currentIndex = current ? parseInt(current.getAttribute("data-index")) : parseInt(this.getAttribute("data-index") || "0");
if (-1 !== currentIndex) {
this.selectedTabIndex = currentIndex;
var tabButtons = this.querySelectorAll("." + buttonClass),
newTabButton = tabButtons[currentIndex];
newTabButton && setActiveTabButton(this, newTabButton, current, !1)
}
this.readyFired || (this.readyFired = !0, this.dispatchEvent(new CustomEvent("ready", {})))
}, EmbyTabs.detachedCallback = function() {
this.scroller && (this.scroller.destroy(), this.scroller = null), dom.removeEventListener(this, "click", onClick, {
passive: !0
}), dom.removeEventListener(this, "focus", onFocus, {
passive: !0,
capture: !0
})
}, EmbyTabs.selectedIndex = function(selected, triggerEvent) {
var tabs = this;
if (null == selected) return tabs.selectedTabIndex || 0;
var current = tabs.selectedIndex();
tabs.selectedTabIndex = selected;
var tabButtons = tabs.querySelectorAll("." + buttonClass);
if (current === selected || !1 === triggerEvent) {
triggerBeforeTabChange(tabs, selected, current), tabs.dispatchEvent(new CustomEvent("tabchange", {
detail: {
selectedTabIndex: selected
}
}));
var currentTabButton = tabButtons[current];
setActiveTabButton(tabs, tabButtons[selected], currentTabButton, !1), current !== selected && currentTabButton && currentTabButton.classList.remove(activeButtonClass)
} else onClick.call(tabs, {
target: tabButtons[selected]
})
}, EmbyTabs.selectNext = function() {
var current = getSelectedTabButton(this),
sibling = getSibling(current, "nextSibling");
sibling && onClick.call(this, {
target: sibling
})
}, EmbyTabs.selectPrevious = function() {
var current = getSelectedTabButton(this),
sibling = getSibling(current, "previousSibling");
sibling && onClick.call(this, {
target: sibling
})
}, EmbyTabs.triggerBeforeTabChange = function(selected) {
var tabs = this;
triggerBeforeTabChange(tabs, tabs.selectedIndex())
}, EmbyTabs.triggerTabChange = function(selected) {
var tabs = this;
tabs.dispatchEvent(new CustomEvent("tabchange", {
detail: {
selectedTabIndex: tabs.selectedIndex()
}
}))
}, EmbyTabs.setTabEnabled = function(index, enabled) {
var btn = this.querySelector('.emby-tab-button[data-index="' + index + '"]');
enabled ? btn.classList.remove("hide") : btn.classList.remove("add")
}, document.registerElement("emby-tabs", {
prototype: EmbyTabs,
extends: "div"
})
});

View file

@ -0,0 +1,31 @@
.emby-textarea {
display: block;
margin: 0;
margin-bottom: 0 !important;
font-size: inherit;
font-family: inherit;
font-weight: inherit;
color: inherit;
padding: .35em .25em;
-webkit-box-sizing: border-box;
box-sizing: border-box;
outline: 0 !important;
-webkit-tap-highlight-color: transparent;
width: 100%
}
.emby-textarea::-moz-focus-inner {
border: 0
}
.textareaLabel {
display: inline-block;
-webkit-transition: all .2s ease-out;
-o-transition: all .2s ease-out;
transition: all .2s ease-out;
margin-bottom: .25em
}
.emby-textarea+.fieldDescription {
margin-top: .25em
}

View file

@ -0,0 +1,55 @@
define(["layoutManager", "browser", "css!./emby-textarea", "registerElement", "emby-input"], function(layoutManager, browser) {
"use strict";
function autoGrow(textarea, maxLines) {
function reset() {
textarea.rows = 1, offset = self.getOffset(textarea), self.rows = textarea.rows || 1, self.lineHeight = textarea.scrollHeight / self.rows - offset / self.rows, self.maxAllowedHeight = self.lineHeight * maxLines - offset
}
function autogrowFn() {
if ((!self.lineHeight || self.lineHeight <= 0) && reset(), self.lineHeight <= 0) return textarea.style.overflowY = "scroll", textarea.style.height = "auto", void(textarea.rows = 3);
var newHeight = 0;
textarea.scrollHeight - offset > self.maxAllowedHeight ? (textarea.style.overflowY = "scroll", newHeight = self.maxAllowedHeight) : (textarea.style.overflowY = "hidden", textarea.style.height = "auto", newHeight = textarea.scrollHeight), textarea.style.height = newHeight + "px"
}
var self = this;
void 0 === maxLines && (maxLines = 999), self.getOffset = function(textarea) {
for (var style = window.getComputedStyle(textarea, null), props = ["paddingTop", "paddingBottom"], offset = 0, i = 0; i < props.length; i++) offset += parseInt(style[props[i]]);
return offset
};
var offset;
textarea.addEventListener("input", autogrowFn), textarea.addEventListener("focus", autogrowFn), textarea.addEventListener("valueset", autogrowFn), autogrowFn()
}
var EmbyTextAreaPrototype = Object.create(HTMLTextAreaElement.prototype),
elementId = 0;
if (Object.getOwnPropertyDescriptor && Object.defineProperty) {
var descriptor = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, "value");
if (descriptor && descriptor.configurable) {
var baseSetMethod = descriptor.set;
descriptor.set = function(value) {
baseSetMethod.call(this, value), this.dispatchEvent(new CustomEvent("valueset", {
bubbles: !1,
cancelable: !1
}))
}, Object.defineProperty(HTMLTextAreaElement.prototype, "value", descriptor)
}
}
EmbyTextAreaPrototype.createdCallback = function() {
this.id || (this.id = "embytextarea" + elementId, elementId++)
}, EmbyTextAreaPrototype.attachedCallback = function() {
if (!this.classList.contains("emby-textarea")) {
this.rows = 1, this.classList.add("emby-textarea");
var parentNode = this.parentNode,
label = this.ownerDocument.createElement("label");
label.innerHTML = this.getAttribute("label") || "", label.classList.add("textareaLabel"), label.htmlFor = this.id, parentNode.insertBefore(label, this), this.addEventListener("focus", function() {
label.classList.add("textareaLabelFocused"), label.classList.remove("textareaLabelUnfocused")
}), this.addEventListener("blur", function() {
label.classList.remove("textareaLabelFocused"), label.classList.add("textareaLabelUnfocused")
}), this.label = function(text) {
label.innerHTML = text
}, new autoGrow(this)
}
}, document.registerElement("emby-textarea", {
prototype: EmbyTextAreaPrototype,
extends: "textarea"
})
});

View file

@ -0,0 +1,155 @@
.mdl-switch {
position: relative;
z-index: 1;
vertical-align: middle;
display: -webkit-inline-box;
display: -webkit-inline-flex;
display: inline-flex;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
-webkit-box-sizing: border-box;
box-sizing: border-box;
width: 100%;
margin: 0;
padding: 0;
overflow: visible;
-webkit-touch-callout: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-box-orient: horizontal;
-webkit-box-direction: reverse;
-webkit-flex-direction: row-reverse;
flex-direction: row-reverse;
-webkit-box-pack: end;
-webkit-justify-content: flex-end;
justify-content: flex-end
}
.toggleContainer {
margin-bottom: 1.8em
}
.mdl-switch__input {
width: 0;
height: 0;
margin: 0;
padding: 0;
opacity: 0;
-ms-appearance: none;
-moz-appearance: none;
-webkit-appearance: none;
appearance: none;
border: none
}
.mdl-switch__trackContainer {
position: relative;
width: 2.9em
}
.mdl-switch__track {
background: rgba(0, 0, 0, .2);
height: 1em;
-webkit-border-radius: 1em;
border-radius: 1em;
cursor: pointer
}
.mdl-switch__input:checked+.mdl-switch__label+.mdl-switch__trackContainer>.mdl-switch__track {
background: rgba(0,164,220, .5)
}
.mdl-switch__input[disabled]+.mdl-switch__label+.mdl-switch__trackContainer>.mdl-switch__track {
background: rgba(0, 0, 0, .12);
cursor: auto
}
.mdl-switch__thumb {
background: #999;
position: absolute;
left: 0;
top: -.25em;
height: 1.44em;
width: 1.44em;
-webkit-border-radius: 50%;
border-radius: 50%;
cursor: pointer;
-webkit-box-shadow: 0 2px 2px 0 rgba(0, 0, 0, .14), 0 3px 1px -2px rgba(0, 0, 0, .2), 0 1px 5px 0 rgba(0, 0, 0, .12);
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, .14), 0 3px 1px -2px rgba(0, 0, 0, .2), 0 1px 5px 0 rgba(0, 0, 0, .12);
-webkit-transition-duration: .28s;
-o-transition-duration: .28s;
transition-duration: .28s;
-webkit-transition-timing-function: cubic-bezier(.4, 0, .2, 1);
-o-transition-timing-function: cubic-bezier(.4, 0, .2, 1);
transition-timing-function: cubic-bezier(.4, 0, .2, 1);
-webkit-transition-property: left;
-o-transition-property: left;
transition-property: left;
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
justify-content: center
}
.mdl-switch__input:checked+.mdl-switch__label+.mdl-switch__trackContainer>.mdl-switch__thumb {
background: #00a4dc;
left: 1.466em;
-webkit-box-shadow: 0 3px .28em 0 rgba(0, 0, 0, .14), 0 3px 3px -2px rgba(0, 0, 0, .2), 0 1px .56em 0 rgba(0, 0, 0, .12);
box-shadow: 0 3px .28em 0 rgba(0, 0, 0, .14), 0 3px 3px -2px rgba(0, 0, 0, .2), 0 1px .56em 0 rgba(0, 0, 0, .12)
}
.mdl-switch__input[disabled]+.mdl-switch__label+.mdl-switch__trackContainer>.mdl-switch__thumb {
background: #bdbdbd;
cursor: auto
}
.mdl-switch__focus-helper {
position: absolute;
top: 50%;
left: 50%;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
display: inline-block;
-webkit-box-sizing: border-box;
box-sizing: border-box;
width: .6em;
height: .6em;
-webkit-border-radius: 50%;
border-radius: 50%;
background-color: transparent
}
.mdl-switch__input:focus+.mdl-switch__label+.mdl-switch__trackContainer .mdl-switch__focus-helper {
-webkit-box-shadow: 0 0 0 1.39em rgba(0, 0, 0, .05);
box-shadow: 0 0 0 1.39em rgba(0, 0, 0, .05)
}
.mdl-switch__input:checked:focus+.mdl-switch__label+.mdl-switch__trackContainer .mdl-switch__focus-helper {
-webkit-box-shadow: 0 0 0 1.39em rgba(0,164,220, .26);
box-shadow: 0 0 0 1.39em rgba(0,164,220, .26);
background-color: rgba(0,164,220, .26)
}
.mdl-switch__label {
cursor: pointer;
margin: 0 0 0 .7em;
display: -webkit-inline-box;
display: -webkit-inline-flex;
display: inline-flex;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center
}
.mdl-switch__input[disabled] .mdl-switch__label {
color: #bdbdbd;
cursor: auto
}

View file

@ -0,0 +1,22 @@
define(["css!./emby-toggle", "registerElement"], function() {
"use strict";
function onKeyDown(e) {
if (13 === e.keyCode) return e.preventDefault(), this.checked = !this.checked, this.dispatchEvent(new CustomEvent("change", {
bubbles: !0
})), !1
}
var EmbyTogglePrototype = Object.create(HTMLInputElement.prototype);
EmbyTogglePrototype.attachedCallback = function() {
if ("true" !== this.getAttribute("data-embytoggle")) {
this.setAttribute("data-embytoggle", "true"), this.classList.add("mdl-switch__input");
var labelElement = this.parentNode;
labelElement.classList.add("mdl-switch"), labelElement.classList.add("mdl-js-switch");
var labelTextElement = labelElement.querySelector("span");
labelElement.insertAdjacentHTML("beforeend", '<div class="mdl-switch__trackContainer"><div class="mdl-switch__track"></div><div class="mdl-switch__thumb"><span class="mdl-switch__focus-helper"></span></div></div>'), labelTextElement.classList.add("toggleButtonLabel"), labelTextElement.classList.add("mdl-switch__label"), this.addEventListener("keydown", onKeyDown)
}
}, document.registerElement("emby-toggle", {
prototype: EmbyTogglePrototype,
extends: "input"
})
});

View file

@ -0,0 +1,54 @@
define([], function() {
"use strict";
function getFetchPromise(request) {
var headers = request.headers || {};
"json" === request.dataType && (headers.accept = "application/json");
var fetchRequest = {
headers: headers,
method: request.type,
credentials: "same-origin"
},
contentType = request.contentType;
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);
var url = request.url;
if (request.query) {
var paramString = paramsToString(request.query);
paramString && (url += "?" + paramString)
}
return request.timeout ? fetchWithTimeout(url, fetchRequest, request.timeout) : fetch(url, fetchRequest)
}
function fetchWithTimeout(url, options, timeoutMs) {
return console.log("fetchWithTimeout: timeoutMs: " + timeoutMs + ", url: " + url), new Promise(function(resolve, reject) {
var timeout = setTimeout(reject, timeoutMs);
options = options || {}, options.credentials = "same-origin", fetch(url, options).then(function(response) {
clearTimeout(timeout), console.log("fetchWithTimeout: succeeded connecting to url: " + url), resolve(response)
}, function(error) {
clearTimeout(timeout), console.log("fetchWithTimeout: timed out connecting to url: " + url), reject()
})
})
}
function paramsToString(params) {
var values = [];
for (var key in params) {
var value = params[key];
null !== value && void 0 !== value && "" !== value && values.push(encodeURIComponent(key) + "=" + encodeURIComponent(value))
}
return values.join("&")
}
function ajax(request) {
if (!request) throw new Error("Request cannot be null");
return request.headers = request.headers || {}, console.log("requesting url: " + request.url), getFetchPromise(request).then(function(response) {
return console.log("response status: " + response.status + ", url: " + request.url), response.status < 400 ? "json" === request.dataType || "application/json" === request.headers.accept ? response.json() : "text" === request.dataType || 0 === (response.headers.get("Content-Type") || "").toLowerCase().indexOf("text/") ? response.text() : response : Promise.reject(response)
}, function(err) {
throw console.log("request failed to url: " + request.url), err
})
}
return {
getFetchPromise: getFetchPromise,
ajax: ajax
}
});

View file

@ -0,0 +1,10 @@
define(["multi-download"], function(multiDownload) {
"use strict";
return {
download: function(items) {
multiDownload(items.map(function(item) {
return item.url
}))
}
}
});

View file

@ -0,0 +1,11 @@
define([], function() {
"use strict";
return {
fileExists: function(path) {
return Promise.reject()
},
directoryExists: function(path) {
return Promise.reject()
}
}
});

View file

@ -0,0 +1,125 @@
define(["require", "dom", "focusManager", "dialogHelper", "loading", "apphost", "inputManager", "layoutManager", "connectionManager", "appRouter", "globalize", "userSettings", "emby-checkbox", "emby-input", "paper-icon-button-light", "emby-select", "material-icons", "css!./../formdialog", "emby-button", "emby-linkbutton", "flexStyles"], function(require, dom, focusManager, dialogHelper, loading, appHost, inputManager, layoutManager, connectionManager, appRouter, globalize, userSettings) {
"use strict";
function onSubmit(e) {
return e.preventDefault(), !1
}
function renderOptions(context, selector, cssClass, items, isCheckedFn) {
var elem = context.querySelector(selector);
items.length ? elem.classList.remove("hide") : elem.classList.add("hide");
var html = "";
html += items.map(function(filter) {
var itemHtml = "",
checkedHtml = isCheckedFn(filter) ? " checked" : "";
return itemHtml += "<label>", itemHtml += '<input is="emby-checkbox" type="checkbox"' + checkedHtml + ' data-filter="' + filter.Id + '" class="' + cssClass + '"/>', itemHtml += "<span>" + filter.Name + "</span>", itemHtml += "</label>"
}).join(""), elem.querySelector(".filterOptions").innerHTML = html
}
function renderDynamicFilters(context, result, options) {
renderOptions(context, ".genreFilters", "chkGenreFilter", result.Genres, function(i) {
var delimeter = -1 === (options.settings.GenreIds || "").indexOf("|") ? "," : "|";
return -1 !== (delimeter + (options.settings.GenreIds || "") + delimeter).indexOf(delimeter + i.Id + delimeter)
})
}
function loadDynamicFilters(context, options) {
var apiClient = connectionManager.getApiClient(options.serverId),
filterMenuOptions = Object.assign(options.filterMenuOptions, {
UserId: apiClient.getCurrentUserId(),
ParentId: options.parentId,
IncludeItemTypes: options.itemTypes.join(",")
});
apiClient.getFilters(filterMenuOptions).then(function(result) {
renderDynamicFilters(context, result, options)
}, function() {})
}
function initEditor(context, settings) {
context.querySelector("form").addEventListener("submit", onSubmit);
var i, length, elems = context.querySelectorAll(".simpleFilter");
for (i = 0, length = elems.length; i < length; i++) "INPUT" === elems[i].tagName ? elems[i].checked = settings[elems[i].getAttribute("data-settingname")] || !1 : elems[i].querySelector("input").checked = settings[elems[i].getAttribute("data-settingname")] || !1;
var videoTypes = settings.VideoTypes ? settings.VideoTypes.split(",") : [];
for (elems = context.querySelectorAll(".chkVideoTypeFilter"), i = 0, length = elems.length; i < length; i++) elems[i].checked = -1 !== videoTypes.indexOf(elems[i].getAttribute("data-filter"));
var seriesStatuses = settings.SeriesStatus ? settings.SeriesStatus.split(",") : [];
for (elems = context.querySelectorAll(".chkSeriesStatus"), i = 0, length = elems.length; i < length; i++) elems[i].checked = -1 !== seriesStatuses.indexOf(elems[i].getAttribute("data-filter"));
context.querySelector(".basicFilterSection .viewSetting:not(.hide)") ? context.querySelector(".basicFilterSection").classList.remove("hide") : context.querySelector(".basicFilterSection").classList.add("hide"), context.querySelector(".featureSection .viewSetting:not(.hide)") ? context.querySelector(".featureSection").classList.remove("hide") : context.querySelector(".featureSection").classList.add("hide")
}
function saveValues(context, settings, settingsKey) {
var i, length, elems = context.querySelectorAll(".simpleFilter");
for (i = 0, length = elems.length; i < length; i++) "INPUT" === elems[i].tagName ? setBasicFilter(context, settingsKey + "-filter-" + elems[i].getAttribute("data-settingname"), elems[i]) : setBasicFilter(context, settingsKey + "-filter-" + elems[i].getAttribute("data-settingname"), elems[i].querySelector("input"));
var videoTypes = [];
for (elems = context.querySelectorAll(".chkVideoTypeFilter"), i = 0, length = elems.length; i < length; i++) elems[i].checked && videoTypes.push(elems[i].getAttribute("data-filter"));
userSettings.setFilter(settingsKey + "-filter-VideoTypes", videoTypes.join(","));
var seriesStatuses = [];
for (elems = context.querySelectorAll(".chkSeriesStatus"), i = 0, length = elems.length; i < length; i++) elems[i].checked && seriesStatuses.push(elems[i].getAttribute("data-filter"));
var genres = [];
for (elems = context.querySelectorAll(".chkGenreFilter"), i = 0, length = elems.length; i < length; i++) elems[i].checked && genres.push(elems[i].getAttribute("data-filter"));
userSettings.setFilter(settingsKey + "-filter-GenreIds", genres.join(","))
}
function setBasicFilter(context, key, elem) {
var value = elem.checked;
value = value || null, userSettings.setFilter(key, value)
}
function centerFocus(elem, horiz, on) {
require(["scrollHelper"], function(scrollHelper) {
var fn = on ? "on" : "off";
scrollHelper.centerFocus[fn](elem, horiz)
})
}
function moveCheckboxFocus(elem, offset) {
for (var parent = dom.parentWithClass(elem, "checkboxList-verticalwrap"), elems = focusManager.getFocusableElements(parent), index = -1, i = 0, length = elems.length; i < length; i++)
if (elems[i] === elem) {
index = i;
break
} index += offset, index = Math.min(elems.length - 1, index), index = Math.max(0, index);
var newElem = elems[index];
newElem && focusManager.focus(newElem)
}
function onInputCommand(e) {
switch (e.detail.command) {
case "left":
moveCheckboxFocus(e.target, -1), e.preventDefault();
break;
case "right":
moveCheckboxFocus(e.target, 1), e.preventDefault()
}
}
function FilterMenu() {}
function bindCheckboxInput(context, on) {
for (var elems = context.querySelectorAll(".checkboxList-verticalwrap"), i = 0, length = elems.length; i < length; i++) on ? inputManager.on(elems[i], onInputCommand) : inputManager.off(elems[i], onInputCommand)
}
return FilterMenu.prototype.show = function(options) {
return new Promise(function(resolve, reject) {
require(["text!./filtermenu.template.html"], function(template) {
var dialogOptions = {
removeOnClose: !0,
scrollY: !1
};
layoutManager.tv ? dialogOptions.size = "fullscreen" : dialogOptions.size = "small";
var dlg = dialogHelper.createDialog(dialogOptions);
dlg.classList.add("formDialog");
var html = "";
html += '<div class="formDialogHeader">', html += '<button is="paper-icon-button-light" class="btnCancel hide-mouse-idle-tv" tabindex="-1"><i class="md-icon">&#xE5C4;</i></button>', html += '<h3 class="formDialogHeaderTitle">${Filters}</h3>', html += "</div>", html += template, dlg.innerHTML = globalize.translateDocument(html, "sharedcomponents");
for (var settingElements = dlg.querySelectorAll(".viewSetting"), i = 0, length = settingElements.length; i < length; i++) - 1 === options.visibleSettings.indexOf(settingElements[i].getAttribute("data-settingname")) ? settingElements[i].classList.add("hide") : settingElements[i].classList.remove("hide");
initEditor(dlg, options.settings), loadDynamicFilters(dlg, options), bindCheckboxInput(dlg, !0), dlg.querySelector(".btnCancel").addEventListener("click", function() {
dialogHelper.close(dlg)
}), layoutManager.tv && centerFocus(dlg.querySelector(".formDialogContent"), !1, !0);
var submitted;
dlg.querySelector("form").addEventListener("change", function() {
submitted = !0
}, !0), dialogHelper.open(dlg).then(function() {
if (bindCheckboxInput(dlg, !1), layoutManager.tv && centerFocus(dlg.querySelector(".formDialogContent"), !1, !1), submitted) return saveValues(dlg, options.settings, options.settingsKey), void resolve();
reject()
})
})
})
}, FilterMenu
});

View file

@ -0,0 +1,108 @@
<div class="formDialogContent smoothScrollY">
<div class="dialogContentInner dialog-content-centered">
<form style="margin:auto;">
<div class="verticalSection verticalSection-extrabottompadding basicFilterSection focuscontainer-x" style="margin-top:2em;">
<div class="checkboxList checkboxList-verticalwrap">
<label class="viewSetting simpleFilter" data-settingname="IsUnplayed">
<input type="checkbox" is="emby-checkbox" />
<span>${Unplayed}</span>
</label>
<label class="viewSetting simpleFilter" data-settingname="IsPlayed">
<input type="checkbox" is="emby-checkbox" />
<span>${Played}</span>
</label>
<label class="viewSetting simpleFilter" data-settingname="IsFavorite">
<input type="checkbox" is="emby-checkbox" />
<span>${Favorite}</span>
</label>
<label class="viewSetting simpleFilter" data-settingname="IsResumable">
<input type="checkbox" is="emby-checkbox" />
<span>${ContinueWatching}</span>
</label>
</div>
</div>
<div class="verticalSection verticalSection-extrabottompadding viewSetting focuscontainer-x" data-settingname="SeriesStatus">
<h2 class="checkboxListLabel">${HeaderSeriesStatus}</h2>
<div class="checkboxList checkboxList-verticalwrap">
<label>
<input type="checkbox" is="emby-checkbox" class="chkSeriesStatus" data-filter="Continuing" />
<span>${Continuing}</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="chkSeriesStatus" data-filter="Ended" />
<span>${Ended}</span>
</label>
</div>
</div>
<div class="verticalSection verticalSection-extrabottompadding hide genreFilters focuscontainer-x">
<h2 class="checkboxListLabel">${Genres}</h2>
<div class="checkboxList checkboxList-verticalwrap filterOptions">
</div>
</div>
<div class="verticalSection verticalSection-extrabottompadding viewSetting focuscontainer-x" data-settingname="VideoType">
<h2 class="checkboxListLabel">${HeaderVideoType}</h2>
<div class="checkboxList checkboxList-verticalwrap">
<label>
<input type="checkbox" is="emby-checkbox" class="simpleFilter" data-settingname="IsHD" />
<span>HD</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="simpleFilter" data-settingname="Is4K" />
<span>4K</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="simpleFilter" data-settingname="IsSD" />
<span>SD</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="simpleFilter" data-settingname="Is3D" />
<span>3D</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="chkVideoTypeFilter" data-filter="Bluray" />
<span>Blu-ray</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="chkVideoTypeFilter" data-filter="Dvd" />
<span>DVD</span>
</label>
</div>
</div>
<div class="verticalSection verticalSection-extrabottompadding featureSection hide focuscontainer-x">
<h2 class="checkboxListLabel">${Features}</h2>
<div class="checkboxList checkboxList-verticalwrap">
<label class="viewSetting simpleFilter" data-settingname="HasSubtitles">
<input type="checkbox" is="emby-checkbox" class="chkFeatureFilter" />
<span>${Subtitles}</span>
</label>
<label class="viewSetting simpleFilter" data-settingname="HasTrailer">
<input type="checkbox" is="emby-checkbox" class="chkFeatureFilter" />
<span>${Trailers}</span>
</label>
<label class="viewSetting simpleFilter" data-settingname="HasSpecialFeature">
<input type="checkbox" is="emby-checkbox" class="chkFeatureFilter" />
<span>${Extras}</span>
</label>
<label class="viewSetting simpleFilter" data-settingname="HasThemeSong">
<input type="checkbox" is="emby-checkbox" class="chkFeatureFilter" />
<span>${ThemeSongs}</span>
</label>
<label class="viewSetting simpleFilter" data-settingname="HasThemeVideo">
<input type="checkbox" is="emby-checkbox" class="chkFeatureFilter" />
<span>${ThemeVideos}</span>
</label>
</div>
</div>
</form>
</div>
</div>

View file

@ -0,0 +1,70 @@
.flex {
display: -webkit-box;
display: -webkit-flex;
display: flex
}
.inline-flex {
display: -webkit-inline-box;
display: -webkit-inline-flex;
display: inline-flex
}
.flex-direction-column {
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-webkit-flex-direction: column;
flex-direction: column
}
.flex-direction-row {
-webkit-box-orient: horizontal;
-webkit-box-direction: normal;
-webkit-flex-direction: row;
flex-direction: row
}
.flex-grow {
-webkit-box-flex: 1;
-webkit-flex-grow: 1;
flex-grow: 1
}
.flex-shrink-zero {
-webkit-flex-shrink: 0;
flex-shrink: 0
}
.align-items-center {
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center
}
.align-items-flex-start {
-webkit-box-align: start;
-webkit-align-items: flex-start;
align-items: flex-start
}
.justify-content-center {
-webkit-box-pack: center;
-webkit-justify-content: center;
justify-content: center
}
.justify-content-flex-end {
-webkit-box-pack: end;
-webkit-justify-content: flex-end;
justify-content: flex-end
}
.flex-wrap-wrap {
-webkit-flex-wrap: wrap;
flex-wrap: wrap
}
.align-self-flex-end {
-webkit-align-self: flex-end;
align-self: flex-end
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,256 @@
define(["dom"], function(dom) {
"use strict";
function pushScope(elem) {
scopes.push(elem)
}
function popScope(elem) {
scopes.length && (scopes.length -= 1)
}
function autoFocus(view, defaultToFirst, findAutoFocusElement) {
var element;
return !1 !== findAutoFocusElement && (element = view.querySelector("*[autofocus]")) ? (focus(element), element) : !1 !== defaultToFirst && (element = getFocusableElements(view, 1, "noautofocus")[0]) ? (focus(element), element) : null
}
function focus(element) {
try {
element.focus({
preventScroll: !0
})
} catch (err) {
console.log("Error in focusManager.autoFocus: " + err)
}
}
function isFocusable(elem) {
return -1 !== focusableTagNames.indexOf(elem.tagName) || !(!elem.classList || !elem.classList.contains("focusable"))
}
function normalizeFocusable(elem, originalElement) {
if (elem) {
var tagName = elem.tagName;
tagName && "HTML" !== tagName && "BODY" !== tagName || (elem = originalElement)
}
return elem
}
function focusableParent(elem) {
for (var originalElement = elem; !isFocusable(elem);) {
var parent = elem.parentNode;
if (!parent) return normalizeFocusable(elem, originalElement);
elem = parent
}
return normalizeFocusable(elem, originalElement)
}
function isCurrentlyFocusableInternal(elem) {
return null !== elem.offsetParent
}
function isCurrentlyFocusable(elem) {
if (elem.disabled) return !1;
if ("-1" === elem.getAttribute("tabindex")) return !1;
if ("INPUT" === elem.tagName) {
var type = elem.type;
if ("range" === type) return !1;
if ("file" === type) return !1
}
return isCurrentlyFocusableInternal(elem)
}
function getDefaultScope() {
return scopes[0] || document.body
}
function getFocusableElements(parent, limit, excludeClass) {
for (var elems = (parent || getDefaultScope()).querySelectorAll(focusableQuery), focusableElements = [], i = 0, length = elems.length; i < length; i++) {
var elem = elems[i];
if ((!excludeClass || !elem.classList.contains(excludeClass)) && (isCurrentlyFocusableInternal(elem) && (focusableElements.push(elem), limit && focusableElements.length >= limit))) break
}
return focusableElements
}
function isFocusContainer(elem, direction) {
if (-1 !== focusableContainerTagNames.indexOf(elem.tagName)) return !0;
var classList = elem.classList;
if (classList.contains("focuscontainer")) return !0;
if (0 === direction) {
if (classList.contains("focuscontainer-x")) return !0;
if (classList.contains("focuscontainer-left")) return !0
} else if (1 === direction) {
if (classList.contains("focuscontainer-x")) return !0;
if (classList.contains("focuscontainer-right")) return !0
} else if (2 === direction) {
if (classList.contains("focuscontainer-y")) return !0
} else if (3 === direction) {
if (classList.contains("focuscontainer-y")) return !0;
if (classList.contains("focuscontainer-down")) return !0
}
return !1
}
function getFocusContainer(elem, direction) {
for (; !isFocusContainer(elem, direction);)
if (!(elem = elem.parentNode)) return getDefaultScope();
return elem
}
function getOffset(elem) {
var box;
if (box = elem.getBoundingClientRect ? elem.getBoundingClientRect() : {
top: 0,
left: 0,
width: 0,
height: 0
}, null === box.right) {
box = {
top: box.top,
left: box.left,
width: box.width,
height: box.height
}, box.right = box.left + box.width, box.bottom = box.top + box.height
}
return box
}
function nav(activeElement, direction, container, focusableElements) {
if (activeElement = activeElement || document.activeElement, activeElement && (activeElement = focusableParent(activeElement)), container = container || (activeElement ? getFocusContainer(activeElement, direction) : getDefaultScope()), !activeElement) return void autoFocus(container, !0, !1);
for (var nearestElement, focusableContainer = dom.parentWithClass(activeElement, "focusable"), rect = getOffset(activeElement), point1x = parseFloat(rect.left) || 0, point1y = parseFloat(rect.top) || 0, point2x = parseFloat(point1x + rect.width - 1) || point1x, point2y = parseFloat(point1y + rect.height - 1) || point1y, sourceMidX = (Math.min, Math.max, rect.left + rect.width / 2), sourceMidY = rect.top + rect.height / 2, focusable = focusableElements || container.querySelectorAll(focusableQuery), minDistance = 1 / 0, i = 0, length = focusable.length; i < length; i++) {
var curr = focusable[i];
if (curr !== activeElement && curr !== focusableContainer) {
var elementRect = getOffset(curr);
if (elementRect.width || elementRect.height) {
switch (direction) {
case 0:
if (elementRect.left >= rect.left) continue;
if (elementRect.right === rect.right) continue;
break;
case 1:
if (elementRect.right <= rect.right) continue;
if (elementRect.left === rect.left) continue;
break;
case 2:
if (elementRect.top >= rect.top) continue;
if (elementRect.bottom >= rect.bottom) continue;
break;
case 3:
if (elementRect.bottom <= rect.bottom) continue;
if (elementRect.top <= rect.top) continue
}
var distX, distY, x = elementRect.left,
y = elementRect.top,
x2 = x + elementRect.width - 1,
y2 = y + elementRect.height - 1,
intersectX = intersects(point1x, point2x, x, x2),
intersectY = intersects(point1y, point2y, y, y2),
midX = elementRect.left + elementRect.width / 2,
midY = elementRect.top + elementRect.height / 2;
switch (direction) {
case 0:
distX = Math.abs(point1x - Math.min(point1x, x2)), distY = intersectY ? 0 : Math.abs(sourceMidY - midY);
break;
case 1:
distX = Math.abs(point2x - Math.max(point2x, x)), distY = intersectY ? 0 : Math.abs(sourceMidY - midY);
break;
case 2:
distY = Math.abs(point1y - Math.min(point1y, y2)), distX = intersectX ? 0 : Math.abs(sourceMidX - midX);
break;
case 3:
distY = Math.abs(point2y - Math.max(point2y, y)), distX = intersectX ? 0 : Math.abs(sourceMidX - midX)
}
var dist = Math.sqrt(distX * distX + distY * distY);
dist < minDistance && (nearestElement = curr, minDistance = dist)
}
}
}
if (nearestElement) {
if (activeElement) {
var nearestElementFocusableParent = dom.parentWithClass(nearestElement, "focusable");
nearestElementFocusableParent && nearestElementFocusableParent !== nearestElement && focusableContainer !== nearestElementFocusableParent && (nearestElement = nearestElementFocusableParent)
}
focus(nearestElement)
}
}
function intersectsInternal(a1, a2, b1, b2) {
return b1 >= a1 && b1 <= a2 || b2 >= a1 && b2 <= a2
}
function intersects(a1, a2, b1, b2) {
return intersectsInternal(a1, a2, b1, b2) || intersectsInternal(b1, b2, a1, a2)
}
function sendText(text) {
document.activeElement.value = text
}
function focusFirst(container, focusableSelector) {
for (var elems = container.querySelectorAll(focusableSelector), i = 0, length = elems.length; i < length; i++) {
var elem = elems[i];
if (isCurrentlyFocusableInternal(elem)) {
focus(elem);
break
}
}
}
function focusLast(container, focusableSelector) {
for (var elems = [].slice.call(container.querySelectorAll(focusableSelector), 0).reverse(), i = 0, length = elems.length; i < length; i++) {
var elem = elems[i];
if (isCurrentlyFocusableInternal(elem)) {
focus(elem);
break
}
}
}
function moveFocus(sourceElement, container, focusableSelector, offset) {
var i, length, elem, elems = container.querySelectorAll(focusableSelector),
list = [];
for (i = 0, length = elems.length; i < length; i++) elem = elems[i], isCurrentlyFocusableInternal(elem) && list.push(elem);
var currentIndex = -1;
for (i = 0, length = list.length; i < length; i++)
if (elem = list[i], sourceElement === elem || elem.contains(sourceElement)) {
currentIndex = i;
break
} if (-1 !== currentIndex) {
var newIndex = currentIndex + offset;
newIndex = Math.max(0, newIndex), newIndex = Math.min(newIndex, list.length - 1);
var newElem = list[newIndex];
newElem && focus(newElem)
}
}
var scopes = [],
focusableTagNames = ["INPUT", "TEXTAREA", "SELECT", "BUTTON", "A"],
focusableContainerTagNames = ["BODY", "DIALOG"],
focusableQuery = focusableTagNames.map(function(t) {
return "INPUT" === t && (t += ':not([type="range"]):not([type="file"])'), t + ':not([tabindex="-1"]):not(:disabled)'
}).join(",") + ",.focusable";
return {
autoFocus: autoFocus,
focus: focus,
focusableParent: focusableParent,
getFocusableElements: getFocusableElements,
moveLeft: function(sourceElement, options) {
nav(sourceElement, 0, options ? options.container : null, options ? options.focusableElements : null)
},
moveRight: function(sourceElement, options) {
nav(sourceElement, 1, options ? options.container : null, options ? options.focusableElements : null)
},
moveUp: function(sourceElement, options) {
nav(sourceElement, 2, options ? options.container : null, options ? options.focusableElements : null)
},
moveDown: function(sourceElement, options) {
nav(sourceElement, 3, options ? options.container : null, options ? options.focusableElements : null)
},
sendText: sendText,
isCurrentlyFocusable: isCurrentlyFocusable,
pushScope: pushScope,
popScope: popScope,
focusFirst: focusFirst,
focusLast: focusLast,
moveFocus: moveFocus
}
});

View file

@ -0,0 +1,39 @@
h1,
h2,
h3 {
font-weight: 500
}
html {
font-family: -apple-system, Helvetica, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", 'Open Sans', sans-serif;
font-size: 93%;
-webkit-text-size-adjust: 100%;
-moz-text-size-adjust: 100%;
text-size-adjust: 100%
}
h1,
h2,
h3 {
font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", 'Open Sans', sans-serif
}
h1 {
font-size: 1.8em
}
h2 {
font-size: 1.5em
}
h3 {
font-size: 1.17em
}
.layout-tv {
font-size: 2.5vh
}
.layout-mobile {
font-size: 90%
}

View file

@ -0,0 +1,33 @@
h1,
h2,
h3 {
font-weight: 500
}
h1 {
font-size: 1.8em
}
.layout-desktop h1 {
font-size: 2em
}
h2 {
font-size: 1.5em
}
h3 {
font-size: 1.17em
}
@media all and (min-height:720px) {
html {
font-size: 20px
}
}
@media all and (min-height:1000px) {
html {
font-size: 27px
}
}

View file

@ -0,0 +1,26 @@
@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: local('Material Icons'), local('MaterialIcons-Regular'), url(flUhRq6tzZclQEJ-Vdg-IuiaDsNc.woff2) format('woff2'), url(flUhRq6tzZclQEJ-Vdg-IuiaDsNa.woff) format('woff')
}
.md-icon {
font-family: 'Material Icons';
font-weight: 400;
font-style: normal;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
-webkit-font-feature-settings: "liga"1;
-moz-font-feature-settings: "liga"1;
font-feature-settings: "liga"1;
line-height: 1;
overflow: hidden;
vertical-align: middle
}

View file

@ -0,0 +1,145 @@
.formDialog,
.formDialogHeader {
display: -webkit-box;
display: -webkit-flex
}
.formDialog,
.formDialogFooter-vertical {
-webkit-box-orient: vertical;
-webkit-box-direction: normal
}
.formDialog {
display: flex;
-webkit-flex-direction: column;
flex-direction: column;
position: relative
}
.formDialogHeader {
padding: 1em .5em;
display: flex;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
-webkit-flex-shrink: 0;
flex-shrink: 0
}
.formDialogHeaderTitle {
margin-left: .25em;
margin-top: 0;
margin-bottom: 0
}
.formDialogContent:not(.no-grow) {
-webkit-box-flex: 1;
-webkit-flex-grow: 1;
flex-grow: 1
}
.dialogContentInner {
padding: .5em 1em 20em
}
.dialogContentInner-mini {
padding-bottom: 10em
}
.dialog-content-centered {
margin: 0 auto;
max-width: 53em
}
.dialogContentTitle {
margin-top: 1em
}
.formDialogFooter {
bottom: 0;
left: 0;
right: 0;
display: -webkit-box;
display: -webkit-flex;
display: flex;
position: absolute;
padding: 1.25em 1em;
z-index: 1;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
justify-content: center;
-webkit-flex-wrap: wrap;
flex-wrap: wrap
}
.formDialogFooter-flex {
position: static;
width: 100%
}
.formDialogFooter-vertical {
padding-bottom: 1.5em;
-webkit-flex-direction: column;
flex-direction: column;
width: 80% !important;
padding-top: .5em
}
.formDialogFooterItem {
margin: .5em !important;
-webkit-box-flex: 1;
-webkit-flex-grow: 1;
flex-grow: 1;
text-align: center;
-webkit-flex-basis: 0;
flex-basis: 0
}
.formDialogFooterItem-vertical {
max-width: none !important;
width: 100%;
-webkit-box-pack: center;
-webkit-justify-content: center;
justify-content: center;
margin: 1em !important
}
.formDialogFooterItem-nomarginbottom {
margin-bottom: 0 !important
}
.formDialogFooterItem-autosize {
-webkit-flex-basis: initial;
flex-basis: initial;
-webkit-box-flex: initial;
-webkit-flex-grow: initial;
flex-grow: initial;
padding-left: 2em;
padding-right: 2em
}
@media all and (min-width:50em) {
.formDialogFooterItem {
max-width: 80%
}
.dialogContentInner {
padding-left: 1.5em;
padding-right: 1.5em
}
}
@media all and (min-width:80em) {
.formDialogFooterItem {
max-width: 70%
}
.dialogContentInner {
padding-left: 2em;
padding-right: 2em
}
}

View file

@ -0,0 +1,12 @@
define(["dom", "fullscreenManager"], function(dom, fullscreenManager) {
"use strict";
function isTargetValid(target) {
return !dom.parentWithTag(target, ["BUTTON", "INPUT", "TEXTAREA"])
}
dom.addEventListener(window, "dblclick", function(e) {
isTargetValid(e.target) && (fullscreenManager.isFullScreen() ? fullscreenManager.exitFullscreen() : fullscreenManager.requestFullscreen())
}, {
passive: !0
})
});

View file

@ -0,0 +1,24 @@
define(["events", "dom"], function(events, dom) {
"use strict";
function fullscreenManager() {}
function onFullScreenChange() {
events.trigger(manager, "fullscreenchange")
}
fullscreenManager.prototype.requestFullscreen = function(element) {
return element = element || document.documentElement, element.requestFullscreen ? void element.requestFullscreen() : element.mozRequestFullScreen ? void element.mozRequestFullScreen() : element.webkitRequestFullscreen ? void element.webkitRequestFullscreen() : element.msRequestFullscreen ? void element.msRequestFullscreen() : ("VIDEO" !== element.tagName && (element = document.querySelector("video") || element), void(element.webkitEnterFullscreen && element.webkitEnterFullscreen()))
}, fullscreenManager.prototype.exitFullscreen = function() {
document.exitFullscreen ? document.exitFullscreen() : document.mozCancelFullScreen ? document.mozCancelFullScreen() : document.webkitExitFullscreen ? document.webkitExitFullscreen() : document.webkitCancelFullscreen ? document.webkitCancelFullscreen() : document.msExitFullscreen && document.msExitFullscreen()
}, fullscreenManager.prototype.isFullScreen = function() {
return !!(document.fullscreen || document.mozFullScreen || document.webkitIsFullScreen || document.msFullscreenElement)
};
var manager = new fullscreenManager;
return dom.addEventListener(document, "fullscreenchange", onFullScreenChange, {
passive: !0
}), dom.addEventListener(document, "webkitfullscreenchange", onFullScreenChange, {
passive: !0
}), dom.addEventListener(document, "mozfullscreenchange", onFullScreenChange, {
passive: !0
}), manager
});

View file

@ -0,0 +1,135 @@
define(["connectionManager", "userSettings", "events"], function(connectionManager, userSettings, events) {
"use strict";
function getCurrentLocale() {
return currentCulture
}
function getCurrentDateTimeLocale() {
return currentDateTimeCulture
}
function getDefaultLanguage() {
var culture = document.documentElement.getAttribute("data-culture");
return culture || (navigator.language ? navigator.language : navigator.userLanguage ? navigator.userLanguage : navigator.languages && navigator.languages.length ? navigator.languages[0] : "en-us")
}
function updateCurrentCulture() {
var culture;
try {
culture = userSettings.language()
} catch (err) {}
culture = culture || getDefaultLanguage(), currentCulture = normalizeLocaleName(culture);
var dateTimeCulture;
try {
dateTimeCulture = userSettings.dateTimeLocale()
} catch (err) {}
currentDateTimeCulture = dateTimeCulture ? normalizeLocaleName(dateTimeCulture) : currentCulture, ensureTranslations(currentCulture)
}
function ensureTranslations(culture) {
for (var i in allTranslations) ensureTranslation(allTranslations[i], culture)
}
function ensureTranslation(translationInfo, culture) {
return translationInfo.dictionaries[culture] ? Promise.resolve() : loadTranslation(translationInfo.translations, culture).then(function(dictionary) {
translationInfo.dictionaries[culture] = dictionary
})
}
function normalizeLocaleName(culture) {
culture = culture.replace("_", "-");
var parts = culture.split("-");
2 === parts.length && parts[0].toLowerCase() === parts[1].toLowerCase() && (culture = parts[0].toLowerCase());
var lower = culture.toLowerCase();
return "ca-es" === lower ? "ca" : "sv-se" === lower ? "sv" : lower
}
function getDictionary(module) {
module || (module = defaultModule());
var translations = allTranslations[module];
return translations ? translations.dictionaries[getCurrentLocale()] : {}
}
function register(options) {
allTranslations[options.name] = {
translations: options.strings || options.translations,
dictionaries: {}
}
}
function loadStrings(options) {
var locale = getCurrentLocale();
return "string" == typeof options ? ensureTranslation(allTranslations[options], locale) : (register(options), ensureTranslation(allTranslations[options.name], locale))
}
function loadTranslation(translations, lang) {
lang = normalizeLocaleName(lang);
var filtered = translations.filter(function(t) {
return normalizeLocaleName(t.lang) === lang
});
return filtered.length || (filtered = translations.filter(function(t) {
return "en-us" === normalizeLocaleName(t.lang)
})), new Promise(function(resolve, reject) {
if (!filtered.length) return void resolve();
var url = filtered[0].path;
url += -1 === url.indexOf("?") ? "?" : "&", url += "v=" + cacheParam;
var xhr = new XMLHttpRequest;
xhr.open("GET", url, !0), xhr.onload = function(e) {
resolve(this.status < 400 ? JSON.parse(this.response) : {})
}, xhr.onerror = function() {
resolve({})
}, xhr.send()
})
}
function translateKey(key) {
var module, parts = key.split("#");
return parts.length > 1 && (module = parts[0], key = parts[1]), translateKeyFromModule(key, module)
}
function translateKeyFromModule(key, module) {
var dictionary = getDictionary(module);
return dictionary ? dictionary[key] || key : key
}
function replaceAll(str, find, replace) {
return str.split(find).join(replace)
}
function translate(key) {
for (var val = translateKey(key), i = 1; i < arguments.length; i++) val = replaceAll(val, "{" + (i - 1) + "}", arguments[i]);
return val
}
function translateHtml(html, module) {
if (module || (module = defaultModule()), !module) throw new Error("module cannot be null or empty");
var startIndex = html.indexOf("${");
if (-1 === startIndex) return html;
startIndex += 2;
var endIndex = html.indexOf("}", startIndex);
if (-1 === endIndex) return html;
var key = html.substring(startIndex, endIndex),
val = translateKeyFromModule(key, module);
return html = html.replace("${" + key + "}", val), translateHtml(html, module)
}
function defaultModule(val) {
return val && (_defaultModule = val), _defaultModule
}
var currentCulture, currentDateTimeCulture, _defaultModule, allTranslations = {},
cacheParam = (new Date).getTime();
return updateCurrentCulture(), events.on(connectionManager, "localusersignedin", updateCurrentCulture), events.on(userSettings, "change", function(e, name) {
"language" !== name && "datetimelocale" !== name || updateCurrentCulture()
}), {
getString: translate,
translate: translate,
translateDocument: translateHtml,
translateHtml: translateHtml,
loadStrings: loadStrings,
defaultModule: defaultModule,
getCurrentLocale: getCurrentLocale,
getCurrentDateTimeLocale: getCurrentDateTimeLocale,
register: register
}
});

View file

@ -0,0 +1,71 @@
define(["dialogHelper", "globalize", "userSettings", "layoutManager", "connectionManager", "require", "loading", "scrollHelper", "emby-checkbox", "emby-radio", "css!./../formdialog", "material-icons"], function(dialogHelper, globalize, userSettings, layoutManager, connectionManager, require, loading, scrollHelper) {
"use strict";
function saveCategories(context, options) {
for (var categories = [], chkCategorys = context.querySelectorAll(".chkCategory"), i = 0, length = chkCategorys.length; i < length; i++) {
var type = chkCategorys[i].getAttribute("data-type");
chkCategorys[i].checked && categories.push(type)
}
categories.length >= 4 && categories.push("series"), categories.push("all"), options.categories = categories
}
function loadCategories(context, options) {
for (var selectedCategories = options.categories || [], chkCategorys = context.querySelectorAll(".chkCategory"), i = 0, length = chkCategorys.length; i < length; i++) {
var type = chkCategorys[i].getAttribute("data-type");
chkCategorys[i].checked = !selectedCategories.length || -1 !== selectedCategories.indexOf(type)
}
}
function save(context) {
var i, length, chkIndicators = context.querySelectorAll(".chkIndicator");
for (i = 0, length = chkIndicators.length; i < length; i++) {
var type = chkIndicators[i].getAttribute("data-type");
userSettings.set("guide-indicator-" + type, chkIndicators[i].checked)
}
userSettings.set("guide-colorcodedbackgrounds", context.querySelector(".chkColorCodedBackgrounds").checked), userSettings.set("livetv-favoritechannelsattop", context.querySelector(".chkFavoriteChannelsAtTop").checked);
var sortBys = context.querySelectorAll(".chkSortOrder");
for (i = 0, length = sortBys.length; i < length; i++)
if (sortBys[i].checked) {
userSettings.set("livetv-channelorder", sortBys[i].value);
break
}
}
function load(context) {
var i, length, chkIndicators = context.querySelectorAll(".chkIndicator");
for (i = 0, length = chkIndicators.length; i < length; i++) {
var type = chkIndicators[i].getAttribute("data-type");
"true" === chkIndicators[i].getAttribute("data-default") ? chkIndicators[i].checked = "false" !== userSettings.get("guide-indicator-" + type) : chkIndicators[i].checked = "true" === userSettings.get("guide-indicator-" + type)
}
context.querySelector(".chkColorCodedBackgrounds").checked = "true" === userSettings.get("guide-colorcodedbackgrounds"), context.querySelector(".chkFavoriteChannelsAtTop").checked = "false" !== userSettings.get("livetv-favoritechannelsattop");
var sortByValue = userSettings.get("livetv-channelorder") || "Number",
sortBys = context.querySelectorAll(".chkSortOrder");
for (i = 0, length = sortBys.length; i < length; i++) sortBys[i].checked = sortBys[i].value === sortByValue
}
function showEditor(options) {
return new Promise(function(resolve, reject) {
var settingsChanged = !1;
require(["text!./guide-settings.template.html"], function(template) {
var dialogOptions = {
removeOnClose: !0,
scrollY: !1
};
layoutManager.tv ? dialogOptions.size = "fullscreen" : dialogOptions.size = "small";
var dlg = dialogHelper.createDialog(dialogOptions);
dlg.classList.add("formDialog");
var html = "";
html += globalize.translateDocument(template, "sharedcomponents"), dlg.innerHTML = html, dlg.addEventListener("change", function() {
settingsChanged = !0
}), dlg.addEventListener("close", function() {
layoutManager.tv && scrollHelper.centerFocus.off(dlg.querySelector(".formDialogContent"), !1), save(dlg), saveCategories(dlg, options), settingsChanged ? resolve() : reject()
}), dlg.querySelector(".btnCancel").addEventListener("click", function() {
dialogHelper.close(dlg)
}), layoutManager.tv && scrollHelper.centerFocus.on(dlg.querySelector(".formDialogContent"), !1), load(dlg), loadCategories(dlg, options), dialogHelper.open(dlg)
})
})
}
return {
show: showEditor
}
});

View file

@ -0,0 +1,68 @@
<div class="formDialogHeader">
<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><i class="md-icon">&#xE5C4;</i></button>
<h3 class="formDialogHeaderTitle">
${Settings}
</h3>
</div>
<div class="formDialogContent smoothScrollY">
<form class="dialogContentInner dialog-content-centered" style="padding-top:2em;">
<h3 class="checkboxListLabel">${SortChannelsBy}</h3>
<label class="radio-label-block"><input type="radio" is="emby-radio" name="ChannelSortOrder" value="Number" class="chkSortOrder" /><span>${ChannelNumber}</span></label>
<label class="radio-label-block"><input type="radio" is="emby-radio" name="ChannelSortOrder" value="DatePlayed" class="chkSortOrder" /><span>${RecentlyWatched}</span></label>
<br />
<label class="checkboxContainer">
<input type="checkbox" is="emby-checkbox" class="chkFavoriteChannelsAtTop" />
<span>${PlaceFavoriteChannelsAtBeginning}</span>
</label>
<h3 class="checkboxListLabel">${ShowIndicatorsFor}</h3>
<div class="checkboxList">
<label>
<input type="checkbox" is="emby-checkbox" class="chkIndicator" data-type="hd" />
<span>${HDPrograms}</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="chkIndicator" data-type="live" data-default="true" />
<span>${LiveBroadcasts}</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="chkIndicator" data-type="new" />
<span>${NewEpisodes}</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="chkIndicator" data-type="premiere" data-default="true" />
<span>${Premieres}</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="chkIndicator" data-type="repeat" />
<span>${RepeatEpisodes}</span>
</label>
</div>
<br />
<label class="checkboxContainer">
<input type="checkbox" is="emby-checkbox" class="chkColorCodedBackgrounds"/>
<span>${EnableColorCodedBackgrounds}</span>
</label>
<h3 class="checkboxListLabel">${Categories}</h3>
<div class="checkboxList">
<label>
<input type="checkbox" is="emby-checkbox" class="chkCategory" data-type="movies" />
<span>${Movies}</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="chkCategory" data-type="sports" />
<span>${Sports}</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="chkCategory" data-type="kids" />
<span>${Kids}</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="chkCategory" data-type="news" />
<span>${News}</span>
</label>
</div>
</form>
</div>

View file

@ -0,0 +1,531 @@
.tvGuideHeader,
.tvguide {
display: -webkit-box;
display: -webkit-flex
}
.channelPrograms,
.programContainer,
.timeslotHeadersInner,
.tvProgram {
position: relative
}
.channelPrograms,
.channelsContainer,
.tvGuideHeader,
.tvguide {
-webkit-box-orient: vertical;
-webkit-box-direction: normal
}
.guideChannelName,
.guideChannelNumber,
.guideProgramName,
.guideProgramNameText {
-o-text-overflow: ellipsis
}
.tvguide {
display: flex;
-webkit-flex-direction: column;
flex-direction: column;
-webkit-box-align: initial;
-webkit-align-items: initial;
align-items: initial
}
.tvGuideHeader {
white-space: nowrap;
width: 100%;
-webkit-flex-direction: column;
flex-direction: column;
-webkit-flex-shrink: 0;
flex-shrink: 0;
display: flex;
contain: layout style paint
}
.layout-desktop .tvGuideHeader {
margin-bottom: .5em
}
.guideHeaderDateSelection {
font-size: 86%;
padding: .4em 0
}
.guide-headerTimeslots {
display: -webkit-box;
display: -webkit-flex;
display: flex
}
.tvProgramSectionHeader {
margin: 0
}
.tvProgram {
display: block;
text-decoration: none;
white-space: nowrap
}
.guideProgramIndicator {
text-transform: uppercase;
-webkit-border-radius: .25em;
border-radius: .25em;
margin-right: .5em;
font-size: 82%;
padding: .2em .25em;
display: -webkit-inline-box;
display: -webkit-inline-flex;
display: inline-flex;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
justify-content: center;
text-align: center;
margin-left: 1em
}
.guide-channelTimeslotHeader {
-webkit-box-pack: center;
-webkit-justify-content: center;
justify-content: center
}
.timeslotHeaders {
white-space: nowrap;
font-weight: 500;
font-size: 120%
}
.programContainer {
white-space: nowrap;
-webkit-box-align: start;
-webkit-align-items: flex-start;
align-items: flex-start;
contain: strict
}
.guideSpacer {
width: .3em;
-webkit-flex-shrink: 0;
flex-shrink: 0
}
.channelPrograms,
.timeslotHeadersInner {
width: 1800vw
}
@media all and (min-width:37.5em) {
.channelPrograms,
.timeslotHeadersInner {
width: 1400vw
}
}
@media all and (min-width:50em) {
.channelPrograms,
.timeslotHeadersInner {
width: 1200vw
}
}
@media all and (min-width:80em) {
.channelPrograms,
.timeslotHeadersInner {
width: 810vw
}
}
.timeslotHeader {
display: -webkit-inline-box;
display: -webkit-inline-flex;
display: inline-flex;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
text-indent: .25em;
width: 2.0833333333333333333333333333333%
}
.guide-channelHeaderCell,
.guide-channelTimeslotHeader,
.programCell {
color: inherit;
cursor: pointer;
vertical-align: middle;
font-family: inherit;
text-decoration: none;
-webkit-box-align: center;
text-align: left;
overflow: hidden
}
.guide-channelHeaderCell,
.guide-channelTimeslotHeader {
padding: 0 !important;
outline: 0 !important;
width: 100%;
font-size: inherit;
-o-text-overflow: ellipsis;
text-overflow: ellipsis;
margin: 0 1px 0 0;
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-align-items: center;
align-items: center;
contain: strict;
-webkit-flex-shrink: 0;
flex-shrink: 0;
-webkit-border-radius: .12em;
border-radius: .12em
}
.guide-channelHeaderCell {
border-width: 1px 1px 1px 0;
border-style: solid;
width: 100%;
height: 4.42em;
contain: strict;
position: relative;
background: 0 0
}
.guide-channelTimeslotHeader {
border: 0 !important;
border-right-color: transparent
}
.channelsContainer,
.guide-channelTimeslotHeader {
width: 24vw
}
@media all and (min-width:31.25em) {
.channelsContainer,
.guide-channelTimeslotHeader {
width: 16vw
}
}
@media all and (min-width:37.5em) {
.channelsContainer,
.guide-channelTimeslotHeader {
width: 16vw
}
}
@media all and (min-width:50em) {
.channelsContainer,
.guide-channelTimeslotHeader {
width: 14vw
}
}
@media all and (min-width:80em) {
.channelsContainer,
.guide-channelTimeslotHeader {
width: 12vw
}
}
.btnGuideViewSettings {
margin: 0;
-webkit-flex-shrink: 0;
flex-shrink: 0
}
.btnGuideViewSettingsIcon {
font-size: 1.5em !important
}
.selectDateIcon {
-webkit-flex-shrink: 0;
flex-shrink: 0
}
@media all and (max-width:50em) {
.guideHdIcon,
.liveTvProgram,
.newTvProgram,
.premiereTvProgram {
display: none
}
}
.channelPrograms,
.programCell {
border-style: solid;
display: -webkit-box;
display: -webkit-flex;
contain: strict
}
.channelPrograms {
white-space: nowrap;
-webkit-box-sizing: border-box;
box-sizing: border-box;
height: 4.42em;
display: flex;
-webkit-flex-direction: column;
flex-direction: column;
border-width: 1px 0
}
.channelPrograms+.channelPrograms,
.guide-channelHeaderCell+.guide-channelHeaderCell {
margin-top: -1px
}
.channelPrograms-tv,
.guide-channelHeaderCell-tv {
height: 3em
}
.guide-channelTimeslotHeader,
.timeslotHeader {
background: 0 0 !important;
height: 2.8em
}
.programGrid {
padding-bottom: 4px;
-webkit-box-flex: 1;
-webkit-flex-grow: 1;
flex-grow: 1
}
.programCell {
background: 0 0;
border-width: 0 0 0 1px;
padding: 0 !important;
width: 100%;
font-size: inherit;
position: absolute;
top: 0;
bottom: 0;
display: flex;
-webkit-align-items: center;
align-items: center;
-webkit-box-flex: 1;
-webkit-flex-grow: 1;
flex-grow: 1;
margin: 0 !important
}
.channelsContainer,
.guideProgramName,
.programGrid {
contain: layout style paint
}
.guide-programNameCaret,
.guideProgramName {
display: -webkit-box;
display: -webkit-flex;
-webkit-box-align: center
}
.guideProgramName {
padding: 0 .7em;
overflow: hidden;
text-overflow: ellipsis;
-webkit-align-items: center;
align-items: center;
display: flex;
position: relative;
-webkit-box-flex: 1;
-webkit-flex-grow: 1;
flex-grow: 1
}
.guide-programNameCaret {
display: flex;
-webkit-align-items: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
justify-content: center;
font-size: 200%
}
.guideProgramNameText {
margin: 0;
font-weight: 400;
overflow: hidden;
text-overflow: ellipsis
}
.guideProgramSecondaryInfo {
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
margin-top: .1em
}
.programIcon {
margin-left: .5em;
height: 1em;
width: 1em;
font-size: 1.6em;
color: #ddd;
-webkit-flex-shrink: 0;
flex-shrink: 0;
-webkit-box-flex: 0;
-webkit-flex-grow: 0;
flex-grow: 0
}
.guide-programTextIcon {
font-weight: 700;
font-size: .9em;
padding: .16em .3em;
-webkit-border-radius: .25em;
border-radius: .25em;
margin-right: .35em;
width: auto;
height: auto
}
.guide-programTextIcon-tv {
font-size: .74em
}
.guideChannelNumber {
padding-left: 1em;
max-width: 30%;
text-overflow: ellipsis;
overflow: hidden;
font-weight: 400;
margin: 0
}
.guideChannelName {
margin-left: auto;
margin-right: 1em;
text-overflow: ellipsis;
overflow: hidden;
max-width: 70%
}
.guideChannelImage {
position: absolute;
right: 8%;
top: 15%;
bottom: 15%;
width: 40%;
-webkit-background-size: contain;
background-size: contain;
background-repeat: no-repeat;
background-position: right center
}
@media all and (min-width:62.5em) {
.guideChannelName {
max-width: 40%
}
}
@media all and (max-width:62.5em) {
.guideChannelNumber {
display: none
}
.guideChannelImage {
width: 70%
}
}
.channelsContainer {
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-flex-shrink: 0;
flex-shrink: 0;
-webkit-flex-direction: column;
flex-direction: column
}
.guide-channelHeaderCell,
.programCell {
outline: 0 !important
}
.seriesTimerIcon,
.timerIcon {
color: #c33 !important
}
.seriesTimerIcon-inactive {
color: inherit !important;
opacity: .7
}
.guideOptions {
-webkit-flex-shrink: 0;
flex-shrink: 0;
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center
}
@media all and (max-width:50em),
all and (max-height:37.5em) {
.tvGuideHeader {
padding-left: 0
}
}
.guideRequiresUnlock {
margin: 1em auto;
text-align: center;
padding: 1em;
-webkit-flex-shrink: 0;
flex-shrink: 0
}
.noRubberBanding {
padding-bottom: 7em
}
.guideDateTabsSlider {
text-align: center
}
.guide-date-tab-button {
padding: .3em .7em !important;
margin: 0 .3em !important;
font-weight: 400
}
.guide-date-tab-button.emby-tab-button-active {
border-color: transparent !important
}
.guide-date-tab-button.emby-button-tv:focus {
-webkit-border-radius: .15em !important;
border-radius: .15em !important;
-webkit-transform: none !important;
transform: none !important
}

View file

@ -0,0 +1,517 @@
define(["require", "inputManager", "browser", "globalize", "connectionManager", "scrollHelper", "serverNotifications", "loading", "datetime", "focusManager", "playbackManager", "userSettings", "imageLoader", "events", "layoutManager", "itemShortcuts", "dom", "css!./guide.css", "programStyles", "material-icons", "scrollStyles", "emby-button", "paper-icon-button-light", "emby-tabs", "emby-scroller", "flexStyles", "registerElement"], function(require, inputManager, browser, globalize, connectionManager, scrollHelper, serverNotifications, loading, datetime, focusManager, playbackManager, userSettings, imageLoader, events, layoutManager, itemShortcuts, dom) {
"use strict";
function showViewSettings(instance) {
require(["guide-settings-dialog"], function(guideSettingsDialog) {
guideSettingsDialog.show(instance.categoryOptions).then(function() {
instance.refresh()
})
})
}
function updateProgramCellOnScroll(cell, scrollPct) {
var left = cell.posLeft;
left || (left = parseFloat(cell.style.left.replace("%", "")), cell.posLeft = left);
var width = cell.posWidth;
width || (width = parseFloat(cell.style.width.replace("%", "")), cell.posWidth = width);
var right = left + width,
newPct = Math.max(Math.min(scrollPct, right), left),
offset = newPct - left,
pctOfWidth = offset / width * 100,
guideProgramName = cell.guideProgramName;
guideProgramName || (guideProgramName = cell.querySelector(".guideProgramName"), cell.guideProgramName = guideProgramName);
var caret = cell.caret;
caret || (caret = cell.querySelector(".guide-programNameCaret"), cell.caret = caret), guideProgramName && (pctOfWidth > 0 && pctOfWidth <= 100 ? (guideProgramName.style.transform = "translateX(" + pctOfWidth + "%)", caret.classList.remove("hide")) : (guideProgramName.style.transform = "none", caret.classList.add("hide")))
}
function updateProgramCellsOnScroll(programGrid, programCells) {
isUpdatingProgramCellScroll || (isUpdatingProgramCellScroll = !0, requestAnimationFrame(function() {
for (var scrollLeft = programGrid.scrollLeft, scrollPct = scrollLeft ? scrollLeft / programGrid.scrollWidth * 100 : 0, i = 0, length = programCells.length; i < length; i++) updateProgramCellOnScroll(programCells[i], scrollPct);
isUpdatingProgramCellScroll = !1
}))
}
function onProgramGridClick(e) {
if (layoutManager.tv) {
var programCell = dom.parentWithClass(e.target, "programCell");
if (programCell) {
var startDate = programCell.getAttribute("data-startdate"),
endDate = programCell.getAttribute("data-enddate");
startDate = datetime.parseISO8601Date(startDate, {
toLocal: !0
}).getTime(), endDate = datetime.parseISO8601Date(endDate, {
toLocal: !0
}).getTime();
var now = (new Date).getTime();
if (now >= startDate && now < endDate) {
var channelId = programCell.getAttribute("data-channelid"),
serverId = programCell.getAttribute("data-serverid");
e.preventDefault(), e.stopPropagation(), playbackManager.play({
ids: [channelId],
serverId: serverId
})
}
}
}
}
function Guide(options) {
function restartAutoRefresh() {
stopAutoRefresh();
autoRefreshInterval = setInterval(function() {
self.refresh()
}, 9e5)
}
function stopAutoRefresh() {
autoRefreshInterval && (clearInterval(autoRefreshInterval), autoRefreshInterval = null)
}
function normalizeDateToTimeslot(date) {
return date.getMinutes() - cellCurationMinutes >= 0 ? date.setHours(date.getHours(), cellCurationMinutes, 0, 0) : date.setHours(date.getHours(), 0, 0, 0), date
}
function showLoading() {
loading.show()
}
function hideLoading() {
loading.hide()
}
function reloadGuide(context, newStartDate, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, focusProgramOnRender) {
var apiClient = connectionManager.getApiClient(options.serverId),
channelQuery = {
StartIndex: 0,
EnableFavoriteSorting: "false" !== userSettings.get("livetv-favoritechannelsattop")
};
channelQuery.UserId = apiClient.getCurrentUserId();
currentChannelLimit = 500, showLoading(), channelQuery.StartIndex = currentStartIndex, channelQuery.Limit = 500, channelQuery.AddCurrentProgram = !1, channelQuery.EnableUserData = !1, channelQuery.EnableImageTypes = "Primary";
var categories = self.categoryOptions.categories || [],
displayMovieContent = !categories.length || -1 !== categories.indexOf("movies"),
displaySportsContent = !categories.length || -1 !== categories.indexOf("sports"),
displayNewsContent = !categories.length || -1 !== categories.indexOf("news"),
displayKidsContent = !categories.length || -1 !== categories.indexOf("kids"),
displaySeriesContent = !categories.length || -1 !== categories.indexOf("series");
displayMovieContent && displaySportsContent && displayNewsContent && displayKidsContent ? (channelQuery.IsMovie = null, channelQuery.IsSports = null, channelQuery.IsKids = null, channelQuery.IsNews = null, channelQuery.IsSeries = null) : (displayNewsContent && (channelQuery.IsNews = !0), displaySportsContent && (channelQuery.IsSports = !0), displayKidsContent && (channelQuery.IsKids = !0), displayMovieContent && (channelQuery.IsMovie = !0), displaySeriesContent && (channelQuery.IsSeries = !0)), "DatePlayed" === userSettings.get("livetv-channelorder") ? (channelQuery.SortBy = "DatePlayed", channelQuery.SortOrder = "Descending") : (channelQuery.SortBy = null, channelQuery.SortOrder = null);
var date = newStartDate;
date = new Date(date.getTime() + 1e3);
var nextDay = new Date(date.getTime() + msPerDay - 2e3),
allowIndicators = dom.getWindowSize().innerWidth >= 600,
renderOptions = {
showHdIcon: allowIndicators && "true" === userSettings.get("guide-indicator-hd"),
showLiveIndicator: allowIndicators && "false" !== userSettings.get("guide-indicator-live"),
showPremiereIndicator: allowIndicators && "false" !== userSettings.get("guide-indicator-premiere"),
showNewIndicator: allowIndicators && "false" !== userSettings.get("guide-indicator-new"),
showRepeatIndicator: allowIndicators && "true" === userSettings.get("guide-indicator-repeat"),
showEpisodeTitle: !layoutManager.tv
};
apiClient.getLiveTvChannels(channelQuery).then(function(channelsResult) {
var btnPreviousPage = context.querySelector(".btnPreviousPage"),
btnNextPage = context.querySelector(".btnNextPage");
channelsResult.TotalRecordCount > 500 ? (context.querySelector(".guideOptions").classList.remove("hide"), btnPreviousPage.classList.remove("hide"), btnNextPage.classList.remove("hide"), channelQuery.StartIndex ? context.querySelector(".btnPreviousPage").disabled = !1 : context.querySelector(".btnPreviousPage").disabled = !0, channelQuery.StartIndex + 500 < channelsResult.TotalRecordCount ? btnNextPage.disabled = !1 : btnNextPage.disabled = !0) : context.querySelector(".guideOptions").classList.add("hide");
var programFields = [],
programQuery = {
UserId: apiClient.getCurrentUserId(),
MaxStartDate: nextDay.toISOString(),
MinEndDate: date.toISOString(),
channelIds: channelsResult.Items.map(function(c) {
return c.Id
}).join(","),
ImageTypeLimit: 1,
EnableImages: !1,
SortBy: "StartDate",
EnableTotalRecordCount: !1,
EnableUserData: !1
};
renderOptions.showHdIcon && programFields.push("IsHD"), programFields.length && (programQuery.Fields = programFields.join("")), apiClient.getLiveTvPrograms(programQuery).then(function(programsResult) {
renderGuide(context, date, channelsResult.Items, programsResult.Items, renderOptions, apiClient, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, focusProgramOnRender), hideLoading()
})
})
}
function getDisplayTime(date) {
if ("string" === (typeof date).toString().toLowerCase()) try {
date = datetime.parseISO8601Date(date, {
toLocal: !0
})
} catch (err) {
return date
}
return datetime.getDisplayTime(date).toLowerCase()
}
function getTimeslotHeadersHtml(startDate, endDateTime) {
var html = "";
for (startDate = new Date(startDate.getTime()), html += '<div class="timeslotHeadersInner">'; startDate.getTime() < endDateTime;) html += '<div class="timeslotHeader">', html += getDisplayTime(startDate), html += "</div>", startDate.setTime(startDate.getTime() + cellDurationMs);
return html
}
function parseDates(program) {
if (!program.StartDateLocal) try {
program.StartDateLocal = datetime.parseISO8601Date(program.StartDate, {
toLocal: !0
})
} catch (err) {}
if (!program.EndDateLocal) try {
program.EndDateLocal = datetime.parseISO8601Date(program.EndDate, {
toLocal: !0
})
} catch (err) {}
return null
}
function getTimerIndicator(item) {
var status;
if ("SeriesTimer" === item.Type) return '<i class="md-icon programIcon seriesTimerIcon">&#xE062;</i>';
if (item.TimerId || item.SeriesTimerId) status = item.Status || "Cancelled";
else {
if ("Timer" !== item.Type) return "";
status = item.Status
}
return item.SeriesTimerId ? "Cancelled" !== status ? '<i class="md-icon programIcon seriesTimerIcon">&#xE062;</i>' : '<i class="md-icon programIcon seriesTimerIcon seriesTimerIcon-inactive">&#xE062;</i>' : '<i class="md-icon programIcon timerIcon">&#xE061;</i>'
}
function getChannelProgramsHtml(context, date, channel, programs, options, listInfo) {
var html = "",
startMs = date.getTime(),
endMs = startMs + msPerDay - 1;
html += '<div class="' + (layoutManager.tv ? "channelPrograms channelPrograms-tv" : "channelPrograms") + '" data-channelid="' + channel.Id + '">';
for (var programsFound, clickAction = layoutManager.tv ? "link" : "programdialog", categories = self.categoryOptions.categories || [], displayMovieContent = !categories.length || -1 !== categories.indexOf("movies"), displaySportsContent = !categories.length || -1 !== categories.indexOf("sports"), displayNewsContent = !categories.length || -1 !== categories.indexOf("news"), displayKidsContent = !categories.length || -1 !== categories.indexOf("kids"), displaySeriesContent = !categories.length || -1 !== categories.indexOf("series"), enableColorCodedBackgrounds = "true" === userSettings.get("guide-colorcodedbackgrounds"), now = (new Date).getTime(), i = listInfo.startIndex, length = programs.length; i < length; i++) {
var program = programs[i];
if (program.ChannelId === channel.Id) {
programsFound = !0, listInfo.startIndex++, parseDates(program);
var startDateLocalMs = program.StartDateLocal.getTime(),
endDateLocalMs = program.EndDateLocal.getTime();
if (!(endDateLocalMs < startMs)) {
if (startDateLocalMs > endMs) break;
items[program.Id] = program;
var renderStartMs = Math.max(startDateLocalMs, startMs),
startPercent = (startDateLocalMs - startMs) / msPerDay;
startPercent *= 100, startPercent = Math.max(startPercent, 0);
var renderEndMs = Math.min(endDateLocalMs, endMs),
endPercent = (renderEndMs - renderStartMs) / msPerDay;
endPercent *= 100;
var cssClass = "programCell itemAction",
accentCssClass = null,
displayInnerContent = !0;
program.IsKids ? (displayInnerContent = displayKidsContent, accentCssClass = "kids") : program.IsSports ? (displayInnerContent = displaySportsContent, accentCssClass = "sports") : program.IsNews ? (displayInnerContent = displayNewsContent, accentCssClass = "news") : program.IsMovie ? (displayInnerContent = displayMovieContent, accentCssClass = "movie") : displayInnerContent = program.IsSeries ? displaySeriesContent : displayMovieContent && displayNewsContent && displaySportsContent && displayKidsContent && displaySeriesContent, displayInnerContent && enableColorCodedBackgrounds && accentCssClass && (cssClass += " programCell-" + accentCssClass), now >= startDateLocalMs && now < endDateLocalMs && (cssClass += " programCell-active");
var timerAttributes = "";
program.TimerId && (timerAttributes += ' data-timerid="' + program.TimerId + '"'), program.SeriesTimerId && (timerAttributes += ' data-seriestimerid="' + program.SeriesTimerId + '"');
if (html += "<button" + (endPercent >= 2 ? ' is="emby-programcell"' : "") + ' data-action="' + clickAction + '"' + timerAttributes + ' data-channelid="' + program.ChannelId + '" data-id="' + program.Id + '" data-serverid="' + program.ServerId + '" data-startdate="' + program.StartDate + '" data-enddate="' + program.EndDate + '" data-type="' + program.Type + '" class="' + cssClass + '" style="left:' + startPercent + "%;width:" + endPercent + '%;">', displayInnerContent) {
html += '<div class="guideProgramName">', html += '<div class="guide-programNameCaret hide"><i class="guideProgramNameCaretIcon md-icon">&#xE314;</i></div>', html += '<div class="guideProgramNameText">' + program.Name;
var indicatorHtml = null;
program.IsLive && options.showLiveIndicator ? indicatorHtml = '<span class="liveTvProgram guideProgramIndicator">' + globalize.translate("sharedcomponents#Live") + "</span>" : program.IsPremiere && options.showPremiereIndicator ? indicatorHtml = '<span class="premiereTvProgram guideProgramIndicator">' + globalize.translate("sharedcomponents#Premiere") + "</span>" : program.IsSeries && !program.IsRepeat && options.showNewIndicator ? indicatorHtml = '<span class="newTvProgram guideProgramIndicator">' + globalize.translate("sharedcomponents#AttributeNew") + "</span>" : program.IsSeries && program.IsRepeat && options.showRepeatIndicator && (indicatorHtml = '<span class="repeatTvProgram guideProgramIndicator">' + globalize.translate("sharedcomponents#Repeat") + "</span>"), html += indicatorHtml || "", program.EpisodeTitle && options.showEpisodeTitle && (html += '<div class="guideProgramSecondaryInfo">', program.EpisodeTitle && options.showEpisodeTitle && (html += '<span class="programSecondaryTitle">' + program.EpisodeTitle + "</span>"), html += "</div>"), html += "</div>", program.IsHD && options.showHdIcon && (layoutManager.tv ? html += '<div class="programIcon guide-programTextIcon guide-programTextIcon-tv">HD</div>' : html += '<div class="programIcon guide-programTextIcon">HD</div>'), html += getTimerIndicator(program), html += "</div>"
}
html += "</button>"
}
} else if (programsFound) break
}
return html += "</div>"
}
function renderChannelHeaders(context, channels, apiClient) {
for (var html = "", i = 0, length = channels.length; i < length; i++) {
var channel = channels[i],
hasChannelImage = channel.ImageTags.Primary,
cssClass = "guide-channelHeaderCell itemAction";
layoutManager.tv && (cssClass += " guide-channelHeaderCell-tv");
var title = [];
if (channel.ChannelNumber && title.push(channel.ChannelNumber), channel.Name && title.push(channel.Name), html += '<button title="' + title.join(" ") + '" type="button" class="' + cssClass + '" data-action="link" data-isfolder="' + channel.IsFolder + '" data-id="' + channel.Id + '" data-serverid="' + channel.ServerId + '" data-type="' + channel.Type + '">', hasChannelImage) {
html += '<div class="guideChannelImage lazy" data-src="' + apiClient.getScaledImageUrl(channel.Id, {
maxHeight: 220,
tag: channel.ImageTags.Primary,
type: "Primary"
}) + '"></div>'
}
channel.ChannelNumber && (html += '<h3 class="guideChannelNumber">' + channel.ChannelNumber + "</h3>"), !hasChannelImage && channel.Name && (html += '<div class="guideChannelName">' + channel.Name + "</div>"), html += "</button>"
}
var channelList = context.querySelector(".channelsContainer");
channelList.innerHTML = html, imageLoader.lazyChildren(channelList)
}
function renderPrograms(context, date, channels, programs, options) {
for (var listInfo = {
startIndex: 0
}, html = [], i = 0, length = channels.length; i < length; i++) html.push(getChannelProgramsHtml(context, date, channels[i], programs, options, listInfo));
programGrid.innerHTML = html.join(""), programCells = programGrid.querySelectorAll("[is=emby-programcell]"), updateProgramCellsOnScroll(programGrid, programCells)
}
function getProgramSortOrder(program, channels) {
for (var channelId = program.ChannelId, channelIndex = -1, i = 0, length = channels.length; i < length; i++)
if (channelId === channels[i].Id) {
channelIndex = i;
break
} return 1e7 * channelIndex + datetime.parseISO8601Date(program.StartDate, {
toLocal: !0
}).getTime() / 6e4
}
function renderGuide(context, date, channels, programs, renderOptions, apiClient, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, focusProgramOnRender) {
programs.sort(function(a, b) {
return getProgramSortOrder(a, channels) - getProgramSortOrder(b, channels)
});
var activeElement = document.activeElement,
itemId = activeElement && activeElement.getAttribute ? activeElement.getAttribute("data-id") : null,
channelRowId = null;
activeElement && (channelRowId = dom.parentWithClass(activeElement, "channelPrograms"), channelRowId = channelRowId && channelRowId.getAttribute ? channelRowId.getAttribute("data-channelid") : null), renderChannelHeaders(context, channels, apiClient);
var startDate = date,
endDate = new Date(startDate.getTime() + msPerDay);
context.querySelector(".timeslotHeaders").innerHTML = getTimeslotHeadersHtml(startDate, endDate), items = {}, renderPrograms(context, date, channels, programs, renderOptions), focusProgramOnRender && focusProgram(context, itemId, channelRowId, focusToTimeMs, startTimeOfDayMs), scrollProgramGridToTimeMs(context, scrollToTimeMs, startTimeOfDayMs)
}
function scrollProgramGridToTimeMs(context, scrollToTimeMs, startTimeOfDayMs) {
scrollToTimeMs -= startTimeOfDayMs;
var pct = scrollToTimeMs / msPerDay;
programGrid.scrollTop = 0;
var scrollPos = pct * programGrid.scrollWidth;
nativeScrollTo(programGrid, scrollPos, !0)
}
function focusProgram(context, itemId, channelRowId, focusToTimeMs, startTimeOfDayMs) {
var focusElem;
if (itemId && (focusElem = context.querySelector('[data-id="' + itemId + '"]')), focusElem) focusManager.focus(focusElem);
else {
var autoFocusParent;
channelRowId && (autoFocusParent = context.querySelector('[data-channelid="' + channelRowId + '"]')), autoFocusParent || (autoFocusParent = programGrid), focusToTimeMs -= startTimeOfDayMs;
for (var pct = focusToTimeMs / msPerDay * 100, programCell = autoFocusParent.querySelector(".programCell"); programCell;) {
var left = (programCell.style.left || "").replace("%", "");
left = left ? parseFloat(left) : 0;
var width = (programCell.style.width || "").replace("%", "");
if (width = width ? parseFloat(width) : 0, left >= pct || left + width >= pct) break;
programCell = programCell.nextSibling
}
programCell ? focusManager.focus(programCell) : focusManager.autoFocus(autoFocusParent, !0)
}
}
function nativeScrollTo(container, pos, horizontal) {
container.scrollTo ? horizontal ? container.scrollTo(pos, 0) : container.scrollTo(0, pos) : horizontal ? container.scrollLeft = Math.round(pos) : container.scrollTop = Math.round(pos)
}
function onProgramGridScroll(context, elem, timeslotHeaders) {
if ((new Date).getTime() - lastHeaderScroll >= 1e3) {
lastGridScroll = (new Date).getTime();
var scrollLeft = elem.scrollLeft;
scrollXPct = 100 * scrollLeft / elem.scrollWidth, nativeScrollTo(timeslotHeaders, scrollLeft, !0)
}
updateProgramCellsOnScroll(elem, programCells)
}
function onTimeslotHeadersScroll(context, elem) {
(new Date).getTime() - lastGridScroll >= 1e3 && (lastHeaderScroll = (new Date).getTime(), nativeScrollTo(programGrid, elem.scrollLeft, !0))
}
function changeDate(page, date, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, focusProgramOnRender) {
var newStartDate = normalizeDateToTimeslot(date);
currentDate = newStartDate, reloadGuide(page, newStartDate, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, focusProgramOnRender)
}
function getDateTabText(date, isActive, tabIndex) {
var cssClass = isActive ? "emby-tab-button guide-date-tab-button emby-tab-button-active" : "emby-tab-button guide-date-tab-button",
html = '<button is="emby-button" class="' + cssClass + '" data-index="' + tabIndex + '" data-date="' + date.getTime() + '">',
tabText = datetime.toLocaleDateString(date, {
weekday: "short"
});
return tabText += "<br/>", tabText += date.getDate(), html += '<div class="emby-button-foreground">' + tabText + "</div>", html += "</button>"
}
function setDateRange(page, guideInfo) {
var today = new Date,
nowHours = today.getHours();
today.setHours(nowHours, 0, 0, 0);
var start = datetime.parseISO8601Date(guideInfo.StartDate, {
toLocal: !0
}),
end = datetime.parseISO8601Date(guideInfo.EndDate, {
toLocal: !0
});
start.setHours(nowHours, 0, 0, 0), end.setHours(0, 0, 0, 0), start.getTime() >= end.getTime() && end.setDate(start.getDate() + 1), start = new Date(Math.max(today, start));
var dateTabsHtml = "",
tabIndex = 0,
date = new Date;
currentDate && date.setTime(currentDate.getTime()), date.setHours(nowHours, 0, 0, 0);
var startTimeOfDayMs = 60 * start.getHours() * 60 * 1e3;
for (startTimeOfDayMs += 60 * start.getMinutes() * 1e3; start <= end;) {
dateTabsHtml += getDateTabText(start, date.getDate() === start.getDate() && date.getMonth() === start.getMonth() && date.getFullYear() === start.getFullYear(), tabIndex), start.setDate(start.getDate() + 1), start.setHours(0, 0, 0, 0), tabIndex++
}
page.querySelector(".emby-tabs-slider").innerHTML = dateTabsHtml, page.querySelector(".guideDateTabs").refresh();
var newDate = new Date,
newDateHours = newDate.getHours(),
scrollToTimeMs = 60 * newDateHours * 60 * 1e3,
minutes = newDate.getMinutes();
minutes >= 30 && (scrollToTimeMs += 18e5), changeDate(page, date, scrollToTimeMs, 60 * (60 * newDateHours + minutes) * 1e3, startTimeOfDayMs, layoutManager.tv)
}
function reloadPage(page) {
showLoading(), connectionManager.getApiClient(options.serverId).getLiveTvGuideInfo().then(function(guideInfo) {
setDateRange(page, guideInfo)
})
}
function getChannelProgramsFocusableElements(container) {
for (var elements = container.querySelectorAll(".programCell"), list = [], currentScrollXPct = scrollXPct + 1, i = 0, length = elements.length; i < length; i++) {
var elem = elements[i],
left = (elem.style.left || "").replace("%", "");
left = left ? parseFloat(left) : 0;
var width = (elem.style.width || "").replace("%", "");
width = width ? parseFloat(width) : 0, left + width >= currentScrollXPct && list.push(elem)
}
return list
}
function onInputCommand(e) {
var container, channelPrograms, focusableElements, newRow, target = e.target,
programCell = dom.parentWithClass(target, "programCell");
switch (e.detail.command) {
case "up":
programCell ? (container = programGrid, channelPrograms = dom.parentWithClass(programCell, "channelPrograms"), newRow = channelPrograms.previousSibling, newRow ? (focusableElements = getChannelProgramsFocusableElements(newRow), focusableElements.length ? container = newRow : focusableElements = null) : container = null) : container = null, lastFocusDirection = e.detail.command, focusManager.moveUp(target, {
container: container,
focusableElements: focusableElements
});
break;
case "down":
programCell ? (container = programGrid, channelPrograms = dom.parentWithClass(programCell, "channelPrograms"), newRow = channelPrograms.nextSibling, newRow ? (focusableElements = getChannelProgramsFocusableElements(newRow), focusableElements.length ? container = newRow : focusableElements = null) : container = null) : container = null, lastFocusDirection = e.detail.command, focusManager.moveDown(target, {
container: container,
focusableElements: focusableElements
});
break;
case "left":
container = programCell ? dom.parentWithClass(programCell, "channelPrograms") : null, container && !programCell.previousSibling && (container = null), lastFocusDirection = e.detail.command, focusManager.moveLeft(target, {
container: container
}), !0;
break;
case "right":
container = programCell ? dom.parentWithClass(programCell, "channelPrograms") : null, lastFocusDirection = e.detail.command, focusManager.moveRight(target, {
container: container
}), !0;
break;
default:
return
}
e.preventDefault(), e.stopPropagation()
}
function onScrollerFocus(e) {
var target = e.target,
programCell = dom.parentWithClass(target, "programCell");
if (programCell) {
var focused = target,
id = focused.getAttribute("data-id"),
item = items[id];
item && events.trigger(self, "focus", [{
item: item
}])
}
if ("left" === lastFocusDirection) programCell && scrollHelper.toStart(programGrid, programCell, !0, !0);
else if ("right" === lastFocusDirection) programCell && scrollHelper.toCenter(programGrid, programCell, !0, !0);
else if ("up" === lastFocusDirection || "down" === lastFocusDirection) {
var verticalScroller = dom.parentWithClass(target, "guideVerticalScroller");
if (verticalScroller) {
var focusedElement = programCell || dom.parentWithTag(target, "BUTTON");
verticalScroller.toCenter(focusedElement, !0)
}
}
}
function setScrollEvents(view, enabled) {
if (layoutManager.tv) {
var guideVerticalScroller = view.querySelector(".guideVerticalScroller");
enabled ? inputManager.on(guideVerticalScroller, onInputCommand) : inputManager.off(guideVerticalScroller, onInputCommand)
}
}
function onTimerCreated(e, apiClient, data) {
for (var programId = data.ProgramId, newTimerId = data.Id, cells = options.element.querySelectorAll('.programCell[data-id="' + programId + '"]'), i = 0, length = cells.length; i < length; i++) {
var cell = cells[i];
cell.querySelector(".timerIcon") || cell.querySelector(".guideProgramName").insertAdjacentHTML("beforeend", '<i class="timerIcon md-icon programIcon">&#xE061;</i>'), newTimerId && cell.setAttribute("data-timerid", newTimerId)
}
}
function onSeriesTimerCreated(e, apiClient, data) {}
function onTimerCancelled(e, apiClient, data) {
for (var id = data.Id, cells = options.element.querySelectorAll('.programCell[data-timerid="' + id + '"]'), i = 0, length = cells.length; i < length; i++) {
var cell = cells[i],
icon = cell.querySelector(".timerIcon");
icon && icon.parentNode.removeChild(icon), cell.removeAttribute("data-timerid")
}
}
function onSeriesTimerCancelled(e, apiClient, data) {
for (var id = data.Id, cells = options.element.querySelectorAll('.programCell[data-seriestimerid="' + id + '"]'), i = 0, length = cells.length; i < length; i++) {
var cell = cells[i],
icon = cell.querySelector(".seriesTimerIcon");
icon && icon.parentNode.removeChild(icon), cell.removeAttribute("data-seriestimerid")
}
}
var self = this,
items = {};
self.options = options, self.categoryOptions = {
categories: []
};
var currentDate, autoRefreshInterval, programCells, lastFocusDirection, programGrid, cellCurationMinutes = 30,
cellDurationMs = 60 * cellCurationMinutes * 1e3,
msPerDay = 864e5,
currentStartIndex = 0,
currentChannelLimit = 0;
self.refresh = function() {
currentDate = null, reloadPage(options.element), restartAutoRefresh()
}, self.pause = function() {
stopAutoRefresh()
}, self.resume = function(refreshData) {
refreshData ? self.refresh() : restartAutoRefresh()
}, self.destroy = function() {
stopAutoRefresh(), events.off(serverNotifications, "TimerCreated", onTimerCreated), events.off(serverNotifications, "SeriesTimerCreated", onSeriesTimerCreated), events.off(serverNotifications, "TimerCancelled", onTimerCancelled), events.off(serverNotifications, "SeriesTimerCancelled", onSeriesTimerCancelled), setScrollEvents(options.element, !1), itemShortcuts.off(options.element), items = {}
};
var lastGridScroll = 0,
lastHeaderScroll = 0,
scrollXPct = 0;
require(["text!./tvguide.template.html"], function(template) {
var context = options.element;
context.classList.add("tvguide"), context.innerHTML = globalize.translateDocument(template, "sharedcomponents"), programGrid = context.querySelector(".programGrid");
var timeslotHeaders = context.querySelector(".timeslotHeaders");
layoutManager.tv ? dom.addEventListener(context.querySelector(".guideVerticalScroller"), "focus", onScrollerFocus, {
capture: !0,
passive: !0
}) : layoutManager.desktop && timeslotHeaders.classList.add("timeslotHeaders-desktop"), (browser.iOS || browser.osx) && (context.querySelector(".channelsContainer").classList.add("noRubberBanding"), programGrid.classList.add("noRubberBanding")), dom.addEventListener(programGrid, "scroll", function(e) {
onProgramGridScroll(context, this, timeslotHeaders)
}, {
passive: !0
}), dom.addEventListener(timeslotHeaders, "scroll", function() {
onTimeslotHeadersScroll(context, this)
}, {
passive: !0
}), programGrid.addEventListener("click", onProgramGridClick), context.querySelector(".btnNextPage").addEventListener("click", function() {
currentStartIndex += currentChannelLimit, reloadPage(context), restartAutoRefresh()
}), context.querySelector(".btnPreviousPage").addEventListener("click", function() {
currentStartIndex = Math.max(currentStartIndex - currentChannelLimit, 0), reloadPage(context), restartAutoRefresh()
}), context.querySelector(".btnGuideViewSettings").addEventListener("click", function() {
showViewSettings(self), restartAutoRefresh()
}), context.querySelector(".guideDateTabs").addEventListener("tabchange", function(e) {
var allTabButtons = e.target.querySelectorAll(".guide-date-tab-button"),
tabButton = allTabButtons[parseInt(e.detail.selectedTabIndex)];
if (tabButton) {
var previousButton = null == e.detail.previousIndex ? null : allTabButtons[parseInt(e.detail.previousIndex)],
date = new Date;
date.setTime(parseInt(tabButton.getAttribute("data-date")));
var scrollToTimeMs, scrollWidth = programGrid.scrollWidth;
if (scrollToTimeMs = scrollWidth ? programGrid.scrollLeft / scrollWidth * msPerDay : 0, previousButton) {
var previousDate = new Date;
previousDate.setTime(parseInt(previousButton.getAttribute("data-date"))), scrollToTimeMs += 60 * previousDate.getHours() * 60 * 1e3, scrollToTimeMs += 60 * previousDate.getMinutes() * 1e3
}
var startTimeOfDayMs = 60 * date.getHours() * 60 * 1e3;
startTimeOfDayMs += 60 * date.getMinutes() * 1e3, changeDate(context, date, scrollToTimeMs, scrollToTimeMs, startTimeOfDayMs, !1)
}
}), setScrollEvents(context, !0), itemShortcuts.on(context), events.trigger(self, "load"), events.on(serverNotifications, "TimerCreated", onTimerCreated), events.on(serverNotifications, "SeriesTimerCreated", onSeriesTimerCreated), events.on(serverNotifications, "TimerCancelled", onTimerCancelled), events.on(serverNotifications, "SeriesTimerCancelled", onSeriesTimerCancelled), self.refresh()
})
}
var isUpdatingProgramCellScroll = !1,
ProgramCellPrototype = Object.create(HTMLButtonElement.prototype);
return ProgramCellPrototype.detachedCallback = function() {
this.posLeft = null, this.posWidth = null, this.guideProgramName = null
}, document.registerElement("emby-programcell", {
prototype: ProgramCellPrototype,
extends: "button"
}), Guide
});

View file

@ -0,0 +1,19 @@
.newTvProgram {
background: #38c;
color: #fff
}
.liveTvProgram {
background: #c33;
color: #fff
}
.premiereTvProgram {
background: #EF6C00;
color: #fff
}
.repeatTvProgram {
background: #009688;
color: #fff
}

View file

@ -0,0 +1,39 @@
<div class="tvGuideHeader">
<div class="guideHeaderDateSelection">
<div is="emby-tabs" class="guideDateTabs focuscontainer-x" data-selectionbar="false">
<div class="emby-tabs-slider guideDateTabsSlider">
</div>
</div>
</div>
<div class="guide-headerTimeslots">
<div class="guide-channelTimeslotHeader">
<button is="paper-icon-button-light" type="button" class="btnGuideViewSettings">
<i class="md-icon btnGuideViewSettingsIcon">&#xE5D3;</i>
</button>
</div>
<div class="timeslotHeaders scrollX guideScroller"></div>
</div>
</div>
<div is="emby-scroller" class="guideVerticalScroller flex flex-grow programContainer guideScroller" data-skipfocuswhenvisible="true" data-horizontal="false">
<div class="scrollSlider flex flex-grow flex-direction-row" style="overflow:hidden;contain: layout style paint;">
<div class="channelsContainer">
<div class="channelList"></div>
</div>
<div class="programGrid scrollX guideScroller flex-grow focuscontainer-right" style="white-space: nowrap;">
</div>
</div>
</div>
<div class="guideOptions hide">
<button is="paper-icon-button-light" type="button" class="btnPreviousPage">
<i class="md-icon">&#xE5C4;</i>
</button>
<button is="paper-icon-button-light" type="button" class="btnNextPage">
<i class="md-icon">&#xE5C8;</i>
</button>
</div>

View file

@ -0,0 +1,15 @@
.headroom {
-webkit-transition: -webkit-transform 140ms linear;
-o-transition: transform 140ms linear;
transition: transform 140ms linear
}
.headroom--pinned {
-webkit-transform: none;
transform: none
}
.headroom--unpinned:not(.headroomDisabled) {
-webkit-transform: translateY(-100%);
transform: translateY(-100%)
}

View file

@ -0,0 +1,136 @@
define(["dom", "layoutManager", "browser", "css!./headroom"], function(dom, layoutManager, browser) {
"use strict";
function Debouncer(callback) {
this.callback = callback, this.ticking = !1
}
function onHeadroomClearedExternally() {
this.state = null
}
function Headroom(elems, options) {
options = Object.assign(Headroom.options, options || {}), this.lastKnownScrollY = 0, this.elems = elems, this.scroller = options.scroller, this.debouncer = onScroll.bind(this), this.offset = options.offset, this.initialised = !1, this.initialClass = options.initialClass, this.unPinnedClass = options.unPinnedClass, this.pinnedClass = options.pinnedClass, this.state = "clear"
}
function onScroll() {
this.paused || requestAnimationFrame(this.rafCallback || (this.rafCallback = this.update.bind(this)))
}
var requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame;
return Debouncer.prototype = {
constructor: Debouncer,
update: function() {
this.callback && this.callback(), this.ticking = !1
},
handleEvent: function() {
this.ticking || (requestAnimationFrame(this.rafCallback || (this.rafCallback = this.update.bind(this))), this.ticking = !0)
}
}, Headroom.prototype = {
constructor: Headroom,
init: function() {
if (browser.supportsCssAnimation()) {
for (var i = 0, length = this.elems.length; i < length; i++) this.elems[i].classList.add(this.initialClass), this.elems[i].addEventListener("clearheadroom", onHeadroomClearedExternally.bind(this));
this.attachEvent()
}
return this
},
add: function(elem) {
browser.supportsCssAnimation() && (elem.classList.add(this.initialClass), elem.addEventListener("clearheadroom", onHeadroomClearedExternally.bind(this)), this.elems.push(elem))
},
remove: function(elem) {
elem.classList.remove(this.unPinnedClass), elem.classList.remove(this.initialClass), elem.classList.remove(this.pinnedClass);
var i = this.elems.indexOf(elem); - 1 !== i && this.elems.splice(i, 1)
},
pause: function() {
this.paused = !0
},
resume: function() {
this.paused = !1
},
destroy: function() {
this.initialised = !1;
for (var i = 0, length = this.elems.length; i < length; i++) {
var classList = this.elems[i].classList;
classList.remove(this.unPinnedClass), classList.remove(this.initialClass), classList.remove(this.pinnedClass)
}
var scrollEventName = this.scroller.getScrollEventName ? this.scroller.getScrollEventName() : "scroll";
dom.removeEventListener(this.scroller, scrollEventName, this.debouncer, {
capture: !1,
passive: !0
})
},
attachEvent: function() {
if (!this.initialised) {
this.lastKnownScrollY = this.getScrollY(), this.initialised = !0;
var scrollEventName = this.scroller.getScrollEventName ? this.scroller.getScrollEventName() : "scroll";
dom.addEventListener(this.scroller, scrollEventName, this.debouncer, {
capture: !1,
passive: !0
}), this.update()
}
},
clear: function() {
if ("clear" !== this.state) {
this.state = "clear";
for (var unpinnedClass = this.unPinnedClass, i = (this.pinnedClass, 0), length = this.elems.length; i < length; i++) {
this.elems[i].classList.remove(unpinnedClass)
}
}
},
pin: function() {
if ("pin" !== this.state) {
this.state = "pin";
for (var unpinnedClass = this.unPinnedClass, pinnedClass = this.pinnedClass, i = 0, length = this.elems.length; i < length; i++) {
var classList = this.elems[i].classList;
classList.remove(unpinnedClass), classList.add(pinnedClass)
}
}
},
unpin: function() {
if ("unpin" !== this.state) {
this.state = "unpin";
for (var unpinnedClass = this.unPinnedClass, i = (this.pinnedClass, 0), length = this.elems.length; i < length; i++) {
this.elems[i].classList.add(unpinnedClass)
}
}
},
getScrollY: function() {
var scroller = this.scroller;
if (scroller.getScrollPosition) return scroller.getScrollPosition();
var pageYOffset = scroller.pageYOffset;
if (void 0 !== pageYOffset) return pageYOffset;
var scrollTop = scroller.scrollTop;
return void 0 !== scrollTop ? scrollTop : (document.documentElement || document.body).scrollTop
},
shouldUnpin: function(currentScrollY) {
var scrollingDown = currentScrollY > this.lastKnownScrollY,
pastOffset = currentScrollY >= this.offset;
return scrollingDown && pastOffset
},
shouldPin: function(currentScrollY) {
var scrollingUp = currentScrollY < this.lastKnownScrollY,
pastOffset = currentScrollY <= this.offset;
return scrollingUp || pastOffset
},
update: function() {
if (!this.paused) {
var currentScrollY = this.getScrollY(),
lastKnownScrollY = this.lastKnownScrollY,
isTv = layoutManager.tv;
if (currentScrollY <= (isTv ? 120 : 10)) this.clear();
else if (this.shouldUnpin(currentScrollY)) this.unpin();
else if (this.shouldPin(currentScrollY)) {
var toleranceExceeded = Math.abs(currentScrollY - lastKnownScrollY) >= 14;
currentScrollY && isTv ? this.unpin() : toleranceExceeded && this.clear()
}
this.lastKnownScrollY = currentScrollY
}
}
}, Headroom.options = {
offset: 0,
scroller: window,
initialClass: "headroom",
unPinnedClass: "headroom--unpinned",
pinnedClass: "headroom--pinned"
}, Headroom
});

View file

@ -0,0 +1,233 @@
define(["require", "apphost", "layoutManager", "focusManager", "globalize", "loading", "connectionManager", "homeSections", "dom", "events", "listViewStyle", "emby-select", "emby-checkbox"], function(require, appHost, layoutManager, focusManager, globalize, loading, connectionManager, homeSections, dom, events) {
"use strict";
function renderViews(page, user, result) {
var folderHtml = "";
folderHtml += '<div class="checkboxList">', folderHtml += result.map(function(i) {
var currentHtml = "",
id = "chkGroupFolder" + i.Id,
isChecked = -1 !== user.Configuration.GroupedFolders.indexOf(i.Id),
checkedHtml = isChecked ? ' checked="checked"' : "";
return currentHtml += "<label>", currentHtml += '<input type="checkbox" is="emby-checkbox" class="chkGroupFolder" data-folderid="' + i.Id + '" id="' + id + '"' + checkedHtml + "/>", currentHtml += "<span>" + i.Name + "</span>", currentHtml += "</label>"
}).join(""), folderHtml += "</div>", page.querySelector(".folderGroupList").innerHTML = folderHtml
}
function getLandingScreenOptions(type) {
var list = [];
return "movies" === type ? (list.push({
name: globalize.translate("sharedcomponents#Movies"),
value: "movies",
isDefault: !0
}), list.push({
name: globalize.translate("sharedcomponents#Suggestions"),
value: "suggestions"
}), list.push({
name: globalize.translate("sharedcomponents#Favorites"),
value: "favorites"
}), list.push({
name: globalize.translate("sharedcomponents#Collections"),
value: "collections"
})) : "tvshows" === type ? (list.push({
name: globalize.translate("sharedcomponents#Shows"),
value: "shows",
isDefault: !0
}), list.push({
name: globalize.translate("sharedcomponents#Suggestions"),
value: "suggestions"
}), list.push({
name: globalize.translate("sharedcomponents#Latest"),
value: "latest"
}), list.push({
name: globalize.translate("sharedcomponents#Favorites"),
value: "favorites"
})) : "music" === type ? (list.push({
name: globalize.translate("sharedcomponents#Suggestions"),
value: "suggestions",
isDefault: !0
}), list.push({
name: globalize.translate("sharedcomponents#Albums"),
value: "albums"
}), list.push({
name: globalize.translate("sharedcomponents#HeaderAlbumArtists"),
value: "albumartists"
}), list.push({
name: globalize.translate("sharedcomponents#Artists"),
value: "artists"
}), list.push({
name: globalize.translate("sharedcomponents#Playlists"),
value: "playlists"
}), list.push({
name: globalize.translate("sharedcomponents#Genres"),
value: "genres"
})) : "livetv" === type && (list.push({
name: globalize.translate("sharedcomponents#Suggestions"),
value: "suggestions",
isDefault: !0
}), list.push({
name: globalize.translate("sharedcomponents#Guide"),
value: "guide"
})), list
}
function getLandingScreenOptionsHtml(type, userValue) {
return getLandingScreenOptions(type).map(function(o) {
var selected = userValue === o.value || o.isDefault && !userValue,
selectedHtml = selected ? " selected" : "";
return '<option value="' + (o.isDefault ? "" : o.value) + '"' + selectedHtml + ">" + o.name + "</option>"
}).join("")
}
function renderViewOrder(context, user, result) {
var html = "",
index = 0;
html += result.Items.map(function(view) {
var currentHtml = "";
return currentHtml += '<div class="listItem viewItem" data-viewid="' + view.Id + '">', currentHtml += '<i class="md-icon listItemIcon">&#xE2C8;</i>', currentHtml += '<div class="listItemBody">', currentHtml += "<div>", currentHtml += view.Name, currentHtml += "</div>", currentHtml += "</div>", currentHtml += '<button type="button" is="paper-icon-button-light" class="btnViewItemUp btnViewItemMove autoSize" title="' + globalize.translate("sharedcomponents#Up") + '"><i class="md-icon">&#xE316;</i></button>', currentHtml += '<button type="button" is="paper-icon-button-light" class="btnViewItemDown btnViewItemMove autoSize" title="' + globalize.translate("sharedcomponents#Down") + '"><i class="md-icon">&#xE313;</i></button>', currentHtml += "</div>", index++, currentHtml
}).join(""), context.querySelector(".viewOrderList").innerHTML = html
}
function updateHomeSectionValues(context, userSettings) {
for (var i = 1; i <= 7; i++) {
var select = context.querySelector("#selectHomeSection" + i),
defaultValue = homeSections.getDefaultSection(i - 1),
option = select.querySelector("option[value=" + defaultValue + "]") || select.querySelector('option[value=""]'),
userValue = userSettings.get("homesection" + (i - 1));
option.value = "", select.value = userValue !== defaultValue && userValue ? userValue : ""
}
context.querySelector(".selectTVHomeScreen").value = userSettings.get("tvhome") || ""
}
function getPerLibrarySettingsHtml(item, user, userSettings, apiClient) {
var isChecked, html = "";
if ("Channel" !== item.Type && "boxsets" !== item.CollectionType && "playlists" !== item.CollectionType || (isChecked = -1 === (user.Configuration.MyMediaExcludes || []).indexOf(item.Id), html += "<div>", html += "<label>", html += '<input type="checkbox" is="emby-checkbox" class="chkIncludeInMyMedia" data-folderid="' + item.Id + '"' + (isChecked ? ' checked="checked"' : "") + "/>", html += "<span>" + globalize.translate("sharedcomponents#DisplayInMyMedia") + "</span>", html += "</label>", html += "</div>"), -1 === ["playlists", "livetv", "boxsets", "channels"].indexOf(item.CollectionType || "") && (isChecked = -1 === user.Configuration.LatestItemsExcludes.indexOf(item.Id), html += '<label class="fldIncludeInLatest">', html += '<input type="checkbox" is="emby-checkbox" class="chkIncludeInLatest" data-folderid="' + item.Id + '"' + (isChecked ? ' checked="checked"' : "") + "/>", html += "<span>" + globalize.translate("sharedcomponents#DisplayInOtherHomeScreenSections") + "</span>", html += "</label>"), html && (html = '<div class="checkboxListContainer">' + html + "</div>"), "movies" === item.CollectionType || "tvshows" === item.CollectionType || "music" === item.CollectionType || "livetv" === item.CollectionType) {
var idForLanding = "livetv" === item.CollectionType ? item.CollectionType : item.Id;
html += '<div class="selectContainer">', html += '<select is="emby-select" class="selectLanding" data-folderid="' + idForLanding + '" label="' + globalize.translate("sharedcomponents#LabelDefaultScreen") + '">';
var userValue = userSettings.get("landing-" + idForLanding);
html += getLandingScreenOptionsHtml(item.CollectionType, userValue), html += "</select>", html += "</div>"
}
if (html) {
var prefix = "";
prefix += '<div class="verticalSection">', prefix += '<h2 class="sectionTitle">', prefix += item.Name, prefix += "</h2>", html = prefix + html, html += "</div>"
}
return html
}
function renderPerLibrarySettings(context, user, userViews, userSettings, apiClient) {
for (var elem = context.querySelector(".perLibrarySettings"), html = "", i = 0, length = userViews.length; i < length; i++) html += getPerLibrarySettingsHtml(userViews[i], user, userSettings, apiClient);
elem.innerHTML = html
}
function loadForm(context, user, userSettings, apiClient) {
context.querySelector(".chkHidePlayedFromLatest").checked = user.Configuration.HidePlayedInLatest || !1, updateHomeSectionValues(context, userSettings);
var promise1 = apiClient.getUserViews({
IncludeHidden: !0
}, user.Id),
promise2 = apiClient.getJSON(apiClient.getUrl("Users/" + user.Id + "/GroupingOptions"));
Promise.all([promise1, promise2]).then(function(responses) {
renderViewOrder(context, user, responses[0]), renderPerLibrarySettings(context, user, responses[0].Items, userSettings, apiClient), renderViews(context, user, responses[1]), loading.hide()
})
}
function onSectionOrderListClick(e) {
var target = dom.parentWithClass(e.target, "btnViewItemMove");
if (target) {
var viewItem = dom.parentWithClass(target, "viewItem");
if (viewItem) {
dom.parentWithClass(viewItem, "paperList");
if (target.classList.contains("btnViewItemDown")) {
var next = viewItem.nextSibling;
next && (viewItem.parentNode.removeChild(viewItem), next.parentNode.insertBefore(viewItem, next.nextSibling))
} else {
var prev = viewItem.previousSibling;
prev && (viewItem.parentNode.removeChild(viewItem), prev.parentNode.insertBefore(viewItem, prev))
}
}
}
}
function getCheckboxItems(selector, context, isChecked) {
for (var inputs = context.querySelectorAll(selector), list = [], i = 0, length = inputs.length; i < length; i++) inputs[i].checked === isChecked && list.push(inputs[i]);
return list
}
function saveUser(context, user, userSettingsInstance, apiClient) {
user.Configuration.HidePlayedInLatest = context.querySelector(".chkHidePlayedFromLatest").checked, user.Configuration.LatestItemsExcludes = getCheckboxItems(".chkIncludeInLatest", context, !1).map(function(i) {
return i.getAttribute("data-folderid")
}), user.Configuration.MyMediaExcludes = getCheckboxItems(".chkIncludeInMyMedia", context, !1).map(function(i) {
return i.getAttribute("data-folderid")
}), user.Configuration.GroupedFolders = getCheckboxItems(".chkGroupFolder", context, !0).map(function(i) {
return i.getAttribute("data-folderid")
});
var i, length, viewItems = context.querySelectorAll(".viewItem"),
orderedViews = [];
for (i = 0, length = viewItems.length; i < length; i++) orderedViews.push(viewItems[i].getAttribute("data-viewid"));
user.Configuration.OrderedViews = orderedViews, userSettingsInstance.set("tvhome", context.querySelector(".selectTVHomeScreen").value), userSettingsInstance.set("homesection0", context.querySelector("#selectHomeSection1").value), userSettingsInstance.set("homesection1", context.querySelector("#selectHomeSection2").value), userSettingsInstance.set("homesection2", context.querySelector("#selectHomeSection3").value), userSettingsInstance.set("homesection3", context.querySelector("#selectHomeSection4").value), userSettingsInstance.set("homesection4", context.querySelector("#selectHomeSection5").value), userSettingsInstance.set("homesection5", context.querySelector("#selectHomeSection6").value), userSettingsInstance.set("homesection6", context.querySelector("#selectHomeSection7").value);
var selectLandings = context.querySelectorAll(".selectLanding");
for (i = 0, length = selectLandings.length; i < length; i++) {
var selectLanding = selectLandings[i];
userSettingsInstance.set("landing-" + selectLanding.getAttribute("data-folderid"), selectLanding.value)
}
return apiClient.updateUserConfiguration(user.Id, user.Configuration)
}
function save(instance, context, userId, userSettings, apiClient, enableSaveConfirmation) {
loading.show(), apiClient.getUser(userId).then(function(user) {
saveUser(context, user, userSettings, apiClient).then(function() {
loading.hide(), enableSaveConfirmation && require(["toast"], function(toast) {
toast(globalize.translate("sharedcomponents#SettingsSaved"))
}), events.trigger(instance, "saved")
}, function() {
loading.hide()
})
})
}
function onSubmit(e) {
var self = this,
apiClient = connectionManager.getApiClient(self.options.serverId),
userId = self.options.userId,
userSettings = self.options.userSettings;
return userSettings.setUserInfo(userId, apiClient).then(function() {
var enableSaveConfirmation = self.options.enableSaveConfirmation;
save(self, self.options.element, userId, userSettings, apiClient, enableSaveConfirmation)
}), e && e.preventDefault(), !1
}
function onChange(e) {
var chkIncludeInMyMedia = dom.parentWithClass(e.target, "chkIncludeInMyMedia");
if (chkIncludeInMyMedia) {
var section = dom.parentWithClass(chkIncludeInMyMedia, "verticalSection"),
fldIncludeInLatest = section.querySelector(".fldIncludeInLatest");
fldIncludeInLatest && (chkIncludeInMyMedia.checked ? fldIncludeInLatest.classList.remove("hide") : fldIncludeInLatest.classList.add("hide"))
}
}
function embed(options, self) {
require(["text!./homescreensettings.template.html"], function(template) {
for (var i = 1; i <= numConfigurableSections; i++) template = template.replace("{section" + i + "label}", globalize.translate("sharedcomponents#LabelHomeScreenSectionValue", i));
options.element.innerHTML = globalize.translateDocument(template, "sharedcomponents"), options.element.querySelector(".viewOrderList").addEventListener("click", onSectionOrderListClick), options.element.querySelector("form").addEventListener("submit", onSubmit.bind(self)), options.element.addEventListener("change", onChange), options.enableSaveButton && options.element.querySelector(".btnSave").classList.remove("hide"), layoutManager.tv ? options.element.querySelector(".selectTVHomeScreenContainer").classList.remove("hide") : options.element.querySelector(".selectTVHomeScreenContainer").classList.add("hide"), self.loadData(options.autoFocus)
})
}
function HomeScreenSettings(options) {
this.options = options, embed(options, this)
}
var numConfigurableSections = 7;
return HomeScreenSettings.prototype.loadData = function(autoFocus) {
var self = this,
context = self.options.element;
loading.show();
var userId = self.options.userId,
apiClient = connectionManager.getApiClient(self.options.serverId),
userSettings = self.options.userSettings;
apiClient.getUser(userId).then(function(user) {
userSettings.setUserInfo(userId, apiClient).then(function() {
self.dataLoaded = !0, loadForm(context, user, userSettings, apiClient), autoFocus && focusManager.autoFocus(context)
})
})
}, HomeScreenSettings.prototype.submit = function() {
onSubmit.call(this)
}, HomeScreenSettings.prototype.destroy = function() {
this.options = null
}, HomeScreenSettings
});

View file

@ -0,0 +1,147 @@
<form style="margin:0 auto;">
<div class="verticalSection verticalSection-extrabottompadding">
<h2 class="sectionTitle">
${HeaderHomeScreen}
</h2>
<div class="selectContainer hide selectTVHomeScreenContainer">
<select is="emby-select" class="selectTVHomeScreen" label="${LabelTVHomeScreen}">
<option value="horizontal">${Horizontal}</option>
<option value="vertical">${Vertical}</option>
</select>
<div class="fieldDescription">Changes take effect after signing out or restarting the app.</div>
</div>
<div class="selectContainer">
<select is="emby-select" id="selectHomeSection1" label="{section1label}">
<option value="smalllibrarytiles">${HeaderMyMedia}</option>
<option value="librarybuttons">${HeaderMyMediaSmall}</option>
<option value="activerecordings">${HeaderActiveRecordings}</option>
<option value="resume">${HeaderContinueWatching}</option>
<option value="resumeaudio">${HeaderContinueListening}</option>
<option value="latestmedia">${HeaderLatestMedia}</option>
<option value="nextup">${HeaderNextUp}</option>
<option value="livetv">${LiveTV}</option>
<option value="none">${None}</option>
</select>
</div>
<div class="selectContainer">
<select is="emby-select" id="selectHomeSection2" label="{section2label}">
<option value="smalllibrarytiles">${HeaderMyMedia}</option>
<option value="librarybuttons">${HeaderMyMediaSmall}</option>
<option value="activerecordings">${HeaderActiveRecordings}</option>
<option value="resume">${HeaderContinueWatching}</option>
<option value="resumeaudio">${HeaderContinueListening}</option>
<option value="latestmedia">${HeaderLatestMedia}</option>
<option value="nextup">${HeaderNextUp}</option>
<option value="livetv">${LiveTV}</option>
<option value="none">${None}</option>
</select>
</div>
<div class="selectContainer">
<select is="emby-select" id="selectHomeSection3" label="{section3label}">
<option value="smalllibrarytiles">${HeaderMyMedia}</option>
<option value="librarybuttons">${HeaderMyMediaSmall}</option>
<option value="activerecordings">${HeaderActiveRecordings}</option>
<option value="resume">${HeaderContinueWatching}</option>
<option value="resumeaudio">${HeaderContinueListening}</option>
<option value="latestmedia">${HeaderLatestMedia}</option>
<option value="nextup">${HeaderNextUp}</option>
<option value="livetv">${LiveTV}</option>
<option value="none">${None}</option>
</select>
</div>
<div class="selectContainer">
<select is="emby-select" id="selectHomeSection4" label="{section4label}">
<option value="smalllibrarytiles">${HeaderMyMedia}</option>
<option value="librarybuttons">${HeaderMyMediaSmall}</option>
<option value="activerecordings">${HeaderActiveRecordings}</option>
<option value="resume">${HeaderContinueWatching}</option>
<option value="resumeaudio">${HeaderContinueListening}</option>
<option value="latestmedia">${HeaderLatestMedia}</option>
<option value="nextup">${HeaderNextUp}</option>
<option value="livetv">${LiveTV}</option>
<option value="none">${None}</option>
</select>
</div>
<div class="selectContainer">
<select is="emby-select" id="selectHomeSection5" label="{section5label}">
<option value="smalllibrarytiles">${HeaderMyMedia}</option>
<option value="librarybuttons">${HeaderMyMediaSmall}</option>
<option value="activerecordings">${HeaderActiveRecordings}</option>
<option value="resume">${HeaderContinueWatching}</option>
<option value="resumeaudio">${HeaderContinueListening}</option>
<option value="latestmedia">${HeaderLatestMedia}</option>
<option value="nextup">${HeaderNextUp}</option>
<option value="livetv">${LiveTV}</option>
<option value="none">${None}</option>
</select>
</div>
<div class="selectContainer">
<select is="emby-select" id="selectHomeSection6" label="{section6label}">
<option value="smalllibrarytiles">${HeaderMyMedia}</option>
<option value="librarybuttons">${HeaderMyMediaSmall}</option>
<option value="activerecordings">${HeaderActiveRecordings}</option>
<option value="resume">${HeaderContinueWatching}</option>
<option value="resumeaudio">${HeaderContinueListening}</option>
<option value="latestmedia">${HeaderLatestMedia}</option>
<option value="nextup">${HeaderNextUp}</option>
<option value="livetv">${LiveTV}</option>
<option value="none">${None}</option>
</select>
</div>
<div class="selectContainer">
<select is="emby-select" id="selectHomeSection7" label="{section7label}">
<option value="smalllibrarytiles">${HeaderMyMedia}</option>
<option value="librarybuttons">${HeaderMyMediaSmall}</option>
<option value="activerecordings">${HeaderActiveRecordings}</option>
<option value="resume">${HeaderContinueWatching}</option>
<option value="resumeaudio">${HeaderContinueListening}</option>
<option value="latestmedia">${HeaderLatestMedia}</option>
<option value="nextup">${HeaderNextUp}</option>
<option value="livetv">${LiveTV}</option>
<option value="none">${None}</option>
</select>
</div>
</div>
<div class="verticalSection verticalSection-extrabottompadding">
<h2 class="sectionTitle">
${HeaderLibraryOrder}
</h2>
<div class="paperList viewOrderList">
</div>
</div>
<div class="perLibrarySettings">
</div>
<div class="verticalSection verticalSection-extrabottompadding">
<label class="checkboxContainer">
<input class="chkHidePlayedFromLatest" type="checkbox" is="emby-checkbox" />
<span>${HideWatchedContentFromLatestMedia}</span>
</label>
</div>
<div class="verticalSection verticalSection-extrabottompadding">
<h2 class="sectionTitle">
${HeaderLibraryFolders}
</h2>
<div>
<p>${LabelSelectFolderGroups}</p>
<div class="folderGroupList"></div>
<div class="fieldDescription checkboxFieldDescription">${LabelSelectFolderGroupsHelp}</div>
</div>
</div>
<button is="emby-button" type="submit" class="raised button-submit block btnSave hide">
<span>${Save}</span>
</button>
</form>

View file

@ -0,0 +1,47 @@
define(["dialogHelper", "layoutManager", "globalize", "require", "events", "homescreenSettings", "paper-icon-button-light", "css!./../formdialog"], function(dialogHelper, layoutManager, globalize, require, events, HomescreenSettings) {
"use strict";
function centerFocus(elem, horiz, on) {
require(["scrollHelper"], function(scrollHelper) {
var fn = on ? "on" : "off";
scrollHelper.centerFocus[fn](elem, horiz)
})
}
function show(options) {
return new Promise(function(resolve, reject) {
require(["text!./homescreensettingsdialog.template.html"], function(template) {
var dialogOptions = {
removeOnClose: !0,
scrollY: !1
};
layoutManager.tv ? dialogOptions.size = "fullscreen" : dialogOptions.size = "medium-tall";
var dlg = dialogHelper.createDialog(dialogOptions);
dlg.classList.add("formDialog");
var html = "",
submitted = !1;
html += globalize.translateDocument(template, "sharedcomponents"), dlg.innerHTML = html, layoutManager.tv && centerFocus(dlg.querySelector(".formDialogContent"), !1, !0);
var homescreenSettingsInstance = new HomescreenSettings({
serverId: options.serverId,
userId: options.userId,
element: dlg.querySelector(".settingsContent"),
userSettings: options.userSettings,
enableSaveButton: !1,
enableSaveConfirmation: !1
});
dialogHelper.open(dlg), dlg.addEventListener("close", function() {
layoutManager.tv && centerFocus(dlg.querySelector(".formDialogContent"), !1, !1), submitted ? resolve() : reject()
}), dlg.querySelector(".btnCancel").addEventListener("click", function(e) {
dialogHelper.close(dlg)
}), dlg.querySelector(".btnSave").addEventListener("click", function(e) {
submitted = !0, homescreenSettingsInstance.submit()
}), events.on(homescreenSettingsInstance, "saved", function() {
submitted = !0, dialogHelper.close(dlg)
})
})
})
}
return {
show: show
}
});

View file

@ -0,0 +1,22 @@
<div class="formDialogHeader">
<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><i class="md-icon">&#xE5C4;</i></button>
<h3 class="formDialogHeaderTitle">
${HeaderDisplaySettings}
</h3>
</div>
<div class="formDialogContent smoothScrollY">
<div class="dialogContentInner dialog-content-centered">
<div class="settingsContent">
</div>
<div class="formDialogFooter">
<button is="emby-button" type="button" class="raised button-submit block formDialogFooterItem btnSave">
<span>${Save}</span>
</button>
</div>
</div>
</div>

View file

@ -0,0 +1,10 @@
.homeLibraryButton {
min-width: 18%;
margin: .5em !important
}
@media all and (max-width:50em) {
.homeLibraryButton {
width: 46% !important
}
}

View file

@ -0,0 +1,618 @@
define(["connectionManager", "cardBuilder", "registrationServices", "appSettings", "dom", "apphost", "layoutManager", "imageLoader", "globalize", "itemShortcuts", "itemHelper", "appRouter", "emby-button", "paper-icon-button-light", "emby-itemscontainer", "emby-scroller", "emby-linkbutton", "css!./homesections"], function(connectionManager, cardBuilder, registrationServices, appSettings, dom, appHost, layoutManager, imageLoader, globalize, itemShortcuts, itemHelper, appRouter) {
"use strict";
function getDefaultSection(index) {
switch (index) {
case 0:
return "smalllibrarytiles";
case 1:
return "resume";
case 2:
return "resumeaudio";
case 3:
return "livetv";
case 4:
return "nextup";
case 5:
return "latestmedia";
case 6:
return "none";
default:
return ""
}
}
function getAllSectionsToShow(userSettings, sectionCount) {
for (var sections = [], i = 0, length = sectionCount; i < length; i++) {
var section = userSettings.get("homesection" + i) || getDefaultSection(i);
"folders" === section && (section = getDefaultSection(0)), sections.push(section)
}
return sections
}
function loadSections(elem, apiClient, user, userSettings) {
return getUserViews(apiClient, user.Id).then(function(userViews) {
var i, length, html = "";
for (i = 0, length = 7; i < length; i++) html += '<div class="verticalSection section' + i + '"></div>';
elem.innerHTML = html, elem.classList.add("homeSectionsContainer");
var promises = [],
sections = getAllSectionsToShow(userSettings, 7);
for (i = 0, length = sections.length; i < length; i++) promises.push(loadSection(elem, apiClient, user, userSettings, userViews, sections, i));
return Promise.all(promises).then(function() {
html = "";
var style = "margin-top:4em;";
return layoutManager.tv && (style += "padding: 0 7.5%;"), html += '<div class="verticalSection padded-left padded-right customizeSection hide" style="' + style + '">', html += '<a href="' + appRouter.getRouteUrl("settings") + '" is="emby-linkbutton" class="raised block"><span>' + globalize.translate("sharedcomponents#HeaderCustomizeHomeScreen") + "</span></a>", html += "</div>", elem.insertAdjacentHTML("beforeend", html), resume(elem, {
refresh: !0,
returnPromise: !1
})
})
})
}
function destroySections(elem) {
var i, length, elems = elem.querySelectorAll(".itemsContainer");
for (i = 0, length = elems.length; i < length; i++) elems[i].fetchData = null, elems[i].parentContainer = null, elems[i].getItemsHtml = null;
elem.innerHTML = ""
}
function pause(elem) {
var i, length, elems = elem.querySelectorAll(".itemsContainer");
for (i = 0, length = elems.length; i < length; i++) elems[i].pause()
}
function resume(elem, options) {
var i, length, elems = elem.querySelectorAll(".itemsContainer"),
promises = [];
for (i = 0, length = elems.length; i < length; i++) promises.push(elems[i].resume(options));
var promise = Promise.all(promises).then(function() {
elem.querySelector(".customizeSection").classList.remove("hide")
});
if (!options || !1 !== options.returnPromise) return promise
}
function loadSection(page, apiClient, user, userSettings, userViews, allSections, index) {
var section = allSections[index],
userId = user.Id,
elem = page.querySelector(".section" + index);
if ("latestmedia" === section) loadRecentlyAdded(elem, apiClient, user, userViews);
else {
if ("librarytiles" === section || "smalllibrarytiles" === section || "smalllibrarytiles-automobile" === section || "librarytiles-automobile" === section) return loadLibraryTiles(elem, apiClient, user, userSettings, "smallBackdrop", userViews, allSections);
if ("librarybuttons" === section) return loadlibraryButtons(elem, apiClient, user, userSettings, userViews);
if ("resume" === section) loadResumeVideo(elem, apiClient, userId);
else if ("resumeaudio" === section) loadResumeAudio(elem, apiClient, userId);
else if ("activerecordings" === section) loadLatestLiveTvRecordings(elem, !0, apiClient, userId);
else {
if ("nextup" !== section) return "onnow" === section || "livetv" === section ? loadOnNow(elem, apiClient, user) : (elem.innerHTML = "", Promise.resolve());
loadNextUp(elem, apiClient, userId)
}
}
return Promise.resolve()
}
function getUserViews(apiClient, userId) {
return apiClient.getUserViews({}, userId || apiClient.getCurrentUserId()).then(function(result) {
return result.Items
})
}
function enableScrollX() {
return !0
}
function getSquareShape() {
return enableScrollX() ? "overflowSquare" : "square"
}
function getThumbShape() {
return enableScrollX() ? "overflowBackdrop" : "backdrop"
}
function getPortraitShape() {
return enableScrollX() ? "autooverflow" : "auto"
}
function getLibraryButtonsHtml(items) {
var html = "";
html += '<div class="verticalSection verticalSection-extrabottompadding">', html += '<div class="sectionTitleContainer sectionTitleContainer-cards">', html += '<h2 class="sectionTitle sectionTitle-cards padded-left">' + globalize.translate("sharedcomponents#HeaderMyMedia") + "</h2>", layoutManager.tv || (html += '<button type="button" is="paper-icon-button-light" class="sectionTitleIconButton btnHomeScreenSettings"><i class="md-icon">&#xE5D3;</i></button>'), html += "</div>", html += '<div is="emby-itemscontainer" class="itemsContainer padded-left padded-right vertical-wrap focuscontainer-x" data-multiselect="false">';
for (var i = 0, length = items.length; i < length; i++) {
var icon, item = items[i];
switch (item.CollectionType) {
case "movies":
icon = "local_movies";
break;
case "music":
icon = "library_music";
break;
case "photos":
icon = "photo";
break;
case "livetv":
case "tvshows":
icon = "live_tv";
break;
case "games":
icon = "folder";
break;
case "trailers":
icon = "local_movies";
break;
case "homevideos":
case "musicvideos":
icon = "video_library";
break;
case "books":
case "channels":
case "playlists":
default:
icon = "folder"
}
html += '<a is="emby-linkbutton" href="' + appRouter.getRouteUrl(item) + '" class="raised homeLibraryButton"><i class="md-icon">' + icon + "</i><span>" + item.Name + "</span></a>"
}
return html += "</div>", html += "</div>"
}
function loadlibraryButtons(elem, apiClient, user, userSettings, userViews) {
return Promise.all([getAppInfo(apiClient), getDownloadsSectionHtml(apiClient, user, userSettings)]).then(function(responses) {
var infoHtml = responses[0],
downloadsHtml = responses[1];
elem.classList.remove("verticalSection");
var html = getLibraryButtonsHtml(userViews);
elem.innerHTML = html + downloadsHtml + infoHtml, bindHomeScreenSettingsIcon(elem, apiClient, user.Id, userSettings), infoHtml && bindAppInfoEvents(elem), imageLoader.lazyChildren(elem)
})
}
function bindAppInfoEvents(elem) {
elem.querySelector(".appInfoSection").addEventListener("click", function(e) {
dom.parentWithClass(e.target, "card") && registrationServices.showPremiereInfo()
})
}
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min
}
function getAppInfo(apiClient) {
var cacheKey = "lastappinfopresent5",
lastDatePresented = parseInt(appSettings.get(cacheKey) || "0");
return lastDatePresented ? (new Date).getTime() - lastDatePresented < 1728e5 ? Promise.resolve("") : registrationServices.validateFeature("dvr", {
showDialog: !1,
viewOnly: !0
}).then(function() {
return appSettings.set(cacheKey, (new Date).getTime()), ""
}, function() {
appSettings.set(cacheKey, (new Date).getTime());
var infos = [getPremiereInfo];
return appHost.supports("otherapppromotions") && infos.push(getTheaterInfo), infos[getRandomInt(0, infos.length - 1)]()
}) : (appSettings.set(cacheKey, (new Date).getTime()), Promise.resolve(""))
}
function getCard(img, shape) {
shape = shape || "backdropCard";
var html = '<div class="card scalableCard ' + shape + " " + shape + '-scalable"><div class="cardBox"><div class="cardScalable"><div class="cardPadder cardPadder-backdrop"></div>';
return html += '<div class="cardContent">', html += '<div class="cardImage lazy" data-src="' + img + '"></div>', html += "</div>", html += "</div></div></div>"
}
function getTheaterInfo() {
var html = "";
html += '<div class="verticalSection appInfoSection">', html += '<div class="sectionTitleContainer sectionTitleContainer-cards">', html += '<h2 class="sectionTitle sectionTitle-cards padded-left">Discover Jellyfin Theater</h2>', html += '<button is="paper-icon-button-light" class="sectionTitleButton" onclick="this.parentNode.parentNode.remove();" class="autoSize"><i class="md-icon">close</i></button>', html += "</div>";
return html += '<div class="padded-left padded-right">', html += '<p class="sectionTitle-cards">A beautiful app for your TV and large screen tablet. Jellyfin Theater runs on Windows, Xbox One, Raspberry Pi, Samsung Smart TVs, Sony PS4, Web Browsers, and more.</p>', html += '<div class="itemsContainer vertical-wrap" is="emby-itemscontainer">', html += getCard("https://raw.githubusercontent.com/MediaBrowser/Emby.Resources/master/apps/theater1.png"), html += getCard("https://raw.githubusercontent.com/MediaBrowser/Emby.Resources/master/apps/theater2.png"), html += getCard("https://raw.githubusercontent.com/MediaBrowser/Emby.Resources/master/apps/theater3.png"), html += "</div>", html += "</div>", html += "</div>"
}
function getPremiereInfo() {
var html = "";
return html += '<div class="verticalSection appInfoSection">', html += '<div class="sectionTitleContainer sectionTitleContainer-cards">', html += '<h2 class="sectionTitle sectionTitle-cards padded-left">Discover Jellyfin Premiere</h2>', html += '<button is="paper-icon-button-light" class="sectionTitleButton" onclick="this.parentNode.parentNode.remove();" class="autoSize"><i class="md-icon">close</i></button>', html += "</div>", html += '<div class="padded-left padded-right">', html += '<p class="sectionTitle-cards">Enjoy Jellyfin DVR, get free access to Jellyfin apps, and more.</p>', html += '<div class="itemsContainer vertical-wrap" is="emby-itemscontainer">', html += getCard("https://raw.githubusercontent.com/MediaBrowser/Emby.Resources/master/apps/theater1.png"), html += getCard("https://raw.githubusercontent.com/MediaBrowser/Emby.Resources/master/apps/theater2.png"), html += getCard("https://raw.githubusercontent.com/MediaBrowser/Emby.Resources/master/apps/theater3.png"), html += "</div>", html += "</div>", html += "</div>"
}
function getFetchLatestItemsFn(serverId, parentId, collectionType) {
return function() {
var apiClient = connectionManager.getApiClient(serverId),
limit = 16;
enableScrollX() ? "music" === collectionType && (limit = 30) : limit = "tvshows" === collectionType ? 5 : "music" === collectionType ? 9 : 8;
var options = {
Limit: limit,
Fields: "PrimaryImageAspectRatio,BasicSyncInfo",
ImageTypeLimit: 1,
EnableImageTypes: "Primary,Backdrop,Thumb",
ParentId: parentId
};
return apiClient.getLatestItems(options)
}
}
function getLatestItemsHtmlFn(itemType, viewType) {
return function(items) {
var shape = "Channel" === itemType || "movies" === viewType ? getPortraitShape() : "music" === viewType ? getSquareShape() : getThumbShape();
return cardBuilder.getCardsHtml({
items: items,
shape: shape,
preferThumb: "movies" !== viewType && "Channel" !== itemType && "music" !== viewType ? "auto" : null,
showUnplayedIndicator: !1,
showChildCountIndicator: !0,
context: "home",
overlayText: !1,
centerText: !0,
overlayPlayButton: "photos" !== viewType,
allowBottomPadding: !enableScrollX() && !0,
cardLayout: !1,
showTitle: "photos" !== viewType,
showYear: "movies" === viewType || "tvshows" === viewType || !viewType,
showParentTitle: "music" === viewType || "tvshows" === viewType || !viewType || !1,
lines: 2
})
}
}
function renderLatestSection(elem, apiClient, user, parent) {
var html = "";
html += '<div class="sectionTitleContainer sectionTitleContainer-cards padded-left">', layoutManager.tv ? html += '<h2 class="sectionTitle sectionTitle-cards">' + globalize.translate("sharedcomponents#LatestFromLibrary", parent.Name) + "</h2>" : (html += '<a is="emby-linkbutton" href="' + appRouter.getRouteUrl(parent, {
section: "latest"
}) + '" class="more button-flat button-flat-mini sectionTitleTextButton">', html += '<h2 class="sectionTitle sectionTitle-cards">', html += globalize.translate("sharedcomponents#LatestFromLibrary", parent.Name), html += "</h2>", html += '<i class="md-icon">&#xE5CC;</i>', html += "</a>"), html += "</div>", enableScrollX() ? html += '<div is="emby-scroller" data-mousewheel="false" data-centerfocus="true" class="padded-top-focusscale padded-bottom-focusscale"><div is="emby-itemscontainer" class="itemsContainer scrollSlider focuscontainer-x padded-left padded-right">' : html += '<div is="emby-itemscontainer" class="itemsContainer padded-left padded-right vertical-wrap focuscontainer-x">', enableScrollX() && (html += "</div>"), html += "</div>", elem.innerHTML = html;
var itemsContainer = elem.querySelector(".itemsContainer");
itemsContainer.fetchData = getFetchLatestItemsFn(apiClient.serverId(), parent.Id, parent.CollectionType), itemsContainer.getItemsHtml = getLatestItemsHtmlFn(parent.Type, parent.CollectionType), itemsContainer.parentContainer = elem
}
function loadRecentlyAdded(elem, apiClient, user, userViews) {
elem.classList.remove("verticalSection");
for (var excludeViewTypes = ["playlists", "livetv", "boxsets", "channels"], i = 0, length = userViews.length; i < length; i++) {
var item = userViews[i];
if (-1 === user.Configuration.LatestItemsExcludes.indexOf(item.Id) && -1 === excludeViewTypes.indexOf(item.CollectionType || [])) {
var frag = document.createElement("div");
frag.classList.add("verticalSection"), frag.classList.add("hide"), elem.appendChild(frag), renderLatestSection(frag, apiClient, user, item)
}
}
}
function getRequirePromise(deps) {
return new Promise(function(resolve, reject) {
require(deps, resolve)
})
}
function showHomeScreenSettings(elem, options) {
return getRequirePromise(["homescreenSettingsDialog"]).then(function(homescreenSettingsDialog) {
return homescreenSettingsDialog.show(options).then(function() {
dom.parentWithClass(elem, "homeSectionsContainer").dispatchEvent(new CustomEvent("settingschange", {
cancelable: !1
}))
})
})
}
function bindHomeScreenSettingsIcon(elem, apiClient, userId, userSettings) {
var btnHomeScreenSettings = elem.querySelector(".btnHomeScreenSettings");
btnHomeScreenSettings && btnHomeScreenSettings.addEventListener("click", function() {
showHomeScreenSettings(elem, {
serverId: apiClient.serverId(),
userId: userId,
userSettings: userSettings
})
})
}
function getDownloadsSectionHtml(apiClient, user, userSettings) {
return appHost.supports("sync") && user.Policy.EnableContentDownloading ? (apiClient.getLatestOfflineItems ? apiClient.getLatestOfflineItems({
Limit: 20,
Filters: "IsNotFolder"
}) : Promise.resolve([])).then(function(items) {
var html = "";
return html += '<div class="verticalSection">', html += '<div class="sectionTitleContainer sectionTitleContainer-cards padded-left">', layoutManager.tv ? html += '<h2 class="sectionTitle sectionTitle-cards">' + globalize.translate("sharedcomponents#HeaderMyDownloads") + "</h2>" : (html += '<a is="emby-linkbutton" href="' + appRouter.getRouteUrl("downloads") + '" class="more button-flat button-flat-mini sectionTitleTextButton">', html += '<h2 class="sectionTitle sectionTitle-cards">', html += globalize.translate("sharedcomponents#HeaderMyDownloads"), html += "</h2>", html += '<i class="md-icon">&#xE5CC;</i>', html += "</a>", html += '<a is="emby-linkbutton" href="' + appRouter.getRouteUrl("managedownloads") + '" class="sectionTitleIconButton"><i class="md-icon">&#xE8B8;</i></a>'), html += "</div>", html += '<div is="emby-scroller" data-mousewheel="false" data-centerfocus="true" class="padded-top-focusscale padded-bottom-focusscale"><div is="emby-itemscontainer" class="scrollSlider focuscontainer-x padded-left padded-right">', html += cardBuilder.getCardsHtml({
items: items,
preferThumb: "auto",
shape: "autooverflow",
overlayText: !1,
showTitle: !0,
showParentTitle: !0,
lazy: !0,
showDetailsMenu: !0,
overlayPlayButton: !0,
context: "home",
centerText: !0,
allowBottomPadding: !1,
cardLayout: !1,
showYear: !0,
lines: 2
}), html += "</div>", html += "</div>"
}) : Promise.resolve("")
}
function loadLibraryTiles(elem, apiClient, user, userSettings, shape, userViews, allSections) {
elem.classList.remove("verticalSection");
var html = "",
scrollX = !layoutManager.desktop;
return userViews.length && (html += '<div class="verticalSection">', html += '<div class="sectionTitleContainer sectionTitleContainer-cards">', html += '<h2 class="sectionTitle sectionTitle-cards padded-left">' + globalize.translate("sharedcomponents#HeaderMyMedia") + "</h2>", layoutManager.tv || (html += '<button type="button" is="paper-icon-button-light" class="sectionTitleIconButton btnHomeScreenSettings"><i class="md-icon">&#xE5D3;</i></button>'), html += "</div>", html += scrollX ? '<div is="emby-scroller" class="padded-top-focusscale padded-bottom-focusscale" data-mousewheel="false" data-centerfocus="true"><div is="emby-itemscontainer" class="scrollSlider focuscontainer-x padded-left padded-right">' : '<div is="emby-itemscontainer" class="itemsContainer padded-left padded-right vertical-wrap focuscontainer-x">', html += cardBuilder.getCardsHtml({
items: userViews,
shape: scrollX ? "overflowSmallBackdrop" : shape,
showTitle: !0,
centerText: !0,
overlayText: !1,
lazy: !0,
transition: !1,
allowBottomPadding: !scrollX
}), scrollX && (html += "</div>"), html += "</div>", html += "</div>"), Promise.all([getAppInfo(apiClient), getDownloadsSectionHtml(apiClient, user, userSettings)]).then(function(responses) {
var infoHtml = responses[0],
downloadsHtml = responses[1];
elem.innerHTML = html + downloadsHtml + infoHtml, bindHomeScreenSettingsIcon(elem, apiClient, user.Id, userSettings), infoHtml && bindAppInfoEvents(elem), imageLoader.lazyChildren(elem)
})
}
function getContinueWatchingFetchFn(serverId) {
return function() {
var limit, apiClient = connectionManager.getApiClient(serverId),
screenWidth = dom.getWindowSize().innerWidth;
enableScrollX() ? limit = 12 : (limit = screenWidth >= 1920 ? 8 : screenWidth >= 1600 ? 8 : screenWidth >= 1200 ? 9 : 6, limit = Math.min(limit, 5));
var options = {
Limit: limit,
Recursive: !0,
Fields: "PrimaryImageAspectRatio,BasicSyncInfo",
ImageTypeLimit: 1,
EnableImageTypes: "Primary,Backdrop,Thumb",
EnableTotalRecordCount: !1,
MediaTypes: "Video"
};
return apiClient.getResumableItems(apiClient.getCurrentUserId(), options)
}
}
function getContinueWatchingItemsHtml(items) {
return cardBuilder.getCardsHtml({
items: items,
preferThumb: !0,
shape: getThumbShape(),
overlayText: !1,
showTitle: !0,
showParentTitle: !0,
lazy: !0,
showDetailsMenu: !0,
overlayPlayButton: !0,
context: "home",
centerText: !0,
allowBottomPadding: !1,
cardLayout: !1,
showYear: !0,
lines: 2
})
}
function loadResumeVideo(elem, apiClient, userId) {
var html = "";
html += '<h2 class="sectionTitle sectionTitle-cards padded-left">' + globalize.translate("sharedcomponents#HeaderContinueWatching") + "</h2>", enableScrollX() ? html += '<div is="emby-scroller" data-mousewheel="false" data-centerfocus="true" class="padded-top-focusscale padded-bottom-focusscale"><div is="emby-itemscontainer" class="itemsContainer scrollSlider focuscontainer-x padded-left padded-right" data-monitor="videoplayback,markplayed">' : html += '<div is="emby-itemscontainer" class="itemsContainer padded-left padded-right vertical-wrap focuscontainer-x" data-monitor="videoplayback,markplayed">', enableScrollX() && (html += "</div>"), html += "</div>", elem.classList.add("hide"), elem.innerHTML = html;
var itemsContainer = elem.querySelector(".itemsContainer");
itemsContainer.fetchData = getContinueWatchingFetchFn(apiClient.serverId()), itemsContainer.getItemsHtml = getContinueWatchingItemsHtml, itemsContainer.parentContainer = elem
}
function getContinueListeningFetchFn(serverId) {
return function() {
var limit, apiClient = connectionManager.getApiClient(serverId),
screenWidth = dom.getWindowSize().innerWidth;
enableScrollX() ? limit = 12 : (limit = screenWidth >= 1920 ? 8 : screenWidth >= 1600 ? 8 : screenWidth >= 1200 ? 9 : 6, limit = Math.min(limit, 5));
var options = {
Limit: limit,
Recursive: !0,
Fields: "PrimaryImageAspectRatio,BasicSyncInfo",
ImageTypeLimit: 1,
EnableImageTypes: "Primary,Backdrop,Thumb",
EnableTotalRecordCount: !1,
MediaTypes: "Audio"
};
return apiClient.getResumableItems(apiClient.getCurrentUserId(), options)
}
}
function getContinueListeningItemsHtml(items) {
return cardBuilder.getCardsHtml({
items: items,
preferThumb: !0,
shape: getThumbShape(),
overlayText: !1,
showTitle: !0,
showParentTitle: !0,
lazy: !0,
showDetailsMenu: !0,
overlayPlayButton: !0,
context: "home",
centerText: !0,
allowBottomPadding: !1,
cardLayout: !1,
showYear: !0,
lines: 2
})
}
function loadResumeAudio(elem, apiClient, userId) {
var html = "";
html += '<h2 class="sectionTitle sectionTitle-cards padded-left">' + globalize.translate("sharedcomponents#HeaderContinueWatching") + "</h2>", enableScrollX() ? html += '<div is="emby-scroller" data-mousewheel="false" data-centerfocus="true" class="padded-top-focusscale padded-bottom-focusscale"><div is="emby-itemscontainer" class="itemsContainer scrollSlider focuscontainer-x padded-left padded-right" data-monitor="audioplayback,markplayed">' : html += '<div is="emby-itemscontainer" class="itemsContainer padded-left padded-right vertical-wrap focuscontainer-x" data-monitor="audioplayback,markplayed">', enableScrollX() && (html += "</div>"), html += "</div>", elem.classList.add("hide"), elem.innerHTML = html;
var itemsContainer = elem.querySelector(".itemsContainer");
itemsContainer.fetchData = getContinueListeningFetchFn(apiClient.serverId()), itemsContainer.getItemsHtml = getContinueListeningItemsHtml, itemsContainer.parentContainer = elem
}
function bindUnlockClick(elem) {
var btnUnlock = elem.querySelector(".btnUnlock");
btnUnlock && btnUnlock.addEventListener("click", function(e) {
registrationServices.validateFeature("livetv", {
viewOnly: !0
}).then(function() {
dom.parentWithClass(elem, "homeSectionsContainer").dispatchEvent(new CustomEvent("settingschange", {
cancelable: !1
}))
})
})
}
function getOnNowFetchFn(serverId) {
return function() {
var apiClient = connectionManager.getApiClient(serverId);
return apiClient.getLiveTvRecommendedPrograms({
userId: apiClient.getCurrentUserId(),
IsAiring: !0,
limit: 24,
ImageTypeLimit: 1,
EnableImageTypes: "Primary,Thumb,Backdrop",
EnableTotalRecordCount: !1,
Fields: "ChannelInfo,PrimaryImageAspectRatio"
})
}
}
function getOnNowItemsHtml(items) {
return cardBuilder.getCardsHtml({
items: items,
preferThumb: "auto",
inheritThumb: !1,
shape: enableScrollX() ? "autooverflow" : "auto",
showParentTitleOrTitle: !0,
showTitle: !0,
centerText: !0,
coverImage: !0,
overlayText: !1,
allowBottomPadding: !enableScrollX(),
showAirTime: !0,
showChannelName: !1,
showAirDateTime: !1,
showAirEndTime: !0,
defaultShape: getThumbShape(),
lines: 3,
overlayPlayButton: !0
})
}
function loadOnNow(elem, apiClient, user) {
if (!user.Policy.EnableLiveTvAccess) return Promise.resolve();
var promises = [];
promises.push(registrationServices.validateFeature("livetv", {
viewOnly: !0,
showDialog: !1
}).then(function() {
return !0
}, function() {
return !1
}));
user.Id;
return promises.push(apiClient.getLiveTvRecommendedPrograms({
userId: apiClient.getCurrentUserId(),
IsAiring: !0,
limit: 1,
ImageTypeLimit: 1,
EnableImageTypes: "Primary,Thumb,Backdrop",
EnableTotalRecordCount: !1,
Fields: "ChannelInfo,PrimaryImageAspectRatio"
})), Promise.all(promises).then(function(responses) {
var registered = responses[0],
result = responses[1],
html = "";
if (result.Items.length && registered) {
elem.classList.remove("padded-left"), elem.classList.remove("padded-right"), elem.classList.remove("padded-bottom"), elem.classList.remove("verticalSection"), html += '<div class="verticalSection">', html += '<div class="sectionTitleContainer sectionTitleContainer-cards padded-left">', html += '<h2 class="sectionTitle sectionTitle-cards">' + globalize.translate("sharedcomponents#LiveTV") + "</h2>", html += "</div>", enableScrollX() ? (html += '<div is="emby-scroller" class="padded-top-focusscale padded-bottom-focusscale" data-mousewheel="false" data-centerfocus="true" data-scrollbuttons="false">', html += '<div class="scrollSlider padded-left padded-right padded-top padded-bottom focuscontainer-x">') : html += '<div class="padded-left padded-right padded-top focuscontainer-x">', html += '<a style="margin-left:.8em;margin-right:0;" is="emby-linkbutton" href="' + appRouter.getRouteUrl("livetv", {
serverId: apiClient.serverId()
}) + '" class="raised"><span>' + globalize.translate("sharedcomponents#Programs") + "</span></a>", html += '<a style="margin-left:.5em;margin-right:0;" is="emby-linkbutton" href="' + appRouter.getRouteUrl("livetv", {
serverId: apiClient.serverId(),
section: "guide"
}) + '" class="raised"><span>' + globalize.translate("sharedcomponents#Guide") + "</span></a>", html += '<a style="margin-left:.5em;margin-right:0;" is="emby-linkbutton" href="' + appRouter.getRouteUrl("recordedtv", {
serverId: apiClient.serverId()
}) + '" class="raised"><span>' + globalize.translate("sharedcomponents#Recordings") + "</span></a>", html += '<a style="margin-left:.5em;margin-right:0;" is="emby-linkbutton" href="' + appRouter.getRouteUrl("livetv", {
serverId: apiClient.serverId(),
section: "dvrschedule"
}) + '" class="raised"><span>' + globalize.translate("sharedcomponents#Schedule") + "</span></a>", html += "</div>", enableScrollX() && (html += "</div>"), html += "</div>", html += "</div>", html += '<div class="verticalSection">', html += '<div class="sectionTitleContainer sectionTitleContainer-cards padded-left">', layoutManager.tv ? html += '<h2 class="sectionTitle sectionTitle-cards">' + globalize.translate("sharedcomponents#HeaderOnNow") + "</h2>" : (html += '<a is="emby-linkbutton" href="' + appRouter.getRouteUrl("livetv", {
serverId: apiClient.serverId(),
section: "onnow"
}) + '" class="more button-flat button-flat-mini sectionTitleTextButton">', html += '<h2 class="sectionTitle sectionTitle-cards">', html += globalize.translate("sharedcomponents#HeaderOnNow"), html += "</h2>", html += '<i class="md-icon">&#xE5CC;</i>', html += "</a>"), html += "</div>", enableScrollX() ? html += '<div is="emby-scroller" data-mousewheel="false" data-centerfocus="true" class="padded-top-focusscale padded-bottom-focusscale"><div is="emby-itemscontainer" class="itemsContainer scrollSlider focuscontainer-x padded-left padded-right" data-refreshinterval="300000">' : html += '<div is="emby-itemscontainer" class="itemsContainer padded-left padded-right vertical-wrap focuscontainer-x" data-refreshinterval="300000">', enableScrollX() && (html += "</div>"), html += "</div>", html += "</div>", elem.innerHTML = html;
var itemsContainer = elem.querySelector(".itemsContainer");
itemsContainer.parentContainer = elem, itemsContainer.fetchData = getOnNowFetchFn(apiClient.serverId()), itemsContainer.getItemsHtml = getOnNowItemsHtml
} else result.Items.length && !registered && (elem.classList.add("padded-left"), elem.classList.add("padded-right"), elem.classList.add("padded-bottom"), html += '<h2 class="sectionTitle">' + globalize.translate("sharedcomponents#LiveTvRequiresUnlock") + "</h2>", html += '<button is="emby-button" type="button" class="raised button-submit block btnUnlock">', html += "<span>" + globalize.translate("sharedcomponents#HeaderBecomeProjectSupporter") + "</span>", html += "</button>", elem.innerHTML = html);
bindUnlockClick(elem)
})
}
function getNextUpFetchFn(serverId) {
return function() {
var apiClient = connectionManager.getApiClient(serverId);
return apiClient.getNextUpEpisodes({
Limit: enableScrollX() ? 24 : 15,
Fields: "PrimaryImageAspectRatio,SeriesInfo,DateCreated,BasicSyncInfo",
UserId: apiClient.getCurrentUserId(),
ImageTypeLimit: 1,
EnableImageTypes: "Primary,Backdrop,Banner,Thumb",
EnableTotalRecordCount: !1
})
}
}
function getNextUpItemsHtml(items) {
return cardBuilder.getCardsHtml({
items: items,
preferThumb: !0,
shape: getThumbShape(),
overlayText: !1,
showTitle: !0,
showParentTitle: !0,
lazy: !0,
overlayPlayButton: !0,
context: "home",
centerText: !0,
allowBottomPadding: !enableScrollX(),
cardLayout: !1
})
}
function loadNextUp(elem, apiClient, userId) {
var html = "";
html += '<div class="sectionTitleContainer sectionTitleContainer-cards padded-left">', layoutManager.tv ? html += '<h2 class="sectionTitle sectionTitle-cards">' + globalize.translate("sharedcomponents#HeaderNextUp") + "</h2>" : (html += '<a is="emby-linkbutton" href="' + appRouter.getRouteUrl("nextup", {
serverId: apiClient.serverId()
}) + '" class="button-flat button-flat-mini sectionTitleTextButton">', html += '<h2 class="sectionTitle sectionTitle-cards">', html += globalize.translate("sharedcomponents#HeaderNextUp"), html += "</h2>", html += '<i class="md-icon">&#xE5CC;</i>', html += "</a>"), html += "</div>", enableScrollX() ? html += '<div is="emby-scroller" data-mousewheel="false" data-centerfocus="true" class="padded-top-focusscale padded-bottom-focusscale"><div is="emby-itemscontainer" class="itemsContainer scrollSlider focuscontainer-x padded-left padded-right" data-monitor="videoplayback,markplayed">' : html += '<div is="emby-itemscontainer" class="itemsContainer padded-left padded-right vertical-wrap focuscontainer-x" data-monitor="videoplayback,markplayed">', enableScrollX() && (html += "</div>"), html += "</div>", elem.classList.add("hide"), elem.innerHTML = html;
var itemsContainer = elem.querySelector(".itemsContainer");
itemsContainer.fetchData = getNextUpFetchFn(apiClient.serverId()), itemsContainer.getItemsHtml = getNextUpItemsHtml, itemsContainer.parentContainer = elem
}
function getLatestRecordingsFetchFn(serverId, activeRecordingsOnly) {
return function() {
var apiClient = connectionManager.getApiClient(serverId);
return apiClient.getLiveTvRecordings({
userId: apiClient.getCurrentUserId(),
Limit: enableScrollX() ? 12 : 5,
Fields: "PrimaryImageAspectRatio,BasicSyncInfo",
EnableTotalRecordCount: !1,
IsLibraryItem: !!activeRecordingsOnly && null,
IsInProgress: !!activeRecordingsOnly || null
})
}
}
function getLatestRecordingItemsHtml(activeRecordingsOnly) {
return function(items) {
return cardBuilder.getCardsHtml({
items: items,
shape: enableScrollX() ? "autooverflow" : "auto",
showTitle: !0,
showParentTitle: !0,
coverImage: !0,
lazy: !0,
showDetailsMenu: !0,
centerText: !0,
overlayText: !1,
showYear: !0,
lines: 2,
overlayPlayButton: !activeRecordingsOnly,
allowBottomPadding: !enableScrollX(),
preferThumb: !0,
cardLayout: !1,
overlayMoreButton: activeRecordingsOnly,
action: activeRecordingsOnly ? "none" : null,
centerPlayButton: activeRecordingsOnly
})
}
}
function loadLatestLiveTvRecordings(elem, activeRecordingsOnly, apiClient, userId) {
var title = activeRecordingsOnly ? globalize.translate("sharedcomponents#HeaderActiveRecordings") : globalize.translate("sharedcomponents#HeaderLatestRecordings"),
html = "";
html += '<div class="sectionTitleContainer sectionTitleContainer-cards">', html += '<h2 class="sectionTitle sectionTitle-cards padded-left">' + title + "</h2>", layoutManager.tv, html += "</div>", enableScrollX() ? html += '<div is="emby-scroller" data-mousewheel="false" data-centerfocus="true" class="padded-top-focusscale padded-bottom-focusscale"><div is="emby-itemscontainer" class="itemsContainer scrollSlider focuscontainer-x padded-left padded-right">' : html += '<div is="emby-itemscontainer" class="itemsContainer padded-left padded-right vertical-wrap focuscontainer-x">', enableScrollX() && (html += "</div>"), html += "</div>", elem.classList.add("hide"), elem.innerHTML = html;
var itemsContainer = elem.querySelector(".itemsContainer");
itemsContainer.fetchData = getLatestRecordingsFetchFn(apiClient.serverId(), activeRecordingsOnly), itemsContainer.getItemsHtml = getLatestRecordingItemsHtml(activeRecordingsOnly), itemsContainer.parentContainer = elem
}
return {
loadLibraryTiles: loadLibraryTiles,
getDefaultSection: getDefaultSection,
loadSections: loadSections,
destroySections: destroySections,
pause: pause,
resume: resume
}
});

View file

@ -0,0 +1,218 @@
define(["events", "browser", "require", "apphost", "appSettings", "htmlMediaHelper"], function(events, browser, require, appHost, appSettings, htmlMediaHelper) {
"use strict";
function getDefaultProfile() {
return new Promise(function(resolve, reject) {
require(["browserdeviceprofile"], function(profileBuilder) {
resolve(profileBuilder({}))
})
})
}
function fade(instance, elem, startingVolume) {
instance._isFadingOut = !0;
var newVolume = Math.max(0, startingVolume - .15);
return console.log("fading volume to " + newVolume), elem.volume = newVolume, newVolume <= 0 ? (instance._isFadingOut = !1, Promise.resolve()) : new Promise(function(resolve, reject) {
cancelFadeTimeout(), fadeTimeout = setTimeout(function() {
fade(instance, elem, newVolume).then(resolve, reject)
}, 100)
})
}
function cancelFadeTimeout() {
var timeout = fadeTimeout;
timeout && (clearTimeout(timeout), fadeTimeout = null)
}
function supportsFade() {
return !browser.tv
}
function requireHlsPlayer(callback) {
require(["hlsjs"], function(hls) {
window.Hls = hls, callback()
})
}
function enableHlsPlayer(url, item, mediaSource, mediaType) {
return htmlMediaHelper.enableHlsJsPlayer(mediaSource.RunTimeTicks, mediaType) ? -1 !== url.indexOf(".m3u8") ? Promise.resolve() : new Promise(function(resolve, reject) {
require(["fetchHelper"], function(fetchHelper) {
fetchHelper.ajax({
url: url,
type: "HEAD"
}).then(function(response) {
"application/x-mpegurl" === (response.headers.get("Content-Type") || "").toLowerCase() ? resolve() : reject()
}, reject)
})
}) : Promise.reject()
}
function HtmlAudioPlayer() {
function setCurrentSrc(elem, options) {
elem.removeEventListener("error", onError), unBindEvents(elem), bindEvents(elem);
var val = options.url;
console.log("playing url: " + val);
var seconds = (options.playerStartPositionTicks || 0) / 1e7;
seconds && (val += "#t=" + seconds), htmlMediaHelper.destroyHlsPlayer(self), self._currentPlayOptions = options;
var crossOrigin = htmlMediaHelper.getCrossOriginValue(options.mediaSource);
return crossOrigin && (elem.crossOrigin = crossOrigin), enableHlsPlayer(val, options.item, options.mediaSource, "Audio").then(function() {
return new Promise(function(resolve, reject) {
requireHlsPlayer(function() {
var hls = new Hls({
manifestLoadingTimeOut: 2e4
});
hls.loadSource(val), hls.attachMedia(elem), htmlMediaHelper.bindEventsToHlsPlayer(self, hls, elem, onError, resolve, reject), self._hlsPlayer = hls, self._currentSrc = val
})
})
}, function() {
return elem.autoplay = !0, htmlMediaHelper.applySrc(elem, val, options).then(function() {
return self._currentSrc = val, htmlMediaHelper.playWithPromise(elem, onError)
})
})
}
function bindEvents(elem) {
elem.addEventListener("timeupdate", onTimeUpdate), elem.addEventListener("ended", onEnded), elem.addEventListener("volumechange", onVolumeChange), elem.addEventListener("pause", onPause), elem.addEventListener("playing", onPlaying), elem.addEventListener("play", onPlay)
}
function unBindEvents(elem) {
elem.removeEventListener("timeupdate", onTimeUpdate), elem.removeEventListener("ended", onEnded), elem.removeEventListener("volumechange", onVolumeChange), elem.removeEventListener("pause", onPause), elem.removeEventListener("playing", onPlaying), elem.removeEventListener("play", onPlay)
}
function createMediaElement() {
var elem = self._mediaElement;
return elem || (elem = document.querySelector(".mediaPlayerAudio"), elem || (elem = document.createElement("audio"), elem.classList.add("mediaPlayerAudio"), elem.classList.add("hide"), document.body.appendChild(elem)), elem.volume = htmlMediaHelper.getSavedVolume(), self._mediaElement = elem, elem)
}
function onEnded() {
htmlMediaHelper.onEndedInternal(self, this, onError)
}
function onTimeUpdate() {
var time = this.currentTime;
self._isFadingOut || (self._currentTime = time, events.trigger(self, "timeupdate"))
}
function onVolumeChange() {
self._isFadingOut || (htmlMediaHelper.saveVolume(this.volume), events.trigger(self, "volumechange"))
}
function onPlaying(e) {
self._started || (self._started = !0, this.removeAttribute("controls"), htmlMediaHelper.seekOnPlaybackStart(self, e.target, self._currentPlayOptions.playerStartPositionTicks)), events.trigger(self, "playing")
}
function onPlay(e) {
events.trigger(self, "unpause")
}
function onPause() {
events.trigger(self, "pause")
}
function onError() {
var errorCode = this.error ? this.error.code || 0 : 0,
errorMessage = this.error ? this.error.message || "" : "";
console.log("Media element error: " + errorCode.toString() + " " + errorMessage);
var type;
switch (errorCode) {
case 1:
return;
case 2:
type = "network";
break;
case 3:
if (self._hlsPlayer) return void htmlMediaHelper.handleHlsJsMediaError(self);
type = "mediadecodeerror";
break;
case 4:
type = "medianotsupported";
break;
default:
return
}
htmlMediaHelper.onErrorInternal(self, type)
}
var self = this;
self.name = "Html Audio Player", self.type = "mediaplayer", self.id = "htmlaudioplayer", self.priority = 1, self.play = function(options) {
return self._started = !1, self._timeUpdated = !1, self._currentTime = null, setCurrentSrc(createMediaElement(), options)
}, self.stop = function(destroyPlayer) {
cancelFadeTimeout();
var elem = self._mediaElement,
src = self._currentSrc;
if (elem && src) {
if (!destroyPlayer || !supportsFade()) return elem.pause(), htmlMediaHelper.onEndedInternal(self, elem, onError), destroyPlayer && self.destroy(), Promise.resolve();
var originalVolume = elem.volume;
return fade(self, elem, elem.volume).then(function() {
elem.pause(), elem.volume = originalVolume, htmlMediaHelper.onEndedInternal(self, elem, onError), destroyPlayer && self.destroy()
})
}
return Promise.resolve()
}, self.destroy = function() {
unBindEvents(self._mediaElement)
}
}
var fadeTimeout;
return HtmlAudioPlayer.prototype.currentSrc = function() {
return this._currentSrc
}, HtmlAudioPlayer.prototype.canPlayMediaType = function(mediaType) {
return "audio" === (mediaType || "").toLowerCase()
}, HtmlAudioPlayer.prototype.getDeviceProfile = function(item) {
return appHost.getDeviceProfile ? appHost.getDeviceProfile(item) : getDefaultProfile()
}, HtmlAudioPlayer.prototype.currentTime = function(val) {
var mediaElement = this._mediaElement;
if (mediaElement) {
if (null != val) return void(mediaElement.currentTime = val / 1e3);
var currentTime = this._currentTime;
return currentTime ? 1e3 * currentTime : 1e3 * (mediaElement.currentTime || 0)
}
}, HtmlAudioPlayer.prototype.duration = function(val) {
var mediaElement = this._mediaElement;
if (mediaElement) {
var duration = mediaElement.duration;
if (htmlMediaHelper.isValidDuration(duration)) return 1e3 * duration
}
return null
}, HtmlAudioPlayer.prototype.seekable = function() {
var mediaElement = this._mediaElement;
if (mediaElement) {
var seekable = mediaElement.seekable;
if (seekable && seekable.length) {
var start = seekable.start(0),
end = seekable.end(0);
return htmlMediaHelper.isValidDuration(start) || (start = 0), htmlMediaHelper.isValidDuration(end) || (end = 0), end - start > 0
}
return !1
}
}, HtmlAudioPlayer.prototype.getBufferedRanges = function() {
var mediaElement = this._mediaElement;
return mediaElement ? htmlMediaHelper.getBufferedRanges(this, mediaElement) : []
}, HtmlAudioPlayer.prototype.pause = function() {
var mediaElement = this._mediaElement;
mediaElement && mediaElement.pause()
}, HtmlAudioPlayer.prototype.resume = function() {
var mediaElement = this._mediaElement;
mediaElement && mediaElement.play()
}, HtmlAudioPlayer.prototype.unpause = function() {
var mediaElement = this._mediaElement;
mediaElement && mediaElement.play()
}, HtmlAudioPlayer.prototype.paused = function() {
var mediaElement = this._mediaElement;
return !!mediaElement && mediaElement.paused
}, HtmlAudioPlayer.prototype.setVolume = function(val) {
var mediaElement = this._mediaElement;
mediaElement && (mediaElement.volume = val / 100)
}, HtmlAudioPlayer.prototype.getVolume = function() {
var mediaElement = this._mediaElement;
if (mediaElement) return Math.min(Math.round(100 * mediaElement.volume), 100)
}, HtmlAudioPlayer.prototype.volumeUp = function() {
this.setVolume(Math.min(this.getVolume() + 2, 100))
}, HtmlAudioPlayer.prototype.volumeDown = function() {
this.setVolume(Math.max(this.getVolume() - 2, 0))
}, HtmlAudioPlayer.prototype.setMute = function(mute) {
var mediaElement = this._mediaElement;
mediaElement && (mediaElement.muted = mute)
}, HtmlAudioPlayer.prototype.isMuted = function() {
var mediaElement = this._mediaElement;
return !!mediaElement && mediaElement.muted
}, HtmlAudioPlayer.prototype.destroy = function() {}, HtmlAudioPlayer
});

View file

@ -0,0 +1,218 @@
define(["appSettings", "browser", "events"], function(appSettings, browser, events) {
"use strict";
function getSavedVolume() {
return appSettings.get("volume") || 1
}
function saveVolume(value) {
value && appSettings.set("volume", value)
}
function getCrossOriginValue(mediaSource) {
return mediaSource.IsRemote ? null : "anonymous"
}
function canPlayNativeHls() {
var media = document.createElement("video");
return !(!media.canPlayType("application/x-mpegURL").replace(/no/, "") && !media.canPlayType("application/vnd.apple.mpegURL").replace(/no/, ""))
}
function enableHlsShakaPlayer(item, mediaSource, mediaType) {
if (window.MediaSource && MediaSource.isTypeSupported) {
if (canPlayNativeHls()) {
if (browser.edge && "Video" === mediaType) return !0;
mediaSource.RunTimeTicks
}
return !0
}
return !1
}
function enableHlsJsPlayer(runTimeTicks, mediaType) {
if (null == window.MediaSource) return !1;
if (browser.iOS) return !1;
if (browser.tizen || browser.web0s) return !1;
if (canPlayNativeHls()) {
if (browser.android && "Audio" === mediaType) return !0;
if (browser.edge, runTimeTicks) return !1
}
return !0
}
function handleHlsJsMediaError(instance, reject) {
var hlsPlayer = instance._hlsPlayer;
if (hlsPlayer) {
var now = Date.now();
window.performance && window.performance.now && (now = performance.now()), !recoverDecodingErrorDate || now - recoverDecodingErrorDate > 3e3 ? (recoverDecodingErrorDate = now, console.log("try to recover media Error ..."), hlsPlayer.recoverMediaError()) : !recoverSwapAudioCodecDate || now - recoverSwapAudioCodecDate > 3e3 ? (recoverSwapAudioCodecDate = now, console.log("try to swap Audio Codec and recover media Error ..."), hlsPlayer.swapAudioCodec(), hlsPlayer.recoverMediaError()) : (console.error("cannot recover, last media error recovery failed ..."), reject ? reject() : onErrorInternal(instance, "mediadecodeerror"))
}
}
function onErrorInternal(instance, type) {
instance.destroyCustomTrack && instance.destroyCustomTrack(instance._mediaElement), events.trigger(instance, "error", [{
type: type
}])
}
function isValidDuration(duration) {
return !(!duration || isNaN(duration) || duration === Number.POSITIVE_INFINITY || duration === Number.NEGATIVE_INFINITY)
}
function setCurrentTimeIfNeeded(element, seconds, allowance) {
Math.abs((element.currentTime || 0) - seconds) >= allowance && (element.currentTime = seconds)
}
function seekOnPlaybackStart(instance, element, ticks) {
var seconds = (ticks || 0) / 1e7;
if (seconds) {
(instance.currentSrc() || "").toLowerCase();
setCurrentTimeIfNeeded(element, seconds, 5), setTimeout(function() {
setCurrentTimeIfNeeded(element, seconds, 10)
}, 2500)
}
}
function applySrc(elem, src, options) {
return window.Windows && options.mediaSource && options.mediaSource.IsLocal ? Windows.Storage.StorageFile.getFileFromPathAsync(options.url).then(function(file) {
var playlist = new Windows.Media.Playback.MediaPlaybackList,
source1 = Windows.Media.Core.MediaSource.createFromStorageFile(file),
startTime = (options.playerStartPositionTicks || 0) / 1e4;
return playlist.items.append(new Windows.Media.Playback.MediaPlaybackItem(source1, startTime)), elem.src = URL.createObjectURL(playlist, {
oneTimeOnly: !0
}), Promise.resolve()
}) : (elem.src = src, Promise.resolve())
}
function onSuccessfulPlay(elem, onErrorFn) {
elem.addEventListener("error", onErrorFn)
}
function playWithPromise(elem, onErrorFn) {
try {
var promise = elem.play();
return promise && promise.then ? promise.catch(function(e) {
var errorName = (e.name || "").toLowerCase();
return "notallowederror" === errorName || "aborterror" === errorName ? (onSuccessfulPlay(elem, onErrorFn), Promise.resolve()) : Promise.reject()
}) : (onSuccessfulPlay(elem, onErrorFn), Promise.resolve())
} catch (err) {
return console.log("error calling video.play: " + err), Promise.reject()
}
}
function destroyCastPlayer(instance) {
var player = instance._castPlayer;
if (player) {
try {
player.unload()
} catch (err) {
console.log(err)
}
instance._castPlayer = null
}
}
function destroyShakaPlayer(instance) {
var player = instance._shakaPlayer;
if (player) {
try {
player.destroy()
} catch (err) {
console.log(err)
}
instance._shakaPlayer = null
}
}
function destroyHlsPlayer(instance) {
var player = instance._hlsPlayer;
if (player) {
try {
player.destroy()
} catch (err) {
console.log(err)
}
instance._hlsPlayer = null
}
}
function destroyFlvPlayer(instance) {
var player = instance._flvPlayer;
if (player) {
try {
player.unload(), player.detachMediaElement(), player.destroy()
} catch (err) {
console.log(err)
}
instance._flvPlayer = null
}
}
function bindEventsToHlsPlayer(instance, hls, elem, onErrorFn, resolve, reject) {
hls.on(Hls.Events.MANIFEST_PARSED, function() {
playWithPromise(elem, onErrorFn).then(resolve, function() {
reject && (reject(), reject = null)
})
}), hls.on(Hls.Events.ERROR, function(event, data) {
switch (console.log("HLS Error: Type: " + data.type + " Details: " + (data.details || "") + " Fatal: " + (data.fatal || !1)), data.type) {
case Hls.ErrorTypes.NETWORK_ERROR:
if (data.response && data.response.code && data.response.code >= 400) return console.log("hls.js response error code: " + data.response.code), hls.destroy(), void(reject ? (reject("servererror"), reject = null) : onErrorInternal(instance, "servererror"))
}
if (data.fatal) switch (data.type) {
case Hls.ErrorTypes.NETWORK_ERROR:
data.response && 0 === data.response.code ? (console.log("hls.js response error code: " + data.response.code), hls.destroy(), reject ? (reject("network"), reject = null) : onErrorInternal(instance, "network")) : (console.log("fatal network error encountered, try to recover"), hls.startLoad());
break;
case Hls.ErrorTypes.MEDIA_ERROR:
console.log("fatal media error encountered, try to recover");
var currentReject = reject;
reject = null, handleHlsJsMediaError(instance, currentReject);
break;
default:
console.log("Cannot recover from hls error - destroy and trigger error"), hls.destroy(), reject ? (reject(), reject = null) : onErrorInternal(instance, "mediadecodeerror")
}
})
}
function onEndedInternal(instance, elem, onErrorFn) {
elem.removeEventListener("error", onErrorFn), elem.src = "", elem.innerHTML = "", elem.removeAttribute("src"), destroyHlsPlayer(instance), destroyFlvPlayer(instance), destroyShakaPlayer(instance), destroyCastPlayer(instance);
var stopInfo = {
src: instance._currentSrc
};
events.trigger(instance, "stopped", [stopInfo]), instance._currentTime = null, instance._currentSrc = null, instance._currentPlayOptions = null
}
function getBufferedRanges(instance, elem) {
var offset, ranges = [],
seekable = elem.buffered || [],
currentPlayOptions = instance._currentPlayOptions;
currentPlayOptions && (offset = currentPlayOptions.transcodingOffsetTicks), offset = offset || 0;
for (var i = 0, length = seekable.length; i < length; i++) {
var start = seekable.start(i),
end = seekable.end(i);
isValidDuration(start) || (start = 0), isValidDuration(end) ? ranges.push({
start: 1e7 * start + offset,
end: 1e7 * end + offset
}) : end = 0
}
return ranges
}
var recoverDecodingErrorDate, recoverSwapAudioCodecDate;
return {
getSavedVolume: getSavedVolume,
saveVolume: saveVolume,
enableHlsJsPlayer: enableHlsJsPlayer,
enableHlsShakaPlayer: enableHlsShakaPlayer,
handleHlsJsMediaError: handleHlsJsMediaError,
isValidDuration: isValidDuration,
onErrorInternal: onErrorInternal,
seekOnPlaybackStart: seekOnPlaybackStart,
applySrc: applySrc,
playWithPromise: playWithPromise,
destroyHlsPlayer: destroyHlsPlayer,
destroyFlvPlayer: destroyFlvPlayer,
destroyCastPlayer: destroyCastPlayer,
bindEventsToHlsPlayer: bindEventsToHlsPlayer,
onEndedInternal: onEndedInternal,
getCrossOriginValue: getCrossOriginValue,
getBufferedRanges: getBufferedRanges
}
});

View file

@ -0,0 +1,699 @@
define(["browser", "require", "events", "apphost", "loading", "dom", "playbackManager", "appRouter", "appSettings", "connectionManager", "htmlMediaHelper", "itemHelper"], function(browser, require, events, appHost, loading, dom, playbackManager, appRouter, appSettings, connectionManager, htmlMediaHelper, itemHelper) {
"use strict";
function tryRemoveElement(elem) {
var parentNode = elem.parentNode;
if (parentNode) try {
parentNode.removeChild(elem)
} catch (err) {
console.log("Error removing dialog element: " + err)
}
}
function enableNativeTrackSupport(currentSrc, track) {
if (track && "Embed" === track.DeliveryMethod) return !0;
if (browser.firefox && -1 !== (currentSrc || "").toLowerCase().indexOf(".m3u8")) return !1;
if (browser.chromecast && -1 !== (currentSrc || "").toLowerCase().indexOf(".m3u8")) return !1;
if (browser.ps4) return !1;
if (browser.web0s) return !1;
if (browser.edge) return !1;
if (browser.iOS && (browser.iosVersion || 10) < 10) return !1;
if (track) {
var format = (track.Codec || "").toLowerCase();
if ("ssa" === format || "ass" === format) return !1
}
return !0
}
function requireHlsPlayer(callback) {
require(["hlsjs"], function(hls) {
window.Hls = hls, callback()
})
}
function getMediaStreamAudioTracks(mediaSource) {
return mediaSource.MediaStreams.filter(function(s) {
return "Audio" === s.Type
})
}
function getMediaStreamTextTracks(mediaSource) {
return mediaSource.MediaStreams.filter(function(s) {
return "Subtitle" === s.Type
})
}
function zoomIn(elem) {
return new Promise(function(resolve, reject) {
elem.style.animation = "htmlvideoplayer-zoomin 240ms ease-in normal", dom.addEventListener(elem, dom.whichAnimationEvent(), resolve, {
once: !0
})
})
}
function normalizeTrackEventText(text) {
return text.replace(/\\N/gi, "\n")
}
function setTracks(elem, tracks, item, mediaSource) {
elem.innerHTML = getTracksHtml(tracks, item, mediaSource)
}
function getTextTrackUrl(track, item, format) {
if (itemHelper.isLocalItem(item) && track.Path) return track.Path;
var url = playbackManager.getSubtitleUrl(track, item.ServerId);
return format && (url = url.replace(".vtt", format)), url
}
function getTracksHtml(tracks, item, mediaSource) {
return tracks.map(function(t) {
if ("External" !== t.DeliveryMethod) return "";
var defaultAttribute = mediaSource.DefaultSubtitleStreamIndex === t.Index ? " default" : "",
language = t.Language || "und",
label = t.Language || "und";
return '<track id="textTrack' + t.Index + '" label="' + label + '" kind="subtitles" src="' + getTextTrackUrl(t, item) + '" srclang="' + language + '"' + defaultAttribute + "></track>"
}).join("")
}
function getDefaultProfile() {
return new Promise(function(resolve, reject) {
require(["browserdeviceprofile"], function(profileBuilder) {
resolve(profileBuilder({}))
})
})
}
function HtmlVideoPlayer() {
function updateVideoUrl(streamInfo) {
var isHls = -1 !== streamInfo.url.toLowerCase().indexOf(".m3u8"),
mediaSource = streamInfo.mediaSource,
item = streamInfo.item;
if (mediaSource && item && !mediaSource.RunTimeTicks && isHls && "Transcode" === streamInfo.playMethod && (browser.iOS || browser.osx)) {
var hlsPlaylistUrl = streamInfo.url.replace("master.m3u8", "live.m3u8");
return loading.show(), console.log("prefetching hls playlist: " + hlsPlaylistUrl), connectionManager.getApiClient(item.ServerId).ajax({
type: "GET",
url: hlsPlaylistUrl
}).then(function() {
return console.log("completed prefetching hls playlist: " + hlsPlaylistUrl), loading.hide(), streamInfo.url = hlsPlaylistUrl, Promise.resolve()
}, function() {
return console.log("error prefetching hls playlist: " + hlsPlaylistUrl), loading.hide(), Promise.resolve()
})
}
return Promise.resolve()
}
function setSrcWithFlvJs(instance, elem, options, url) {
return new Promise(function(resolve, reject) {
require(["flvjs"], function(flvjs) {
var flvPlayer = flvjs.createPlayer({
type: "flv",
url: url
}, {
seekType: "range",
lazyLoad: !1
});
flvPlayer.attachMediaElement(elem), flvPlayer.load(), flvPlayer.play().then(resolve, reject), instance._flvPlayer = flvPlayer, self._currentSrc = url
})
})
}
function setSrcWithHlsJs(instance, elem, options, url) {
return new Promise(function(resolve, reject) {
requireHlsPlayer(function() {
var hls = new Hls({
manifestLoadingTimeOut: 2e4
});
hls.loadSource(url), hls.attachMedia(elem), htmlMediaHelper.bindEventsToHlsPlayer(self, hls, elem, onError, resolve, reject), self._hlsPlayer = hls, self._currentSrc = url
})
})
}
function setCurrentSrcChromecast(instance, elem, options, url) {
elem.autoplay = !0;
var lrd = new cast.receiver.MediaManager.LoadRequestData;
lrd.currentTime = (options.playerStartPositionTicks || 0) / 1e7, lrd.autoplay = !0, lrd.media = new cast.receiver.media.MediaInformation, lrd.media.contentId = url, lrd.media.contentType = options.mimeType, lrd.media.streamType = cast.receiver.media.StreamType.OTHER, lrd.media.customData = options, console.log("loading media url into mediaManager");
try {
return mediaManager.load(lrd), self._currentSrc = url, Promise.resolve()
} catch (err) {
return console.log("mediaManager error: " + err), Promise.reject()
}
}
function onMediaManagerLoadMedia(event) {
self._castPlayer && self._castPlayer.unload(), self._castPlayer = null;
var protocol, data = event.data,
media = event.data.media || {},
url = media.contentId,
contentType = media.contentType.toLowerCase(),
mediaElement = (media.customData, self._mediaElement),
host = new cast.player.api.Host({
url: url,
mediaElement: mediaElement
});
protocol = cast.player.api.CreateHlsStreamingProtocol(host), console.log("loading playback url: " + url), console.log("contentType: " + contentType), host.onError = function(errorCode) {
console.log("Fatal Error - " + errorCode)
}, mediaElement.autoplay = !1, self._castPlayer = new cast.player.api.Player(host), self._castPlayer.load(protocol, data.currentTime || 0), self._castPlayer.playWhenHaveEnoughData()
}
function initMediaManager() {
mediaManager.defaultOnLoad = mediaManager.onLoad.bind(mediaManager), mediaManager.onLoad = onMediaManagerLoadMedia.bind(self), mediaManager.defaultOnStop = mediaManager.onStop.bind(mediaManager), mediaManager.onStop = function(event) {
playbackManager.stop(), mediaManager.defaultOnStop(event)
}
}
function setCurrentSrc(elem, options) {
elem.removeEventListener("error", onError);
var val = options.url;
console.log("playing url: " + val);
var seconds = (options.playerStartPositionTicks || 0) / 1e7;
seconds && (val += "#t=" + seconds), htmlMediaHelper.destroyHlsPlayer(self), htmlMediaHelper.destroyFlvPlayer(self), htmlMediaHelper.destroyCastPlayer(self);
var tracks = getMediaStreamTextTracks(options.mediaSource);
if (null != (subtitleTrackIndexToSetOnPlaying = null == options.mediaSource.DefaultSubtitleStreamIndex ? -1 : options.mediaSource.DefaultSubtitleStreamIndex) && subtitleTrackIndexToSetOnPlaying >= 0) {
var initialSubtitleStream = options.mediaSource.MediaStreams[subtitleTrackIndexToSetOnPlaying];
initialSubtitleStream && "Encode" !== initialSubtitleStream.DeliveryMethod || (subtitleTrackIndexToSetOnPlaying = -1)
}
audioTrackIndexToSetOnPlaying = "Transcode" === options.playMethod ? null : options.mediaSource.DefaultAudioStreamIndex, self._currentPlayOptions = options;
var crossOrigin = htmlMediaHelper.getCrossOriginValue(options.mediaSource);
return crossOrigin && (elem.crossOrigin = crossOrigin), browser.chromecast && -1 !== val.indexOf(".m3u8") && options.mediaSource.RunTimeTicks ? (setTracks(elem, tracks, options.item, options.mediaSource), setCurrentSrcChromecast(self, elem, options, val)) : htmlMediaHelper.enableHlsJsPlayer(options.mediaSource.RunTimeTicks, "Video") && -1 !== val.indexOf(".m3u8") ? (setTracks(elem, tracks, options.item, options.mediaSource), setSrcWithHlsJs(self, elem, options, val)) : "Transcode" !== options.playMethod && "flv" === options.mediaSource.Container ? (setTracks(elem, tracks, options.item, options.mediaSource), setSrcWithFlvJs(self, elem, options, val)) : (elem.autoplay = !0, htmlMediaHelper.applySrc(elem, val, options).then(function() {
return setTracks(elem, tracks, options.item, options.mediaSource), self._currentSrc = val, htmlMediaHelper.playWithPromise(elem, onError)
}))
}
function isAudioStreamSupported(stream, deviceProfile) {
var codec = (stream.Codec || "").toLowerCase();
return !codec || (!deviceProfile || (deviceProfile.DirectPlayProfiles || []).filter(function(p) {
return "Video" === p.Type && (!p.AudioCodec || -1 !== p.AudioCodec.toLowerCase().indexOf(codec))
}).length > 0)
}
function getSupportedAudioStreams() {
var profile = self._lastProfile;
return getMediaStreamAudioTracks(self._currentPlayOptions.mediaSource).filter(function(stream) {
return isAudioStreamSupported(stream, profile)
})
}
function onEnded() {
destroyCustomTrack(this), htmlMediaHelper.onEndedInternal(self, this, onError)
}
function onTimeUpdate(e) {
var time = this.currentTime;
time && !self._timeUpdated && (self._timeUpdated = !0, ensureValidVideo(this)), self._currentTime = time;
var currentPlayOptions = self._currentPlayOptions;
if (currentPlayOptions) {
var timeMs = 1e3 * time;
timeMs += (currentPlayOptions.transcodingOffsetTicks || 0) / 1e4, updateSubtitleText(timeMs)
}
events.trigger(self, "timeupdate")
}
function onVolumeChange() {
htmlMediaHelper.saveVolume(this.volume), events.trigger(self, "volumechange")
}
function onNavigatedToOsd() {
var dlg = videoDialog;
dlg && (dlg.classList.remove("videoPlayerContainer-withBackdrop"), dlg.classList.remove("videoPlayerContainer-onTop"), onStartedAndNavigatedToOsd())
}
function onStartedAndNavigatedToOsd() {
setCurrentTrackElement(subtitleTrackIndexToSetOnPlaying), null != audioTrackIndexToSetOnPlaying && self.canSetAudioStreamIndex() && self.setAudioStreamIndex(audioTrackIndexToSetOnPlaying)
}
function onPlaying(e) {
self._started || (self._started = !0, this.removeAttribute("controls"), loading.hide(), htmlMediaHelper.seekOnPlaybackStart(self, e.target, self._currentPlayOptions.playerStartPositionTicks), self._currentPlayOptions.fullscreen ? appRouter.showVideoOsd().then(onNavigatedToOsd) : (appRouter.setTransparency("backdrop"), videoDialog.classList.remove("videoPlayerContainer-withBackdrop"), videoDialog.classList.remove("videoPlayerContainer-onTop"), onStartedAndNavigatedToOsd())), events.trigger(self, "playing")
}
function onPlay(e) {
events.trigger(self, "unpause")
}
function ensureValidVideo(elem) {
if (elem === self._mediaElement && 0 === elem.videoWidth && 0 === elem.videoHeight) {
var mediaSource = (self._currentPlayOptions || {}).mediaSource;
if (!mediaSource || mediaSource.RunTimeTicks) return void htmlMediaHelper.onErrorInternal(self, "mediadecodeerror")
}
}
function onClick() {
events.trigger(self, "click")
}
function onDblClick() {
events.trigger(self, "dblclick")
}
function onPause() {
events.trigger(self, "pause")
}
function onError() {
var errorCode = this.error ? this.error.code || 0 : 0,
errorMessage = this.error ? this.error.message || "" : "";
console.log("Media element error: " + errorCode.toString() + " " + errorMessage);
var type;
switch (errorCode) {
case 1:
return;
case 2:
type = "network";
break;
case 3:
if (self._hlsPlayer) return void htmlMediaHelper.handleHlsJsMediaError(self);
type = "mediadecodeerror";
break;
case 4:
type = "medianotsupported";
break;
default:
return
}
htmlMediaHelper.onErrorInternal(self, type)
}
function destroyCustomTrack(videoElement) {
if (self._resizeObserver && (self._resizeObserver.disconnect(), self._resizeObserver = null), videoSubtitlesElem) {
var subtitlesContainer = videoSubtitlesElem.parentNode;
subtitlesContainer && tryRemoveElement(subtitlesContainer), videoSubtitlesElem = null
}
if (currentTrackEvents = null, videoElement)
for (var allTracks = videoElement.textTracks || [], i = 0; i < allTracks.length; i++) {
var currentTrack = allTracks[i]; - 1 !== currentTrack.label.indexOf("manualTrack") && (currentTrack.mode = "disabled")
}
customTrackIndex = -1, currentClock = null, self._currentAspectRatio = null;
var renderer = currentAssRenderer;
renderer && renderer.setEnabled(!1), currentAssRenderer = null
}
function fetchSubtitlesUwp(track, item) {
return Windows.Storage.StorageFile.getFileFromPathAsync(track.Path).then(function(storageFile) {
return Windows.Storage.FileIO.readTextAsync(storageFile).then(function(text) {
return JSON.parse(text)
})
})
}
function fetchSubtitles(track, item) {
return window.Windows && itemHelper.isLocalItem(item) ? fetchSubtitlesUwp(track, item) : new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest,
url = getTextTrackUrl(track, item, ".js");
xhr.open("GET", url, !0), xhr.onload = function(e) {
resolve(JSON.parse(this.response))
}, xhr.onerror = reject, xhr.send()
})
}
function setTrackForCustomDisplay(videoElement, track) {
if (!track) return void destroyCustomTrack(videoElement);
if (customTrackIndex !== track.Index) {
var item = self._currentPlayOptions.item;
destroyCustomTrack(videoElement), customTrackIndex = track.Index, renderTracksEvents(videoElement, track, item), lastCustomTrackMs = 0
}
}
function renderWithLibjass(videoElement, track, item) {
var rendererSettings = {};
browser.ps4 ? rendererSettings.enableSvg = !1 : (browser.edge || browser.msie) && (rendererSettings.enableSvg = !1), rendererSettings.enableSvg = !1, require(["libjass", "ResizeObserver"], function(libjass, ResizeObserver) {
libjass.ASS.fromUrl(getTextTrackUrl(track, item)).then(function(ass) {
var clock = new libjass.renderers.ManualClock;
currentClock = clock;
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), self._resizeObserver || (self._resizeObserver = new ResizeObserver(onVideoResize, {}), self._resizeObserver.observe(videoElement))
} catch (ex) {}
})
}, function() {
htmlMediaHelper.onErrorInternal(self, "mediadecodeerror")
})
})
}
function onVideoResize() {
browser.iOS ? setTimeout(resetVideoRendererSize, 500) : resetVideoRendererSize()
}
function resetVideoRendererSize() {
var renderer = currentAssRenderer;
if (renderer) {
var videoElement = self._mediaElement,
width = videoElement.offsetWidth,
height = videoElement.offsetHeight;
console.log("videoElement resized: " + width + "x" + height), renderer.resize(width, height, 0, 0)
}
}
function requiresCustomSubtitlesElement() {
if (browser.ps4) return !0;
if (browser.firefox || browser.web0s) return !0;
if (browser.edge) return !0;
if (browser.iOS) {
var userAgent = navigator.userAgent.toLowerCase();
if ((-1 !== userAgent.indexOf("os 9") || -1 !== userAgent.indexOf("os 8")) && -1 === userAgent.indexOf("safari")) return !0
}
return !1
}
function renderSubtitlesWithCustomElement(videoElement, track, item) {
fetchSubtitles(track, item).then(function(data) {
if (!videoSubtitlesElem) {
var subtitlesContainer = document.createElement("div");
subtitlesContainer.classList.add("videoSubtitles"), subtitlesContainer.innerHTML = '<div class="videoSubtitlesInner"></div>', videoSubtitlesElem = subtitlesContainer.querySelector(".videoSubtitlesInner"), setSubtitleAppearance(subtitlesContainer, videoSubtitlesElem), videoElement.parentNode.appendChild(subtitlesContainer), currentTrackEvents = data.TrackEvents
}
})
}
function setSubtitleAppearance(elem, innerElem) {
require(["userSettings", "subtitleAppearanceHelper"], function(userSettings, subtitleAppearanceHelper) {
subtitleAppearanceHelper.applyStyles({
text: innerElem,
window: elem
}, userSettings.getSubtitleAppearanceSettings())
})
}
function getCueCss(appearance, selector) {
var html = selector + "::cue {";
return html += appearance.text.map(function(s) {
return s.name + ":" + s.value + "!important;"
}).join(""), html += "}"
}
function setCueAppearance() {
require(["userSettings", "subtitleAppearanceHelper"], function(userSettings, subtitleAppearanceHelper) {
var elementId = self.id + "-cuestyle",
styleElem = document.querySelector("#" + elementId);
styleElem || (styleElem = document.createElement("style"), styleElem.id = elementId, styleElem.type = "text/css", document.getElementsByTagName("head")[0].appendChild(styleElem)), styleElem.innerHTML = getCueCss(subtitleAppearanceHelper.getStyles(userSettings.getSubtitleAppearanceSettings(), !0), ".htmlvideoplayer")
})
}
function renderTracksEvents(videoElement, track, item) {
if (!itemHelper.isLocalItem(item) || track.IsExternal) {
var format = (track.Codec || "").toLowerCase();
if ("ssa" === format || "ass" === format) return void renderWithLibjass(videoElement, track, item);
if (requiresCustomSubtitlesElement()) return void renderSubtitlesWithCustomElement(videoElement, track, item)
}
for (var trackElement = null, expectedId = "manualTrack" + track.Index, allTracks = videoElement.textTracks, i = 0; i < allTracks.length; i++) {
var currentTrack = allTracks[i];
if (currentTrack.label === expectedId) {
trackElement = currentTrack;
break
}
currentTrack.mode = "disabled"
}
trackElement ? trackElement.mode = "showing" : (trackElement = videoElement.addTextTrack("subtitles", "manualTrack" + track.Index, track.Language || "und"), fetchSubtitles(track, item).then(function(data) {
console.log("downloaded " + data.TrackEvents.length + " track events"), data.TrackEvents.forEach(function(trackEvent) {
var trackCueObject = window.VTTCue || window.TextTrackCue,
cue = new trackCueObject(trackEvent.StartPositionTicks / 1e7, trackEvent.EndPositionTicks / 1e7, normalizeTrackEventText(trackEvent.Text));
trackElement.addCue(cue)
}), trackElement.mode = "showing"
}))
}
function updateSubtitleText(timeMs) {
var clock = currentClock;
if (clock) try {
clock.seek(timeMs / 1e3)
} catch (err) {
console.log("Error in libjass: " + err)
} else {
var trackEvents = currentTrackEvents,
subtitleTextElement = videoSubtitlesElem;
if (trackEvents && subtitleTextElement) {
for (var selectedTrackEvent, ticks = 1e4 * timeMs, i = 0; i < trackEvents.length; i++) {
var currentTrackEvent = trackEvents[i];
if (currentTrackEvent.StartPositionTicks <= ticks && currentTrackEvent.EndPositionTicks >= ticks) {
selectedTrackEvent = currentTrackEvent;
break
}
}
selectedTrackEvent && selectedTrackEvent.Text ? (subtitleTextElement.innerHTML = normalizeTrackEventText(selectedTrackEvent.Text), subtitleTextElement.classList.remove("hide")) : subtitleTextElement.classList.add("hide")
}
}
}
function setCurrentTrackElement(streamIndex) {
console.log("Setting new text track index to: " + streamIndex);
var mediaStreamTextTracks = getMediaStreamTextTracks(self._currentPlayOptions.mediaSource),
track = -1 === streamIndex ? null : mediaStreamTextTracks.filter(function(t) {
return t.Index === streamIndex
})[0];
enableNativeTrackSupport(self._currentSrc, track) ? (setTrackForCustomDisplay(self._mediaElement, null), -1 !== streamIndex && setCueAppearance()) : (setTrackForCustomDisplay(self._mediaElement, track), streamIndex = -1, track = null);
for (var expectedId = "textTrack" + streamIndex, trackIndex = -1 !== streamIndex && track ? mediaStreamTextTracks.indexOf(track) : -1, modes = ["disabled", "showing", "hidden"], allTracks = self._mediaElement.textTracks, i = 0; i < allTracks.length; i++) {
var currentTrack = allTracks[i];
console.log("currentTrack id: " + currentTrack.id);
var mode;
if (console.log("expectedId: " + expectedId + "--currentTrack.Id:" + currentTrack.id), browser.msie || browser.edge) mode = trackIndex === i ? 1 : 0;
else {
if (-1 !== currentTrack.label.indexOf("manualTrack")) continue;
mode = currentTrack.id === expectedId ? 1 : 0
}
console.log("Setting track " + i + " mode to: " + mode), currentTrack.mode = modes[mode]
}
}
function createMediaElement(options) {
return (browser.tv || browser.iOS || browser.mobile) && (options.backdropUrl = null), new Promise(function(resolve, reject) {
var dlg = document.querySelector(".videoPlayerContainer");
dlg ? (options.backdropUrl && (dlg.classList.add("videoPlayerContainer-withBackdrop"), dlg.style.backgroundImage = "url('" + options.backdropUrl + "')"), resolve(dlg.querySelector("video"))) : require(["css!./style"], function() {
loading.show();
var dlg = document.createElement("div");
dlg.classList.add("videoPlayerContainer"), options.backdropUrl && (dlg.classList.add("videoPlayerContainer-withBackdrop"), dlg.style.backgroundImage = "url('" + options.backdropUrl + "')"), options.fullscreen && dlg.classList.add("videoPlayerContainer-onTop");
var html = "",
cssClass = "htmlvideoplayer";
browser.chromecast || (cssClass += " htmlvideoplayer-moveupsubtitles"), appHost.supports("htmlvideoautoplay") ? html += '<video class="' + cssClass + '" preload="metadata" autoplay="autoplay" webkit-playsinline playsinline>' : html += '<video class="' + cssClass + '" preload="metadata" autoplay="autoplay" controls="controls" webkit-playsinline playsinline>', html += "</video>", dlg.innerHTML = html;
var videoElement = dlg.querySelector("video");
videoElement.volume = htmlMediaHelper.getSavedVolume(), videoElement.addEventListener("timeupdate", onTimeUpdate), videoElement.addEventListener("ended", onEnded), videoElement.addEventListener("volumechange", onVolumeChange), videoElement.addEventListener("pause", onPause), videoElement.addEventListener("playing", onPlaying), videoElement.addEventListener("play", onPlay), videoElement.addEventListener("click", onClick), videoElement.addEventListener("dblclick", onDblClick), document.body.insertBefore(dlg, document.body.firstChild), videoDialog = dlg, self._mediaElement = videoElement, mediaManager && (mediaManager.embyInit || (initMediaManager(), mediaManager.embyInit = !0), mediaManager.setMediaElement(videoElement)), options.fullscreen && browser.supportsCssAnimation() && !browser.slow ? zoomIn(dlg).then(function() {
resolve(videoElement)
}) : resolve(videoElement)
})
})
}
browser.edgeUwp ? this.name = "Windows Video Player" : this.name = "Html Video Player", this.type = "mediaplayer", this.id = "htmlvideoplayer", this.priority = 1;
var videoDialog, subtitleTrackIndexToSetOnPlaying, audioTrackIndexToSetOnPlaying, currentClock, currentAssRenderer, videoSubtitlesElem, currentTrackEvents, lastCustomTrackMs = 0,
customTrackIndex = -1,
self = this;
self.currentSrc = function() {
return self._currentSrc
}, self.play = function(options) {
return browser.msie && "Transcode" === options.playMethod && !window.MediaSource ? (alert("Playback of this content is not supported in Internet Explorer. For a better experience, try a modern browser such as Microsoft Edge, Google Chrome, Firefox or Opera."), Promise.reject()) : (self._started = !1, self._timeUpdated = !1, self._currentTime = null, createMediaElement(options).then(function(elem) {
return updateVideoUrl(options, options.mediaSource).then(function() {
return setCurrentSrc(elem, options)
})
}))
}, self.setSubtitleStreamIndex = function(index) {
setCurrentTrackElement(index)
}, self.setAudioStreamIndex = function(index) {
var streams = getSupportedAudioStreams();
if (!(streams.length < 2)) {
var i, length, stream, audioIndex = -1;
for (i = 0, length = streams.length; i < length && (stream = streams[i], audioIndex++, stream.Index !== index); i++);
if (-1 !== audioIndex) {
var elem = self._mediaElement;
if (elem) {
var elemAudioTracks = elem.audioTracks || [];
for (console.log("found " + elemAudioTracks.length + " audio tracks"), i = 0, length = elemAudioTracks.length; i < length; i++) audioIndex === i ? (console.log("setting audio track " + i + " to enabled"), elemAudioTracks[i].enabled = !0) : (console.log("setting audio track " + i + " to disabled"), elemAudioTracks[i].enabled = !1);
setTimeout(function() {
elem.currentTime = elem.currentTime
}, 100)
}
}
}
}, self.stop = function(destroyPlayer) {
var elem = self._mediaElement,
src = self._currentSrc;
return elem && (src && elem.pause(), htmlMediaHelper.onEndedInternal(self, elem, onError), destroyPlayer && self.destroy()), destroyCustomTrack(elem), Promise.resolve()
}, self.destroy = function() {
htmlMediaHelper.destroyHlsPlayer(self), htmlMediaHelper.destroyFlvPlayer(self), appRouter.setTransparency("none");
var videoElement = self._mediaElement;
videoElement && (self._mediaElement = null, destroyCustomTrack(videoElement), videoElement.removeEventListener("timeupdate", onTimeUpdate), videoElement.removeEventListener("ended", onEnded), videoElement.removeEventListener("volumechange", onVolumeChange), videoElement.removeEventListener("pause", onPause), videoElement.removeEventListener("playing", onPlaying), videoElement.removeEventListener("play", onPlay), videoElement.removeEventListener("click", onClick), videoElement.removeEventListener("dblclick", onDblClick), videoElement.parentNode.removeChild(videoElement));
var dlg = videoDialog;
dlg && (videoDialog = null, dlg.parentNode.removeChild(dlg))
}, self.destroyCustomTrack = destroyCustomTrack
}
function getDeviceProfileInternal(item, options) {
return appHost.getDeviceProfile ? appHost.getDeviceProfile(item, options) : getDefaultProfile()
}
function getSupportedFeatures() {
var list = [],
video = document.createElement("video");
return document.pictureInPictureEnabled ? list.push("PictureInPicture") : browser.ipad ? -1 === navigator.userAgent.toLowerCase().indexOf("os 9") && video.webkitSupportsPresentationMode && video.webkitSupportsPresentationMode && "function" == typeof video.webkitSetPresentationMode && list.push("PictureInPicture") : window.Windows && Windows.UI.ViewManagement.ApplicationView.getForCurrentView().isViewModeSupported(Windows.UI.ViewManagement.ApplicationViewMode.compactOverlay) && list.push("PictureInPicture"), list.push("SetBrightness"), list
}
function onPictureInPictureError(err) {
console.log("Picture in picture error: " + err.toString())
}
var mediaManager;
HtmlVideoPlayer.prototype.canPlayMediaType = function(mediaType) {
return "video" === (mediaType || "").toLowerCase()
}, HtmlVideoPlayer.prototype.supportsPlayMethod = function(playMethod, item) {
return !appHost.supportsPlayMethod || appHost.supportsPlayMethod(playMethod, item)
}, HtmlVideoPlayer.prototype.getDeviceProfile = function(item, options) {
var instance = this;
return getDeviceProfileInternal(item, options).then(function(profile) {
return instance._lastProfile = profile, profile
})
};
var supportedFeatures;
return HtmlVideoPlayer.prototype.supports = function(feature) {
return supportedFeatures || (supportedFeatures = getSupportedFeatures()), -1 !== supportedFeatures.indexOf(feature)
}, HtmlVideoPlayer.prototype.currentTime = function(val) {
var mediaElement = this._mediaElement;
if (mediaElement) {
if (null != val) return void(mediaElement.currentTime = val / 1e3);
var currentTime = this._currentTime;
return currentTime ? 1e3 * currentTime : 1e3 * (mediaElement.currentTime || 0)
}
}, HtmlVideoPlayer.prototype.duration = function(val) {
var mediaElement = this._mediaElement;
if (mediaElement) {
var duration = mediaElement.duration;
if (htmlMediaHelper.isValidDuration(duration)) return 1e3 * duration
}
return null
}, HtmlVideoPlayer.prototype.canSetAudioStreamIndex = function(index) {
if (browser.tizen || browser.orsay) return !0;
var video = this._mediaElement;
return !(!video || !video.audioTracks)
}, HtmlVideoPlayer.prototype.setPictureInPictureEnabled = function(isEnabled) {
var video = this._mediaElement;
document.pictureInPictureEnabled ? video && (isEnabled ? video.requestPictureInPicture().catch(onPictureInPictureError) : document.exitPictureInPicture().catch(onPictureInPictureError)) : window.Windows ? (this.isPip = isEnabled, isEnabled ? Windows.UI.ViewManagement.ApplicationView.getForCurrentView().tryEnterViewModeAsync(Windows.UI.ViewManagement.ApplicationViewMode.compactOverlay) : Windows.UI.ViewManagement.ApplicationView.getForCurrentView().tryEnterViewModeAsync(Windows.UI.ViewManagement.ApplicationViewMode.default)) : video && video.webkitSupportsPresentationMode && "function" == typeof video.webkitSetPresentationMode && video.webkitSetPresentationMode(isEnabled ? "picture-in-picture" : "inline")
}, HtmlVideoPlayer.prototype.isPictureInPictureEnabled = function() {
if (document.pictureInPictureEnabled) return !!document.pictureInPictureElement;
if (window.Windows) return this.isPip || !1;
var video = this._mediaElement;
return !!video && "picture-in-picture" === video.webkitPresentationMode
}, HtmlVideoPlayer.prototype.setBrightness = function(val) {
var elem = this._mediaElement;
if (elem) {
val = Math.max(0, val), val = Math.min(100, val);
var rawValue = val;
rawValue = Math.max(20, rawValue);
var cssValue = rawValue >= 100 ? "none" : rawValue / 100;
elem.style["-webkit-filter"] = "brightness(" + cssValue + ");", elem.style.filter = "brightness(" + cssValue + ")", elem.brightnessValue = val, events.trigger(this, "brightnesschange")
}
}, HtmlVideoPlayer.prototype.getBrightness = function() {
var elem = this._mediaElement;
if (elem) {
var val = elem.brightnessValue;
return null == val ? 100 : val
}
}, HtmlVideoPlayer.prototype.seekable = function() {
var mediaElement = this._mediaElement;
if (mediaElement) {
var seekable = mediaElement.seekable;
if (seekable && seekable.length) {
var start = seekable.start(0),
end = seekable.end(0);
return htmlMediaHelper.isValidDuration(start) || (start = 0), htmlMediaHelper.isValidDuration(end) || (end = 0), end - start > 0
}
return !1
}
}, HtmlVideoPlayer.prototype.pause = function() {
var mediaElement = this._mediaElement;
mediaElement && mediaElement.pause()
}, HtmlVideoPlayer.prototype.resume = function() {
var mediaElement = this._mediaElement;
mediaElement && mediaElement.play()
}, HtmlVideoPlayer.prototype.unpause = function() {
var mediaElement = this._mediaElement;
mediaElement && mediaElement.play()
}, HtmlVideoPlayer.prototype.paused = function() {
var mediaElement = this._mediaElement;
return !!mediaElement && mediaElement.paused
}, HtmlVideoPlayer.prototype.setVolume = function(val) {
var mediaElement = this._mediaElement;
mediaElement && (mediaElement.volume = val / 100)
}, HtmlVideoPlayer.prototype.getVolume = function() {
var mediaElement = this._mediaElement;
if (mediaElement) return Math.min(Math.round(100 * mediaElement.volume), 100)
}, HtmlVideoPlayer.prototype.volumeUp = function() {
this.setVolume(Math.min(this.getVolume() + 2, 100))
}, HtmlVideoPlayer.prototype.volumeDown = function() {
this.setVolume(Math.max(this.getVolume() - 2, 0))
}, HtmlVideoPlayer.prototype.setMute = function(mute) {
var mediaElement = this._mediaElement;
mediaElement && (mediaElement.muted = mute)
}, HtmlVideoPlayer.prototype.isMuted = function() {
var mediaElement = this._mediaElement;
return !!mediaElement && mediaElement.muted
}, HtmlVideoPlayer.prototype.setAspectRatio = function(val) {}, HtmlVideoPlayer.prototype.getAspectRatio = function() {
return this._currentAspectRatio
}, HtmlVideoPlayer.prototype.getSupportedAspectRatios = function() {
return []
}, HtmlVideoPlayer.prototype.togglePictureInPicture = function() {
return this.setPictureInPictureEnabled(!this.isPictureInPictureEnabled())
}, HtmlVideoPlayer.prototype.getBufferedRanges = function() {
var mediaElement = this._mediaElement;
return mediaElement ? htmlMediaHelper.getBufferedRanges(this, mediaElement) : []
}, HtmlVideoPlayer.prototype.getStats = function() {
var mediaElement = this._mediaElement,
playOptions = this._currentPlayOptions || [],
categories = [];
if (!mediaElement) return Promise.resolve({
categories: categories
});
var mediaCategory = {
stats: [],
type: "media"
};
if (categories.push(mediaCategory), playOptions.url) {
var link = document.createElement("a");
link.setAttribute("href", playOptions.url);
var protocol = (link.protocol || "").replace(":", "");
protocol && mediaCategory.stats.push({
label: "Protocol:",
value: protocol
}), link = null
}
this._hlsPlayer || this._shakaPlayer ? mediaCategory.stats.push({
label: "Stream type:",
value: "HLS"
}) : mediaCategory.stats.push({
label: "Stream type:",
value: "Video"
});
var videoCategory = {
stats: [],
type: "video"
};
categories.push(videoCategory);
var rect = mediaElement.getBoundingClientRect ? mediaElement.getBoundingClientRect() : {},
height = rect.height,
width = rect.width;
if (width && height && !browser.tv && videoCategory.stats.push({
label: "Player dimensions:",
value: width + "x" + height
}), height = mediaElement.videoHeight, width = mediaElement.videoWidth, width && height && videoCategory.stats.push({
label: "Video resolution:",
value: width + "x" + height
}), mediaElement.getVideoPlaybackQuality) {
var playbackQuality = mediaElement.getVideoPlaybackQuality(),
droppedVideoFrames = playbackQuality.droppedVideoFrames || 0;
videoCategory.stats.push({
label: "Dropped frames:",
value: droppedVideoFrames
});
var corruptedVideoFrames = playbackQuality.corruptedVideoFrames || 0;
videoCategory.stats.push({
label: "Corrupted frames:",
value: corruptedVideoFrames
})
}
var audioCategory = {
stats: [],
type: "audio"
};
categories.push(audioCategory);
var sinkId = mediaElement.sinkId;
return sinkId && audioCategory.stats.push({
label: "Sink Id:",
value: sinkId
}), Promise.resolve({
categories: categories
})
}, browser.chromecast && (mediaManager = new cast.receiver.MediaManager(document.createElement("video"))), HtmlVideoPlayer
});

View file

@ -0,0 +1,94 @@
.videoPlayerContainer {
position: fixed !important;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center
}
.videoPlayerContainer:not(.videoPlayerContainer-withBackdrop) {
background: #000 !important
}
.videoPlayerContainer-withBackdrop {
background-repeat: no-repeat;
background-position: center center;
-webkit-background-size: cover;
background-size: cover;
background-attachment: fixed;
background-color: #000
}
.videoPlayerContainer-onTop {
z-index: 1000
}
.htmlvideoplayer {
margin: 0 !important;
padding: 0 !important;
width: 100%;
height: 100%
}
.htmlvideoplayer::cue {
background-color: transparent;
text-shadow: .14em .14em .14em rgba(0, 0, 0, 1);
-webkit-font-smoothing: antialiased;
font-family: inherit
}
.htmlvideoplayer-moveupsubtitles::-webkit-media-text-track-display {
margin-top: -2em
}
.videoSubtitles {
position: fixed;
bottom: 10%;
text-align: center;
left: 0;
right: 0;
color: #fff;
font-size: 170%
}
.videoSubtitlesInner {
max-width: 70%;
background-color: rgba(0, 0, 0, .8);
padding: .25em;
margin: auto;
display: inline-block
}
@-webkit-keyframes htmlvideoplayer-zoomin {
from {
-webkit-transform: scale3d(.2, .2, .2);
transform: scale3d(.2, .2, .2);
opacity: .6
}
to {
-webkit-transform: none;
transform: none;
opacity: initial
}
}
@keyframes htmlvideoplayer-zoomin {
from {
-webkit-transform: scale3d(.2, .2, .2);
transform: scale3d(.2, .2, .2);
opacity: .6
}
to {
-webkit-transform: none;
transform: none;
opacity: initial
}
}

View file

@ -0,0 +1,126 @@
define(["loading", "apphost", "dialogHelper", "connectionManager", "imageLoader", "browser", "layoutManager", "scrollHelper", "globalize", "require", "emby-checkbox", "emby-button", "paper-icon-button-light", "emby-linkbutton", "formDialogStyle", "cardStyle"], function(loading, appHost, dialogHelper, connectionManager, imageLoader, browser, layoutManager, scrollHelper, globalize, require) {
"use strict";
function getBaseRemoteOptions() {
var options = {};
return options.itemId = currentItemId, options
}
function reloadBrowsableImages(page, apiClient) {
loading.show();
var options = getBaseRemoteOptions();
options.type = browsableImageType, options.startIndex = browsableImageStartIndex, options.limit = browsableImagePageSize, options.IncludeAllLanguages = page.querySelector("#chkAllLanguages").checked;
var provider = selectedProvider || "";
provider && (options.ProviderName = provider), apiClient.getAvailableRemoteImages(options).then(function(result) {
renderRemoteImages(page, apiClient, result, browsableImageType, options.startIndex, options.limit), page.querySelector("#selectBrowsableImageType").value = browsableImageType;
var providersHtml = result.Providers.map(function(p) {
return '<option value="' + p + '">' + p + "</option>"
}),
selectImageProvider = page.querySelector("#selectImageProvider");
selectImageProvider.innerHTML = '<option value="">' + globalize.translate("sharedcomponents#All") + "</option>" + providersHtml, selectImageProvider.value = provider, loading.hide()
})
}
function renderRemoteImages(page, apiClient, imagesResult, imageType, startIndex, limit) {
page.querySelector(".availableImagesPaging").innerHTML = getPagingHtml(startIndex, limit, imagesResult.TotalRecordCount);
for (var html = "", i = 0, length = imagesResult.Images.length; i < length; i++) html += getRemoteImageHtml(imagesResult.Images[i], imageType, apiClient);
var availableImagesList = page.querySelector(".availableImagesList");
availableImagesList.innerHTML = html, imageLoader.lazyChildren(availableImagesList);
var btnNextPage = page.querySelector(".btnNextPage"),
btnPreviousPage = page.querySelector(".btnPreviousPage");
btnNextPage && btnNextPage.addEventListener("click", function() {
browsableImageStartIndex += browsableImagePageSize, reloadBrowsableImages(page, apiClient)
}), btnPreviousPage && btnPreviousPage.addEventListener("click", function() {
browsableImageStartIndex -= browsableImagePageSize, reloadBrowsableImages(page, apiClient)
})
}
function getPagingHtml(startIndex, limit, totalRecordCount) {
var html = "",
recordsEnd = Math.min(startIndex + limit, totalRecordCount),
showControls = totalRecordCount > limit;
return html += '<div class="listPaging">', html += '<span style="margin-right: 10px;">', html += (totalRecordCount ? startIndex + 1 : 0) + "-" + recordsEnd + " of " + totalRecordCount, html += "</span>", showControls && (html += '<div data-role="controlgroup" data-type="horizontal" style="display:inline-block;">', html += '<button is="paper-icon-button-light" title="' + globalize.translate("sharedcomponents#Previous") + '" class="btnPreviousPage autoSize" ' + (startIndex ? "" : "disabled") + '><i class="md-icon">&#xE5C4;</i></button>', html += '<button is="paper-icon-button-light" title="' + globalize.translate("sharedcomponents#Next") + '" class="btnNextPage autoSize" ' + (startIndex + limit >= totalRecordCount ? "disabled" : "") + '><i class="md-icon">arrow_forward</i></button>', html += "</div>"), html += "</div>"
}
function parentWithClass(elem, className) {
for (; !elem.classList || !elem.classList.contains(className);)
if (!(elem = elem.parentNode)) return null;
return elem
}
function downloadRemoteImage(page, apiClient, url, type, provider) {
var options = getBaseRemoteOptions();
options.Type = type, options.ImageUrl = url, options.ProviderName = provider, loading.show(), apiClient.downloadRemoteImage(options).then(function() {
hasChanges = !0;
var dlg = parentWithClass(page, "dialog");
dialogHelper.close(dlg)
})
}
function getDisplayUrl(url, apiClient) {
return apiClient.getUrl("Images/Remote", {
imageUrl: url
})
}
function getRemoteImageHtml(image, imageType, apiClient) {
var tagName = layoutManager.tv ? "button" : "div",
enableFooterButtons = !layoutManager.tv,
html = "",
cssClass = "card scalableCard imageEditorCard",
cardBoxCssClass = "cardBox visualCardBox",
shape = "backdrop";
return shape = "Backdrop" === imageType || "Art" === imageType || "Thumb" === imageType || "Logo" === imageType ? "backdrop" : "Banner" === imageType ? "banner" : "Disc" === imageType ? "square" : "Episode" === currentItemType ? "backdrop" : "MusicAlbum" === currentItemType || "MusicArtist" === currentItemType ? "square" : "portrait", cssClass += " " + shape + "Card " + shape + "Card-scalable", "button" === tagName ? (cssClass += " btnImageCard", layoutManager.tv && !browser.slow && (cardBoxCssClass += " cardBox-focustransform"), layoutManager.tv && (cardBoxCssClass += " card-focuscontent cardBox-withfocuscontent"), html += '<button type="button" class="' + cssClass + '"') : html += '<div class="' + cssClass + '"', html += ' data-imageprovider="' + image.ProviderName + '" data-imageurl="' + image.Url + '" data-imagetype="' + image.Type + '"', html += ">", html += '<div class="' + cardBoxCssClass + '">', html += '<div class="cardScalable visualCardBox-cardScalable" style="background-color:transparent;">', html += '<div class="cardPadder-' + shape + '"></div>', html += '<div class="cardContent">', layoutManager.tv || !appHost.supports("externallinks") ? html += '<div class="cardImageContainer lazy" data-src="' + getDisplayUrl(image.Url, apiClient) + '" style="background-position:center bottom;"></div>' : html += '<a is="emby-linkbutton" target="_blank" href="' + getDisplayUrl(image.Url, apiClient) + '" class="button-link cardImageContainer lazy" data-src="' + getDisplayUrl(image.Url, apiClient) + '" style="background-position:center bottom;"></a>', html += "</div>", html += "</div>", html += '<div class="cardFooter visualCardBox-cardFooter">', html += '<div class="cardText cardTextCentered">' + image.ProviderName + "</div>", (image.Width || image.Height || image.Language) && (html += '<div class="cardText cardText-secondary cardTextCentered">', image.Width && image.Height ? (html += image.Width + " x " + image.Height, image.Language && (html += " • " + image.Language)) : image.Language && (html += image.Language), html += "</div>"), null != image.CommunityRating && (html += '<div class="cardText cardText-secondary cardTextCentered">', "Likes" === image.RatingType ? html += image.CommunityRating + (1 === image.CommunityRating ? " like" : " likes") : image.CommunityRating ? (html += image.CommunityRating.toFixed(1), image.VoteCount && (html += " • " + image.VoteCount + (1 === image.VoteCount ? " vote" : " votes"))) : html += "Unrated", html += "</div>"), enableFooterButtons && (html += '<div class="cardText cardTextCentered">', html += '<button is="paper-icon-button-light" class="btnDownloadRemoteImage autoSize" raised" title="' + globalize.translate("sharedcomponents#Download") + '"><i class="md-icon">&#xE2C0;</i></button>', html += "</div>"), html += "</div>", html += "</div>", html += "</" + tagName + ">"
}
function initEditor(page, apiClient) {
page.querySelector("#selectBrowsableImageType").addEventListener("change", function() {
browsableImageType = this.value, browsableImageStartIndex = 0, selectedProvider = null, reloadBrowsableImages(page, apiClient)
}), page.querySelector("#selectImageProvider").addEventListener("change", function() {
browsableImageStartIndex = 0, selectedProvider = this.value, reloadBrowsableImages(page, apiClient)
}), page.querySelector("#chkAllLanguages").addEventListener("change", function() {
browsableImageStartIndex = 0, reloadBrowsableImages(page, apiClient)
}), page.addEventListener("click", function(e) {
var btnDownloadRemoteImage = parentWithClass(e.target, "btnDownloadRemoteImage");
if (btnDownloadRemoteImage) {
var card = parentWithClass(btnDownloadRemoteImage, "card");
return void downloadRemoteImage(page, apiClient, card.getAttribute("data-imageurl"), card.getAttribute("data-imagetype"), card.getAttribute("data-imageprovider"))
}
var btnImageCard = parentWithClass(e.target, "btnImageCard");
btnImageCard && downloadRemoteImage(page, apiClient, btnImageCard.getAttribute("data-imageurl"), btnImageCard.getAttribute("data-imagetype"), btnImageCard.getAttribute("data-imageprovider"))
})
}
function showEditor(itemId, serverId, itemType) {
loading.show(), require(["text!./imagedownloader.template.html"], function(template) {
var apiClient = connectionManager.getApiClient(serverId);
currentItemId = itemId, currentItemType = itemType;
var dialogOptions = {
removeOnClose: !0
};
layoutManager.tv ? dialogOptions.size = "fullscreen" : dialogOptions.size = "fullscreen-border";
var dlg = dialogHelper.createDialog(dialogOptions);
dlg.innerHTML = globalize.translateDocument(template, "sharedcomponents"), layoutManager.tv && scrollHelper.centerFocus.on(dlg, !1), dlg.addEventListener("close", onDialogClosed), dialogHelper.open(dlg);
var editorContent = dlg.querySelector(".formDialogContent");
initEditor(editorContent, apiClient), dlg.querySelector(".btnCancel").addEventListener("click", function() {
dialogHelper.close(dlg)
}), reloadBrowsableImages(editorContent, apiClient)
})
}
function onDialogClosed() {
var dlg = this;
layoutManager.tv && scrollHelper.centerFocus.off(dlg, !1), loading.hide(), hasChanges ? currentResolve() : currentReject()
}
var currentItemId, currentItemType, currentResolve, currentReject, selectedProvider, hasChanges = !1,
browsableImagePageSize = browser.slow ? 6 : 30,
browsableImageStartIndex = 0,
browsableImageType = "Primary";
return {
show: function(itemId, serverId, itemType, imageType) {
return new Promise(function(resolve, reject) {
currentResolve = resolve, currentReject = reject, hasChanges = !1, browsableImageStartIndex = 0, browsableImageType = imageType || "Primary", selectedProvider = null, showEditor(itemId, serverId, itemType)
})
}
}
});

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