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

move emby-webcomponents to components and reflect paths

This commit is contained in:
vitorsemeano 2019-02-23 18:05:42 +00:00
parent e91cbf8438
commit 6ddc62857d
275 changed files with 20 additions and 20 deletions

View file

@ -0,0 +1,113 @@
.actionSheet {
display: flex;
justify-content: center;
padding: 0;
border: none;
max-height: 84%;
border-radius: .1em !important;
}
.actionsheet-not-fullscreen {
max-width: 90%;
max-height: 90%;
}
.actionsheet-fullscreen {
max-height: none;
border-radius: 0 !important;
}
.actionSheetContent-centered {
text-align: center;
align-items: center;
}
.actionSheetContent {
margin: 0 !important;
padding: .4em 0 !important;
flex-direction: column;
display: flex;
justify-content: center;
flex-grow: 1;
overflow: hidden;
}
.actionSheetMenuItem {
font-weight: inherit;
box-shadow: none;
flex-shrink: 0;
}
.actionSheetMenuItem:focus {
transform: none !important;
}
.actionsheetListItemBody {
padding: .4em 1em .4em .6em !important;
}
.actionSheetItemText {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: middle;
flex-grow: 1;
display: flex;
justify-content: flex-start;
}
.actionSheetItemAsideText {
opacity: .7;
font-size: 90%;
display: flex;
justify-content: flex-end;
flex-shrink: 0;
margin-left: 5em;
margin-right: .5em;
}
.actionSheetScroller {
/* Override default style being applied by polymer */
margin-bottom: 0 !important;
display: flex;
flex-direction: column;
width: 100%;
}
.actionSheetScroller-tv {
max-height: 64%;
max-width: 60%;
width: auto;
}
.actionsheetDivider {
height: .07em;
margin: .25em 0;
flex-shrink: 0;
}
.actionSheetTitle {
margin: .6em 0 .7em !important;
padding: 0 .9em;
flex-grow: 0;
}
.actionSheetText {
padding: 0 1em;
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,363 @@
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;
var results = [];
if (!doc) {
return results;
}
var box;
var elem;
for (var i = 0, length = elems.length; i < length; i++) {
elem = elems[i];
// Support: BlackBerry 5, iOS 3 (original iPhone)
// If we don't have gBCR, just use 0,0 rather than error
if (elem.getBoundingClientRect) {
box = elem.getBoundingClientRect();
} else {
box = { 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();
var windowHeight = windowSize.innerHeight;
var windowWidth = windowSize.innerWidth;
if (windowWidth < 600 || windowHeight < 600) {
return null;
}
var pos = getOffsets([options.positionTo])[0];
if (options.positionY !== 'top') {
pos.top += (pos.height || 0) / 2;
}
pos.left += (pos.width || 0) / 2;
var height = dlg.offsetHeight || 300;
var width = dlg.offsetWidth || 160;
// Account for popup size
pos.top -= height / 2;
pos.left -= width / 2;
// Avoid showing too close to the bottom
var overflowX = pos.left + width - windowWidth;
var overflowY = pos.top + height - windowHeight;
if (overflowX > 0) {
pos.left -= (overflowX + 20);
}
if (overflowY > 0) {
pos.top -= (overflowY + 20);
}
pos.top += (options.offsetTop || 0);
pos.left += (options.offsetLeft || 0);
// Do some boundary checking
pos.top = Math.max(pos.top, 10);
pos.left = Math.max(pos.left, 10);
return pos;
}
function centerFocus(elem, horiz, on) {
require(['scrollHelper'], function (scrollHelper) {
var fn = on ? 'on' : 'off';
scrollHelper.centerFocus[fn](elem, horiz);
});
}
function show(options) {
// items
// positionTo
// showCancel
// title
var dialogOptions = {
removeOnClose: true,
enableHistory: options.enableHistory,
scrollY: false
};
var backButton = false;
var isFullscreen;
if (layoutManager.tv) {
dialogOptions.size = 'fullscreen';
isFullscreen = true;
backButton = true;
dialogOptions.autoFocus = true;
} else {
dialogOptions.modal = false;
dialogOptions.entryAnimation = options.entryAnimation;
dialogOptions.exitAnimation = options.exitAnimation;
dialogOptions.entryAnimationDuration = options.entryAnimationDuration || 140;
dialogOptions.exitAnimationDuration = options.exitAnimationDuration || 100;
dialogOptions.autoFocus = false;
}
var dlg = dialogHelper.createDialog(dialogOptions);
if (isFullscreen) {
dlg.classList.add('actionsheet-fullscreen');
} else {
dlg.classList.add('actionsheet-not-fullscreen');
}
dlg.classList.add('actionSheet');
if (options.dialogClass) {
dlg.classList.add(options.dialogClass);
}
var html = '';
var scrollClassName = layoutManager.tv ? 'scrollY smoothScrollY hiddenScrollY' : 'scrollY';
var style = '';
// Admittedly a hack but right now the scrollbar is being factored into the width which is causing truncation
if (options.items.length > 20) {
var minWidth = dom.getWindowSize().innerWidth >= 300 ? 240 : 200;
style += "min-width:" + minWidth + "px;";
}
var i, length, option;
var renderIcon = false;
var icons = [];
var itemIcon;
for (i = 0, length = options.items.length; i < length; i++) {
option = options.items[i];
itemIcon = option.icon || (option.selected ? 'check' : null);
if (itemIcon) {
renderIcon = true;
}
icons.push(itemIcon || '');
}
if (layoutManager.tv) {
html += '<button is="paper-icon-button-light" class="btnCloseActionSheet hide-mouse-idle-tv" tabindex="-1"><i class="md-icon">&#xE5C4;</i></button>';
}
// If any items have an icon, give them all an icon just to make sure they're all lined up evenly
var center = options.title && (!renderIcon /*|| itemsWithIcons.length != options.items.length*/);
if (center || layoutManager.tv) {
html += '<div class="actionSheetContent actionSheetContent-centered">';
} else {
html += '<div class="actionSheetContent">';
}
if (options.title) {
html += '<h1 class="actionSheetTitle">';
html += options.title;
html += '</h1>';
}
if (options.text) {
html += '<p class="actionSheetText">';
html += options.text;
html += '</p>';
}
var scrollerClassName = 'actionSheetScroller';
if (layoutManager.tv) {
scrollerClassName += ' actionSheetScroller-tv focuscontainer-x focuscontainer-y';
}
html += '<div class="' + scrollerClassName + ' ' + scrollClassName + '" style="' + style + '">';
var menuItemClass = 'listItem listItem-button actionSheetMenuItem';
if (options.border || options.shaded) {
menuItemClass += ' listItem-border';
}
if (options.menuItemClass) {
menuItemClass += ' ' + options.menuItemClass;
}
if (layoutManager.tv) {
menuItemClass += ' listItem-focusscale';
}
if (layoutManager.mobile) {
menuItemClass += ' actionsheet-xlargeFont';
}
for (i = 0, length = options.items.length; i < length; i++) {
option = options.items[i];
if (option.divider) {
html += '<div class="actionsheetDivider"></div>';
continue;
}
var autoFocus = option.selected && layoutManager.tv ? ' autoFocus' : '';
// Check for null in case int 0 was passed in
var optionId = option.id == null || option.id === '' ? option.value : option.id;
html += '<button' + autoFocus + ' is="emby-button" type="button" class="' + menuItemClass + '" data-id="' + optionId + '">';
itemIcon = icons[i];
if (itemIcon) {
html += '<i class="actionsheetMenuItemIcon listItemIcon listItemIcon-transparent md-icon">' + itemIcon + '</i>';
}
else if (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>';
if (option.secondaryText) {
html += '<div class="listItemBodyText secondary">';
html += option.secondaryText;
html += '</div>';
}
html += '</div>';
if (option.asideText) {
html += '<div class="listItemAside actionSheetItemAsideText">';
html += option.asideText;
html += '</div>';
}
html += '</button>';
}
if (options.showCancel) {
html += '<div class="buttons">';
html += '<button is="emby-button" type="button" class="btnCloseActionSheet">' + globalize.translate('ButtonCancel') + '</button>';
html += '</div>';
}
html += '</div>';
dlg.innerHTML = html;
if (layoutManager.tv) {
centerFocus(dlg.querySelector('.actionSheetScroller'), false, true);
}
var btnCloseActionSheet = dlg.querySelector('.btnCloseActionSheet');
if (btnCloseActionSheet) {
dlg.querySelector('.btnCloseActionSheet').addEventListener('click', function () {
dialogHelper.close(dlg);
});
}
// Seeing an issue in some non-chrome browsers where this is requiring a double click
//var eventName = browser.firefox ? 'mousedown' : 'click';
var selectedId;
var timeout;
if (options.timeout) {
timeout = setTimeout(function () {
dialogHelper.close(dlg);
}, options.timeout);
}
return new Promise(function (resolve, reject) {
var isResolved;
dlg.addEventListener('click', function (e) {
var actionSheetMenuItem = dom.parentWithClass(e.target, 'actionSheetMenuItem');
if (actionSheetMenuItem) {
selectedId = actionSheetMenuItem.getAttribute('data-id');
if (options.resolveOnClick) {
if (options.resolveOnClick.indexOf) {
if (options.resolveOnClick.indexOf(selectedId) !== -1) {
resolve(selectedId);
isResolved = true;
}
} else {
resolve(selectedId);
isResolved = true;
}
}
dialogHelper.close(dlg);
}
});
dlg.addEventListener('close', function () {
if (layoutManager.tv) {
centerFocus(dlg.querySelector('.actionSheetScroller'), false, false);
}
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
if (!isResolved) {
if (selectedId != null) {
if (options.callback) {
options.callback(selectedId);
}
resolve(selectedId);
} else {
reject();
}
}
});
dialogHelper.open(dlg);
var pos = options.positionTo && dialogOptions.size !== 'fullscreen' ? getPosition(options, dlg) : null;
if (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,34 @@
define(['dialog', 'globalize'], function (dialog, globalize) {
'use strict';
return function (text, title) {
var options;
if (typeof text === 'string') {
options = {
title: title,
text: text
};
} else {
options = text;
}
var items = [];
items.push({
name: globalize.translate('ButtonGotIt'),
id: 'ok',
type: 'submit'
});
options.buttons = items;
return dialog(options).then(function (result) {
if (result === 'ok') {
return Promise.resolve();
}
return Promise.reject();
});
};
});

View file

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

View file

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

View file

@ -0,0 +1,324 @@
define(['focusManager', 'layoutManager', 'dom', 'css!./style.css', 'paper-icon-button-light', 'material-icons'], function (focusManager, layoutManager, dom) {
'use strict';
var selectedButtonClass = 'alphaPickerButton-selected';
function focus() {
var scope = this;
var selected = scope.querySelector('.' + selectedButtonClass);
if (selected) {
focusManager.focus(selected);
} else {
focusManager.autoFocus(scope, true);
}
}
function getAlphaPickerButtonClassName(vertical) {
var alphaPickerButtonClassName = 'alphaPickerButton';
if (layoutManager.tv) {
alphaPickerButtonClassName += ' alphaPickerButton-tv';
}
if (vertical) {
alphaPickerButtonClassName += ' alphaPickerButton-vertical';
}
return 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');
if (layoutManager.tv) {
element.classList.add('alphaPicker-tv');
}
var vertical = element.classList.contains('alphaPicker-vertical');
if (vertical) {
} else {
element.classList.add('focuscontainer-x');
}
var html = '';
var letters;
var alphaPickerButtonClassName = getAlphaPickerButtonClassName(vertical);
var rowClassName = 'alphaPickerRow';
if (vertical) {
rowClassName += ' alphaPickerRow-vertical';
}
html += '<div class="' + rowClassName + '">';
if (options.mode === 'keyboard') {
// space_bar icon
html += '<button data-value=" " is="paper-icon-button-light" class="' + alphaPickerButtonClassName + '"><i class="md-icon alphaPickerButtonIcon">&#xE256;</i></button>';
} else {
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('');
if (options.mode === 'keyboard') {
// backspace icon
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>';
} else {
html += '</div>';
}
element.innerHTML = html;
element.classList.add('focusable');
element.focus = focus;
}
function AlphaPicker(options) {
var self = this;
this.options = options;
var element = options.element;
var itemsContainer = options.itemsContainer;
var itemClass = options.itemClass;
var itemFocusValue;
var itemFocusTimeout;
function onItemFocusTimeout() {
itemFocusTimeout = null;
self.value(itemFocusValue);
}
var alphaFocusedElement;
var alphaFocusTimeout;
function onAlphaFocusTimeout() {
alphaFocusTimeout = null;
if (document.activeElement === alphaFocusedElement) {
var value = alphaFocusedElement.getAttribute('data-value');
self.value(value, true);
}
}
function onAlphaPickerInKeyboardModeClick(e) {
var alphaPickerButton = dom.parentWithClass(e.target, 'alphaPickerButton');
if (alphaPickerButton) {
var value = alphaPickerButton.getAttribute('data-value');
element.dispatchEvent(new CustomEvent("alphavalueclicked", {
cancelable: false,
detail: {
value: value
}
}));
}
}
function onAlphaPickerClick(e) {
var alphaPickerButton = dom.parentWithClass(e.target, 'alphaPickerButton');
if (alphaPickerButton) {
var value = alphaPickerButton.getAttribute('data-value');
if ((this._currentValue || '').toUpperCase() === value.toUpperCase()) {
self.value(null, true);
} else {
self.value(value, true);
}
}
}
function onAlphaPickerFocusIn(e) {
if (alphaFocusTimeout) {
clearTimeout(alphaFocusTimeout);
alphaFocusTimeout = null;
}
var alphaPickerButton = dom.parentWithClass(e.target, 'alphaPickerButton');
if (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');
if (prefix && prefix.length) {
itemFocusValue = prefix[0];
if (itemFocusTimeout) {
clearTimeout(itemFocusTimeout);
}
itemFocusTimeout = setTimeout(onItemFocusTimeout, 100);
}
}
}
self.enabled = function (enabled) {
if (enabled) {
if (itemsContainer) {
itemsContainer.addEventListener('focus', onItemsFocusIn, true);
}
if (options.mode === 'keyboard') {
element.addEventListener('click', onAlphaPickerInKeyboardModeClick);
}
if (options.valueChangeEvent !== 'click') {
element.addEventListener('focus', onAlphaPickerFocusIn, true);
} else {
element.addEventListener('click', onAlphaPickerClick.bind(this));
}
} else {
if (itemsContainer) {
itemsContainer.removeEventListener('focus', onItemsFocusIn, true);
}
element.removeEventListener('click', onAlphaPickerInKeyboardModeClick);
element.removeEventListener('focus', onAlphaPickerFocusIn, true);
element.removeEventListener('click', onAlphaPickerClick.bind(this));
}
};
render(element, options);
this.enabled(true);
this.visible(true);
}
AlphaPicker.prototype.value = function (value, applyValue) {
var element = this.options.element;
var btn, selected;
if (value !== undefined) {
if (value != null) {
value = value.toUpperCase();
this._currentValue = value;
if (this.options.mode !== 'keyboard') {
selected = element.querySelector('.' + selectedButtonClass);
try {
btn = element.querySelector('.alphaPickerButton[data-value=\'' + value + '\']');
} catch (err) {
console.log('Error in querySelector: ' + err);
}
if (btn && btn !== selected) {
btn.classList.add(selectedButtonClass);
}
if (selected && selected !== btn) {
selected.classList.remove(selectedButtonClass);
}
}
} else {
this._currentValue = value;
selected = element.querySelector('.' + selectedButtonClass);
if (selected) {
selected.classList.remove(selectedButtonClass);
}
}
}
if (applyValue) {
element.dispatchEvent(new CustomEvent("alphavaluechanged", {
cancelable: false,
detail: {
value: value
}
}));
}
return this._currentValue;
};
AlphaPicker.prototype.on = function (name, fn) {
var element = this.options.element;
element.addEventListener(name, fn);
};
AlphaPicker.prototype.off = function (name, fn) {
var element = this.options.element;
element.removeEventListener(name, fn);
};
AlphaPicker.prototype.visible = function (visible) {
var element = this.options.element;
element.style.visibility = visible ? 'visible' : 'hidden';
};
AlphaPicker.prototype.values = function () {
var element = this.options.element;
var elems = element.querySelectorAll('.alphaPickerButton');
var values = [];
for (var 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, true);
};
AlphaPicker.prototype.destroy = function () {
var element = this.options.element;
this.enabled(false);
element.classList.remove('focuscontainer-x');
this.options = null;
};
return AlphaPicker;
});

View file

@ -0,0 +1,139 @@
.alphaPicker {
text-align: center;
display: flex;
flex-direction: column;
align-self: center;
}
.alphaPicker-vertical {
line-height: 1;
}
.alphaPicker-fixed {
position: fixed;
bottom: 5.5em;
z-index: 999999;
}
.alphaPickerRow {
display: flex;
align-items: center;
justify-content: center;
flex-direction: row;
}
.alphaPickerRow-vertical {
flex-direction: column;
}
.alphaPickerButton {
border: 0 !important;
cursor: pointer;
outline: none !important;
vertical-align: middle;
font-family: inherit;
font-size: inherit;
min-width: initial;
margin: 0;
padding: .1em .4em;
width: auto;
border-radius: .1em;
font-weight: normal;
flex-shrink: 0;
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 {
/* Assign a fixed width to ensure they have the same dimensions and avoid throwing off directional navigation */
width: 1.5em;
display: flex;
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,13 @@
.appfooter {
position: fixed;
left: 0;
right: 0;
z-index: 1;
bottom: 0;
transition: transform 180ms linear;
contain: layout style;
}
.appfooter.headroom--unpinned {
transform: translateY(100%)!important;
}

View file

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

View file

@ -0,0 +1,156 @@
define(['appStorage', 'events'], function (appStorage, events) {
'use strict';
function getKey(name, userId) {
if (userId) {
name = userId + '-' + name;
}
return name;
}
function AppSettings() {
}
AppSettings.prototype.enableAutoLogin = function (val) {
if (val != null) {
this.set('enableAutoLogin', val.toString());
}
return this.get('enableAutoLogin') !== 'false';
};
AppSettings.prototype.enableAutomaticBitrateDetection = function (isInNetwork, mediaType, val) {
var key = 'enableautobitratebitrate-' + mediaType + '-' + isInNetwork;
if (val != null) {
if (isInNetwork && mediaType === 'Audio') {
val = true;
}
this.set(key, val.toString());
}
if (isInNetwork && mediaType === 'Audio') {
return true;
} else {
return this.get(key) !== 'false';
}
};
AppSettings.prototype.maxStreamingBitrate = function (isInNetwork, mediaType, val) {
var key = 'maxbitrate-' + mediaType + '-' + isInNetwork;
if (val != null) {
if (isInNetwork && mediaType === 'Audio') {
// nothing to do, this is always a max value
} else {
this.set(key, val);
}
}
if (isInNetwork && mediaType === 'Audio') {
// return a huge number so that it always direct plays
return 150000000;
} else {
return parseInt(this.get(key) || '0') || 1500000;
}
};
AppSettings.prototype.maxStaticMusicBitrate = function (val) {
if (val !== undefined) {
this.set('maxStaticMusicBitrate', val);
}
var defaultValue = 320000;
return parseInt(this.get('maxStaticMusicBitrate') || defaultValue.toString()) || defaultValue;
};
AppSettings.prototype.maxChromecastBitrate = function (val) {
if (val != null) {
this.set('chromecastBitrate1', val);
}
val = this.get('chromecastBitrate1');
return val ? parseInt(val) : null;
};
AppSettings.prototype.syncOnlyOnWifi = function (val) {
if (val != null) {
this.set('syncOnlyOnWifi', val.toString());
}
return this.get('syncOnlyOnWifi') !== 'false';
};
AppSettings.prototype.syncPath = function (val) {
if (val != null) {
this.set('syncPath', val);
}
return this.get('syncPath');
};
AppSettings.prototype.cameraUploadServers = function (val) {
if (val != null) {
this.set('cameraUploadServers', val.join(','));
}
val = this.get('cameraUploadServers');
if (val) {
return val.split(',');
}
return [];
};
AppSettings.prototype.runAtStartup = function (val) {
if (val != null) {
this.set('runatstartup', val.toString());
}
return this.get('runatstartup') === 'true';
};
AppSettings.prototype.set = function (name, value, userId) {
var currentValue = this.get(name, userId);
appStorage.setItem(getKey(name, userId), value);
if (currentValue !== value) {
events.trigger(this, 'change', [name]);
}
};
AppSettings.prototype.get = function (name, userId) {
return appStorage.getItem(getKey(name, userId));
};
AppSettings.prototype.enableSystemExternalPlayers = function (val) {
if (val != null) {
this.set('enableSystemExternalPlayers', val.toString());
}
return this.get('enableSystemExternalPlayers') === 'true';
};
return new AppSettings();
});

View file

@ -0,0 +1,358 @@
define(['browser', 'connectionManager', 'playbackManager', 'dom', 'css!./style'], function (browser, connectionManager, playbackManager, dom) {
'use strict';
function enableAnimation(elem) {
if (browser.slow) {
return false;
}
return true;
}
function enableRotation() {
if (browser.tv) {
return false;
}
// Causes high cpu usage
if (browser.firefox) {
return false;
}
return true;
}
function Backdrop() {
}
Backdrop.prototype.load = function (url, parent, existingBackdropImage) {
var img = new Image();
var self = this;
img.onload = function () {
if (self.isDestroyed) {
return;
}
var backdropImage = document.createElement('div');
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);
if (!enableAnimation(backdropImage)) {
if (existingBackdropImage && existingBackdropImage.parentNode) {
existingBackdropImage.parentNode.removeChild(existingBackdropImage);
}
internalBackdrop(true);
return;
}
var onAnimationComplete = function () {
dom.removeEventListener(backdropImage, dom.whichAnimationEvent(), onAnimationComplete, {
once: true
});
if (backdropImage === self.currentAnimatingElement) {
self.currentAnimatingElement = null;
}
if (existingBackdropImage && existingBackdropImage.parentNode) {
existingBackdropImage.parentNode.removeChild(existingBackdropImage);
}
};
dom.addEventListener(backdropImage, dom.whichAnimationEvent(), onAnimationComplete, {
once: true
});
internalBackdrop(true);
};
img.src = url;
};
Backdrop.prototype.cancelAnimation = function () {
var elem = this.currentAnimatingElement;
if (elem) {
elem.classList.remove('backdropImageFadeIn');
this.currentAnimatingElement = null;
}
};
Backdrop.prototype.destroy = function () {
this.isDestroyed = true;
this.cancelAnimation();
};
var backdropContainer;
function getBackdropContainer() {
if (!backdropContainer) {
backdropContainer = document.querySelector('.backdropContainer');
}
if (!backdropContainer) {
backdropContainer = document.createElement('div');
backdropContainer.classList.add('backdropContainer');
document.body.insertBefore(backdropContainer, document.body.firstChild);
}
return backdropContainer;
}
function clearBackdrop(clearAll) {
clearRotation();
if (currentLoadingBackdrop) {
currentLoadingBackdrop.destroy();
currentLoadingBackdrop = null;
}
var elem = getBackdropContainer();
elem.innerHTML = '';
if (clearAll) {
hasExternalBackdrop = false;
}
internalBackdrop(false);
}
var backgroundContainer;
function getBackgroundContainer() {
if (!backgroundContainer) {
backgroundContainer = document.querySelector('.backgroundContainer');
}
return backgroundContainer;
}
function setBackgroundContainerBackgroundEnabled() {
if (hasInternalBackdrop || hasExternalBackdrop) {
getBackgroundContainer().classList.add('withBackdrop');
} else {
getBackgroundContainer().classList.remove('withBackdrop');
}
}
var hasInternalBackdrop;
function internalBackdrop(enabled) {
hasInternalBackdrop = enabled;
setBackgroundContainerBackgroundEnabled();
}
var hasExternalBackdrop;
function externalBackdrop(enabled) {
hasExternalBackdrop = enabled;
setBackgroundContainerBackgroundEnabled();
}
function getRandom(min, max) {
return Math.floor(Math.random() * (max - min) + min);
}
var currentLoadingBackdrop;
function setBackdropImage(url) {
if (currentLoadingBackdrop) {
currentLoadingBackdrop.destroy();
currentLoadingBackdrop = null;
}
var elem = getBackdropContainer();
var 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;
}
var standardWidths = [480, 720, 1280, 1440, 1920];
function getBackdropMaxWidth() {
var width = dom.getWindowSize().innerWidth;
if (standardWidths.indexOf(width) !== -1) {
return width;
}
var roundScreenTo = 100;
width = Math.floor(width / roundScreenTo) * roundScreenTo;
return Math.min(width, 1920);
}
function getItemImageUrls(item, imageOptions) {
imageOptions = imageOptions || {};
var apiClient = connectionManager.getApiClient(item.ServerId);
if (item.BackdropImageTags && item.BackdropImageTags.length > 0) {
return item.BackdropImageTags.map(function (imgTag, index) {
return apiClient.getScaledImageUrl(item.BackdropItemId || item.Id, Object.assign(imageOptions, {
type: "Backdrop",
tag: imgTag,
maxWidth: getBackdropMaxWidth(),
index: index
}));
});
}
if (item.ParentBackdropItemId && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length) {
return item.ParentBackdropImageTags.map(function (imgTag, index) {
return apiClient.getScaledImageUrl(item.ParentBackdropItemId, Object.assign(imageOptions, {
type: "Backdrop",
tag: imgTag,
maxWidth: getBackdropMaxWidth(),
index: index
}));
});
}
return [];
}
function getImageUrls(items, imageOptions) {
var list = [];
var onImg = function (img) {
list.push(img);
};
for (var i = 0, length = items.length; i < length; i++) {
var itemImages = getItemImageUrls(items[i], imageOptions);
itemImages.forEach(onImg);
}
return list;
}
function arraysEqual(a, b) {
if (a === b) {
return true;
}
if (a == null || b == null) {
return false;
}
if (a.length !== b.length) {
return false;
}
// If you don't care about the order of the elements inside
// the array, you should sort both arrays here.
for (var i = 0; i < a.length; ++i) {
if (a[i] !== b[i]) {
return false;
}
}
return true;
}
var rotationInterval;
var currentRotatingImages = [];
var currentRotationIndex = -1;
function setBackdrops(items, imageOptions, enableImageRotation) {
var images = getImageUrls(items, imageOptions);
if (images.length) {
startRotation(images, enableImageRotation);
} else {
clearBackdrop();
}
}
function startRotation(images, enableImageRotation) {
if (arraysEqual(images, currentRotatingImages)) {
return;
}
clearRotation();
currentRotatingImages = images;
currentRotationIndex = -1;
if (images.length > 1 && enableImageRotation !== false && enableRotation()) {
rotationInterval = setInterval(onRotationInterval, 24000);
}
onRotationInterval();
}
function onRotationInterval() {
if (playbackManager.isPlayingLocally(['Video'])) {
return;
}
var newIndex = currentRotationIndex + 1;
if (newIndex >= currentRotatingImages.length) {
newIndex = 0;
}
currentRotationIndex = newIndex;
setBackdropImage(currentRotatingImages[newIndex]);
}
function clearRotation() {
var interval = rotationInterval;
if (interval) {
clearInterval(interval);
}
rotationInterval = null;
currentRotatingImages = [];
currentRotationIndex = -1;
}
function setBackdrop(url, imageOptions) {
if (url) {
if (typeof url !== 'string') {
url = getImageUrls([url], imageOptions)[0];
}
}
if (url) {
clearRotation();
setBackdropImage(url);
} else {
clearBackdrop();
}
}
return {
setBackdrops: setBackdrops,
setBackdrop: setBackdrop,
clear: clearBackdrop,
externalBackdrop: externalBackdrop
};
});

View file

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

310
src/components/browser.js Normal file
View file

@ -0,0 +1,310 @@
define([], function () {
'use strict';
function isTv() {
// This is going to be really difficult to get right
var userAgent = navigator.userAgent.toLowerCase();
if (userAgent.indexOf('tv') !== -1) {
return true;
}
if (userAgent.indexOf('samsungbrowser') !== -1) {
return true;
}
if (userAgent.indexOf('nintendo') !== -1) {
return true;
}
if (userAgent.indexOf('viera') !== -1) {
return true;
}
if (userAgent.indexOf('webos') !== -1) {
return true;
}
return false;
}
function isMobile(userAgent) {
var terms = [
'mobi',
'ipad',
'iphone',
'ipod',
'silk',
'gt-p1000',
'nexus 7',
'kindle fire',
'opera mini'
];
var lower = userAgent.toLowerCase();
for (var i = 0, length = terms.length; i < length; i++) {
if (lower.indexOf(terms[i]) !== -1) {
return true;
}
}
return false;
}
function isStyleSupported(prop, value) {
if (typeof window === 'undefined') {
return false;
}
// If no value is supplied, use "inherit"
value = arguments.length === 2 ? value : 'inherit';
// Try the native standard method first
if ('CSS' in window && 'supports' in window.CSS) {
return window.CSS.supports(prop, value);
}
// Check Opera's native method
if ('supportsCSS' in window) {
return window.supportsCSS(prop, value);
}
// need try/catch because it's failing on tizen
try {
// Convert to camel-case for DOM interactions
var camel = prop.replace(/-([a-z]|[0-9])/ig, function (all, letter) {
return (letter + '').toUpperCase();
});
// Check if the property is supported
var support = (camel in el.style);
// Create test element
var el = document.createElement('div');
// Assign the property and value to invoke
// the CSS interpreter
el.style.cssText = prop + ':' + value;
// Ensure both the property and value are
// supported and return
return support && (el.style[camel] !== '');
} catch (err) {
return false;
}
}
function hasKeyboard(browser) {
if (browser.touch) {
return true;
}
if (browser.xboxOne) {
return true;
}
if (browser.ps4) {
return true;
}
if (browser.edgeUwp) {
// This is OK for now, but this won't always be true
// Should we use this?
// https://gist.github.com/wagonli/40d8a31bd0d6f0dd7a5d
return true;
}
if (browser.tv) {
return true;
}
return false;
}
function iOSversion() {
if (/iP(hone|od|ad)/.test(navigator.platform)) {
// supports iOS 2.0 and later: <http://bit.ly/TJjs1V>
var v = (navigator.appVersion).match(/OS (\d+)_(\d+)_?(\d+)?/);
return [parseInt(v[1], 10), parseInt(v[2], 10), parseInt(v[3] || 0, 10)];
}
}
var _supportsCssAnimation;
var _supportsCssAnimationWithPrefix;
function supportsCssAnimation(allowPrefix) {
if (allowPrefix) {
if (_supportsCssAnimationWithPrefix === true || _supportsCssAnimationWithPrefix === false) {
return _supportsCssAnimationWithPrefix;
}
} else {
if (_supportsCssAnimation === true || _supportsCssAnimation === false) {
return _supportsCssAnimation;
}
}
var animation = false,
animationstring = 'animation',
keyframeprefix = '',
domPrefixes = ['Webkit', 'O', 'Moz'],
pfx = '',
elm = document.createElement('div');
if (elm.style.animationName !== undefined) { animation = true; }
if (animation === false && allowPrefix) {
for (var i = 0; i < domPrefixes.length; i++) {
if (elm.style[domPrefixes[i] + 'AnimationName'] !== undefined) {
pfx = domPrefixes[i];
animationstring = pfx + 'Animation';
keyframeprefix = '-' + pfx.toLowerCase() + '-';
animation = true;
break;
}
}
}
if (allowPrefix) {
_supportsCssAnimationWithPrefix = animation;
return _supportsCssAnimationWithPrefix;
} else {
_supportsCssAnimation = animation;
return _supportsCssAnimation;
}
}
var uaMatch = 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) ||
[];
var versionMatch = /(version)[ \/]([\w.]+)/.exec(ua);
var platform_match = /(ipad)/.exec(ua) ||
/(iphone)/.exec(ua) ||
/(windows)/.exec(ua) ||
/(android)/.exec(ua) ||
[];
var browser = match[1] || "";
if (browser === "edge") {
platform_match = [""];
} else {
if (ua.indexOf("windows phone") !== -1 || ua.indexOf("iemobile") !== -1) {
// http://www.neowin.net/news/ie11-fakes-user-agent-to-fool-gmail-in-windows-phone-81-gdr1-update
browser = "msie";
}
else if (ua.indexOf("like gecko") !== -1 && ua.indexOf('webkit') === -1 && ua.indexOf('opera') === -1 && ua.indexOf('chrome') === -1 && ua.indexOf('safari') === -1) {
browser = "msie";
}
}
if (browser === 'opr') {
browser = 'opera';
}
var version;
if (versionMatch && versionMatch.length > 2) {
version = versionMatch[2];
}
version = version || match[2] || "0";
var versionMajor = parseInt(version.split('.')[0]);
if (isNaN(versionMajor)) {
versionMajor = 0;
}
return {
browser: browser,
version: version,
platform: platform_match[0] || "",
versionMajor: versionMajor
};
};
var userAgent = navigator.userAgent;
var matched = uaMatch(userAgent);
var browser = {};
if (matched.browser) {
browser[matched.browser] = true;
browser.version = matched.version;
browser.versionMajor = matched.versionMajor;
}
if (matched.platform) {
browser[matched.platform] = true;
}
if (!browser.chrome && !browser.msie && !browser.edge && !browser.opera && userAgent.toLowerCase().indexOf("webkit") !== -1) {
browser.safari = true;
}
if (userAgent.toLowerCase().indexOf("playstation 4") !== -1) {
browser.ps4 = true;
browser.tv = true;
}
if (isMobile(userAgent)) {
browser.mobile = true;
}
browser.xboxOne = userAgent.toLowerCase().indexOf('xbox') !== -1;
browser.animate = typeof document !== 'undefined' && document.documentElement.animate != null;
browser.tizen = userAgent.toLowerCase().indexOf('tizen') !== -1 || self.tizen != null;
browser.web0s = userAgent.toLowerCase().indexOf('Web0S'.toLowerCase()) !== -1;
browser.edgeUwp = browser.edge && (userAgent.toLowerCase().indexOf('msapphost') !== -1 || userAgent.toLowerCase().indexOf('webview') !== -1);
if (!browser.tizen) {
browser.orsay = userAgent.toLowerCase().indexOf('smarthub') !== -1;
}
if (browser.edgeUwp) {
browser.edge = true;
}
browser.tv = isTv();
browser.operaTv = browser.tv && userAgent.toLowerCase().indexOf('opr/') !== -1;
if (!isStyleSupported('display', 'flex')) {
browser.noFlex = true;
}
if (browser.mobile || browser.tv) {
browser.slow = true;
}
if (typeof document !== 'undefined') {
if (('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) {
browser.touch = true;
}
}
browser.keyboard = hasKeyboard(browser);
browser.supportsCssAnimation = supportsCssAnimation;
browser.osx = userAgent.toLowerCase().indexOf('os x') !== -1;
browser.iOS = browser.ipad || browser.iphone || browser.ipod;
if (browser.iOS) {
browser.iOSVersion = iOSversion();
browser.iOSVersion = browser.iOSVersion[0] + (browser.iOSVersion[1] / 10);
}
browser.chromecast = browser.chrome && userAgent.toLowerCase().indexOf('crkey') !== -1;
return browser;
});

View file

@ -0,0 +1,916 @@
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 true;
}
var userAgent = navigator.userAgent.toLowerCase();
if (browser.chromecast) {
var isChromecastUltra = userAgent.indexOf('aarch64') !== -1;
if (isChromecastUltra) {
return true;
}
}
// Unfortunately haven't yet found a canPlayType for proper detection
if (browser.iOS && (browser.iOSVersion || 0) >= 11) {
return true;
}
return !!(videoTestElement.canPlayType && videoTestElement.canPlayType('video/hevc; codecs="hevc, aac"').replace(/no/, ''));
}
var _supportsTextTracks;
function supportsTextTracks() {
if (browser.tizen || browser.orsay) {
return true;
}
if (_supportsTextTracks == null) {
_supportsTextTracks = document.createElement('video').textTracks != null;
}
// For now, until ready
return _supportsTextTracks;
}
var _canPlayHls;
function canPlayHls(src) {
if (_canPlayHls == null) {
_canPlayHls = canPlayNativeHls() || canPlayHlsWithMSE();
}
return _canPlayHls;
}
function canPlayNativeHls() {
if (browser.tizen || browser.orsay) {
return true;
}
var media = document.createElement('video');
if (media.canPlayType('application/x-mpegURL').replace(/no/, '') ||
media.canPlayType('application/vnd.apple.mpegURL').replace(/no/, '')) {
return true;
}
return false;
}
function canPlayHlsWithMSE() {
if (window.MediaSource != null) {
// text tracks dont work with this in firefox
return true;
}
return false;
}
function canPlayAudioFormat(format) {
var typeString;
if (format === 'flac') {
if (browser.tizen || browser.orsay || browser.web0s) {
return true;
}
if (browser.edgeUwp) {
return true;
}
}
else if (format === 'wma') {
if (browser.tizen || browser.orsay) {
return true;
}
if (browser.edgeUwp) {
return true;
}
}
else if (format === 'opus') {
typeString = 'audio/ogg; codecs="opus"';
if (document.createElement('audio').canPlayType(typeString).replace(/no/, '')) {
return true;
}
return false;
}
else if (format === 'mp2') {
// For now
return false;
}
if (format === 'webma') {
typeString = 'audio/webm';
} else if (format === 'mp2') {
typeString = 'audio/mpeg';
} else if (format === 'ogg' || format === 'oga') {
// chrome says probably, but seeing failures
if (browser.chrome) {
return false;
}
typeString = 'audio/' + format;
} else {
typeString = 'audio/' + format;
}
if (document.createElement('audio').canPlayType(typeString).replace(/no/, '')) {
return true;
}
return false;
}
function testCanPlayMkv(videoTestElement) {
if (browser.tizen || browser.orsay || browser.web0s) {
return true;
}
if (videoTestElement.canPlayType('video/x-matroska').replace(/no/, '') ||
videoTestElement.canPlayType('video/mkv').replace(/no/, '')) {
return true;
}
var userAgent = navigator.userAgent.toLowerCase();
// Unfortunately there's no real way to detect mkv support
if (browser.chrome) {
// Not supported on opera tv
if (browser.operaTv) {
return false;
}
// Filter out browsers based on chromium that don't support mkv
if (userAgent.indexOf('vivaldi') !== -1 || userAgent.indexOf('opera') !== -1) {
return false;
}
return true;
}
if (browser.edgeUwp) {
return true;
}
return false;
}
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 getFlvMseDirectPlayProfile() {
var videoAudioCodecs = ['aac'];
if (!browser.edge && !browser.msie) {
videoAudioCodecs.push('mp3');
}
return {
Container: 'flv',
Type: 'Video',
VideoCodec: 'h264',
AudioCodec: videoAudioCodecs.join(',')
};
}
function getDirectPlayProfileForVideoContainer(container, videoAudioCodecs, videoTestElement, options) {
var supported = false;
var profileContainer = container;
var 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;
//if (!supported && window.MediaSource != null && window.MediaSource.isTypeSupported('video/mp4; codecs="avc1.42E01E,mp4a.40.2"')) {
// return getFlvMseDirectPlayProfile();
//}
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');
if (supportsVc1()) {
videoCodecs.push('vc1');
}
if (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');
if (canPlayH265(videoTestElement, options)) {
videoCodecs.push('h265');
videoCodecs.push('hevc');
}
if (supportsVc1()) {
videoCodecs.push('vc1');
}
if (supportsMpeg2Video()) {
videoCodecs.push('mpeg2video');
}
profileContainer = 'ts,mpegts';
break;
default:
break;
}
if (!supported) {
return null;
}
return {
Container: profileContainer,
Type: 'Video',
VideoCodec: videoCodecs.join(','),
AudioCodec: videoAudioCodecs.join(',')
};
}
function getMaxBitrate() {
return 120000000;
}
function getGlobalMaxVideoBitrate() {
var userAgent = navigator.userAgent.toLowerCase();
if (browser.chromecast) {
var isChromecastUltra = userAgent.indexOf('aarch64') !== -1;
if (isChromecastUltra) {
return null;
}
// This is a hack to try and detect chromecast on vizio
if (self.screen && self.screen.width >= 3800) {
return null;
}
return 30000000;
}
var isTizenFhd = false;
if (browser.tizen) {
try {
var isTizenUhd = webapis.productinfo.isUdPanelSupported();
isTizenFhd = !isTizenUhd;
console.log("isTizenFhd = " + isTizenFhd);
} catch (error) {
console.log("isUdPanelSupported() error code = " + error.code);
}
}
return browser.ps4 ? 8000000 :
(browser.xboxOne ? 12000000 :
(browser.edgeUwp ? null :
(browser.tizen && isTizenFhd ? 20000000 : null)));
}
function supportsAc3(videoTestElement) {
if (browser.edgeUwp || browser.tizen || browser.orsay || browser.web0s) {
return true;
}
return (videoTestElement.canPlayType('audio/mp4; codecs="ac-3"').replace(/no/, '') && !browser.osx && !browser.iOS);
}
function supportsEac3(videoTestElement) {
if (browser.tizen || browser.orsay || browser.web0s) {
return true;
}
return videoTestElement.canPlayType('audio/mp4; codecs="ec-3"').replace(/no/, '');
}
return function (options) {
options = options || {};
var physicalAudioChannels = options.audioChannels || (browser.tv || browser.ps4 || browser.xboxOne ? 6 : 2);
var bitrateSetting = getMaxBitrate();
var videoTestElement = document.createElement('video');
var canPlayVp8 = videoTestElement.canPlayType('video/webm; codecs="vp8"').replace(/no/, '');
var canPlayVp9 = videoTestElement.canPlayType('video/webm; codecs="vp9"').replace(/no/, '');
var webmAudioCodecs = ['vorbis'];
var canPlayMkv = testCanPlayMkv(videoTestElement);
var profile = {};
profile.MaxStreamingBitrate = bitrateSetting;
profile.MaxStaticBitrate = 100000000;
profile.MusicStreamingTranscodingBitrate = Math.min(bitrateSetting, 192000);
profile.DirectPlayProfiles = [];
var videoAudioCodecs = [];
var hlsVideoAudioCodecs = [];
var supportsMp3VideoAudio = videoTestElement.canPlayType('video/mp4; codecs="avc1.640029, mp4a.69"').replace(/no/, '') ||
videoTestElement.canPlayType('video/mp4; codecs="avc1.640029, mp4a.6B"').replace(/no/, '');
// Not sure how to test for this
var supportsMp2VideoAudio = browser.edgeUwp || browser.tizen || browser.orsay || browser.web0s;
var maxVideoWidth = browser.xboxOne ?
(self.screen ? self.screen.width : null) :
null;
if (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) {
// prioritize this first
videoAudioCodecs.push('aac');
}
// Only put mp3 first if mkv support is there
// Otherwise with HLS and mp3 audio we're seeing some browsers
// safari is lying
if (supportsAc3(videoTestElement)) {
videoAudioCodecs.push('ac3');
var eAc3 = supportsEac3(videoTestElement);
if (eAc3) {
videoAudioCodecs.push('eac3');
}
// This works in edge desktop, but not mobile
// TODO: Retest this on mobile
var supportsAc3InHls = (!browser.edge || !browser.touch || browser.edgeUwp);
if (supportsAc3InHls) {
hlsVideoAudioCodecs.push('ac3');
if (eAc3) {
hlsVideoAudioCodecs.push('eac3');
}
}
}
if (canPlayAacVideoAudio && browser.chromecast && videoAudioCodecs.indexOf('aac') === -1) {
// prioritize this first
videoAudioCodecs.push('aac');
}
if (supportsMp3VideoAudio) {
videoAudioCodecs.push('mp3');
// PS4 fails to load HLS with mp3 audio
if (!browser.ps4) {
// mp3 encoder only supports 2 channels, so only make that preferred if we're only requesting 2 channels
// Also apply it for chromecast because it no longer supports AAC 5.1
if (physicalAudioChannels <= 2) {
hlsVideoAudioCodecs.push('mp3');
}
}
}
if (canPlayAacVideoAudio) {
if (videoAudioCodecs.indexOf('aac') === -1) {
videoAudioCodecs.push('aac');
}
hlsVideoAudioCodecs.push('aac');
}
if (supportsMp3VideoAudio) {
// PS4 fails to load HLS with mp3 audio
if (!browser.ps4) {
if (hlsVideoAudioCodecs.indexOf('mp3') === -1) {
hlsVideoAudioCodecs.push('mp3');
}
}
}
if (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');
// DTS audio not supported in 2018 models (Tizen 4.0)
if (v && parseFloat(v) >= parseFloat('4.0')) {
supportsDts = false;
}
}
if (supportsDts) {
videoAudioCodecs.push('dca');
videoAudioCodecs.push('dts');
}
if (browser.tizen || browser.orsay || browser.web0s) {
videoAudioCodecs.push('pcm_s16le');
videoAudioCodecs.push('pcm_s24le');
}
if (options.supportsTrueHd) {
videoAudioCodecs.push('truehd');
}
if (browser.tizen || browser.orsay) {
videoAudioCodecs.push('aac_latm');
}
if (canPlayAudioFormat('opus')) {
videoAudioCodecs.push('opus');
hlsVideoAudioCodecs.push('opus');
webmAudioCodecs.push('opus');
}
if (canPlayAudioFormat('flac')) {
videoAudioCodecs.push('flac');
}
videoAudioCodecs = videoAudioCodecs.filter(function (c) {
return (options.disableVideoAudioCodecs || []).indexOf(c) === -1;
});
hlsVideoAudioCodecs = hlsVideoAudioCodecs.filter(function (c) {
return (options.disableHlsVideoAudioCodecs || []).indexOf(c) === -1;
});
var mp4VideoCodecs = [];
var hlsVideoCodecs = [];
if (canPlayH264(videoTestElement)) {
mp4VideoCodecs.push('h264');
hlsVideoCodecs.push('h264');
}
if (canPlayH265(videoTestElement, options)) {
mp4VideoCodecs.push('h265');
mp4VideoCodecs.push('hevc');
if (browser.tizen || browser.web0s) {
hlsVideoCodecs.push('h265');
hlsVideoCodecs.push('hevc');
}
}
if (supportsMpeg2Video()) {
mp4VideoCodecs.push('mpeg2video');
}
if (supportsVc1()) {
mp4VideoCodecs.push('vc1');
}
if (browser.tizen || browser.orsay) {
mp4VideoCodecs.push('msmpeg4v2');
}
if (canPlayVp8) {
mp4VideoCodecs.push('vp8');
}
if (canPlayVp9) {
mp4VideoCodecs.push('vp9');
}
if (canPlayVp8 || browser.tizen || browser.orsay) {
videoAudioCodecs.push('vorbis');
}
if (mp4VideoCodecs.length) {
profile.DirectPlayProfiles.push({
Container: 'mp4,m4v',
Type: 'Video',
VideoCodec: mp4VideoCodecs.join(','),
AudioCodec: videoAudioCodecs.join(',')
});
}
if (canPlayMkv && mp4VideoCodecs.length) {
profile.DirectPlayProfiles.push({
Container: 'mkv',
Type: 'Video',
VideoCodec: mp4VideoCodecs.join(','),
AudioCodec: videoAudioCodecs.join(',')
});
}
// These are formats we can't test for but some devices will support
['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 i != null;
}).forEach(function (i) {
profile.DirectPlayProfiles.push(i);
});
['opus', 'mp3', 'mp2', 'aac', 'flac', 'alac', 'webma', 'wma', 'wav', 'ogg', 'oga'].filter(canPlayAudioFormat).forEach(function (audioFormat) {
if (audioFormat === 'mp2') {
profile.DirectPlayProfiles.push({
Container: 'mp2,mp3',
Type: 'Audio',
AudioCodec: audioFormat
});
}
else if (audioFormat === 'mp3') {
profile.DirectPlayProfiles.push({
Container: audioFormat,
Type: 'Audio',
AudioCodec: audioFormat
});
} else {
profile.DirectPlayProfiles.push({
Container: audioFormat === 'webma' ? 'webma,webm' : audioFormat,
Type: 'Audio'
});
}
// aac also appears in the m4a container
if (audioFormat === 'aac' || audioFormat === 'alac') {
profile.DirectPlayProfiles.push({
Container: 'm4a',
AudioCodec: audioFormat,
Type: 'Audio'
});
}
});
if (canPlayVp8) {
profile.DirectPlayProfiles.push({
Container: 'webm',
Type: 'Video',
AudioCodec: webmAudioCodecs.join(','),
VideoCodec: 'VP8'
});
}
if (canPlayVp9) {
profile.DirectPlayProfiles.push({
Container: 'webm',
Type: 'Video',
AudioCodec: webmAudioCodecs.join(','),
VideoCodec: 'VP9'
});
}
profile.TranscodingProfiles = [];
var hlsBreakOnNonKeyFrames = browser.iOS || browser.osx || browser.edge || !canPlayNativeHls() ? true : false;
if (canPlayHls() && browser.enableHlsAudio !== false) {
profile.TranscodingProfiles.push({
// hlsjs, edge, and android all seem to require ts container
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
});
}
// For streaming, prioritize opus transcoding after mp3/aac. It is too problematic with random failures
// But for static (offline sync), it will be just fine.
// Prioritize aac higher because the encoder can accept more channels than mp3
['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()
});
});
if (canPlayMkv && !browser.tizen && !browser.orsay && options.enableMkvProgressive !== false) {
profile.TranscodingProfiles.push({
Container: 'mkv',
Type: 'Video',
AudioCodec: videoAudioCodecs.join(','),
VideoCodec: mp4VideoCodecs.join(','),
Context: 'Streaming',
MaxAudioChannels: physicalAudioChannels.toString(),
CopyTimestamps: true
});
}
if (canPlayMkv) {
profile.TranscodingProfiles.push({
Container: 'mkv',
Type: 'Video',
AudioCodec: videoAudioCodecs.join(','),
VideoCodec: mp4VideoCodecs.join(','),
Context: 'Static',
MaxAudioChannels: physicalAudioChannels.toString(),
CopyTimestamps: true
});
}
if (canPlayHls() && options.enableHls !== false) {
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
});
}
if (canPlayVp8) {
profile.TranscodingProfiles.push({
Container: 'webm',
Type: 'Video',
AudioCodec: 'vorbis',
VideoCodec: 'vpx',
Context: 'Streaming',
Protocol: 'http',
// If audio transcoding is needed, limit channels to number of physical audio channels
// Trying to transcode to 5 channels when there are only 2 speakers generally does not sound good
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;
var aacCodecProfileConditions = [];
// Handle he-aac not supported
if (!videoTestElement.canPlayType('video/mp4; codecs="avc1.640029, mp4a.40.5"').replace(/no/, '')) {
// TODO: This needs to become part of the stream url in order to prevent stream copy
aacCodecProfileConditions.push({
Condition: 'NotEquals',
Property: 'AudioProfile',
Value: 'HE-AAC'
});
}
if (!supportsSecondaryAudio) {
aacCodecProfileConditions.push({
Condition: 'Equals',
Property: 'IsSecondaryAudio',
Value: 'false',
IsRequired: 'false'
});
}
if (browser.chromecast) {
aacCodecProfileConditions.push({
Condition: 'LessThanEqual',
Property: 'AudioChannels',
Value: '2',
IsRequired: true
});
}
if (aacCodecProfileConditions.length) {
profile.CodecProfiles.push({
Type: 'VideoAudio',
Codec: 'aac',
Conditions: aacCodecProfileConditions
});
}
if (!supportsSecondaryAudio) {
profile.CodecProfiles.push({
Type: 'VideoAudio',
Conditions: [
{
Condition: 'Equals',
Property: 'IsSecondaryAudio',
Value: 'false',
IsRequired: 'false'
}
]
});
}
var maxH264Level = browser.chromecast ? 42 : 51;
var h264Profiles = 'high|main|baseline|constrained baseline';
if (maxH264Level >= 51 && browser.chrome && !browser.osx) {
h264Profiles += '|high 10';
}
profile.CodecProfiles.push({
Type: 'Video',
Codec: 'h264',
Conditions: [
{
Condition: 'NotEquals',
Property: 'IsAnamorphic',
Value: 'true',
IsRequired: false
},
{
Condition: 'EqualsAny',
Property: 'VideoProfile',
Value: h264Profiles
},
{
Condition: 'LessThanEqual',
Property: 'VideoLevel',
Value: maxH264Level.toString()
}]
});
if (!browser.edgeUwp && !browser.tizen && !browser.orsay && !browser.web0s) {
//profile.CodecProfiles[profile.CodecProfiles.length - 1].Conditions.push({
// Condition: 'NotEquals',
// Property: 'IsAVC',
// Value: 'false',
// IsRequired: false
//});
//profile.CodecProfiles[profile.CodecProfiles.length - 1].Conditions.push({
// Condition: 'NotEquals',
// Property: 'IsInterlaced',
// Value: 'true',
// IsRequired: false
//});
}
if (maxVideoWidth) {
profile.CodecProfiles[profile.CodecProfiles.length - 1].Conditions.push({
Condition: 'LessThanEqual',
Property: 'Width',
Value: maxVideoWidth.toString(),
IsRequired: false
});
}
var globalMaxVideoBitrate = (getGlobalMaxVideoBitrate() || '').toString();
var h264MaxVideoBitrate = globalMaxVideoBitrate;
if (h264MaxVideoBitrate) {
profile.CodecProfiles[profile.CodecProfiles.length - 1].Conditions.push({
Condition: 'LessThanEqual',
Property: 'VideoBitrate',
Value: h264MaxVideoBitrate,
IsRequired: true
});
}
var globalVideoConditions = [];
if (globalMaxVideoBitrate) {
globalVideoConditions.push({
Condition: 'LessThanEqual',
Property: 'VideoBitrate',
Value: globalMaxVideoBitrate
});
}
if (maxVideoWidth) {
globalVideoConditions.push({
Condition: 'LessThanEqual',
Property: 'Width',
Value: maxVideoWidth.toString(),
IsRequired: false
});
}
if (globalVideoConditions.length) {
profile.CodecProfiles.push({
Type: 'Video',
Conditions: globalVideoConditions
});
}
if (browser.chromecast) {
profile.CodecProfiles.push({
Type: 'Audio',
Codec: 'flac',
Conditions: [
{
Condition: 'LessThanEqual',
Property: 'AudioSampleRate',
Value: '96000'
}]
});
}
// Subtitle profiles
// External vtt or burn in
profile.SubtitleProfiles = [];
if (supportsTextTracks()) {
profile.SubtitleProfiles.push({
Format: 'vtt',
Method: 'External'
});
}
profile.ResponseProfiles = [];
profile.ResponseProfiles.push({
Type: 'Video',
Container: 'm4v',
MimeType: 'video/mp4'
});
return profile;
};
});

View file

@ -0,0 +1,816 @@
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-color: transparent !important;
background: none !important;
margin: 0;
padding: 0;
display: block;
color: inherit !important;
-webkit-tap-highlight-color: rgba(0,0,0,0);
outline: none !important;
cursor: pointer;
contain: layout style;
flex-shrink: 0;
font-weight: inherit !important;
}
.card-nofocustransform {
contain: layout style paint;
}
.itemsContainer {
display: flex;
}
.vertical-list {
display: flex;
flex-direction: column;
flex-wrap: nowrap;
}
.vertical-wrap {
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
.vertical-wrap.centered {
justify-content: center;
}
.cardScalable {
position: relative;
contain: layout style;
}
.cardPadder-backdrop, .cardPadder-mixedBackdrop, .cardPadder-smallBackdrop, .cardPadder-overflowBackdrop, .cardPadder-overflowSmallBackdrop {
padding-bottom: 56.25%;
contain: strict;
}
.cardPadder-square, .cardPadder-mixedSquare, .cardPadder-overflowSquare, .overflowSquareCard-textCardPadder {
padding-bottom: 100%;
contain: strict;
}
.cardPadder-portrait, .cardPadder-mixedPortrait, .cardPadder-overflowPortrait, .overflowPortraitCard-textCardPadder {
padding-bottom: 150%;
contain: strict;
}
.cardPadder-banner {
padding-bottom: 18.5%;
contain: strict;
}
.cardBox {
padding: 0 !important;
margin: .42em;
transition: none;
border: 0 solid transparent;
/* These both are needed in case cardBox is a button */
-webkit-tap-highlight-color: rgba(0,0,0,0);
outline: none !important;
contain: layout style;
}
/*.cardBox-withfocuscontent {
margin: .68em;
}*/
@media (min-width: 50em) {
.cardBox {
margin: .9em;
}
}
.cardBox-withfocuscontent-large {
margin: .4em;
}
/*.card-focuscontent {
border: .12em solid transparent;
}*/
.card-focuscontent-large {
border: .5em solid transparent;
}
.cardBox-focustransform {
will-change: transform;
transition: transform 200ms ease-out;
}
.card:focus > .cardBox-focustransform {
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;
font-weight: inherit !important;
}
.btnCardOptions {
position: absolute;
bottom: .25em;
right: 0;
margin: 0 !important;
z-index: 1;
}
.mediaSourceIndicator {
display: flex;
position: absolute;
align-items: center;
justify-content: center;
top: .3em;
left: .3em;
text-align: center;
vertical-align: middle;
width: 1.6em;
height: 1.6em;
border-radius: 50%;
color: #fff;
background: rgb(51, 136, 204);
}
.cardImageContainer {
background-size: contain;
background-repeat: no-repeat;
background-position: center center;
display: -webkit-flex;
display: flex;
align-items: center;
justify-content: center;
position: relative;
background-clip: content-box !important;
color: inherit;
/* This is only needed for scalable cards */
height: 100%;
contain: strict;
}
.chapterCardImageContainer {
background-color: #000;
border-radius: 0;
}
.textCardImageContainer {
background-color: #333;
}
.cardContent {
overflow: hidden;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
/* Needed in case this is a button */
display: block;
/* Needed in case this is a button */
margin: 0 !important;
/* Needed in safari */
height: 100%;
-webkit-tap-highlight-color: rgba(0,0,0,0);
outline: none !important;
contain: strict;
}
.cardContent-button {
border: 0 !important;
padding: 0 !important;
cursor: pointer;
color: inherit;
width: 100%;
vertical-align: middle;
font-family: inherit;
font-size: inherit;
}
.cardContent-button:not(.defaultCardBackground) {
background-color: transparent;
}
.visualCardBox .cardContent {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.cardContent-shadow {
box-shadow: 0 0.0725em 0.29em 0 rgba(0, 0, 0, 0.37);
}
/*.card:focus .cardContent-shadow {
box-shadow: 0 .63em 1.26em rgba(0, 0, 0, 0.37);
}*/
.cardImageContainer {
display: flex;
}
.cardImage {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-size: contain;
background-repeat: no-repeat;
background-position: center bottom;
}
.cardImage-img {
max-height: 100%;
max-width: 100%;
/* This is simply for lazy image purposes, to ensure the image is visible sooner when scrolling */
min-height: 70%;
min-width: 70%;
margin: auto;
}
.coveredImage-img {
width: 100%;
height: 100%;
}
.coveredImage-noscale-img {
max-height: none;
max-width: none;
}
.coveredImage {
background-size: 100% 100%;
background-position: center center;
}
.coveredImage-noScale {
background-size: cover;
}
.cardFooter {
padding: .3em .3em .5em .3em;
position: relative;
}
.visualCardBox {
box-shadow: 0 0.0725em 0.29em 0 rgba(0, 0, 0, 0.37);
border-radius: .145em;
}
.innerCardFooter {
background: rgba(0,0,0,.7);
position: absolute;
bottom: 0;
left: 0;
text-align: left;
z-index: 1;
overflow: hidden;
max-width: 100%;
color: #fff;
}
.innerCardFooterClear {
background-color: transparent;
}
.fullInnerCardFooter {
right: 0;
}
.cardText {
padding: .06em .5em;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-align: left;
}
.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;
background-size: 70% auto;
background-repeat: no-repeat;
background-position: center center;
}
.cardTextCentered {
text-align: center;
}
.cardText-rightmargin {
margin-right: 2em;
}
.cardDefaultText {
white-space: normal;
text-align: center;
}
.textActionButton {
border: 0 !important;
background: transparent;
border: 0 !important;
padding: 0 !important;
cursor: pointer;
-webkit-tap-highlight-color: rgba(0,0,0,0);
outline: none !important;
color: inherit;
vertical-align: middle;
font-family: inherit;
font-size: inherit;
/*display: flex;
align-items: center;
justify-content: center;*/
}
.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: flex;
align-items: center;
contain: layout style;
}
.cardProgramAttributeIndicators {
top: 0;
left: 0;
position: absolute;
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;
border-radius: 100em;
width: 1.5em !important;
height: 1.5em !important;
justify-content: center;
align-items: center;
display: flex;
font-size: 1.66956521739130434em !important;
}
.cardOverlayButton-centered {
bottom: initial;
right: initial;
position: static;
position: absolute;
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);
transition: transform 200ms ease-out;
}
.cardOverlayButton-centered:hover {
transform: scale(1.2, 1.2);
}
.bannerCard {
width: 100%;
}
.backdropCard {
width: 100%;
}
.smallBackdropCard {
width: 50%;
}
.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) {
.smallBackdropCard {
width: 33.333333333333333333333333333333%;
}
.squareCard, .portraitCard {
width: 33.333333333333333333333333333333%;
}
}
@media (min-width: 43.75em) {
.squareCard, .portraitCard {
width: 25%;
}
}
@media (min-width: 48.125em) {
.backdropCard {
width: 33.333333333333333333333333333333%;
}
}
@media (min-width: 50em) {
.bannerCard {
width: 50%;
}
.squareCard, .portraitCard {
width: 20%;
}
.smallBackdropCard {
width: 25%;
}
}
@media (min-width: 62.5em) {
.smallBackdropCard {
width: 20%;
}
}
@media (min-width: 75em) {
.backdropCard {
width: 25%;
}
.squareCard, .portraitCard {
width: 16.666666666666666666666666666667%;
}
.bannerCard {
width: 33.333333333333333333333333333333%;
}
.smallBackdropCard {
width: 16.666666666666666666666666666667%;
}
}
@media (min-width: 87.5em) {
.squareCard, .portraitCard {
width: 14.285714285714285714285714285714%;
}
.smallBackdropCard {
width: 14.285714285714285714285714285714%;
}
}
@media (min-width: 100em) {
.smallBackdropCard {
width: 12.5%;
}
.backdropCard {
width: 20%;
}
.squareCard, .portraitCard {
width: 12.5%;
}
}
@media (min-width: 120em) {
.squareCard, .portraitCard {
width: 11.111111111111111111111111111111%;
}
}
@media (min-width: 131.25em) {
.bannerCard {
width: 25%;
}
.squareCard, .portraitCard {
width: 10%;
}
}
@media (min-width: 156.25em) {
.backdropCard {
width: 16.666666666666666666666666666667%;
}
}
.itemsContainer-tv > .backdropCard {
width: 25%;
}
.itemsContainer-tv > .squareCard {
width: 16.666666666666666666666666666667%;
}
.itemsContainer-tv > .portraitCard {
width: 16.666666666666666666666666666667%;
}
.overflowBackdropCard {
width: 72vw;
}
.overflowSmallBackdropCard {
width: 72vw;
}
.overflowSquareCard, .overflowPortraitCard {
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) {
.overflowSquareCard, .overflowPortraitCard {
width: 23.3vw;
}
}
@media (min-width: 48.125em) {
.overflowBackdropCard, .overflowSmallBackdropCard {
width: 30vw;
}
}
@media (orientation: landscape) {
.overflowBackdropCard, .overflowSmallBackdropCard {
width: 30vw;
}
.overflowSquareCard, .overflowPortraitCard {
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) {
.overflowSquareCard, .overflowPortraitCard {
width: 18.4vw;
}
}
@media (min-width: 75em) {
.overflowBackdropCard, .overflowSmallBackdropCard {
width: 23.3vw;
}
.overflowSquareCard, .overflowPortraitCard {
width: 15.5vw;
}
}
@media (min-width: 87.5em) {
.overflowSquareCard, .overflowPortraitCard {
width: 13.3vw;
}
}
@media (min-width: 100em) {
.overflowBackdropCard, .overflowSmallBackdropCard {
width: 18.7vw;
}
.overflowSquareCard, .overflowPortraitCard {
width: 11.6vw;
}
}
@media (min-width: 120em) {
.overflowSquareCard, .overflowPortraitCard {
width: 10.3vw;
}
}
@media (min-width: 131.25em) {
.overflowSquareCard, .overflowPortraitCard {
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 > .overflowSquareCard, .itemsContainer-tv > .overflowPortraitCard {
width: 15.6vw;
}
.itemsContainer-tv > .overflowSmallBackdropCard {
width: 18.8vw;
}
.cardOverlayContainer {
background: radial-gradient(farthest-corner at 50% 50%,rgba(30,30,30,.5) 50%,#2c2c2c 100%);
opacity: 0;
transition: opacity .2s;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
user-select: none;
}
.card-hoverable :hover .cardOverlayContainer {
opacity: 1;
}
.cardOverlayButton-hover {
opacity: 0;
transition: opacity .2s;
background: transparent;
color: #fff !important;
padding: .5em;
}
.cardOverlayButtonIcon-hover {
background: transparent !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;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,140 @@
define(['datetime', 'imageLoader', 'connectionManager', 'layoutManager', 'browser'], function (datetime, imageLoader, connectionManager, layoutManager, browser) {
'use strict';
function buildChapterCardsHtml(item, chapters, options) {
var className = 'card itemAction chapterCard';
if (layoutManager.tv && (browser.animate || browser.edge)) {
className += ' card-focusscale';
}
var mediaStreams = ((item.MediaSources || [])[0] || {}).MediaStreams || [];
var videoStream = mediaStreams.filter(function (i) {
return i.Type === 'Video';
})[0] || {};
var shape = (options.backdropShape || 'backdrop');
if (videoStream.Width && videoStream.Height) {
if ((videoStream.Width / videoStream.Height) <= 1.2) {
shape = (options.squareShape || 'square');
}
}
className += ' ' + shape + 'Card';
if (options.block || options.rows) {
className += ' block';
}
var html = '';
var itemsInRow = 0;
var apiClient = connectionManager.getApiClient(item.ServerId);
for (var i = 0, length = chapters.length; i < length; i++) {
if (options.rows && itemsInRow === 0) {
html += '<div class="cardColumn">';
}
var chapter = chapters[i];
html += buildChapterCard(item, apiClient, chapter, i, options, className, shape);
itemsInRow++;
if (options.rows && itemsInRow >= options.rows) {
itemsInRow = 0;
html += '</div>';
}
}
return html;
}
function getImgUrl(item, chapter, index, maxWidth, apiClient) {
if (chapter.ImageTag) {
return apiClient.getScaledImageUrl(item.Id, {
maxWidth: maxWidth,
tag: chapter.ImageTag,
type: "Chapter",
index: index
});
}
return null;
}
function buildChapterCard(item, apiClient, chapter, index, options, className, shape) {
var imgUrl = getImgUrl(item, chapter, index, options.width || 400, apiClient);
var cardImageContainerClass = 'cardContent cardContent-shadow cardImageContainer chapterCardImageContainer';
if (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 + '"';
var cardImageContainer = imgUrl ? ('<div class="' + cardImageContainerClass + ' lazy" data-src="' + imgUrl + '">') : ('<div class="' + cardImageContainerClass + '">');
if (!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';
var cardScalableClass = 'cardScalable';
if (layoutManager.tv) {
var enableFocusTransfrom = !browser.slow && !browser.edge;
cardScalableClass += ' card-focuscontent';
if (enableFocusTransfrom) {
cardBoxCssClass += ' cardBox-focustransform cardBox-withfocuscontent';
} else {
cardBoxCssClass += ' cardBox-withfocuscontent-large';
cardScalableClass += ' card-focuscontent-large';
}
}
var html = '<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>';
return html;
}
function buildChapterCards(item, chapters, options) {
if (options.parentContainer) {
// Abort if the container has been disposed
if (!document.body.contains(options.parentContainer)) {
return;
}
if (chapters.length) {
options.parentContainer.classList.remove('hide');
} else {
options.parentContainer.classList.add('hide');
return;
}
}
var html = buildChapterCardsHtml(item, chapters, options);
options.itemsContainer.innerHTML = html;
imageLoader.lazyChildren(options.itemsContainer);
}
return {
buildChapterCards: buildChapterCards
};
});

View file

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

View file

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

View file

@ -0,0 +1,228 @@
define(['events'], function (events) {
'use strict';
// LinkParser
//
// https://github.com/ravisorg/LinkParser
//
// Locate and extract almost any URL within a string. Handles protocol-less domains, IPv4 and
// IPv6, unrecognised TLDs, and more.
//
// This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
// http://creativecommons.org/licenses/by-sa/4.0/
(function () {
// Original URL regex from the Android android.text.util.Linkify function, found here:
// http://stackoverflow.com/a/19696443
//
// However there were problems with it, most probably related to the fact it was
// written in 2007, and it's been highly modified.
//
// 1) I didn't like the fact that it was tied to specific TLDs, since new ones
// are being added all the time it wouldn't be reasonable to expect developer to
// be continually updating their regular expressions.
//
// 2) It didn't allow unicode characters in the domains which are now allowed in
// many languages, (including some IDN TLDs). Again these are constantly being
// added to and it doesn't seem reasonable to hard-code them. Note this ended up
// not being possible in standard JS due to the way it handles multibyte strings.
// It is possible using XRegExp, however a big performance hit results. Disabled
// for now.
//
// 3) It didn't allow for IPv6 hostnames
// IPv6 regex from http://stackoverflow.com/a/17871737
//
// 4) It was very poorly commented
//
// 5) It wasn't as smart as it could have been about what should be part of a
// URL and what should be part of human language.
var protocols = "(?:(?:http|https|rtsp|ftp):\\/\\/)";
var credentials = "(?:(?:[a-z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-f0-9]{2})){1,64}" // username (1-64 normal or url escaped characters)
+ "(?:\\:(?:[a-z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-f0-9]{2})){1,25})?" // followed by optional password (: + 1-25 normal or url escaped characters)
+ "\\@)";
// IPv6 Regex http://forums.intermapper.com/viewtopic.php?t=452
// by Dartware, LLC is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License
// http://intermapper.com/
var ipv6 = "("
+ "(([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|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[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|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[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|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[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|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[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|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[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|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[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|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))"
+ ")(%.+)?";
var ipv4 = "(?: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])";
// This would have been a lot cleaner if JS RegExp supported conditionals...
var linkRegExpString =
// begin match for protocol / username / password / host
"(?:"
// ============================
// If we have a recognized protocol at the beginning of the URL, we're
// more relaxed about what we accept, because we assume the user wants
// this to be a URL, and we're not accidentally matching human language
+ protocols + "?"
// optional username:password@
+ credentials + "?"
// IP address (both v4 and v6)
+ "(?:"
// IPv6
+ ipv6
// IPv4
+ "|" + ipv4
+ ")"
// end match for protocol / username / password / host
+ ")"
// optional port number
+ "(?:\\:\\d{1,5})?"
// plus optional path and query params (no unicode allowed here?)
+ "(?:"
+ "\\/(?:"
// some characters we'll accept because it's unlikely human language
// would use them after a URL unless they were part of the url
+ "(?:[a-z0-9\\/\\@\\&\\#\\~\\*\\_\\-\\+])"
+ "|(?:\\%[a-f0-9]{2})"
// some characters are much more likely to be used AFTER a url and
// were not intended to be included in the url itself. Mostly end
// of sentence type things. It's also likely that the URL would
// still work if any of these characters were missing from the end
// because we parsed it incorrectly. For these characters to be accepted
// they must be followed by another character that we're reasonably
// sure is part of the url
+ "|(?:[\\;\\?\\:\\.\\!\\'\\(\\)\\,\\=]+(?=(?:[a-z0-9\\/\\@\\&\\#\\~\\*\\_\\-\\+])|(?:\\%[a-f0-9]{2})))"
+ ")*"
+ "|\\b|\$"
+ ")";
// regex = XRegExp(regex,'gi');
var linkRegExp = RegExp(linkRegExpString, 'gi');
var protocolRegExp = RegExp('^' + protocols, 'i');
// if url doesn't begin with a known protocol, add http by default
function ensureProtocol(url) {
if (!url.match(protocolRegExp)) {
url = "http://" + url;
}
return url;
}
// look for links in the text
var LinkParser = {
parse: function (text) {
var links = [];
var match;
while (match = linkRegExp.exec(text)) {
// console.log(matches);
var txt = match[0];
var pos = match.index;
var len = txt.length;
var url = ensureProtocol(text);
links.push({ 'pos': pos, 'text': txt, 'len': len, 'url': url });
}
return links;
}
};
window.LinkParser = LinkParser;
})();
var cache = {};
function isValidIpAddress(address) {
var links = LinkParser.parse(address);
return links.length == 1;
}
function isLocalIpAddress(address) {
address = address.toLowerCase();
if (address.indexOf('127.0.0.1') !== -1) {
return true;
}
if (address.indexOf('localhost') !== -1) {
return true;
}
return false;
}
function getServerAddress(apiClient) {
var serverAddress = apiClient.serverAddress();
if (isValidIpAddress(serverAddress) && !isLocalIpAddress(serverAddress)) {
return Promise.resolve(serverAddress);
}
var cachedValue = getCachedValue(serverAddress);
if (cachedValue) {
return Promise.resolve(cachedValue);
}
return apiClient.getEndpointInfo().then(function (endpoint) {
if (endpoint.IsInNetwork) {
return apiClient.getPublicSystemInfo().then(function (info) {
addToCache(serverAddress, info.LocalAddress);
return info.LocalAddress;
});
} else {
addToCache(serverAddress, serverAddress);
return serverAddress;
}
});
}
function clearCache() {
cache = {};
}
function addToCache(key, value) {
cache[key] = {
value: value,
time: new Date().getTime()
};
}
function getCachedValue(key) {
var obj = cache[key];
if (obj && (new Date().getTime() - obj.time) < 180000) {
return obj.value;
}
return null;
}
events.on(ConnectionManager, 'localusersignedin', clearCache);
events.on(ConnectionManager, 'localusersignedout', clearCache);
return {
getServerAddress: getServerAddress
};
});

File diff suppressed because it is too large Load diff

View file

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

View file

@ -0,0 +1,287 @@
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';
var currentServerId;
function parentWithClass(elem, className) {
while (!elem.classList || !elem.classList.contains(className)) {
elem = elem.parentNode;
if (!elem) {
return null;
}
}
return elem;
}
function onSubmit(e) {
loading.show();
var panel = parentWithClass(this, 'dialog');
var collectionId = panel.querySelector('#selectCollectionToAddTo').value;
var apiClient = connectionManager.getApiClient(currentServerId);
if (collectionId) {
addToCollection(apiClient, panel, collectionId);
} else {
createCollection(apiClient, panel);
}
e.preventDefault();
return false;
}
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 = true;
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 = true;
dialogHelper.close(dlg);
require(['toast'], function (toast) {
toast(globalize.translate('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: true,
IncludeItemTypes: "BoxSet",
SortBy: "SortName",
EnableTotalRecordCount: false
};
var apiClient = connectionManager.getApiClient(currentServerId);
apiClient.getItems(apiClient.getCurrentUserId(), options).then(function (result) {
var html = '';
html += '<option value="">' + globalize.translate('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 = '';
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('NewCollectionHelp');
html += '</div>';
html += '<div class="fldSelectCollection">';
html += '<br/>';
html += '<br/>';
html += '<div class="selectContainer">';
html += '<select is="emby-select" label="' + globalize.translate('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('LabelName') + '" />';
html += '<div class="fieldDescription">' + globalize.translate('NewCollectionNameExample') + '</div>';
html += '</div>';
html += '<label class="checkboxContainer">';
html += '<input is="emby-checkbox" type="checkbox" id="chkEnableInternetMetadata" />';
html += '<span>' + globalize.translate('SearchForCollectionInternetMetadata') + '</span>';
html += '</label>';
// newCollectionInfo
html += '</div>';
html += '<div class="formDialogFooter">';
html += '<button is="emby-button" type="submit" class="raised btnSubmit block formDialogFooterItem button-submit">' + globalize.translate('ButtonOk') + '</button>';
html += '</div>';
html += '<input type="hidden" class="fldSelectedItemIds" />';
html += '</form>';
html += '</div>';
html += '</div>';
return html;
}
function initEditor(content, items) {
content.querySelector('#selectCollectionToAddTo').addEventListener('change', function () {
if (this.value) {
content.querySelector('.newCollectionInfo').classList.add('hide');
content.querySelector('#txtNewCollectionName').removeAttribute('required');
} else {
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(',');
if (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() {
}
CollectionEditor.prototype.show = function (options) {
var items = options.items || {};
currentServerId = options.serverId;
var dialogOptions = {
removeOnClose: true,
scrollY: false
};
if (layoutManager.tv) {
dialogOptions.size = 'fullscreen';
} else {
dialogOptions.size = 'small';
}
var dlg = dialogHelper.createDialog(dialogOptions);
dlg.classList.add('formDialog');
var html = '';
var title = items.length ? globalize.translate('HeaderAddToCollection') : globalize.translate('NewCollection');
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>';
if (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('Help') + '"><i class="md-icon">&#xE88E;</i><span style="margin-left:.25em;">' + globalize.translate('Help') + '</span></a>';
}
html += '</div>';
html += getEditorHtml();
dlg.innerHTML = html;
initEditor(dlg, items);
dlg.querySelector('.btnCancel').addEventListener('click', function () {
dialogHelper.close(dlg);
});
if (layoutManager.tv) {
centerFocus(dlg.querySelector('.formDialogContent'), false, true);
}
return dialogHelper.open(dlg).then(function () {
if (layoutManager.tv) {
centerFocus(dlg.querySelector('.formDialogContent'), false, false);
}
if (dlg.submitted) {
return Promise.resolve();
}
return Promise.reject();
});
};
return CollectionEditor;
});

View file

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

View file

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

282
src/components/datetime.js Normal file
View file

@ -0,0 +1,282 @@
define(['globalize'], function (globalize) {
'use strict';
function parseISO8601Date(s, toLocal) {
// parenthese matches:
// year month day hours minutes seconds
// dotmilliseconds
// tzstring plusminus hours minutes
var re = /(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(\.\d+)?(Z|([+-])(\d{2}):(\d{2}))?/;
var d = s.match(re);
// "2010-12-07T11:00:00.000-09:00" parses to:
// ["2010-12-07T11:00:00.000-09:00", "2010", "12", "07", "11",
// "00", "00", ".000", "-09:00", "-", "09", "00"]
// "2010-12-07T11:00:00.000Z" parses to:
// ["2010-12-07T11:00:00.000Z", "2010", "12", "07", "11",
// "00", "00", ".000", "Z", undefined, undefined, undefined]
if (!d) {
throw "Couldn't parse ISO 8601 date string '" + s + "'";
}
// parse strings, leading zeros into proper ints
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]);
// Date.UTC(year, month[, date[, hrs[, min[, sec[, ms]]]]])
// note that month is 0-11, not 1-12
// see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Date/UTC
var ms = Date.UTC(d[1], d[2] - 1, d[3], d[4], d[5], d[6]);
// if there are milliseconds, add them
if (d[7] > 0) {
ms += Math.round(d[7] * 1000);
}
// if there's a timezone, calculate it
if (d[8] !== "Z" && d[10]) {
var offset = d[10] * 60 * 60 * 1000;
if (d[11]) {
offset += d[11] * 60 * 1000;
}
if (d[9] === "-") {
ms -= offset;
} else {
ms += offset;
}
} else if (toLocal === false) {
ms += new Date().getTimezoneOffset() * 60000;
}
return new Date(ms);
}
function getDisplayRunningTime(ticks) {
var ticksPerHour = 36000000000;
var ticksPerMinute = 600000000;
var ticksPerSecond = 10000000;
var parts = [];
var hours = ticks / ticksPerHour;
hours = Math.floor(hours);
if (hours) {
parts.push(hours);
}
ticks -= (hours * ticksPerHour);
var minutes = ticks / ticksPerMinute;
minutes = Math.floor(minutes);
ticks -= (minutes * ticksPerMinute);
if (minutes < 10 && hours) {
minutes = '0' + minutes;
}
parts.push(minutes);
var seconds = ticks / ticksPerSecond;
seconds = Math.floor(seconds);
if (seconds < 10) {
seconds = '0' + seconds;
}
parts.push(seconds);
return parts.join(':');
}
var toLocaleTimeStringSupportsLocales = function () {
try {
new Date().toLocaleTimeString('i');
} catch (e) {
return e.name === 'RangeError';
}
return false;
}();
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');
}
options = options || {};
if (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');
}
options = options || {};
if (toLocaleTimeStringSupportsLocales) {
var currentLocale = globalize.getCurrentDateTimeLocale();
if (currentLocale) {
return date.toLocaleDateString(currentLocale, options);
}
}
// This is essentially a hard-coded polyfill
var optionList = getOptionList(options);
if (optionList.length === 1 && optionList[0].name === 'weekday') {
var weekday = [];
weekday[0] = "Sun";
weekday[1] = "Mon";
weekday[2] = "Tue";
weekday[3] = "Wed";
weekday[4] = "Thu";
weekday[5] = "Fri";
weekday[6] = "Sat";
return weekday[date.getDay()];
}
return date.toLocaleDateString();
}
function toLocaleTimeString(date, options) {
if (!date) {
throw new Error('date cannot be null');
}
options = options || {};
if (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 ((typeof date).toString().toLowerCase() === 'string') {
try {
date = parseISO8601Date(date, true);
} catch (err) {
return date;
}
}
if (toLocaleTimeStringSupportsLocales) {
return toLocaleTimeString(date, {
hour: 'numeric',
minute: '2-digit'
});
}
var time = toLocaleTimeString(date);
var timeLower = time.toLowerCase();
if (timeLower.indexOf('am') !== -1 || timeLower.indexOf('pm') !== -1) {
time = timeLower;
var hour = date.getHours() % 12;
var suffix = date.getHours() > 11 ? 'pm' : 'am';
if (!hour) {
hour = 12;
}
var minutes = date.getMinutes();
if (minutes < 10) {
minutes = '0' + minutes;
}
minutes = ':' + minutes;
time = hour + minutes + suffix;
} else {
var timeParts = time.split(':');
// Trim off seconds
if (timeParts.length > 2) {
// setting to 2 also handles '21:00:28 GMT+9:30'
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();
var day = yesterday.getDate() + offsetInDays;
yesterday.setDate(day); // automatically adjusts month/year appropriately
return date.getFullYear() === yesterday.getFullYear() && date.getMonth() === yesterday.getMonth() && date.getDate() === day;
}
return {
parseISO8601Date: parseISO8601Date,
getDisplayRunningTime: getDisplayRunningTime,
toLocaleDateString: toLocaleDateString,
toLocaleString: toLocaleString,
getDisplayTime: getDisplayTime,
isRelativeDay: isRelativeDay,
toLocaleTimeString: toLocaleTimeString,
supportsLocalization: function () {
return toLocaleTimeStringSupportsLocales;
}
};
});

View file

@ -0,0 +1,57 @@
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;
var itemId = item.Id;
var parentId = item.SeasonId || item.SeriesId || item.ParentId;
var serverId = item.ServerId;
var msg = globalize.translate('ConfirmDeleteItem');
var title = globalize.translate('HeaderDeleteItem');
var apiClient = connectionManager.getApiClient(item.ServerId);
return confirm({
title: title,
text: msg,
confirmText: globalize.translate('Delete'),
primary: 'cancel'
}).then(function () {
return apiClient.deleteItem(itemId).then(function () {
if (options.navigate) {
if (parentId) {
appRouter.showItem(parentId, serverId);
} else {
appRouter.goHome();
}
}
}, function (err) {
var result = function () {
return Promise.reject(err);
};
return alertText(globalize.translate('ErrorDeletingItem')).then(result, result);
});
});
}
return {
deleteItem: deleteItem
};
});

View file

@ -0,0 +1,133 @@
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) {
var dialogOptions = {
removeOnClose: true,
scrollY: false
};
var enableTvLayout = layoutManager.tv;
if (enableTvLayout) {
dialogOptions.size = 'fullscreen';
}
var dlg = dialogHelper.createDialog(dialogOptions);
dlg.classList.add('formDialog');
dlg.innerHTML = globalize.translateHtml(template, 'core');
dlg.classList.add('align-items-center');
dlg.classList.add('justify-content-center');
var formDialogContent = dlg.querySelector('.formDialogContent');
formDialogContent.classList.add('no-grow');
if (enableTvLayout) {
formDialogContent.style['max-width'] = '50%';
formDialogContent.style['max-height'] = '60%';
scrollHelper.centerFocus.on(formDialogContent, false);
} else {
formDialogContent.style.maxWidth = (Math.min((options.buttons.length * 150) + 200, dom.getWindowSize().innerWidth - 50)) + 'px';
dlg.classList.add('dialog-fullscreen-lowres');
}
//dlg.querySelector('.btnCancel').addEventListener('click', function (e) {
// dialogHelper.close(dlg);
//});
if (options.title) {
dlg.querySelector('.formDialogHeaderTitle').innerHTML = options.title || '';
} else {
dlg.querySelector('.formDialogHeaderTitle').classList.add('hide');
}
var displayText = options.html || options.text || '';
dlg.querySelector('.text').innerHTML = displayText;
if (!displayText) {
dlg.querySelector('.dialogContentInner').classList.add('hide');
}
var i, length;
var html = '';
var hasDescriptions = false;
for (i = 0, length = options.buttons.length; i < length; i++) {
var item = options.buttons[i];
var autoFocus = i === 0 ? ' autofocus' : '';
var buttonClass = 'btnOption raised formDialogFooterItem formDialogFooterItem-autosize';
if (item.type) {
buttonClass += ' button-' + item.type;
}
if (item.description) {
hasDescriptions = true;
}
if (hasDescriptions) {
buttonClass += ' formDialogFooterItem-vertical formDialogFooterItem-nomarginbottom';
}
html += '<button is="emby-button" type="button" class="' + buttonClass + '" data-id="' + item.id + '"' + autoFocus + '>' + item.name + '</button>';
if (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;
if (hasDescriptions) {
dlg.querySelector('.formDialogFooter').classList.add('formDialogFooter-vertical');
}
var dialogResult;
function onButtonClick() {
dialogResult = this.getAttribute('data-id');
dialogHelper.close(dlg);
}
var buttons = dlg.querySelectorAll('.btnOption');
for (i = 0, length = buttons.length; i < length; i++) {
buttons[i].addEventListener('click', onButtonClick);
}
return dialogHelper.open(dlg).then(function () {
if (enableTvLayout) {
scrollHelper.centerFocus.off(dlg.querySelector('.formDialogContent'), false);
}
if (dialogResult) {
return dialogResult;
} else {
return Promise.reject();
}
});
}
return function (text, title) {
var options;
if (typeof text === 'string') {
options = {
title: title,
text: text
};
} else {
options = text;
}
return 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,173 @@
.dialogContainer {
display: flex;
align-items: 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;
border-radius: .2em;
-webkit-font-smoothing: antialiased;
border: 0;
padding: 0;
will-change: transform, opacity;
/* Strict does not work well with actionsheet */
contain: style paint;
box-shadow: 0 16px 24px 2px rgba(0, 0, 0, 0.14), 0 6px 30px 5px rgba(0, 0, 0, 0.12), 0 8px 10px -5px rgba(0, 0, 0, 0.4);
}
.dialog-fixedSize {
border-radius: 0;
max-height: none;
max-width: none;
contain: layout style paint;
}
.dialog-fullscreen {
/* Needed due to formDialog style */
position: fixed !important;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: 0;
box-shadow: none;
}
@keyframes scaledown {
from {
opacity: 1;
transform: none;
}
to {
opacity: 0;
transform: scale(.5);
}
}
@keyframes scaleup {
from {
transform: scale(.5);
opacity: 0;
}
to {
transform: none;
opacity: 1;
}
}
@keyframes fadein {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fadeout {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
@keyframes slideup {
from {
opacity: 0;
transform: translate3d(0, 30%, 0);
}
to {
opacity: 1;
transform: none;
}
}
@keyframes slidedown {
from {
opacity: 1;
transform: none;
}
to {
opacity: 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;
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;
transition: opacity ease-out 0.2s;
will-change: opacity;
}
.dialogBackdropOpened {
opacity: .5;
}

View file

@ -0,0 +1,486 @@
define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager', 'dom', 'css!./dialoghelper.css', 'scrollStyles'], function (appRouter, focusManager, browser, layoutManager, inputManager, dom) {
'use strict';
var globalOnOpenCallback;
function enableAnimation() {
// too slow
if (browser.tv) {
return false;
}
return browser.supportsCssAnimation();
}
function removeCenterFocus(dlg) {
if (layoutManager.tv) {
if (dlg.classList.contains('scrollX')) {
centerFocus(dlg, true, false);
}
else if (dlg.classList.contains('smoothScrollY')) {
centerFocus(dlg, false, false);
}
}
}
function tryRemoveElement(elem) {
var parentNode = elem.parentNode;
if (parentNode) {
// Seeing crashes in edge webview
try {
parentNode.removeChild(elem);
} catch (err) {
console.log('Error removing dialog element: ' + err);
}
}
}
function DialogHashHandler(dlg, hash, resolve) {
var self = this;
self.originalUrl = window.location.href;
var activeElement = document.activeElement;
var removeScrollLockOnClose = false;
function onHashChange(e) {
var isBack = self.originalUrl === window.location.href;
if (isBack || !isOpened(dlg)) {
window.removeEventListener('popstate', onHashChange);
}
if (isBack) {
self.closedByBack = true;
closeDialog(dlg);
}
}
function onBackCommand(e) {
if (e.detail.command === 'back') {
self.closedByBack = true;
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');
if (removeScrollLockOnClose) {
document.body.classList.remove('noScroll');
}
if (!self.closedByBack && isHistoryEnabled(dlg)) {
var state = history.state || {};
if (state.dialogId === hash) {
history.back();
}
}
if (layoutManager.tv) {
focusManager.focus(activeElement);
}
if (dlg.getAttribute('data-removeonclose') !== 'false') {
removeCenterFocus(dlg);
var dialogContainer = dlg.dialogContainer;
if (dialogContainer) {
tryRemoveElement(dialogContainer);
dlg.dialogContainer = null;
} else {
tryRemoveElement(dlg);
}
}
//resolve();
// if we just called history.back(), then use a timeout to allow the history events to fire first
setTimeout(function () {
resolve({
element: dlg,
closedByBack: self.closedByBack
});
}, 1);
}
dlg.addEventListener('close', onDialogClosed);
var center = !dlg.classList.contains('dialog-fixedSize');
if (center) {
dlg.classList.add('centeredDialog');
}
dlg.classList.remove('hide');
addBackdropOverlay(dlg);
dlg.classList.add('opened');
dlg.dispatchEvent(new CustomEvent('open', {
bubbles: false,
cancelable: false
}));
if (dlg.getAttribute('data-lockscroll') === 'true' && !document.body.classList.contains('noScroll')) {
document.body.classList.add('noScroll');
removeScrollLockOnClose = true;
}
animateDialogOpen(dlg);
if (isHistoryEnabled(dlg)) {
appRouter.pushState({ dialogId: hash }, "Dialog", '#' + hash);
window.addEventListener('popstate', onHashChange);
} else {
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;
// trigger reflow or the backdrop will not animate
void backdrop.offsetWidth;
backdrop.classList.add('dialogBackdropOpened');
dom.addEventListener((dlg.dialogContainer || backdrop), 'click', function (e) {
if (e.target === dlg.dialogContainer) {
close(dlg);
}
}, {
passive: true
});
}
function isHistoryEnabled(dlg) {
return dlg.getAttribute('data-history') === 'true';
}
function open(dlg) {
if (globalOnOpenCallback) {
globalOnOpenCallback(dlg);
}
var parent = dlg.parentNode;
if (parent) {
parent.removeChild(dlg);
}
var dialogContainer = document.createElement('div');
dialogContainer.classList.add('dialogContainer');
dialogContainer.appendChild(dlg);
dlg.dialogContainer = dialogContainer;
document.body.appendChild(dialogContainer);
return new Promise(function (resolve, reject) {
new DialogHashHandler(dlg, 'dlg' + new Date().getTime(), resolve);
});
}
function isOpened(dlg) {
//return dlg.opened;
return !dlg.classList.contains('hide');
}
function close(dlg) {
if (isOpened(dlg)) {
if (isHistoryEnabled(dlg)) {
history.back();
} else {
closeDialog(dlg);
}
}
}
function closeDialog(dlg) {
if (!dlg.classList.contains('hide')) {
dlg.dispatchEvent(new CustomEvent('closing', {
bubbles: false,
cancelable: false
}));
var onAnimationFinish = function () {
focusManager.popScope(dlg);
dlg.classList.add('hide');
dlg.dispatchEvent(new CustomEvent('close', {
bubbles: false,
cancelable: false
}));
};
animateDialogClose(dlg, onAnimationFinish);
}
}
function animateDialogOpen(dlg) {
var onAnimationFinish = function () {
focusManager.pushScope(dlg);
if (dlg.getAttribute('data-autofocus') === 'true') {
focusManager.autoFocus(dlg);
}
};
if (enableAnimation()) {
var onFinish = function () {
dom.removeEventListener(dlg, dom.whichAnimationEvent(), onFinish, {
once: true
});
onAnimationFinish();
};
dom.addEventListener(dlg, dom.whichAnimationEvent(), onFinish, {
once: true
});
return;
}
onAnimationFinish();
}
function animateDialogClose(dlg, onAnimationFinish) {
if (enableAnimation()) {
var animated = true;
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 = false;
break;
}
var onFinish = function () {
dom.removeEventListener(dlg, dom.whichAnimationEvent(), onFinish, {
once: true
});
onAnimationFinish();
};
dom.addEventListener(dlg, dom.whichAnimationEvent(), onFinish, {
once: true
});
if (animated) {
return;
}
}
onAnimationFinish();
}
var supportsOverscrollBehavior = 'overscroll-behavior-y' in document.body.style;
function shouldLockDocumentScroll(options) {
if (supportsOverscrollBehavior && (options.size || !browser.touch)) {
return false;
}
if (options.lockScroll != null) {
return options.lockScroll;
}
if (options.size === 'fullscreen') {
return true;
}
if (options.size) {
return true;
}
return browser.touch;
}
function removeBackdrop(dlg) {
var backdrop = dlg.backdrop;
if (!backdrop) {
return;
}
dlg.backdrop = null;
var onAnimationFinish = function () {
tryRemoveElement(backdrop);
};
if (enableAnimation()) {
backdrop.classList.remove('dialogBackdropOpened');
// this is not firing animatonend
setTimeout(onAnimationFinish, 300);
return;
}
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 || {};
// If there's no native dialog support, use a plain div
// Also not working well in samsung tizen browser, content inside not clickable
// Just go ahead and always use a plain div because we're seeing issues overlaying absoltutely positioned content over a modal dialog
var dlg = document.createElement('div');
dlg.classList.add('focuscontainer');
dlg.classList.add('hide');
if (shouldLockDocumentScroll(options)) {
dlg.setAttribute('data-lockscroll', 'true');
}
if (options.enableHistory !== false && appRouter.enableNativeHistory()) {
dlg.setAttribute('data-history', 'true');
}
// without this safari will scroll the background instead of the dialog contents
// but not needed here since this is already on top of an existing dialog
// but skip it in IE because it's causing the entire browser to hang
// Also have to disable for firefox because it's causing select elements to not be clickable
if (options.modal !== false) {
dlg.setAttribute('modal', 'modal');
}
if (options.autoFocus !== false) {
dlg.setAttribute('data-autofocus', 'true');
}
var defaultEntryAnimation;
var defaultExitAnimation;
defaultEntryAnimation = 'scaleup';
defaultExitAnimation = 'scaledown';
var entryAnimation = options.entryAnimation || defaultEntryAnimation;
var exitAnimation = options.exitAnimation || defaultExitAnimation;
// If it's not fullscreen then lower the default animation speed to make it open really fast
var entryAnimationDuration = options.entryAnimationDuration || (options.size !== 'fullscreen' ? 180 : 280);
var exitAnimationDuration = options.exitAnimationDuration || (options.size !== 'fullscreen' ? 120 : 220);
dlg.animationConfig = {
// scale up
'entry': {
name: entryAnimation,
timing: {
duration: entryAnimationDuration,
easing: 'ease-out'
}
},
// fade out
'exit': {
name: exitAnimation,
timing: {
duration: exitAnimationDuration,
easing: 'ease-out',
fill: 'both'
}
}
};
dlg.classList.add('dialog');
if (options.scrollX) {
dlg.classList.add('scrollX');
dlg.classList.add('smoothScrollX');
if (layoutManager.tv) {
centerFocus(dlg, true, true);
}
}
else if (options.scrollY !== false) {
dlg.classList.add('smoothScrollY');
if (layoutManager.tv) {
centerFocus(dlg, false, true);
}
}
if (options.removeOnClose) {
dlg.setAttribute('data-removeonclose', 'true');
}
if (options.size) {
dlg.classList.add('dialog-fixedSize');
dlg.classList.add('dialog-' + options.size);
}
if (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';
break;
default:
break;
}
}
return dlg;
}
return {
open: open,
close: close,
createDialog: createDialog,
setOnOpen: function (val) {
globalOnOpenCallback = val;
}
};
});

View file

@ -0,0 +1,339 @@
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;
if (t.isDefault && !isDashboard) {
value = '';
}
else if (t.isDefaultServerDashboard && isDashboard) {
value = '';
}
return '<option value="' + value + '">' + t.name + '</option>';
}).join('');
}
function loadScreensavers(context, userSettings) {
var selectScreensaver = context.querySelector('.selectScreensaver');
var options = pluginManager.ofType('screensaver').map(function (plugin) {
return {
name: plugin.name,
value: plugin.id
};
});
options.unshift({
name: globalize.translate('None'),
value: 'none'
});
selectScreensaver.innerHTML = options.map(function (o) {
return '<option value="' + o.value + '">' + o.name + '</option>';
}).join('');
selectScreensaver.value = userSettings.screensaver();
if (!selectScreensaver.value) {
// TODO: set the default instead of none
selectScreensaver.value = 'none';
}
}
function loadSoundEffects(context, userSettings) {
var selectSoundEffects = context.querySelector('.selectSoundEffects');
var options = pluginManager.ofType('soundeffects').map(function (plugin) {
return {
name: plugin.name,
value: plugin.id
};
});
options.unshift({
name: globalize.translate('None'),
value: 'none'
});
selectSoundEffects.innerHTML = options.map(function (o) {
return '<option value="' + o.value + '">' + o.name + '</option>';
}).join('');
selectSoundEffects.value = userSettings.soundEffects();
if (!selectSoundEffects.value) {
// TODO: set the default instead of none
selectSoundEffects.value = 'none';
}
}
function loadSkins(context, userSettings) {
var selectSkin = context.querySelector('.selectSkin');
var 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();
if (!selectSkin.value && options.length) {
selectSkin.value = options[0].value;
}
if (options.length > 1 && appHost.supports('skins')) {
context.querySelector('.selectSkinContainer').classList.remove('hide');
} else {
context.querySelector('.selectSkinContainer').classList.add('hide');
}
}
function showOrHideMissingEpisodesField(context, user, apiClient) {
if (browser.tizen || browser.web0s) {
context.querySelector('.fldDisplayMissingEpisodes').classList.add('hide');
return;
}
context.querySelector('.fldDisplayMissingEpisodes').classList.remove('hide');
}
function loadForm(context, user, userSettings, apiClient) {
var loggedInUserId = apiClient.getCurrentUserId();
var userId = user.Id;
if (user.Policy.IsAdministrator) {
context.querySelector('.selectDashboardThemeContainer').classList.remove('hide');
} else {
context.querySelector('.selectDashboardThemeContainer').classList.add('hide');
}
if (appHost.supports('displaylanguage')) {
context.querySelector('.languageSection').classList.remove('hide');
} else {
context.querySelector('.languageSection').classList.add('hide');
}
if (appHost.supports('displaymode')) {
context.querySelector('.fldDisplayMode').classList.remove('hide');
} else {
context.querySelector('.fldDisplayMode').classList.add('hide');
}
if (appHost.supports('externallinks')) {
context.querySelector('.learnHowToContributeContainer').classList.remove('hide');
} else {
context.querySelector('.learnHowToContributeContainer').classList.add('hide');
}
if (appHost.supports('runatstartup')) {
context.querySelector('.fldAutorun').classList.remove('hide');
} else {
context.querySelector('.fldAutorun').classList.add('hide');
}
if (appHost.supports('soundeffects')) {
context.querySelector('.fldSoundEffects').classList.remove('hide');
} else {
context.querySelector('.fldSoundEffects').classList.add('hide');
}
if (appHost.supports('screensaver')) {
context.querySelector('.selectScreensaverContainer').classList.remove('hide');
} else {
context.querySelector('.selectScreensaverContainer').classList.add('hide');
}
if (datetime.supportsLocalization()) {
context.querySelector('.fldDateTimeLocale').classList.remove('hide');
} else {
context.querySelector('.fldDateTimeLocale').classList.add('hide');
}
if (!browser.tizen && !browser.web0s) {
context.querySelector('.fldBackdrops').classList.remove('hide');
context.querySelector('.fldThemeSong').classList.remove('hide');
context.querySelector('.fldThemeVideo').classList.remove('hide');
} else {
context.querySelector('.fldBackdrops').classList.add('hide');
context.querySelector('.fldThemeSong').classList.add('hide');
context.querySelector('.fldThemeVideo').classList.add('hide');
}
context.querySelector('.chkRunAtStartup').checked = appSettings.runAtStartup();
var selectTheme = context.querySelector('#selectTheme');
var selectDashboardTheme = context.querySelector('#selectDashboardTheme');
fillThemes(selectTheme);
fillThemes(selectDashboardTheme, true);
loadScreensavers(context, userSettings);
loadSoundEffects(context, userSettings);
loadSkins(context, userSettings);
context.querySelector('.chkDisplayMissingEpisodes').checked = user.Configuration.DisplayMissingEpisodes || false;
context.querySelector('#chkThemeSong').checked = userSettings.enableThemeSongs();
context.querySelector('#chkThemeVideo').checked = userSettings.enableThemeVideos();
context.querySelector('#chkBackdrops').checked = userSettings.enableBackdrops();
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) {
appSettings.runAtStartup(context.querySelector('.chkRunAtStartup').checked);
user.Configuration.DisplayMissingEpisodes = context.querySelector('.chkDisplayMissingEpisodes').checked;
if (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);
if (user.Id === apiClient.getCurrentUserId()) {
skinManager.setTheme(userSettingsInstance.theme());
}
layoutManager.setLayout(context.querySelector('.selectLayout').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();
if (enableSaveConfirmation) {
require(['toast'], function (toast) {
toast(globalize.translate('SettingsSaved'));
});
}
events.trigger(instance, 'saved');
}, function () {
loading.hide();
});
});
}
function onSubmit(e) {
var self = this;
var apiClient = connectionManager.getApiClient(self.options.serverId);
var userId = self.options.userId;
var userSettings = self.options.userSettings;
userSettings.setUserInfo(userId, apiClient).then(function () {
var enableSaveConfirmation = self.options.enableSaveConfirmation;
save(self, self.options.element, userId, userSettings, apiClient, enableSaveConfirmation);
});
// Disable default form submission
if (e) {
e.preventDefault();
}
return false;
}
function embed(options, self) {
require(['text!./displaysettings.template.html'], function (template) {
options.element.innerHTML = globalize.translateDocument(template, 'core');
options.element.querySelector('form').addEventListener('submit', onSubmit.bind(self));
if (options.enableSaveButton) {
options.element.querySelector('.btnSave').classList.remove('hide');
}
self.loadData(options.autoFocus);
});
}
function DisplaySettings(options) {
this.options = options;
embed(options, this);
}
DisplaySettings.prototype.loadData = function (autoFocus) {
var self = this;
var context = self.options.element;
loading.show();
var userId = self.options.userId;
var apiClient = connectionManager.getApiClient(self.options.serverId);
var userSettings = self.options.userSettings;
return apiClient.getUser(userId).then(function (user) {
return userSettings.setUserInfo(userId, apiClient).then(function () {
self.dataLoaded = true;
loadForm(context, user, userSettings, apiClient);
if (autoFocus) {
focusManager.autoFocus(context);
}
});
});
};
DisplaySettings.prototype.submit = function () {
onSubmit.call(this);
};
DisplaySettings.prototype.destroy = function () {
this.options = null;
};
return DisplaySettings;
});

View file

@ -0,0 +1,183 @@
<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="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>

179
src/components/dom.js Normal file
View file

@ -0,0 +1,179 @@
define([], function () {
'use strict';
function parentWithAttribute(elem, name, value) {
while ((value ? elem.getAttribute(name) !== value : !elem.getAttribute(name))) {
elem = elem.parentNode;
if (!elem || !elem.getAttribute) {
return null;
}
}
return elem;
}
function parentWithTag(elem, tagNames) {
// accept both string and array passed in
if (!Array.isArray(tagNames)) {
tagNames = [tagNames];
}
while (tagNames.indexOf(elem.tagName || '') === -1) {
elem = elem.parentNode;
if (!elem) {
return null;
}
}
return elem;
}
function containsAnyClass(classList, classNames) {
for (var i = 0, length = classNames.length; i < length; i++) {
if (classList.contains(classNames[i])) {
return true;
}
}
return false;
}
function parentWithClass(elem, classNames) {
// accept both string and array passed in
if (!Array.isArray(classNames)) {
classNames = [classNames];
}
while (!elem.classList || !containsAnyClass(elem.classList, classNames)) {
elem = elem.parentNode;
if (!elem) {
return null;
}
}
return elem;
}
var supportsCaptureOption = false;
try {
var opts = Object.defineProperty({}, 'capture', {
get: function () {
supportsCaptureOption = true;
}
});
window.addEventListener("test", null, opts);
} catch (e) { }
function addEventListenerWithOptions(target, type, handler, options) {
var optionsOrCapture = options;
if (!supportsCaptureOption) {
optionsOrCapture = options.capture;
}
target.addEventListener(type, handler, optionsOrCapture);
}
function removeEventListenerWithOptions(target, type, handler, options) {
var optionsOrCapture = options;
if (!supportsCaptureOption) {
optionsOrCapture = options.capture;
}
target.removeEventListener(type, handler, optionsOrCapture);
}
var windowSize;
var windowSizeEventsBound;
function clearWindowSize() {
windowSize = null;
}
function getWindowSize() {
if (!windowSize) {
windowSize = {
innerHeight: window.innerHeight,
innerWidth: window.innerWidth
};
if (!windowSizeEventsBound) {
windowSizeEventsBound = true;
addEventListenerWithOptions(window, "orientationchange", clearWindowSize, { passive: true });
addEventListenerWithOptions(window, 'resize', clearWindowSize, { passive: true });
}
}
return windowSize;
}
var _animationEvent;
function whichAnimationEvent() {
if (_animationEvent) {
return _animationEvent;
}
var t,
el = document.createElement("div");
var animations = {
"animation": "animationend",
"OAnimation": "oAnimationEnd",
"MozAnimation": "animationend",
"WebkitAnimation": "webkitAnimationEnd"
};
for (t in animations) {
if (el.style[t] !== undefined) {
_animationEvent = animations[t];
return animations[t];
}
}
_animationEvent = 'animationend';
return _animationEvent;
}
function whichAnimationCancelEvent() {
return whichAnimationEvent().replace('animationend', 'animationcancel').replace('AnimationEnd', 'AnimationCancel');
}
var _transitionEvent;
function whichTransitionEvent() {
if (_transitionEvent) {
return _transitionEvent;
}
var t,
el = document.createElement("div");
var transitions = {
"transition": "transitionend",
"OTransition": "oTransitionEnd",
"MozTransition": "transitionend",
"WebkitTransition": "webkitTransitionEnd"
};
for (t in transitions) {
if (el.style[t] !== undefined) {
_transitionEvent = transitions[t];
return transitions[t];
}
}
_transitionEvent = 'transitionend';
return _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,200 @@
.emby-button {
position: relative;
display: inline-flex;
align-items: center;
box-sizing: border-box;
margin: 0 .29em;
text-align: center;
font-size: inherit;
font-family: inherit;
color: inherit;
outline-width: 0;
-moz-user-select: none;
-ms-user-select: none;
-webkit-user-select: none;
user-select: none;
cursor: pointer;
z-index: 0;
padding: .86em 1em;
vertical-align: middle;
border: 0;
vertical-align: middle;
border-radius: .2em;
/* These are getting an outline in opera tv browsers, which run chrome 30 */
outline: none !important;
position: relative;
font-weight: 600;
/* Disable webkit tap highlighting */
-webkit-tap-highlight-color: rgba(0,0,0,0);
text-decoration: none;
/* Not crazy about this but it normalizes heights between anchors and buttons */
line-height: 1.35;
}
.emby-button::-moz-focus-inner {
border: 0;
}
.button-flat {
background: transparent;
}
.button-flat:hover {
opacity: .5;
}
.button-link {
background: transparent;
margin: 0;
padding: 0;
vertical-align: initial;
}
.button-link-inline {
display: inline;
}
.button-link:hover {
text-decoration: underline;
}
.emby-button-focusscale {
transition: transform 180ms ease-out !important;
-webkit-transform-origin: center center;
transform-origin: center center;
}
.emby-button-focusscale:focus {
transform: scale(1.16);
z-index: 1;
}
.emby-button > i {
/* For non-fab buttons that have icons */
font-size: 1.36em;
}
.button-link > i {
font-size: 1em;
}
.fab {
display: inline-flex;
border-radius: 50%;
padding: .6em;
box-sizing: border-box;
align-items: center;
justify-content: center;
text-align: center;
}
.emby-button.block {
display: block;
align-items: center;
justify-content: center;
margin: .25em 0;
width: 100%;
}
.paper-icon-button-light {
position: relative;
display: inline-flex;
align-items: center;
box-sizing: border-box;
margin: 0 .29em;
background: transparent;
text-align: center;
font-size: inherit;
font-family: inherit;
color: 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;
vertical-align: middle;
border: 0;
vertical-align: middle;
/* These are getting an outline in opera tv browsers, which run chrome 30 */
outline: none !important;
position: relative;
overflow: hidden;
border-radius: 50%;
/* Disable webkit tap highlighting */
-webkit-tap-highlight-color: rgba(0,0,0,0);
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;
/* Make sure its on top of the ripple */
position: relative;
z-index: 1;
vertical-align: middle;
}
.paper-icon-button-light > img {
width: 1.72em;
/* Can't use 100% height or it will stretch past the boundaries in safari */
/*height: 100%;*/
max-height: 100%;
/* Make sure its on top of the ripple */
position: relative;
z-index: 1;
vertical-align: middle;
}
.emby-button-foreground {
position: relative;
z-index: 1;
}
.icon-button-focusscale {
transition: transform 180ms ease-out !important;
-webkit-transform-origin: center center;
transform-origin: center center;
}
.icon-button-focusscale:focus {
transform: scale(1.3);
z-index: 1;
}
.btnFilterWithBubble {
position: relative;
}
.filterButtonBubble {
color: #fff;
position: absolute;
background: #444;
top: 0;
right: 0;
/* padding: .5em; */
width: 1.6em;
height: 1.6em;
z-index: 100000000;
display: flex;
align-items: center;
justify-content: center;
font-size: 82%;
border-radius: 100em;
box-shadow: 0px 4px 5px 0px rgba(0, 0, 0, 0.14), 0px 1px 10px 0px rgba(0, 0, 0, 0.12), 0px 2px 4px -1px rgba(0, 0, 0, 0.2);
background: #03A9F4;
font-weight: bold;
}

View file

@ -0,0 +1,99 @@
define(['browser', 'dom', 'layoutManager', 'shell', 'appRouter', 'apphost', 'css!./emby-button', 'registerElement'], function (browser, dom, layoutManager, shell, appRouter, appHost) {
'use strict';
var EmbyButtonPrototype = Object.create(HTMLButtonElement.prototype);
var EmbyLinkButtonPrototype = Object.create(HTMLAnchorElement.prototype);
function openPremiumInfo() {
require(['registrationServices'], function (registrationServices) {
registrationServices.showPremiereInfo();
});
}
function onAnchorClick(e) {
var href = this.getAttribute('href') || '';
if (href !== '#') {
if (this.getAttribute('target')) {
if (href.indexOf('emby.media/premiere') !== -1 && !appHost.supports('externalpremium')) {
e.preventDefault();
openPremiumInfo();
}
else if (!appHost.supports('targetblank')) {
e.preventDefault();
shell.openUrl(href);
}
} else {
appRouter.handleAnchorClick(e);
}
} else {
e.preventDefault();
}
}
EmbyButtonPrototype.createdCallback = function () {
if (this.classList.contains('emby-button')) {
return;
}
this.classList.add('emby-button');
if (browser.firefox) {
// a ff hack is needed for vertical alignment
this.classList.add('button-link-inline');
}
if (layoutManager.tv) {
if (this.getAttribute('data-focusscale') !== 'false') {
this.classList.add('emby-button-focusscale');
}
this.classList.add('emby-button-tv');
}
};
EmbyButtonPrototype.attachedCallback = function () {
if (this.tagName === 'A') {
dom.removeEventListener(this, 'click', onAnchorClick, {
});
dom.addEventListener(this, 'click', onAnchorClick, {
});
if (this.getAttribute('data-autohide') === 'true') {
if (appHost.supports('externallinks')) {
this.classList.remove('hide');
} else {
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'
});
// For extension purposes
return EmbyButtonPrototype;
});

View file

@ -0,0 +1,19 @@
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');
if (layoutManager.tv) {
this.classList.add('icon-button-focusscale');
}
};
document.registerElement('paper-icon-button-light', {
prototype: EmbyButtonPrototype,
extends: 'button'
});
});

View file

@ -0,0 +1,150 @@
.emby-checkbox-label {
position: relative;
z-index: 1;
vertical-align: middle;
display: inline-flex;
box-sizing: border-box;
width: 100%;
margin: 0;
padding: 0;
padding-left: 2.4em;
align-items: center;
height: 2.35em;
cursor: pointer;
}
.checkboxFieldDescription {
padding-left: 2.4em;
}
.checkboxContainer {
margin-bottom: 1.8em;
display: flex;
}
.checkboxListContainer {
margin-bottom: 1.8em;
}
.checkboxContainer-withDescription {
flex-direction: column;
}
.emby-checkbox {
position: absolute;
/* This is for focusing purposes, so the focusManager doesn't skip over it */
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;
display: inline-block;
box-sizing: border-box;
width: 1.83em;
height: 1.83em;
margin: 0;
overflow: hidden;
border: 2px solid currentcolor;
border-radius: .14em;
z-index: 2;
display: flex;
align-items: center;
justify-content: center;
}
/* Commenting this out - set by theme */
/*.emby-checkbox:checked + span + span + .checkboxOutline {
border-color: #52B54B;
}*/
.emby-checkbox-focushelper {
position: absolute;
top: -0.915em;
left: -0.915em;
width: 3.66em;
height: 3.66em;
display: inline-block;
box-sizing: border-box;
margin: 3px 0 0 0;
border-radius: 50%;
background-color: transparent;
}
/* Commenting this out - set by theme */
/*.emby-checkbox:focus + span + .emby-checkbox-focushelper {
background-color: rgba(82, 181, 75, 0.26);
}*/
.checkboxIcon {
font-size: 1.6em;
color: #fff;
}
.checkboxIcon-checked {
display: none;
}
.emby-checkbox:checked + span + span + .checkboxOutline > .checkboxIcon-checked {
/* background-color set by theme */
/*background-color: #52B54B;*/
display: flex !important;
}
.emby-checkbox:checked + span + span + .checkboxOutline > .checkboxIcon-unchecked {
/* background-color set by theme */
display: none !important;
}
.emby-checkbox:checked[disabled] + span + span + .checkboxOutline > .checkboxIcon {
background-color: rgba(0, 0, 0, 0.26);
}
.checkboxLabel {
position: relative;
margin: 0;
}
.checkboxList > .emby-checkbox-label {
display: flex;
margin: .5em 0;
}
.checkboxList-verticalwrap {
display: flex;
flex-wrap: wrap;
}
.checkboxList-verticalwrap > .emby-checkbox-label {
display: inline-flex;
margin: .3em 0 .3em 0;
width: 12em;
}
.checkboxList-paperList {
padding: 1em !important;
}
.checkboxListLabel {
margin-bottom: .25em;
}
@-webkit-keyframes repaintChrome {
from {
padding: 0;
}
to {
padding: 0;
}
}

View file

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

View file

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

View file

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

View file

@ -0,0 +1,40 @@
.emby-input {
display: block;
margin: 0;
margin-bottom: 0 !important;
/* Remove select styling */
/* Font size must the 16px or larger to prevent iOS page zoom on focus */
font-size: 110%;
/* General select styles: change as needed */
font-family: inherit;
font-weight: inherit;
padding: .4em .25em;
/* Prevent padding from causing width overflow */
-webkit-box-sizing: border-box;
box-sizing: border-box;
outline: none !important;
-webkit-tap-highlight-color: rgba(0,0,0,0);
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,125 @@
define(['layoutManager', 'browser', 'dom', 'css!./emby-input', 'registerElement'], function (layoutManager, browser, dom) {
'use strict';
var EmbyInputPrototype = Object.create(HTMLInputElement.prototype);
var inputId = 0;
var supportsFloatingLabel = false;
if (Object.getOwnPropertyDescriptor && Object.defineProperty) {
var descriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value');
// descriptor returning null in webos
if (descriptor && descriptor.configurable) {
var baseSetMethod = descriptor.set;
descriptor.set = function (value) {
baseSetMethod.call(this, value);
this.dispatchEvent(new CustomEvent('valueset', {
bubbles: false,
cancelable: false
}));
};
Object.defineProperty(HTMLInputElement.prototype, 'value', descriptor);
supportsFloatingLabel = true;
}
}
EmbyInputPrototype.createdCallback = function () {
if (!this.id) {
this.id = 'embyinput' + inputId;
inputId++;
} if (this.classList.contains('emby-input')) {
return;
}
this.classList.add('emby-input');
var parentNode = this.parentNode;
var document = this.ownerDocument;
var 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);
// For Samsung orsay devices
if (document.attachIME) {
document.attachIME(this);
}
label.classList.add('inputLabelFocused');
label.classList.remove('inputLabelUnfocused');
}, {
passive: true
});
dom.addEventListener(this, 'blur', function () {
onChange.call(this);
label.classList.remove('inputLabelFocused');
label.classList.add('inputLabelUnfocused');
}, {
passive: true
});
dom.addEventListener(this, 'change', onChange, {
passive: true
});
dom.addEventListener(this, 'input', onChange, {
passive: true
});
dom.addEventListener(this, 'valueset', onChange, {
passive: true
});
if (browser.orsay) {
if (this === document.activeElement) {
//Make sure the IME pops up if this is the first/default element on the page
if (document.attachIME) {
document.attachIME(this);
}
}
}
};
function onChange() {
var label = this.labelElement;
if (this.value) {
label.classList.remove('inputLabel-float');
} else {
var instanceSupportsFloat = supportsFloatingLabel && this.type !== 'date' && this.type !== 'time';
if (instanceSupportsFloat) {
label.classList.add('inputLabel-float');
}
}
}
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,77 @@
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];
if (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');
}
if (info.ItemId === indicator.itemId) {
var progress = parseFloat(info.Progress);
if (progress && progress < 100) {
this.classList.remove('hide');
} else {
this.classList.add('hide');
}
this.setProgress(progress);
}
}
var EmbyItemRefreshIndicatorPrototype = Object.create(EmbyProgressRing);
EmbyItemRefreshIndicatorPrototype.createdCallback = function () {
// base method
if (EmbyProgressRing.createdCallback) {
EmbyProgressRing.createdCallback.call(this);
}
addNotificationEvent(this, 'RefreshProgress', onRefreshProgress);
};
EmbyItemRefreshIndicatorPrototype.attachedCallback = function () {
// base method
if (EmbyProgressRing.attachedCallback) {
EmbyProgressRing.attachedCallback.call(this);
}
};
EmbyItemRefreshIndicatorPrototype.detachedCallback = function () {
// base method
if (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,548 @@
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';
var ItemsContainerProtoType = Object.create(HTMLDivElement.prototype);
function onClick(e) {
var itemsContainer = this;
var target = e.target;
var multiSelect = itemsContainer.multiSelect;
if (multiSelect) {
if (multiSelect.onContainerClick.call(itemsContainer, e) === false) {
return;
}
}
itemShortcuts.onClick.call(itemsContainer, e);
}
function disableEvent(e) {
e.preventDefault();
e.stopPropagation();
return false;
}
function onContextMenu(e) {
var itemsContainer = this;
var target = e.target;
var card = dom.parentWithAttribute(target, 'data-id');
// check for serverId, it won't be present on selectserver
if (card && card.getAttribute('data-serverid')) {
inputManager.trigger('menu', {
sourceElement: card
});
e.preventDefault();
e.stopPropagation();
return false;
}
}
function getShortcutOptions() {
return {
click: false
};
}
ItemsContainerProtoType.enableMultiSelect = function (enabled) {
var current = this.multiSelect;
if (!enabled) {
if (current) {
current.destroy();
this.multiSelect = null;
}
return;
}
if (current) {
return;
}
var self = this;
require(['multiSelect'], function (MultiSelect) {
self.multiSelect = new MultiSelect({
container: self,
bindOnClick: false
});
});
};
function onDrop(evt, itemsContainer) {
var el = evt.item;
var newIndex = evt.newIndex;
var itemId = el.getAttribute('data-playlistitemid');
var playlistId = el.getAttribute('data-playlistid');
if (!playlistId) {
var oldIndex = evt.oldIndex;
el.dispatchEvent(new CustomEvent('itemdrop', {
detail: {
oldIndex: oldIndex,
newIndex: newIndex,
playlistItemId: itemId
},
bubbles: true,
cancelable: false
}));
return;
}
var serverId = el.getAttribute('data-serverid');
var 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();
});
}
ItemsContainerProtoType.enableDragReordering = function (enabled) {
var current = this.sortable;
if (!enabled) {
if (current) {
current.destroy();
this.sortable = null;
}
return;
}
if (current) {
return;
}
var self = this;
require(['sortable'], function (Sortable) {
self.sortable = new Sortable(self, {
draggable: ".listItem",
handle: '.listViewDragHandle',
// dragging ended
onEnd: function (/**Event*/evt) {
return onDrop(evt, self);
}
});
});
};
function onUserDataChanged(e, apiClient, userData) {
var itemsContainer = this;
require(['cardBuilder'], function (cardBuilder) {
cardBuilder.onUserDataChanged(userData, itemsContainer);
});
var eventsToMonitor = getEventsToMonitor(itemsContainer);
// TODO: Check user data change reason?
if (eventsToMonitor.indexOf('markfavorite') !== -1) {
itemsContainer.notifyRefreshNeeded();
}
else if (eventsToMonitor.indexOf('markplayed') !== -1) {
itemsContainer.notifyRefreshNeeded();
}
}
function getEventsToMonitor(itemsContainer) {
var monitor = itemsContainer.getAttribute('data-monitor');
if (monitor) {
return monitor.split(',');
}
return [];
}
function onTimerCreated(e, apiClient, data) {
var itemsContainer = this;
if (getEventsToMonitor(itemsContainer).indexOf('timers') !== -1) {
itemsContainer.notifyRefreshNeeded();
return;
}
var programId = data.ProgramId;
// This could be null, not supported by all tv providers
var newTimerId = data.Id;
require(['cardBuilder'], function (cardBuilder) {
cardBuilder.onTimerCreated(programId, newTimerId, itemsContainer);
});
}
function onSeriesTimerCreated(e, apiClient, data) {
var itemsContainer = this;
if (getEventsToMonitor(itemsContainer).indexOf('seriestimers') !== -1) {
itemsContainer.notifyRefreshNeeded();
return;
}
}
function onTimerCancelled(e, apiClient, data) {
var itemsContainer = this;
if (getEventsToMonitor(itemsContainer).indexOf('timers') !== -1) {
itemsContainer.notifyRefreshNeeded();
return;
}
var id = data.Id;
require(['cardBuilder'], function (cardBuilder) {
cardBuilder.onTimerCancelled(id, itemsContainer);
});
}
function onSeriesTimerCancelled(e, apiClient, data) {
var itemsContainer = this;
if (getEventsToMonitor(itemsContainer).indexOf('seriestimers') !== -1) {
itemsContainer.notifyRefreshNeeded();
return;
}
var id = data.Id;
require(['cardBuilder'], function (cardBuilder) {
cardBuilder.onSeriesTimerCancelled(id, itemsContainer);
});
}
function onLibraryChanged(e, apiClient, data) {
var itemsContainer = this;
var eventsToMonitor = getEventsToMonitor(itemsContainer);
if (eventsToMonitor.indexOf('seriestimers') !== -1 || eventsToMonitor.indexOf('timers') !== -1) {
// yes this is an assumption
return;
}
var itemsAdded = data.ItemsAdded || [];
var itemsRemoved = data.ItemsRemoved || [];
if (!itemsAdded.length && !itemsRemoved.length) {
return;
}
var parentId = itemsContainer.getAttribute('data-parentid');
if (parentId) {
var foldersAddedTo = data.FoldersAddedTo || [];
var foldersRemovedFrom = data.FoldersRemovedFrom || [];
var collectionFolders = data.CollectionFolders || [];
if (foldersAddedTo.indexOf(parentId) === -1 && foldersRemovedFrom.indexOf(parentId) === -1 && collectionFolders.indexOf(parentId) === -1) {
return;
}
}
itemsContainer.notifyRefreshNeeded();
}
function onPlaybackStopped(e, stopInfo) {
var itemsContainer = this;
var state = stopInfo.state;
var eventsToMonitor = getEventsToMonitor(itemsContainer);
if (state.NowPlayingItem && state.NowPlayingItem.MediaType === 'Video') {
if (eventsToMonitor.indexOf('videoplayback') !== -1) {
itemsContainer.notifyRefreshNeeded(true);
return;
}
}
else if (state.NowPlayingItem && state.NowPlayingItem.MediaType === 'Audio') {
if (eventsToMonitor.indexOf('audioplayback') !== -1) {
itemsContainer.notifyRefreshNeeded(true);
return;
}
}
}
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];
if (handler) {
owner = owner || serverNotifications;
events.off(owner, name, handler);
instance['event_' + name] = null;
}
}
ItemsContainerProtoType.createdCallback = function () {
this.classList.add('itemsContainer');
};
ItemsContainerProtoType.attachedCallback = function () {
this.addEventListener('click', onClick);
if (browser.touch) {
this.addEventListener('contextmenu', disableEvent);
} else {
if (this.getAttribute('data-contextmenu') !== 'false') {
this.addEventListener('contextmenu', onContextMenu);
}
}
if (layoutManager.desktop || layoutManager.mobile) {
if (this.getAttribute('data-multiselect') !== 'false') {
this.enableMultiSelect(true);
}
}
if (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);
if (this.getAttribute('data-dragreorder') === 'true') {
this.enableDragReordering(true);
}
};
ItemsContainerProtoType.detachedCallback = function () {
clearRefreshInterval(this);
this.enableMultiSelect(false);
this.enableDragReordering(false);
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, true);
this.paused = true;
};
ItemsContainerProtoType.resume = function (options) {
this.paused = false;
var refreshIntervalEndTime = this.refreshIntervalEndTime;
if (refreshIntervalEndTime) {
var remainingMs = refreshIntervalEndTime - new Date().getTime();
if (remainingMs > 0 && !this.needsRefresh) {
resetRefreshInterval(this, remainingMs);
} else {
this.needsRefresh = true;
this.refreshIntervalEndTime = null;
}
}
if (this.needsRefresh || (options && options.refresh)) {
return this.refreshItems();
}
return Promise.resolve();
};
ItemsContainerProtoType.refreshItems = function () {
if (!this.fetchData) {
return Promise.resolve();
}
if (this.paused) {
this.needsRefresh = true;
return Promise.resolve();
}
this.needsRefresh = false;
return this.fetchData().then(onDataFetched.bind(this));
};
ItemsContainerProtoType.notifyRefreshNeeded = function (isInForeground) {
if (this.paused) {
this.needsRefresh = true;
return;
}
var timeout = this.refreshTimeout;
if (timeout) {
clearTimeout(timeout);
}
if (isInForeground === true) {
this.refreshItems();
} else {
this.refreshTimeout = setTimeout(this.refreshItems.bind(this), 10000);
}
};
function clearRefreshInterval(itemsContainer, isPausing) {
if (itemsContainer.refreshInterval) {
clearInterval(itemsContainer.refreshInterval);
itemsContainer.refreshInterval = null;
if (!isPausing) {
itemsContainer.refreshIntervalEndTime = null;
}
}
}
function resetRefreshInterval(itemsContainer, intervalMs) {
clearRefreshInterval(itemsContainer);
if (!intervalMs) {
intervalMs = parseInt(itemsContainer.getAttribute('data-refreshinterval') || '0');
}
if (intervalMs) {
itemsContainer.refreshInterval = setInterval(itemsContainer.notifyRefreshNeeded.bind(itemsContainer), intervalMs);
itemsContainer.refreshIntervalEndTime = new Date().getTime() + intervalMs;
}
}
function onDataFetched(result) {
var items = result.Items || result;
var parentContainer = this.parentContainer;
if (parentContainer) {
if (items.length) {
parentContainer.classList.remove('hide');
} else {
parentContainer.classList.add('hide');
}
}
// Scroll back up so they can see the results from the beginning
// TODO: Find scroller
//window.scrollTo(0, 0);
var activeElement = document.activeElement;
var focusId;
var hasActiveElement;
if (this.contains(activeElement)) {
hasActiveElement = true;
focusId = activeElement.getAttribute('data-id');
}
this.innerHTML = this.getItemsHtml(items);
imageLoader.lazyChildren(this);
if (hasActiveElement) {
setFocus(this, focusId);
}
resetRefreshInterval(this);
if (this.afterRefresh) {
this.afterRefresh(result);
}
}
function setFocus(itemsContainer, focusId) {
if (focusId) {
var newElement = itemsContainer.querySelector('[data-id="' + focusId + '"]');
if (newElement) {
try {
focusManager.focus(newElement);
return;
}
catch (err) {
}
}
}
focusManager.autoFocus(itemsContainer);
}
document.registerElement('emby-itemscontainer', {
prototype: ItemsContainerProtoType,
extends: 'div'
});
});

View file

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

View file

@ -0,0 +1,105 @@
define(['require', 'css!./emby-progressring', 'registerElement'], function (require) {
'use strict';
var EmbyProgressRing = Object.create(HTMLDivElement.prototype);
EmbyProgressRing.createdCallback = function () {
this.classList.add('progressring');
var instance = this;
require(['text!./emby-progressring.template.html'], function (template) {
instance.innerHTML = template;
//if (window.MutationObserver) {
// // create an observer instance
// var observer = new MutationObserver(function (mutations) {
// mutations.forEach(function (mutation) {
// instance.setProgress(parseFloat(instance.getAttribute('data-progress') || '0'));
// });
// });
// // configuration of the observer:
// var config = { attributes: true, childList: false, characterData: false };
// // pass in the target node, as well as the observer options
// observer.observe(instance, config);
// instance.observer = observer;
//}
instance.setProgress(parseFloat(instance.getAttribute('data-progress') || '0'));
});
};
EmbyProgressRing.setProgress = function (progress) {
progress = Math.floor(progress);
var angle;
if (progress < 25) {
angle = -90 + (progress / 100) * 360;
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)';
}
else if (progress >= 25 && progress < 50) {
angle = -90 + ((progress - 25) / 100) * 360;
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)';
}
else if (progress >= 50 && progress < 75) {
angle = -90 + ((progress - 50) / 100) * 360;
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)';
}
else if (progress >= 75 && progress <= 100) {
angle = -90 + ((progress - 75) / 100) * 360;
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;
if (observer) {
// later, you can stop observing
observer.disconnect();
this.observer = null;
}
};
document.registerElement('emby-progressring', {
prototype: EmbyProgressRing,
extends: 'div'
});
return 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,107 @@
.mdl-radio {
position: relative;
line-height: 24px;
display: inline-block;
box-sizing: border-box;
margin: 0;
padding-left: 0;
}
.radio-label-block {
display: flex;
align-items: center;
margin-top: .5em;
margin-bottom: .5em;
}
.mdl-radio {
padding-left: 24px;
}
.mdl-radio__button {
line-height: 24px;
position: absolute;
/* 1px is for focusing purposes, so the focusManager doesn't skip over it */
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;
box-sizing: border-box;
width: 16px;
height: 16px;
margin: 0;
cursor: pointer;
border: 2px solid currentcolor;
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, 0.26);
cursor: auto;
}
.mdl-radio__inner-circle {
position: absolute;
z-index: 1;
margin: 0;
top: 8px;
left: 4px;
box-sizing: border-box;
width: 8px;
height: 8px;
cursor: pointer;
transition-duration: 0.28s;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-property: -webkit-transform;
transition-property: transform;
transition-property: transform, -webkit-transform;
-webkit-transform: scale3d(0, 0, 0);
transform: scale3d(0, 0, 0);
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, 0.26);
cursor: auto;
}
.mdl-radio__button:focus + .mdl-radio__label + .mdl-radio__outer-circle + .mdl-radio__inner-circle {
box-shadow: 0 0 0px 10px rgba(255, 255, 255, 0.76);
}
.mdl-radio__button:checked:focus + .mdl-radio__label + .mdl-radio__outer-circle + .mdl-radio__inner-circle {
box-shadow: 0 0 0 10px rgba(0, 164, 220, 0.26)
}
.mdl-radio__label {
cursor: pointer;
}
.mdl-radio__button:disabled + .mdl-radio__label {
color: rgba(0,0,0, 0.26);
cursor: auto;
}

View file

@ -0,0 +1,48 @@
define(['css!./emby-radio', 'registerElement'], function () {
'use strict';
var EmbyRadioPrototype = Object.create(HTMLInputElement.prototype);
function onKeyDown(e) {
// Don't submit form on enter
if (e.keyCode === 13) {
e.preventDefault();
this.checked = true;
return false;
}
}
EmbyRadioPrototype.attachedCallback = function () {
if (this.getAttribute('data-radio') === 'true') {
return;
}
this.setAttribute('data-radio', 'true');
this.classList.add('mdl-radio__button');
var labelElement = this.parentNode;
//labelElement.classList.add('"mdl-radio mdl-js-radio mdl-js-ripple-effect');
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,60 @@
.emby-scrollbuttons-scroller {
position: relative;
}
.scrollbuttoncontainer {
position: absolute;
top: 0;
bottom: 0;
align-items: 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: -moz-linear-gradient(left,#000 0,rgba(0,0,0,0) 100%);
background: -webkit-linear-gradient(left,#000 0,rgba(0,0,0,0) 100%);
background: linear-gradient(to right,#000,rgba(0,0,0,0));
}
.scrollbuttoncontainer-right {
background: rgba(20, 20, 20, .5);
background: -moz-linear-gradient(right,#000 0,rgba(0,0,0,0) 100%);
background: -webkit-linear-gradient(right,#000 0,rgba(0,0,0,0) 100%);
background: linear-gradient(to left,#000,rgba(0,0,0,0));
}
.emby-scrollbuttons-scroller:hover .scrollbuttoncontainer {
display: flex;
}
.scrollbuttoncontainer-left {
left: 0;
}
.scrollbuttoncontainer-right {
right: 0;
}
.emby-scrollbuttons-scrollbutton {
margin: 0 -.2em;
transition: transform 160ms ease-out;
}
.scrollbuttoncontainer:hover > .emby-scrollbuttons-scrollbutton {
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,201 @@
define(['layoutManager', 'dom', 'css!./emby-scrollbuttons', 'registerElement', 'paper-icon-button-light'], function (layoutManager, dom) {
'use strict';
var EmbyScrollButtonsPrototype = Object.create(HTMLDivElement.prototype);
EmbyScrollButtonsPrototype.createdCallback = function () {
};
function getScrollButtonContainerHtml(direction) {
var html = '';
var hide = direction === 'left' ? ' hide' : '';
html += '<div class="scrollbuttoncontainer scrollbuttoncontainer-' + direction + hide + '">';
var icon = direction === 'left' ? '&#xE5CB;' : '&#xE5CC;';
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>';
return html;
}
function getScrollPosition(parent) {
if (parent.getScrollPosition) {
return parent.getScrollPosition();
}
return 0;
}
function getScrollWidth(parent) {
if (parent.getScrollSize) {
return parent.getScrollSize();
}
return 0;
}
function onScrolledToPosition(scrollButtons, pos, scrollWidth) {
if (pos > 0) {
scrollButtons.scrollButtonsLeft.classList.remove('hide');
} else {
scrollButtons.scrollButtonsLeft.classList.add('hide');
}
if (scrollWidth > 0) {
pos += scrollButtons.offsetWidth;
if (pos >= scrollWidth) {
scrollButtons.scrollButtonsRight.classList.add('hide');
} else {
scrollButtons.scrollButtonsRight.classList.remove('hide');
}
}
}
function onScroll(e) {
var scrollButtons = this;
var scroller = this.scroller;
var pos = getScrollPosition(scroller);
var scrollWidth = getScrollWidth(scroller);
onScrolledToPosition(scrollButtons, pos, scrollWidth);
}
function getStyleValue(style, name) {
var value = style.getPropertyValue(name);
if (!value) {
return 0;
}
value = value.replace('px', '');
if (!value) {
return 0;
}
value = parseInt(value);
if (isNaN(value)) {
return 0;
}
return value;
}
function getScrollSize(elem) {
var scrollSize = elem.offsetWidth;
var style = window.getComputedStyle(elem, null);
var paddingLeft = getStyleValue(style, 'padding-left');
if (paddingLeft) {
scrollSize -= paddingLeft;
}
var paddingRight = getStyleValue(style, 'padding-right');
if (paddingRight) {
scrollSize -= paddingRight;
}
var slider = elem.getScrollSlider();
style = window.getComputedStyle(slider, null);
paddingLeft = getStyleValue(style, 'padding-left');
if (paddingLeft) {
scrollSize -= paddingLeft;
}
paddingRight = getStyleValue(style, 'padding-right');
if (paddingRight) {
scrollSize -= paddingRight;
}
return scrollSize;
}
function onScrollButtonClick(e) {
var parent = dom.parentWithAttribute(this, 'is', 'emby-scroller');
var direction = this.getAttribute('data-direction');
var scrollSize = getScrollSize(parent);
var pos = getScrollPosition(parent);
var newPos;
if (direction === 'left') {
newPos = Math.max(0, pos - scrollSize);
} else {
newPos = pos + scrollSize;
}
parent.scrollToPosition(newPos, false);
}
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: false,
passive: true
});
};
EmbyScrollButtonsPrototype.detachedCallback = function () {
var parent = this.scroller;
this.scroller = null;
var scrollHandler = this.scrollHandler;
if (parent && scrollHandler) {
parent.removeScrollEventListener(scrollHandler, {
capture: false,
passive: true
});
}
this.scrollHandler = null;
this.scrollButtonsLeft = null;
this.scrollButtonsRight = null;
};
document.registerElement('emby-scrollbuttons', {
prototype: EmbyScrollButtonsPrototype,
extends: 'div'
});
});

View file

@ -0,0 +1,224 @@
define(['scroller', 'dom', 'layoutManager', 'inputManager', 'focusManager', 'browser', 'registerElement'], function (scroller, dom, layoutManager, inputManager, focusManager, browser) {
'use strict';
var ScrollerProtoType = Object.create(HTMLDivElement.prototype);
ScrollerProtoType.createdCallback = function () {
this.classList.add('emby-scroller');
};
function initCenterFocus(elem, scrollerInstance) {
dom.addEventListener(elem, 'focus', function (e) {
var focused = focusManager.focusableParent(e.target);
if (focused) {
scrollerInstance.toCenter(focused);
}
}, {
capture: true,
passive: true
});
}
ScrollerProtoType.scrollToBeginning = function () {
if (this.scroller) {
this.scroller.slideTo(0, true);
}
};
ScrollerProtoType.toStart = function (elem, immediate) {
if (this.scroller) {
this.scroller.toStart(elem, immediate);
}
};
ScrollerProtoType.toCenter = function (elem, immediate) {
if (this.scroller) {
this.scroller.toCenter(elem, immediate);
}
};
ScrollerProtoType.scrollToPosition = function (pos, immediate) {
if (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) {
if (this.scroller) {
dom.addEventListener(this.scroller.getScrollFrame(), this.scroller.getScrollEventName(), fn, options);
}
};
ScrollerProtoType.removeScrollEventListener = function (fn, options) {
if (this.scroller) {
dom.removeEventListener(this.scroller.getScrollFrame(), this.scroller.getScrollEventName(), fn, options);
}
};
function onInputCommand(e) {
var cmd = e.detail.command;
if (cmd === 'end') {
focusManager.focusLast(this, '.' + this.getAttribute('data-navcommands'));
e.preventDefault();
e.stopPropagation();
}
else if (cmd === 'pageup') {
focusManager.moveFocus(e.target, this, '.' + this.getAttribute('data-navcommands'), -12);
e.preventDefault();
e.stopPropagation();
}
else if (cmd === 'pagedown') {
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
});
// initialise
headroom.init();
headroom.add(document.querySelector('.skinHeader'));
elem.headroom = headroom;
});
}
ScrollerProtoType.attachedCallback = function () {
if (this.getAttribute('data-navcommands')) {
inputManager.on(this, onInputCommand);
}
var horizontal = this.getAttribute('data-horizontal') !== 'false';
var slider = this.querySelector('.scrollSlider');
if (horizontal) {
slider.style['white-space'] = 'nowrap';
}
var bindHeader = this.getAttribute('data-bindheader') === 'true';
var scrollFrame = this;
var enableScrollButtons = layoutManager.desktop && horizontal && this.getAttribute('data-scrollbuttons') !== 'false';
var options = {
horizontal: horizontal,
mouseDragging: 1,
mouseWheel: this.getAttribute('data-mousewheel') !== 'false',
touchDragging: 1,
slidee: slider,
scrollBy: 200,
speed: horizontal ? 270 : 240,
//immediateSpeed: pageOptions.immediateSpeed,
elasticBounds: 1,
dragHandle: 1,
scrollWidth: this.getAttribute('data-scrollsize') === 'auto' ? null : 5000000,
autoImmediate: true,
skipSlideToWhenVisible: this.getAttribute('data-skipfocuswhenvisible') === 'true',
dispatchScrollEvent: enableScrollButtons || bindHeader || this.getAttribute('data-scrollevent') === 'true',
hideScrollbar: enableScrollButtons || this.getAttribute('data-hidescrollbar') === 'true',
allowNativeSmoothScroll: this.getAttribute('data-allownativesmoothscroll') === 'true' && !enableScrollButtons,
allowNativeScroll: !enableScrollButtons,
forceHideScrollbars: enableScrollButtons,
// In edge, with the native scroll, the content jumps around when hovering over the buttons
requireAnimation: enableScrollButtons && browser.edge
};
// If just inserted it might not have any height yet - yes this is a hack
this.scroller = new scroller(scrollFrame, options);
this.scroller.init();
if (layoutManager.tv && this.getAttribute('data-centerfocus')) {
initCenterFocus(this, this.scroller);
}
if (bindHeader) {
initHeadroom(this);
}
if (enableScrollButtons) {
loadScrollButtons(this);
}
};
function loadScrollButtons(scroller) {
require(['emby-scrollbuttons'], function () {
scroller.insertAdjacentHTML('beforeend', '<div is="emby-scrollbuttons"></div>');
});
}
ScrollerProtoType.pause = function () {
var headroom = this.headroom;
if (headroom) {
headroom.pause();
}
};
ScrollerProtoType.resume = function () {
var headroom = this.headroom;
if (headroom) {
headroom.resume();
}
};
ScrollerProtoType.detachedCallback = function () {
if (this.getAttribute('data-navcommands')) {
inputManager.off(this, onInputCommand);
}
var headroom = this.headroom;
if (headroom) {
headroom.destroy();
this.headroom = null;
}
var scrollerInstance = this.scroller;
if (scrollerInstance) {
scrollerInstance.destroy();
this.scroller = null;
}
};
document.registerElement('emby-scroller', {
prototype: ScrollerProtoType,
extends: 'div'
});
});

View file

@ -0,0 +1,110 @@
.emby-select {
display: block;
margin: 0;
margin-bottom: 0 !important;
/* Remove select styling */
/* Font size must the 16px or larger to prevent iOS page zoom on focus */
font-size: 110%;
/* General select styles: change as needed */
font-family: inherit;
font-weight: inherit;
padding: .5em 1.9em .5em .5em;
/* Prevent padding from causing width overflow */
box-sizing: border-box;
outline: none !important;
-webkit-tap-highlight-color: rgba(0,0,0,0);
width: 100%;
}
.emby-select[disabled] {
background: none !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 {
transition: transform 180ms ease-out !important;
-webkit-transform-origin: center center;
transform-origin: center center;
}
.emby-select-focusscale:focus {
transform: scale(1.04);
z-index: 1;
}
.emby-select + .fieldDescription {
margin-top: .25em;
}
.selectContainer {
margin-bottom: 1.8em;
position: relative;
}
.selectContainer-inline {
display: inline-flex;
margin-bottom: 0;
align-items: center;
}
.selectLabel {
display: block;
margin-bottom: .25em;
}
.selectContainer-inline > .selectLabel {
margin-bottom: 0;
margin-right: .5em;
flex-shrink: 0;
}
.emby-select-withcolor {
-webkit-appearance: none;
appearance: none;
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 {
align-self: flex-end;
}

View file

@ -0,0 +1,168 @@
define(['layoutManager', 'browser', 'actionsheet', 'css!./emby-select', 'registerElement'], function (layoutManager, browser, actionsheet) {
'use strict';
var EmbySelectPrototype = Object.create(HTMLSelectElement.prototype);
function enableNativeMenu() {
if (browser.edgeUwp || browser.xboxOne) {
return true;
}
// Doesn't seem to work at all
if (browser.tizen || browser.orsay || browser.web0s) {
return false;
}
// Take advantage of the native input methods
if (browser.tv) {
return true;
}
if (layoutManager.tv) {
return false;
}
return true;
}
function triggerChange(select) {
var evt = document.createEvent("HTMLEvents");
evt.initEvent("change", false, true);
select.dispatchEvent(evt);
}
function setValue(select, value) {
select.value = value;
}
function showActionSheet(select) {
var labelElem = getLabel(select);
var 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) {
var elem = select.previousSibling;
while (elem && elem.tagName !== 'LABEL') {
elem = elem.previousSibling;
}
return elem;
}
function onFocus(e) {
var label = getLabel(this);
if (label) {
label.classList.add('selectLabelFocused');
}
}
function onBlur(e) {
var label = getLabel(this);
if (label) {
label.classList.remove('selectLabelFocused');
}
}
function onMouseDown(e) {
// e.button=0 for primary (left) mouse button click
if (!e.button && !enableNativeMenu()) {
e.preventDefault();
showActionSheet(this);
}
}
function onKeyDown(e) {
switch (e.keyCode) {
case 13:
if (!enableNativeMenu()) {
e.preventDefault();
showActionSheet(this);
}
return;
case 37:
case 38:
case 39:
case 40:
if (layoutManager.tv) {
e.preventDefault();
}
return;
default:
break;
}
}
var inputId = 0;
EmbySelectPrototype.createdCallback = function () {
if (!this.id) {
this.id = 'embyselect' + inputId;
inputId++;
}
if (!browser.firefox) {
this.classList.add('emby-select-withcolor');
if (layoutManager.tv) {
this.classList.add('emby-select-tv-withcolor');
}
}
if (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')) {
return;
}
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);
if (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) {
var label = this.parentNode.querySelector('label');
label.innerHTML = text;
};
document.registerElement('emby-select', {
prototype: EmbySelectPrototype,
extends: 'select'
});
});

View file

@ -0,0 +1,235 @@
_:-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: 150%;/*150% is needed, else ie and edge won't display the thumb properly*/
background: transparent;
-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;
-ms-flex-item-align: center;
align-self: center;
z-index: 1;
cursor: pointer;
margin: 0;
/* Disable webkit tap highlighting */
-webkit-tap-highlight-color: rgba(0,0,0,0);
display: block;
/**************************** Tracks ****************************/
/**************************** Thumbs ****************************/
/**************************** 0-value ****************************/
/**************************** Disabled ****************************/
}
.mdl-slider::-moz-focus-outer {
border: 0;
}
.mdl-slider::-ms-tooltip {
display: none;
}
.mdl-slider::-webkit-slider-runnable-track {
background: transparent;
}
.mdl-slider::-moz-range-track {
background: #444;
border: none;
width: calc(100% - 20px);
}
.mdl-slider::-moz-range-progress {
background: #00a4dc;
width: calc(100% - 20px);
}
.mdl-slider::-ms-track {
background: none;
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;
box-sizing: border-box;
border-radius: 50%;
background: #00a4dc;
border: none;
transition: transform 300ms cubic-bezier(0.4, 0, 0.2, 1), border 0.18s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.18s cubic-bezier(0.4, 0, 0.2, 1), background 0.28s cubic-bezier(0.4, 0, 0.2, 1);
}
.mdl-slider-hoverthumb::-webkit-slider-thumb {
transform: scale(.7, .7);
}
.mdl-slider:hover::-webkit-slider-thumb {
transform: none;
}
.slider-no-webkit-thumb::-webkit-slider-thumb {
opacity: 0 !important;
}
.mdl-slider::-moz-range-thumb {
-moz-appearance: none;
width: 0.9em;
height: 0.9em;
box-sizing: border-box;
border-radius: 50%;
background-image: none;
background: #00a4dc;
border: none;
transform: Scale(1.4, 1.4);
}
.mdl-slider::-ms-thumb {
-webkit-appearance: none;
width: 1.8em;
height: 1.8em;
box-sizing: border-box;
border-radius: 50%;
background: #00a4dc;
border: none;
transform: scale(.9, .9);
transition: transform 300ms cubic-bezier(0.4, 0, 0.2, 1), border 0.18s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.18s cubic-bezier(0.4, 0, 0.2, 1), background 0.28s cubic-bezier(0.4, 0, 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: none;
display: flex;
flex-direction: row;
}
.mdl-slider-background-flex-container {
padding-left: 10px;
padding-right: 10px;
width: 100%;
box-sizing: border-box;
margin-top: -.05em;
top: 50%;
position: absolute;
}
.mdl-slider-background-flex {
background: #333;
height: .2em;
margin-top: -.08em;
width: 100%;
top: 50%;
left: 0;
display: flex;
overflow: hidden;
border: 0;
padding: 0;
}
.mdl-slider-background-flex-inner {
position: relative;
width: 100%;
}
.mdl-slider-background-lower {
/*transition: width 0.18s cubic-bezier(0.4, 0, 0.2, 1);*/
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%;
/*transition: transform 0.18s cubic-bezier(0.4, 0, 0.2, 1);*/
transform-origin: left center;
transform: scaleX(0);
}
.mdl-slider-background-upper {
/*transition: left 0.18s cubic-bezier(0.4, 0, 0.2, 1), width 0.18s cubic-bezier(0.4, 0, 0.2, 1);*/
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;
transform: translate3d(-48%, -120%, 0);
background: #282828;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
}
.sliderBubbleText {
margin: 0;
padding: .5em .75em;
}

View file

@ -0,0 +1,272 @@
define(['browser', 'dom', 'layoutManager', 'css!./emby-slider', 'registerElement', 'emby-input'], function (browser, dom, layoutManager) {
'use strict';
var EmbySliderPrototype = Object.create(HTMLInputElement.prototype);
var supportsNativeProgressStyle = browser.firefox;
var supportsValueSetOverride = false;
var enableWidthWithTransform;
if (Object.getOwnPropertyDescriptor && Object.defineProperty) {
var descriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value');
// descriptor returning null in webos
if (descriptor && descriptor.configurable) {
supportsValueSetOverride = true;
}
}
function updateValues() {
var range = this;
var value = range.value;
// put this on a callback. Doing it within the event sometimes causes the slider to get hung up and not respond
requestAnimationFrame(function () {
var backgroundLower = range.backgroundLower;
if (backgroundLower) {
var fraction = (value - range.min) / (range.max - range.min);
if (enableWidthWithTransform) {
backgroundLower.style.transform = 'scaleX(' + (fraction) + ')';
} else {
fraction *= 100;
backgroundLower.style.width = fraction + '%';
}
}
});
}
function updateBubble(range, value, bubble, bubbleText) {
requestAnimationFrame(function () {
bubble.style.left = value + '%';
if (range.getBubbleHtml) {
value = range.getBubbleHtml(value);
} else {
if (range.getBubbleText) {
value = range.getBubbleText(value);
} else {
value = Math.round(value);
}
value = '<h1 class="sliderBubbleText">' + value + '</h1>';
}
bubble.innerHTML = value;
});
}
EmbySliderPrototype.attachedCallback = function () {
if (this.getAttribute('data-embyslider') === 'true') {
return;
}
if (enableWidthWithTransform == null) {
//enableWidthWithTransform = browser.supportsCssAnimation();
}
this.setAttribute('data-embyslider', 'true');
this.classList.add('mdl-slider');
this.classList.add('mdl-js-slider');
if (browser.noFlex) {
this.classList.add('slider-no-webkit-thumb');
}
if (!layoutManager.mobile) {
this.classList.add('mdl-slider-hoverthumb');
}
var containerElement = this.parentNode;
containerElement.classList.add('mdl-slider-container');
var htmlToInsert = '';
if (!supportsNativeProgressStyle) {
htmlToInsert += '<div class="mdl-slider-background-flex-container">';
htmlToInsert += '<div class="mdl-slider-background-flex">';
htmlToInsert += '<div class="mdl-slider-background-flex-inner">';
// the more of these, the more ranges we can display
htmlToInsert += '<div class="mdl-slider-background-upper"></div>';
if (enableWidthWithTransform) {
htmlToInsert += '<div class="mdl-slider-background-lower mdl-slider-background-lower-withtransform"></div>';
} else {
htmlToInsert += '<div class="mdl-slider-background-lower"></div>';
}
htmlToInsert += '</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');
var hasHideClass = sliderBubble.classList.contains('hide');
dom.addEventListener(this, 'input', function (e) {
this.dragging = true;
updateBubble(this, this.value, sliderBubble);
if (hasHideClass) {
sliderBubble.classList.remove('hide');
hasHideClass = false;
}
}, {
passive: true
});
dom.addEventListener(this, 'change', function () {
this.dragging = false;
updateValues.call(this);
sliderBubble.classList.add('hide');
hasHideClass = true;
}, {
passive: true
});
// In firefox this feature disrupts the ability to move the slider
if (!browser.firefox) {
dom.addEventListener(this, (window.PointerEvent ? 'pointermove' : 'mousemove'), function (e) {
if (!this.dragging) {
var rect = this.getBoundingClientRect();
var clientX = e.clientX;
var bubbleValue = (clientX - rect.left) / rect.width;
bubbleValue *= 100;
updateBubble(this, bubbleValue, sliderBubble);
if (hasHideClass) {
sliderBubble.classList.remove('hide');
hasHideClass = false;
}
}
}, {
passive: true
});
dom.addEventListener(this, (window.PointerEvent ? 'pointerleave' : 'mouseleave'), function () {
sliderBubble.classList.add('hide');
hasHideClass = true;
}, {
passive: true
});
}
if (!supportsNativeProgressStyle) {
if (supportsValueSetOverride) {
this.addEventListener('valueset', updateValues);
} else {
startInterval(this);
}
}
};
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) {
if (!runtime) {
return [];
}
return ranges.map(function (r) {
return {
start: (r.start / runtime) * 100,
end: (r.end / runtime) * 100
};
});
}
EmbySliderPrototype.setBufferedRanges = function (ranges, runtime, position) {
var elem = this.backgroundUpper;
if (!elem) {
return;
}
if (runtime != null) {
ranges = mapRangesFromRuntimeToPercent(ranges, runtime);
position = (position / runtime) * 100;
}
for (var i = 0, length = ranges.length; i < length; i++) {
var range = ranges[i];
if (position != null) {
if (position >= range.end) {
continue;
}
}
setRange(elem, range.start, range.end);
return;
}
setRange(elem, 0, 0);
};
EmbySliderPrototype.setIsClear = function (isClear) {
var backgroundLower = this.backgroundLower;
if (backgroundLower) {
if (isClear) {
backgroundLower.classList.add('mdl-slider-background-lower-clear');
} else {
backgroundLower.classList.remove('mdl-slider-background-lower-clear');
}
}
};
function startInterval(range) {
var interval = range.interval;
if (interval) {
clearInterval(interval);
}
range.interval = setInterval(updateValues.bind(range), 100);
}
EmbySliderPrototype.detachedCallback = function () {
var interval = this.interval;
if (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,47 @@
.emby-tab-button {
background: transparent;
box-shadow: none;
cursor: pointer;
outline: none !important;
width: auto;
font-family: inherit;
font-size: inherit;
display: inline-block;
vertical-align: middle;
flex-shrink: 0;
margin: 0;
padding: 1em .9em;
position: relative;
height: auto;
min-width: initial;
line-height: initial;
border-radius: 0 !important;
overflow: hidden;
font-weight: 600;
}
/*.emby-tab-button-active {
color: #52B54B;
}
.emby-tab-button-active.emby-button-tv {
color: #fff;
}*/
.emby-tab-button.emby-button-tv:focus {
/*color: #52B54B;*/
transform: scale(1.32);
transform-origin: center center;
}
.emby-tabs-slider {
position: relative;
}
.emby-tab-button-ripple-effect {
background: rgba(0,0,0,.7) !important;
}
.tabContent:not(.is-active) {
display: none;
}

View file

@ -0,0 +1,370 @@
define(['dom', 'scroller', 'browser', 'layoutManager', 'focusManager', 'registerElement', 'css!./emby-tabs', 'scrollStyles'], function (dom, scroller, browser, layoutManager, focusManager) {
'use strict';
var EmbyTabs = Object.create(HTMLDivElement.prototype);
var buttonClass = 'emby-tab-button';
var activeButtonClass = buttonClass + '-active';
function setActiveTabButton(tabs, newButton, oldButton, animate) {
newButton.classList.add(activeButtonClass);
}
function getFocusCallback(tabs, e) {
return function () {
onClick.call(tabs, e);
};
}
function onFocus(e) {
if (layoutManager.tv) {
if (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);
if (tabPanel) {
tabPanel.classList.remove('is-active');
}
}
function addActivePanelClass(tabs, index) {
var tabPanel = getTabPanel(tabs, index);
if (tabPanel) {
tabPanel.classList.add('is-active');
}
}
function fadeInRight(elem) {
var pct = browser.mobile ? '4%' : '0.5%';
var 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
}
}));
if (previousIndex != null && previousIndex !== index) {
removeActivePanelClass(tabs, previousIndex);
}
var newPanel = getTabPanel(tabs, index);
if (newPanel) {
// animate new panel ?
if (newPanel.animate) {
fadeInRight(newPanel);
}
newPanel.classList.add('is-active');
}
}
function onClick(e) {
if (this.focusTimeout) {
clearTimeout(this.focusTimeout);
}
var tabs = this;
var current = tabs.querySelector('.' + activeButtonClass);
var tabButton = dom.parentWithClass(e.target, buttonClass);
if (tabButton && tabButton !== current) {
if (current) {
current.classList.remove(activeButtonClass);
}
var previousIndex = current ? parseInt(current.getAttribute('data-index')) : null;
setActiveTabButton(tabs, tabButton, current, true);
var index = parseInt(tabButton.getAttribute('data-index'));
triggerBeforeTabChange(tabs, index, previousIndex);
// If toCenter is called syncronously within the click event, it sometimes ends up canceling it
setTimeout(function () {
tabs.selectedTabIndex = index;
tabs.dispatchEvent(new CustomEvent("tabchange", {
detail: {
selectedTabIndex: index,
previousIndex: previousIndex
}
}));
}, 120);
if (tabs.scroller) {
tabs.scroller.toCenter(tabButton, false);
}
}
}
function initScroller(tabs) {
if (tabs.scroller) {
return;
}
var contentScrollSlider = tabs.querySelector('.emby-tabs-slider');
if (contentScrollSlider) {
tabs.scroller = new scroller(tabs, {
horizontal: 1,
itemNav: 0,
mouseDragging: 1,
touchDragging: 1,
slidee: contentScrollSlider,
smart: true,
releaseSwing: true,
scrollBy: 200,
speed: 120,
elasticBounds: 1,
dragHandle: 1,
dynamicHandle: 1,
clickBar: 1,
hiddenScroll: true,
// In safari the transform is causing the headers to occasionally disappear or flicker
requireAnimation: !browser.safari,
allowNativeSmoothScroll: true
});
tabs.scroller.init();
} else {
tabs.classList.add('scrollX');
tabs.classList.add('hiddenScrollX');
tabs.classList.add('smoothScrollX');
}
}
EmbyTabs.createdCallback = function () {
if (this.classList.contains('emby-tabs')) {
return;
}
this.classList.add('emby-tabs');
this.classList.add('focusable');
dom.addEventListener(this, 'click', onClick, {
passive: true
});
dom.addEventListener(this, 'focus', onFocus, {
passive: true,
capture: true
});
};
EmbyTabs.focus = function () {
var selected = this.querySelector('.' + activeButtonClass);
if (selected) {
focusManager.focus(selected);
} else {
focusManager.autoFocus(this);
}
};
EmbyTabs.refresh = function () {
if (this.scroller) {
this.scroller.reload();
}
};
EmbyTabs.attachedCallback = function () {
initScroller(this);
var current = this.querySelector('.' + activeButtonClass);
var currentIndex = current ? parseInt(current.getAttribute('data-index')) : parseInt(this.getAttribute('data-index') || '0');
if (currentIndex !== -1) {
this.selectedTabIndex = currentIndex;
var tabButtons = this.querySelectorAll('.' + buttonClass);
var newTabButton = tabButtons[currentIndex];
if (newTabButton) {
setActiveTabButton(this, newTabButton, current, false);
}
}
if (!this.readyFired) {
this.readyFired = true;
this.dispatchEvent(new CustomEvent("ready", {}));
}
};
EmbyTabs.detachedCallback = function () {
if (this.scroller) {
this.scroller.destroy();
this.scroller = null;
}
dom.removeEventListener(this, 'click', onClick, {
passive: true
});
dom.removeEventListener(this, 'focus', onFocus, {
passive: true,
capture: true
});
};
function getSelectedTabButton(elem) {
return elem.querySelector('.' + activeButtonClass);
}
EmbyTabs.selectedIndex = function (selected, triggerEvent) {
var tabs = this;
if (selected == null) {
return tabs.selectedTabIndex || 0;
}
var current = tabs.selectedIndex();
tabs.selectedTabIndex = selected;
var tabButtons = tabs.querySelectorAll('.' + buttonClass);
if (current === selected || triggerEvent === false) {
triggerBeforeTabChange(tabs, selected, current);
tabs.dispatchEvent(new CustomEvent("tabchange", {
detail: {
selectedTabIndex: selected
}
}));
var currentTabButton = tabButtons[current];
setActiveTabButton(tabs, tabButtons[selected], currentTabButton, false);
if (current !== selected && currentTabButton) {
currentTabButton.classList.remove(activeButtonClass);
}
} else {
onClick.call(tabs, {
target: tabButtons[selected]
});
//tabButtons[selected].click();
}
};
function getSibling(elem, method) {
var sibling = elem[method];
while (sibling) {
if (sibling.classList.contains(buttonClass)) {
if (!sibling.classList.contains('hide')) {
return sibling;
}
}
sibling = sibling[method];
}
return null;
}
EmbyTabs.selectNext = function () {
var current = getSelectedTabButton(this);
var sibling = getSibling(current, 'nextSibling');
if (sibling) {
onClick.call(this, {
target: sibling
});
}
};
EmbyTabs.selectPrevious = function () {
var current = getSelectedTabButton(this);
var sibling = getSibling(current, 'previousSibling');
if (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 tabs = this;
var btn = this.querySelector('.emby-tab-button[data-index="' + index + '"]');
if (enabled) {
btn.classList.remove('hide');
} else {
btn.classList.remove('add');
}
};
document.registerElement('emby-tabs', {
prototype: EmbyTabs,
extends: 'div'
});
});

View file

@ -0,0 +1,32 @@
.emby-textarea {
display: block;
margin: 0;
margin-bottom: 0 !important;
/* Remove select styling */
/* Font size must the 16px or larger to prevent iOS page zoom on focus */
font-size: inherit;
/* General select styles: change as needed */
font-family: inherit;
font-weight: inherit;
color: inherit;
padding: .35em .25em;
/* Prevent padding from causing width overflow */
box-sizing: border-box;
outline: none !important;
-webkit-tap-highlight-color: rgba(0,0,0,0);
width: 100%;
}
.emby-textarea::-moz-focus-inner {
border: 0;
}
.textareaLabel {
display: inline-block;
transition: all .2s ease-out;
margin-bottom: .25em;
}
.emby-textarea + .fieldDescription {
margin-top: .25em;
}

View file

@ -0,0 +1,138 @@
define(['layoutManager', 'browser', 'css!./emby-textarea', 'registerElement', 'emby-input'], function (layoutManager, browser) {
'use strict';
function autoGrow(textarea, maxLines) {
var self = this;
if (maxLines === undefined) {
maxLines = 999;
}
/**
* Calculates the vertical padding of the element
* @param textarea
* @returns {number}
*/
self.getOffset = function (textarea) {
var style = window.getComputedStyle(textarea, null),
props = ['paddingTop', 'paddingBottom'],
offset = 0;
for (var i = 0; i < props.length; i++) {
offset += parseInt(style[props[i]]);
}
return offset;
};
var offset;
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();
}
if (self.lineHeight <= 0) {
textarea.style.overflowY = 'scroll';
textarea.style.height = 'auto';
textarea.rows = 3;
return;
}
var newHeight = 0, hasGrown = false;
if ((textarea.scrollHeight - offset) > self.maxAllowedHeight) {
textarea.style.overflowY = 'scroll';
newHeight = self.maxAllowedHeight;
}
else {
textarea.style.overflowY = 'hidden';
textarea.style.height = 'auto';
newHeight = textarea.scrollHeight/* - offset*/;
hasGrown = true;
}
textarea.style.height = newHeight + 'px';
}
// Call autogrowFn() when textarea's value is changed
textarea.addEventListener('input', autogrowFn);
textarea.addEventListener('focus', autogrowFn);
textarea.addEventListener('valueset', autogrowFn);
autogrowFn();
}
var EmbyTextAreaPrototype = Object.create(HTMLTextAreaElement.prototype);
var elementId = 0;
if (Object.getOwnPropertyDescriptor && Object.defineProperty) {
var descriptor = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value');
// descriptor returning null in webos
if (descriptor && descriptor.configurable) {
var baseSetMethod = descriptor.set;
descriptor.set = function (value) {
baseSetMethod.call(this, value);
this.dispatchEvent(new CustomEvent('valueset', {
bubbles: false,
cancelable: false
}));
};
Object.defineProperty(HTMLTextAreaElement.prototype, 'value', descriptor);
}
}
EmbyTextAreaPrototype.createdCallback = function () {
if (!this.id) {
this.id = 'embytextarea' + elementId;
elementId++;
}
};
EmbyTextAreaPrototype.attachedCallback = function () {
if (this.classList.contains('emby-textarea')) {
return;
}
this.rows = 1;
this.classList.add('emby-textarea');
var parentNode = this.parentNode;
var 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,122 @@
.mdl-switch {
position: relative;
z-index: 1;
vertical-align: middle;
display: inline-flex;
align-items: center;
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;
flex-direction: row-reverse;
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(128,128,128, 0.5);
height: 1em;
border-radius: 1em;
cursor: pointer;
}
.mdl-switch__input:checked + .mdl-switch__label + .mdl-switch__trackContainer > .mdl-switch__track {
background: rgba(0, 164, 220, 0.5);
}
.mdl-switch__input[disabled] + .mdl-switch__label + .mdl-switch__trackContainer > .mdl-switch__track {
background: rgba(0,0,0, 0.12);
cursor: auto;
}
.mdl-switch__thumb {
background: #999;
position: absolute;
left: 0;
top: -.25em;
height: 1.44em;
width: 1.44em;
border-radius: 50%;
cursor: pointer;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(0, 0, 0, 0.12);
transition-duration: 0.28s;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-property: left;
display: flex;
align-items: center;
justify-content: center;
}
.mdl-switch__input:checked + .mdl-switch__label + .mdl-switch__trackContainer > .mdl-switch__thumb {
background: #00a4dc;
left: 1.466em;
box-shadow: 0 3px 0.28em 0 rgba(0, 0, 0, 0.14), 0 3px 3px -2px rgba(0, 0, 0, 0.2), 0 1px .56em 0 rgba(0, 0, 0, 0.12);
}
.mdl-switch__input[disabled] + .mdl-switch__label + .mdl-switch__trackContainer > .mdl-switch__thumb {
background: rgb(189,189,189);
cursor: auto;
}
.mdl-switch__focus-helper {
position: absolute;
top: 50%;
left: 50%;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
display: inline-block;
box-sizing: border-box;
width: .6em;
height: .6em;
border-radius: 50%;
background-color: transparent;
}
.mdl-switch__input:focus + .mdl-switch__label + .mdl-switch__trackContainer .mdl-switch__focus-helper {
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 {
box-shadow: 0 0 0 1.39em rgba(0, 164, 220, 0.26);
background-color: rgba(0, 164, 220, 0.26);
}
.mdl-switch__label {
cursor: pointer;
margin: 0;
display: inline-flex;
align-items: center;
margin-left: .7em;
}
.mdl-switch__input[disabled] .mdl-switch__label {
color: rgb(189,189,189);
cursor: auto;
}

View file

@ -0,0 +1,50 @@
define(['css!./emby-toggle', 'registerElement'], function () {
'use strict';
var EmbyTogglePrototype = Object.create(HTMLInputElement.prototype);
function onKeyDown(e) {
// Don't submit form on enter
if (e.keyCode === 13) {
e.preventDefault();
this.checked = !this.checked;
this.dispatchEvent(new CustomEvent('change', {
bubbles: true
}));
return false;
}
}
EmbyTogglePrototype.attachedCallback = function () {
if (this.getAttribute('data-embytoggle') === 'true') {
return;
}
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,132 @@
define([], function () {
'use strict';
function getFetchPromise(request) {
var headers = request.headers || {};
if (request.dataType === 'json') {
headers.accept = 'application/json';
}
var fetchRequest = {
headers: headers,
method: request.type,
credentials: 'same-origin'
};
var contentType = request.contentType;
if (request.data) {
if (typeof request.data === 'string') {
fetchRequest.body = request.data;
} else {
fetchRequest.body = paramsToString(request.data);
contentType = contentType || 'application/x-www-form-urlencoded; charset=UTF-8';
}
}
if (contentType) {
headers['Content-Type'] = contentType;
}
var url = request.url;
if (request.query) {
var paramString = paramsToString(request.query);
if (paramString) {
url += '?' + paramString;
}
}
if (!request.timeout) {
return fetch(url, fetchRequest);
}
return fetchWithTimeout(url, fetchRequest, request.timeout);
}
function fetchWithTimeout(url, options, timeoutMs) {
console.log('fetchWithTimeout: timeoutMs: ' + timeoutMs + ', url: ' + url);
return 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];
if (value !== null && value !== undefined && value !== '') {
values.push(encodeURIComponent(key) + "=" + encodeURIComponent(value));
}
}
return values.join('&');
}
function ajax(request) {
if (!request) {
throw new Error("Request cannot be null");
}
request.headers = request.headers || {};
console.log('requesting url: ' + request.url);
return getFetchPromise(request).then(function (response) {
console.log('response status: ' + response.status + ', url: ' + request.url);
if (response.status < 400) {
if (request.dataType === 'json' || request.headers.accept === 'application/json') {
return response.json();
} else if (request.dataType === 'text' || (response.headers.get('Content-Type') || '').toLowerCase().indexOf('text/') === 0) {
return response.text();
} else {
return response;
}
} else {
return Promise.reject(response);
}
}, function (err) {
console.log('request failed to url: ' + request.url);
throw err;
});
}
return {
getFetchPromise: getFetchPromise,
ajax: ajax
};
});

View file

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

View file

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

View file

@ -0,0 +1,348 @@
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) {
e.preventDefault();
return false;
}
function renderOptions(context, selector, cssClass, items, isCheckedFn) {
var elem = context.querySelector(selector);
if (items.length) {
elem.classList.remove('hide');
} else {
elem.classList.add('hide');
}
var html = '';
html += items.map(function (filter) {
var itemHtml = '';
var checkedHtml = isCheckedFn(filter) ? ' checked' : '';
itemHtml += '<label>';
itemHtml += '<input is="emby-checkbox" type="checkbox"' + checkedHtml + ' data-filter="' + filter.Id + '" class="' + cssClass + '"/>';
itemHtml += '<span>' + filter.Name + '</span>';
itemHtml += '</label>';
return itemHtml;
}).join('');
elem.querySelector('.filterOptions').innerHTML = html;
}
function renderDynamicFilters(context, result, options) {
// If there's a huge number of these they will be really show to render
//if (result.Tags) {
// result.Tags.length = Math.min(result.Tags.length, 50);
//}
renderOptions(context, '.genreFilters', 'chkGenreFilter', result.Genres, function (i) {
// Switching from | to ,
var delimeter = (options.settings.GenreIds || '').indexOf('|') === -1 ? ',' : '|';
return (delimeter + (options.settings.GenreIds || '') + delimeter).indexOf(delimeter + i.Id + delimeter) !== -1;
});
//renderOptions(context, '.officialRatingFilters', 'chkOfficialRatingFilter', result.OfficialRatings, function (i) {
// var delimeter = '|';
// return (delimeter + (query.OfficialRatings || '') + delimeter).indexOf(delimeter + i + delimeter) != -1;
//});
//renderOptions(context, '.tagFilters', 'chkTagFilter', result.Tags, function (i) {
// var delimeter = '|';
// return (delimeter + (query.Tags || '') + delimeter).indexOf(delimeter + i + delimeter) != -1;
//});
//renderOptions(context, '.yearFilters', 'chkYearFilter', result.Years, function (i) {
// var delimeter = ',';
// return (delimeter + (query.Years || '') + delimeter).indexOf(delimeter + i + delimeter) != -1;
//});
}
function loadDynamicFilters(context, options) {
var apiClient = connectionManager.getApiClient(options.serverId);
var 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 () {
// older server
});
}
function initEditor(context, settings) {
context.querySelector('form').addEventListener('submit', onSubmit);
var elems = context.querySelectorAll('.simpleFilter');
var i, length;
for (i = 0, length = elems.length; i < length; i++) {
if (elems[i].tagName === 'INPUT') {
elems[i].checked = settings[elems[i].getAttribute('data-settingname')] || false;
} else {
elems[i].querySelector('input').checked = settings[elems[i].getAttribute('data-settingname')] || false;
}
}
var videoTypes = settings.VideoTypes ? settings.VideoTypes.split(',') : [];
elems = context.querySelectorAll('.chkVideoTypeFilter');
for (i = 0, length = elems.length; i < length; i++) {
elems[i].checked = videoTypes.indexOf(elems[i].getAttribute('data-filter')) !== -1;
}
var seriesStatuses = settings.SeriesStatus ? settings.SeriesStatus.split(',') : [];
elems = context.querySelectorAll('.chkSeriesStatus');
for (i = 0, length = elems.length; i < length; i++) {
elems[i].checked = seriesStatuses.indexOf(elems[i].getAttribute('data-filter')) !== -1;
}
if (context.querySelector('.basicFilterSection .viewSetting:not(.hide)')) {
context.querySelector('.basicFilterSection').classList.remove('hide');
} else {
context.querySelector('.basicFilterSection').classList.add('hide');
}
if (context.querySelector('.featureSection .viewSetting:not(.hide)')) {
context.querySelector('.featureSection').classList.remove('hide');
} else {
context.querySelector('.featureSection').classList.add('hide');
}
}
function saveValues(context, settings, settingsKey) {
var elems = context.querySelectorAll('.simpleFilter');
var i, length;
for (i = 0, length = elems.length; i < length; i++) {
if (elems[i].tagName === 'INPUT') {
setBasicFilter(context, settingsKey + '-filter-' + elems[i].getAttribute('data-settingname'), elems[i]);
} else {
setBasicFilter(context, settingsKey + '-filter-' + elems[i].getAttribute('data-settingname'), elems[i].querySelector('input'));
}
}
// Video type
var videoTypes = [];
elems = context.querySelectorAll('.chkVideoTypeFilter');
for (i = 0, length = elems.length; i < length; i++) {
if (elems[i].checked) {
videoTypes.push(elems[i].getAttribute('data-filter'));
}
}
userSettings.setFilter(settingsKey + '-filter-VideoTypes', videoTypes.join(','));
// Series status
var seriesStatuses = [];
elems = context.querySelectorAll('.chkSeriesStatus');
for (i = 0, length = elems.length; i < length; i++) {
if (elems[i].checked) {
seriesStatuses.push(elems[i].getAttribute('data-filter'));
}
}
// Genres
var genres = [];
elems = context.querySelectorAll('.chkGenreFilter');
for (i = 0, length = elems.length; i < length; i++) {
if (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 ? 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) {
var parent = dom.parentWithClass(elem, 'checkboxList-verticalwrap');
var elems = focusManager.getFocusableElements(parent);
var index = -1;
for (var 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];
if (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();
break;
default:
break;
}
}
function FilterMenu() {
}
function bindCheckboxInput(context, on) {
var elems = context.querySelectorAll('.checkboxList-verticalwrap');
for (var i = 0, length = elems.length; i < length; i++) {
if (on) {
inputManager.on(elems[i], onInputCommand);
} else {
inputManager.off(elems[i], onInputCommand);
}
}
}
FilterMenu.prototype.show = function (options) {
return new Promise(function (resolve, reject) {
require(['text!./filtermenu.template.html'], function (template) {
var dialogOptions = {
removeOnClose: true,
scrollY: false
};
if (layoutManager.tv) {
dialogOptions.size = 'fullscreen';
} else {
dialogOptions.size = 'small';
}
var dlg = dialogHelper.createDialog(dialogOptions);
dlg.classList.add('formDialog');
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, 'core');
var settingElements = dlg.querySelectorAll('.viewSetting');
for (var i = 0, length = settingElements.length; i < length; i++) {
if (options.visibleSettings.indexOf(settingElements[i].getAttribute('data-settingname')) === -1) {
settingElements[i].classList.add('hide');
} else {
settingElements[i].classList.remove('hide');
}
}
initEditor(dlg, options.settings);
loadDynamicFilters(dlg, options);
bindCheckboxInput(dlg, true);
dlg.querySelector('.btnCancel').addEventListener('click', function () {
dialogHelper.close(dlg);
});
if (layoutManager.tv) {
centerFocus(dlg.querySelector('.formDialogContent'), false, true);
}
var submitted;
dlg.querySelector('form').addEventListener('change', function () {
submitted = true;
//if (options.onChange) {
// saveValues(dlg, options.settings, options.settingsKey);
// options.onChange();
//}
}, true);
dialogHelper.open(dlg).then(function () {
bindCheckboxInput(dlg, false);
if (layoutManager.tv) {
centerFocus(dlg.querySelector('.formDialogContent'), false, false);
}
if (submitted) {
//if (!options.onChange) {
saveValues(dlg, options.settings, options.settingsKey);
resolve();
//}
return;
}
reject();
});
});
});
};
return 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,47 @@
.flex {
display: flex;
}
.inline-flex {
display: inline-flex;
}
.flex-direction-column {
flex-direction: column;
}
.flex-direction-row {
flex-direction: row;
}
.flex-grow {
flex-grow: 1;
}
.flex-shrink-zero {
flex-shrink: 0;
}
.align-items-center {
align-items: center;
}
.align-items-flex-start {
align-items: flex-start;
}
.justify-content-center {
justify-content: center;
}
.justify-content-flex-end {
justify-content: flex-end;
}
.flex-wrap-wrap {
flex-wrap: wrap;
}
.align-self-flex-end {
align-self: flex-end;
}

7
src/components/flvjs/flv.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,551 @@
define(['dom'], function (dom) {
'use strict';
var scopes = [];
function pushScope(elem) {
scopes.push(elem);
}
function popScope(elem) {
if (scopes.length) {
scopes.length -= 1;
}
}
function autoFocus(view, defaultToFirst, findAutoFocusElement) {
var element;
if (findAutoFocusElement !== false) {
element = view.querySelector('*[autofocus]');
if (element) {
focus(element);
return element;
}
}
if (defaultToFirst !== false) {
element = getFocusableElements(view, 1, 'noautofocus')[0];
if (element) {
focus(element);
return element;
}
}
return null;
}
function focus(element) {
try {
element.focus({
preventScroll: true
});
} catch (err) {
console.log('Error in focusManager.autoFocus: ' + err);
}
}
var focusableTagNames = ['INPUT', 'TEXTAREA', 'SELECT', 'BUTTON', 'A'];
var focusableContainerTagNames = ['BODY', 'DIALOG'];
var focusableQuery = focusableTagNames.map(function (t) {
if (t === 'INPUT') {
t += ':not([type="range"]):not([type="file"])';
}
return t + ':not([tabindex="-1"]):not(:disabled)';
}).join(',') + ',.focusable';
function isFocusable(elem) {
if (focusableTagNames.indexOf(elem.tagName) !== -1) {
return true;
}
if (elem.classList && elem.classList.contains('focusable')) {
return true;
}
return false;
}
function normalizeFocusable(elem, originalElement) {
if (elem) {
var tagName = elem.tagName;
if (!tagName || tagName === 'HTML' || tagName === 'BODY') {
elem = originalElement;
}
}
return elem;
}
function focusableParent(elem) {
var originalElement = elem;
while (!isFocusable(elem)) {
var parent = elem.parentNode;
if (!parent) {
return normalizeFocusable(elem, originalElement);
}
elem = parent;
}
return normalizeFocusable(elem, originalElement);
}
// Determines if a focusable element can be focused at a given point in time
function isCurrentlyFocusableInternal(elem) {
// http://stackoverflow.com/questions/19669786/check-if-element-is-visible-in-dom
if (elem.offsetParent === null) {
return false;
}
return true;
}
// Determines if a focusable element can be focused at a given point in time
function isCurrentlyFocusable(elem) {
if (elem.disabled) {
return false;
}
if (elem.getAttribute('tabindex') === "-1") {
return false;
}
if (elem.tagName === 'INPUT') {
var type = elem.type;
if (type === 'range') {
return false;
}
if (type === 'file') {
return false;
}
}
return isCurrentlyFocusableInternal(elem);
}
function getDefaultScope() {
return scopes[0] || document.body;
}
function getFocusableElements(parent, limit, excludeClass) {
var elems = (parent || getDefaultScope()).querySelectorAll(focusableQuery);
var focusableElements = [];
for (var i = 0, length = elems.length; i < length; i++) {
var elem = elems[i];
if (excludeClass && elem.classList.contains(excludeClass)) {
continue;
}
if (isCurrentlyFocusableInternal(elem)) {
focusableElements.push(elem);
if (limit && focusableElements.length >= limit) {
break;
}
}
}
return focusableElements;
}
function isFocusContainer(elem, direction) {
if (focusableContainerTagNames.indexOf(elem.tagName) !== -1) {
return true;
}
var classList = elem.classList;
if (classList.contains('focuscontainer')) {
return true;
}
if (direction === 0) {
if (classList.contains('focuscontainer-x')) {
return true;
}
if (classList.contains('focuscontainer-left')) {
return true;
}
}
else if (direction === 1) {
if (classList.contains('focuscontainer-x')) {
return true;
}
if (classList.contains('focuscontainer-right')) {
return true;
}
}
else if (direction === 2) {
if (classList.contains('focuscontainer-y')) {
return true;
}
}
else if (direction === 3) {
if (classList.contains('focuscontainer-y')) {
return true;
}
if (classList.contains('focuscontainer-down')) {
return true;
}
}
return false;
}
function getFocusContainer(elem, direction) {
while (!isFocusContainer(elem, direction)) {
elem = elem.parentNode;
if (!elem) {
return getDefaultScope();
}
}
return elem;
}
function getOffset(elem) {
var box;
// Support: BlackBerry 5, iOS 3 (original iPhone)
// If we don't have gBCR, just use 0,0 rather than error
if (elem.getBoundingClientRect) {
box = elem.getBoundingClientRect();
} else {
box = {
top: 0,
left: 0,
width: 0,
height: 0
};
}
if (box.right === null) {
// Create a new object because some browsers will throw an error when trying to set data onto the Rect object
var newBox = {
top: box.top,
left: box.left,
width: box.width,
height: box.height
};
box = newBox;
box.right = box.left + box.width;
box.bottom = box.top + box.height;
}
return box;
}
function nav(activeElement, direction, container, focusableElements) {
activeElement = activeElement || document.activeElement;
if (activeElement) {
activeElement = focusableParent(activeElement);
}
container = container || (activeElement ? getFocusContainer(activeElement, direction) : getDefaultScope());
if (!activeElement) {
autoFocus(container, true, false);
return;
}
var focusableContainer = dom.parentWithClass(activeElement, 'focusable');
var rect = getOffset(activeElement);
// Get elements and work out x/y points
var cache = [],
point1x = parseFloat(rect.left) || 0,
point1y = parseFloat(rect.top) || 0,
point2x = parseFloat(point1x + rect.width - 1) || point1x,
point2y = parseFloat(point1y + rect.height - 1) || point1y,
// Shortcuts to help with compression
min = Math.min,
max = Math.max;
var sourceMidX = rect.left + (rect.width / 2);
var sourceMidY = rect.top + (rect.height / 2);
var focusable = focusableElements || container.querySelectorAll(focusableQuery);
var maxDistance = Infinity;
var minDistance = maxDistance;
var nearestElement;
for (var i = 0, length = focusable.length; i < length; i++) {
var curr = focusable[i];
if (curr === activeElement) {
continue;
}
// Don't refocus into the same container
if (curr === focusableContainer) {
continue;
}
//if (!isCurrentlyFocusableInternal(curr)) {
// continue;
//}
var elementRect = getOffset(curr);
// not currently visible
if (!elementRect.width && !elementRect.height) {
continue;
}
switch (direction) {
case 0:
// left
if (elementRect.left >= rect.left) {
continue;
}
if (elementRect.right === rect.right) {
continue;
}
break;
case 1:
// right
if (elementRect.right <= rect.right) {
continue;
}
if (elementRect.left === rect.left) {
continue;
}
break;
case 2:
// up
if (elementRect.top >= rect.top) {
continue;
}
if (elementRect.bottom >= rect.bottom) {
continue;
}
break;
case 3:
// down
if (elementRect.bottom <= rect.bottom) {
continue;
}
if (elementRect.top <= rect.top) {
continue;
}
break;
default:
break;
}
var x = elementRect.left,
y = elementRect.top,
x2 = x + elementRect.width - 1,
y2 = y + elementRect.height - 1;
var intersectX = intersects(point1x, point2x, x, x2);
var intersectY = intersects(point1y, point2y, y, y2);
var midX = elementRect.left + (elementRect.width / 2);
var midY = elementRect.top + (elementRect.height / 2);
var distX;
var distY;
switch (direction) {
case 0:
// left
distX = Math.abs(point1x - Math.min(point1x, x2));
distY = intersectY ? 0 : Math.abs(sourceMidY - midY);
break;
case 1:
// right
distX = Math.abs(point2x - Math.max(point2x, x));
distY = intersectY ? 0 : Math.abs(sourceMidY - midY);
break;
case 2:
// up
distY = Math.abs(point1y - Math.min(point1y, y2));
distX = intersectX ? 0 : Math.abs(sourceMidX - midX);
break;
case 3:
// down
distY = Math.abs(point2y - Math.max(point2y, y));
distX = intersectX ? 0 : Math.abs(sourceMidX - midX);
break;
default:
break;
}
var dist = Math.sqrt(distX * distX + distY * distY);
if (dist < minDistance) {
nearestElement = curr;
minDistance = dist;
}
}
if (nearestElement) {
// See if there's a focusable container, and if so, send the focus command to that
if (activeElement) {
var nearestElementFocusableParent = dom.parentWithClass(nearestElement, 'focusable');
if (nearestElementFocusableParent && nearestElementFocusableParent !== nearestElement) {
if (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) {
var elem = document.activeElement;
elem.value = text;
}
function focusFirst(container, focusableSelector) {
var elems = container.querySelectorAll(focusableSelector);
for (var i = 0, length = elems.length; i < length; i++) {
var elem = elems[i];
if (isCurrentlyFocusableInternal(elem)) {
focus(elem);
break;
}
}
}
function focusLast(container, focusableSelector) {
var elems = [].slice.call(container.querySelectorAll(focusableSelector), 0).reverse();
for (var 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 elems = container.querySelectorAll(focusableSelector);
var list = [];
var i, length, elem;
for (i = 0, length = elems.length; i < length; i++) {
elem = elems[i];
if (isCurrentlyFocusableInternal(elem)) {
list.push(elem);
}
}
var currentIndex = -1;
for (i = 0, length = list.length; i < length; i++) {
elem = list[i];
if (sourceElement === elem || elem.contains(sourceElement)) {
currentIndex = i;
break;
}
}
if (currentIndex === -1) {
return;
}
var newIndex = currentIndex + offset;
newIndex = Math.max(0, newIndex);
newIndex = Math.min(newIndex, list.length - 1);
var newElem = list[newIndex];
if (newElem) {
focus(newElem);
}
}
return {
autoFocus: autoFocus,
focus: focus,
focusableParent: focusableParent,
getFocusableElements: getFocusableElements,
moveLeft: function (sourceElement, options) {
var container = options ? options.container : null;
var focusableElements = options ? options.focusableElements : null;
nav(sourceElement, 0, container, focusableElements);
},
moveRight: function (sourceElement, options) {
var container = options ? options.container : null;
var focusableElements = options ? options.focusableElements : null;
nav(sourceElement, 1, container, focusableElements);
},
moveUp: function (sourceElement, options) {
var container = options ? options.container : null;
var focusableElements = options ? options.focusableElements : null;
nav(sourceElement, 2, container, focusableElements);
},
moveDown: function (sourceElement, options) {
var container = options ? options.container : null;
var focusableElements = options ? options.focusableElements : null;
nav(sourceElement, 3, container, focusableElements);
},
sendText: sendText,
isCurrentlyFocusable: isCurrentlyFocusable,
pushScope: pushScope,
popScope: popScope,
focusFirst: focusFirst,
focusLast: focusLast,
moveFocus: moveFocus
};
});

View file

@ -0,0 +1,37 @@
html {
font-family: -apple-system, "Helvetica", system-ui, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen-Sans", "Ubuntu", "Cantarell", "Helvetica Neue", 'Open Sans', sans-serif;
}
html {
font-size: 93%;
-webkit-text-size-adjust: 100%;
text-size-adjust: 100%;
}
h1, h2, h3 {
/* For better bolding, since Helvetica does not support 500 weight, and 600 is too thick */
font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen-Sans", "Ubuntu", "Cantarell", "Helvetica Neue", 'Open Sans', sans-serif;
}
h1 {
font-weight: 500;
font-size: 1.8em;
}
h2 {
font-weight: 500;
font-size: 1.5em;
}
h3 {
font-weight: 500;
font-size: 1.17em;
}
.layout-tv {
font-size: 2.5vh;
}
.layout-mobile {
font-size: 90%;
}

View file

@ -0,0 +1,32 @@
h1 {
font-weight: 500;
font-size: 1.8em;
}
.layout-desktop h1 {
font-size: 2em;
}
h2 {
font-weight: 500;
font-size: 1.5em;
}
h3 {
font-weight: 500;
font-size: 1.17em;
}
@media all and (min-height: 720px) {
html {
font-size: 20px;
}
}
/* This is supposed to be 1080p, but had to reduce the min height to account for possible browser chrome */
@media all and (min-height: 1000px) {
html {
font-size: 27px;
}
}

View file

@ -0,0 +1,25 @@
@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: normal;
font-style: normal;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-feature-settings: 'liga';
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
font-feature-settings: "liga" 1;
line-height: 1;
overflow: hidden;
vertical-align: middle;
}

View file

@ -0,0 +1,115 @@
.formDialog {
display: flex;
flex-direction: column;
position: relative;
}
.formDialogHeader {
padding: 1em .5em;
display: flex;
align-items: center;
flex-shrink: 0;
}
.formDialogHeaderTitle {
margin-left: .25em;
/* In case of h1, h2, h3 */
margin-top: 0;
margin-bottom: 0;
}
.formDialogContent:not(.no-grow) {
flex-grow: 1;
}
.dialogContentInner {
padding: .5em 1em 20em 1em;
}
.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: flex;
position: absolute;
padding: 1.25em 1em;
/* Without this emby-checkbox is able to appear on top */
z-index: 1;
align-items: center;
justify-content: center;
flex-wrap: wrap;
}
.formDialogFooter-flex {
position: static;
width: 100%;
}
.formDialogFooter-vertical {
padding-bottom: 1.5em;
flex-direction: column;
width: 80% !important;
padding-top: .5em;
}
.formDialogFooterItem {
margin: .5em !important;
flex-grow: 1;
text-align: center;
flex-basis: 0;
}
.formDialogFooterItem-vertical {
max-width: none !important;
width: 100%;
justify-content: center;
margin: 1em !important;
}
.formDialogFooterItem-nomarginbottom {
margin-bottom: 0 !important;
}
.formDialogFooterItem-autosize {
flex-basis: 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,26 @@
define(['dom', 'fullscreenManager'], function (dom, fullscreenManager) {
'use strict';
function isTargetValid(target) {
if (dom.parentWithTag(target, ['BUTTON', 'INPUT', 'TEXTAREA'])) {
return false;
}
return true;
}
dom.addEventListener(window, 'dblclick', function (e) {
if (isTargetValid(e.target)) {
if (fullscreenManager.isFullScreen()) {
fullscreenManager.exitFullscreen();
} else {
fullscreenManager.requestFullscreen();
}
}
}, {
passive: true
});
});

View file

@ -0,0 +1,83 @@
define(['events', 'dom'], function (events, dom) {
'use strict';
function fullscreenManager() {
}
fullscreenManager.prototype.requestFullscreen = function (element) {
element = element || document.documentElement;
if (element.requestFullscreen) {
element.requestFullscreen();
return;
} else if (element.mozRequestFullScreen) {
element.mozRequestFullScreen();
return;
} else if (element.webkitRequestFullscreen) {
element.webkitRequestFullscreen();
return;
} else if (element.msRequestFullscreen) {
element.msRequestFullscreen();
return;
}
// Hack - This is only available for video elements in ios safari
if (element.tagName !== 'VIDEO') {
element = document.querySelector('video') || element;
}
if (element.webkitEnterFullscreen) {
element.webkitEnterFullscreen();
}
};
fullscreenManager.prototype.exitFullscreen = function () {
if (!this.isFullScreen()) {
return;
}
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
} else if (document.webkitCancelFullscreen) {
document.webkitCancelFullscreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
}
};
// TODO: use screenfull.js
fullscreenManager.prototype.isFullScreen = function () {
return document.fullscreen ||
document.mozFullScreen ||
document.webkitIsFullScreen ||
document.msFullscreenElement || /* IE/Edge syntax */
document.fullscreenElement || /* Standard syntax */
document.webkitFullscreenElement || /* Chrome, Safari and Opera syntax */
document.mozFullScreenElement; /* Firefox syntax */
};
var manager = new fullscreenManager();
function onFullScreenChange() {
events.trigger(manager, 'fullscreenchange');
}
dom.addEventListener(document, 'fullscreenchange', onFullScreenChange, {
passive: true
});
dom.addEventListener(document, 'webkitfullscreenchange', onFullScreenChange, {
passive: true
});
dom.addEventListener(document, 'mozfullscreenchange', onFullScreenChange, {
passive: true
});
return manager;
});

269
src/components/globalize.js Normal file
View file

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

View file

@ -0,0 +1,172 @@
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) {
var categories = [];
var chkCategorys = context.querySelectorAll('.chkCategory');
for (var i = 0, length = chkCategorys.length; i < length; i++) {
var type = chkCategorys[i].getAttribute('data-type');
if (chkCategorys[i].checked) {
categories.push(type);
}
}
if (categories.length >= 4) {
categories.push('series');
}
// differentiate between none and all
categories.push('all');
options.categories = categories;
}
function loadCategories(context, options) {
var selectedCategories = options.categories || [];
var chkCategorys = context.querySelectorAll('.chkCategory');
for (var i = 0, length = chkCategorys.length; i < length; i++) {
var type = chkCategorys[i].getAttribute('data-type');
chkCategorys[i].checked = !selectedCategories.length || selectedCategories.indexOf(type) !== -1;
}
}
function save(context) {
var i, length;
var 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;
var chkIndicators = context.querySelectorAll('.chkIndicator');
for (i = 0, length = chkIndicators.length; i < length; i++) {
var type = chkIndicators[i].getAttribute('data-type');
if (chkIndicators[i].getAttribute('data-default') === 'true') {
chkIndicators[i].checked = userSettings.get('guide-indicator-' + type) !== 'false';
} else {
chkIndicators[i].checked = userSettings.get('guide-indicator-' + type) === 'true';
}
}
context.querySelector('.chkColorCodedBackgrounds').checked = userSettings.get('guide-colorcodedbackgrounds') === 'true';
context.querySelector('.chkFavoriteChannelsAtTop').checked = userSettings.get('livetv-favoritechannelsattop') !== 'false';
var sortByValue = userSettings.get('livetv-channelorder') || 'Number';
var sortBys = context.querySelectorAll('.chkSortOrder');
for (i = 0, length = sortBys.length; i < length; i++) {
sortBys[i].checked = sortBys[i].value === sortByValue;
}
}
function onSortByChange() {
var newValue = this.value;
if (this.checked) {
var changed = options.query.SortBy !== newValue;
options.query.SortBy = newValue.replace('_', ',');
options.query.StartIndex = 0;
if (options.callback && changed) {
options.callback();
}
}
}
function showEditor(options) {
return new Promise(function (resolve, reject) {
var settingsChanged = false;
require(['text!./guide-settings.template.html'], function (template) {
var dialogOptions = {
removeOnClose: true,
scrollY: false
};
if (layoutManager.tv) {
dialogOptions.size = 'fullscreen';
} else {
dialogOptions.size = 'small';
}
var dlg = dialogHelper.createDialog(dialogOptions);
dlg.classList.add('formDialog');
var html = '';
html += globalize.translateDocument(template, 'core');
dlg.innerHTML = html;
dlg.addEventListener('change', function () {
settingsChanged = true;
});
dlg.addEventListener('close', function () {
if (layoutManager.tv) {
scrollHelper.centerFocus.off(dlg.querySelector('.formDialogContent'), false);
}
save(dlg);
saveCategories(dlg, options);
if (settingsChanged) {
resolve();
} else {
reject();
}
});
dlg.querySelector('.btnCancel').addEventListener('click', function () {
dialogHelper.close(dlg);
});
if (layoutManager.tv) {
scrollHelper.centerFocus.on(dlg.querySelector('.formDialogContent'), false);
}
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,430 @@
.tvguide {
display: flex;
flex-direction: column;
align-items: initial;
}
.tvGuideHeader {
white-space: nowrap;
width: 100%;
flex-direction: column;
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: flex;
}
.tvProgramSectionHeader {
margin: 0;
}
.tvProgram {
display: block;
text-decoration: none;
white-space: nowrap;
position: relative;
}
.guideProgramIndicator {
text-transform: uppercase;
border-radius: .25em;
margin-right: .5em;
font-size: 82%;
padding: .2em .25em;
display: inline-flex;
align-items: center;
justify-content: center;
text-align: center;
margin-left: 1em;
}
.guide-channelTimeslotHeader {
flex-shrink: 0;
justify-content: center;
}
.timeslotHeaders {
white-space: nowrap;
font-weight: 500;
font-size: 120%;
}
.programContainer {
white-space: nowrap;
position: relative;
align-items: flex-start;
contain: strict;
}
.channelPrograms {
white-space: nowrap;
position: relative;
contain: strict;
box-sizing: border-box;
}
.timeslotHeadersInner {
position: relative;
}
.guideSpacer {
width: .3em;
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: inline-flex;
align-items: center;
text-indent: .25em;
width: 2.0833333333333333333333333333333%;
}
.guide-channelHeaderCell, .guide-channelTimeslotHeader {
padding: 0 !important;
cursor: pointer;
outline: none !important;
width: 100%;
vertical-align: middle;
font-family: inherit;
font-size: inherit;
overflow: hidden;
text-overflow: ellipsis;
margin: 0 1px 0 0;
display: flex;
align-items: center;
text-decoration: none;
/* Needed in firefox */
text-align: left;
contain: strict;
flex-shrink: 0;
border-radius: .12em;
color: inherit;
}
.guide-channelHeaderCell {
border-width: 1px 1px 1px 0;
border-style: solid;
width: 100%;
height: 4.42em;
contain: strict;
position: relative;
background: transparent;
}
.guide-channelTimeslotHeader {
border: 0 !important;
}
/* Important - have to put the fixed width on channelsContainer, not the individual channelHeaderCell
This was causing channelsContainer to extend beyond the fixed width on ps4, tizen, lg and opera tv.
*/
.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;
flex-shrink: 0;
}
.btnGuideViewSettingsIcon {
font-size: 1.5em !important;
}
.selectDateIcon {
flex-shrink: 0;
}
@media all and (max-width: 50em) {
.newTvProgram, .liveTvProgram, .premiereTvProgram, .guideHdIcon {
display: none;
}
}
.channelPrograms {
height: 4.42em;
contain: strict;
display: flex;
flex-direction: column;
border-style: solid;
border-width: 1px 0 1px 0;
}
.channelPrograms + .channelPrograms, .guide-channelHeaderCell + .guide-channelHeaderCell {
margin-top: -1px;
}
.channelPrograms-tv, .guide-channelHeaderCell-tv {
height: 3em;
}
.guide-channelTimeslotHeader {
border-right-color: transparent;
}
.guide-channelTimeslotHeader, .timeslotHeader {
background: transparent !important;
height: 2.8em;
}
.programGrid {
padding-bottom: 4px;
flex-grow: 1;
}
.programCell {
color: inherit;
background: transparent;
border-style: solid;
border-width: 0 0 0 1px;
padding: 0 !important;
cursor: pointer;
outline: none !important;
width: 100%;
vertical-align: middle;
font-family: inherit;
font-size: inherit;
position: absolute;
top: 0;
bottom: 0;
display: flex;
text-decoration: none;
overflow: hidden;
align-items: center;
/* Needed for Firefox */
text-align: left;
contain: strict;
flex-grow: 1;
margin: 0 !important;
}
.guideProgramName {
padding: 0 .7em 0;
overflow: hidden;
text-overflow: ellipsis;
align-items: center;
display: flex;
position: relative;
flex-grow: 1;
contain: layout style paint;
/*transition: transform 60ms ease-out;*/
}
.guide-programNameCaret {
display: flex;
align-items: center;
justify-content: center;
font-size: 200%;
}
.guideProgramNameText {
margin: 0;
font-weight: normal;
overflow: hidden;
text-overflow: ellipsis;
}
.guideProgramSecondaryInfo {
display: flex;
align-items: center;
margin-top: .1em;
}
.programIcon {
margin-left: .5em;
height: 1em;
width: 1em;
font-size: 1.6em;
color: #ddd;
flex-shrink: 0;
flex-grow: 0;
}
.guide-programTextIcon {
font-weight: bold;
font-size: .9em;
padding: .16em .3em;
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: normal;
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%;
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: flex;
flex-shrink: 0;
flex-direction: column;
}
.channelsContainer, .programGrid {
contain: layout style paint;
}
.programCell, .guide-channelHeaderCell {
outline: none !important;
}
.timerIcon, .seriesTimerIcon {
color: #cc3333 !important;
}
.seriesTimerIcon-inactive {
color: inherit !important;
opacity: .7;
}
.guideOptions {
flex-shrink: 0;
display: flex;
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;
flex-shrink: 0;
}
.noRubberBanding {
/* This is needed to combat the rubber banding in iOS */
padding-bottom: 7em;
}
.guideDateTabsSlider {
text-align: center;
}
.guide-date-tab-button {
padding: .3em .7em !important;
margin: 0 .3em !important;
font-weight: normal;
}
.guide-date-tab-button.emby-tab-button-active {
border-color: transparent !important;
}
.guide-date-tab-button.emby-button-tv:focus {
border-radius: .15em !important;
transform: none !important;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,19 @@
.newTvProgram {
background: #3388cc;
color: #fff;
}
.liveTvProgram {
background: #cc3333;
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,11 @@
.headroom {
transition: transform 140ms linear;
}
.headroom--pinned {
transform: none;
}
.headroom--unpinned:not(.headroomDisabled) {
transform: translateY(-100%);
}

View file

@ -0,0 +1,349 @@
/*!
* headroom.js v0.7.0 - Give your page some headroom. Hide your header until you need it
* Copyright (c) 2014 Nick Williams - http://wicky.nillia.ms/headroom.js
* License: MIT
*/
define(['dom', 'layoutManager', 'browser', 'css!./headroom'], function (dom, layoutManager, browser) {
'use strict';
/* exported features */
var requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame;
/**
* Handles debouncing of events via requestAnimationFrame
* @see http://www.html5rocks.com/en/tutorials/speed/animations/
* @param {Function} callback The callback to handle whichever event
*/
function Debouncer(callback) {
this.callback = callback;
this.ticking = false;
}
Debouncer.prototype = {
constructor: Debouncer,
/**
* dispatches the event to the supplied callback
* @private
*/
update: function () {
if (this.callback) {
this.callback();
}
this.ticking = false;
},
/**
* Attach this as the event listeners
*/
handleEvent: function () {
if (!this.ticking) {
requestAnimationFrame(this.rafCallback || (this.rafCallback = this.update.bind(this)));
this.ticking = true;
}
}
};
function onHeadroomClearedExternally() {
this.state = null;
}
/**
* UI enhancement for fixed headers.
* Hides header when scrolling down
* Shows header when scrolling up
* @constructor
* @param {DOMElement} elem the header element
* @param {Object} options options for the widget
*/
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 = false;
this.initialClass = options.initialClass;
this.unPinnedClass = options.unPinnedClass;
this.pinnedClass = options.pinnedClass;
this.state = 'clear';
}
function onScroll() {
if (this.paused) {
return;
}
requestAnimationFrame(this.rafCallback || (this.rafCallback = this.update.bind(this)));
}
Headroom.prototype = {
constructor: Headroom,
/**
* Initialises the widget
*/
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) {
if (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);
if (i !== -1) {
this.elems.splice(i, 1);
}
},
pause: function () {
this.paused = true;
},
resume: function () {
this.paused = false;
},
/**
* Unattaches events and removes any classes that were added
*/
destroy: function () {
this.initialised = false;
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: false,
passive: true
});
},
/**
* Attaches the scroll event
* @private
*/
attachEvent: function () {
if (!this.initialised) {
this.lastKnownScrollY = this.getScrollY();
this.initialised = true;
var scrollEventName = this.scroller.getScrollEventName ? this.scroller.getScrollEventName() : 'scroll';
dom.addEventListener(this.scroller, scrollEventName, this.debouncer, {
capture: false,
passive: true
});
this.update();
}
},
/**
* Unpins the header if it's currently pinned
*/
clear: function () {
if (this.state === 'clear') {
return;
}
this.state = 'clear';
var unpinnedClass = this.unPinnedClass;
var pinnedClass = this.pinnedClass;
for (var i = 0, length = this.elems.length; i < length; i++) {
var classList = this.elems[i].classList;
classList.remove(unpinnedClass);
//classList.remove(pinnedClass);
}
},
/**
* Unpins the header if it's currently pinned
*/
pin: function () {
if (this.state === 'pin') {
return;
}
this.state = 'pin';
var unpinnedClass = this.unPinnedClass;
var pinnedClass = this.pinnedClass;
for (var i = 0, length = this.elems.length; i < length; i++) {
var classList = this.elems[i].classList;
classList.remove(unpinnedClass);
classList.add(pinnedClass);
}
},
/**
* Unpins the header if it's currently pinned
*/
unpin: function () {
if (this.state === 'unpin') {
return;
}
this.state = 'unpin';
var unpinnedClass = this.unPinnedClass;
var pinnedClass = this.pinnedClass;
for (var i = 0, length = this.elems.length; i < length; i++) {
var classList = this.elems[i].classList;
classList.add(unpinnedClass);
//classList.remove(pinnedClass);
}
},
/**
* Gets the Y scroll position
* @see https://developer.mozilla.org/en-US/docs/Web/API/Window.scrollY
* @return {Number} pixels the page has scrolled along the Y-axis
*/
getScrollY: function () {
var scroller = this.scroller;
if (scroller.getScrollPosition) {
return scroller.getScrollPosition();
}
var pageYOffset = scroller.pageYOffset;
if (pageYOffset !== undefined) {
return pageYOffset;
}
var scrollTop = scroller.scrollTop;
if (scrollTop !== undefined) {
return scrollTop;
}
return (document.documentElement || document.body).scrollTop;
},
/**
* determine if it is appropriate to unpin
* @param {int} currentScrollY the current y scroll position
* @return {bool} true if should unpin, false otherwise
*/
shouldUnpin: function (currentScrollY) {
var scrollingDown = currentScrollY > this.lastKnownScrollY,
pastOffset = currentScrollY >= this.offset;
return scrollingDown && pastOffset;
},
/**
* determine if it is appropriate to pin
* @param {int} currentScrollY the current y scroll position
* @return {bool} true if should pin, false otherwise
*/
shouldPin: function (currentScrollY) {
var scrollingUp = currentScrollY < this.lastKnownScrollY,
pastOffset = currentScrollY <= this.offset;
return scrollingUp || pastOffset;
},
/**
* Handles updating the state of the widget
*/
update: function () {
if (this.paused) {
return;
}
var currentScrollY = this.getScrollY();
var lastKnownScrollY = this.lastKnownScrollY;
var 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;
if (currentScrollY && isTv) {
this.unpin();
} else if (toleranceExceeded) {
this.clear();
}
} else if (isTv) {
//this.clear();
}
this.lastKnownScrollY = currentScrollY;
}
};
/**
* Default options
* @type {Object}
*/
Headroom.options = {
offset: 0,
scroller: window,
initialClass: 'headroom',
unPinnedClass: 'headroom--unpinned',
pinnedClass: 'headroom--pinned'
};
return Headroom;
});

View file

@ -0,0 +1,543 @@
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";
var numConfigurableSections = 7;
function renderViews(page, user, result) {
var folderHtml = '';
folderHtml += '<div class="checkboxList">';
folderHtml += result.map(function (i) {
var currentHtml = '';
var id = 'chkGroupFolder' + i.Id;
var isChecked = user.Configuration.GroupedFolders.indexOf(i.Id) !== -1;
var checkedHtml = isChecked ? ' checked="checked"' : '';
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>';
return currentHtml;
}).join('');
folderHtml += '</div>';
page.querySelector('.folderGroupList').innerHTML = folderHtml;
}
function getLandingScreenOptions(type) {
var list = [];
if (type === 'movies') {
list.push({
name: globalize.translate('Movies'),
value: 'movies',
isDefault: true
});
list.push({
name: globalize.translate('Suggestions'),
value: 'suggestions'
});
list.push({
name: globalize.translate('Favorites'),
value: 'favorites'
});
list.push({
name: globalize.translate('Collections'),
value: 'collections'
});
}
else if (type === 'tvshows') {
list.push({
name: globalize.translate('Shows'),
value: 'shows',
isDefault: true
});
list.push({
name: globalize.translate('Suggestions'),
value: 'suggestions'
});
list.push({
name: globalize.translate('Latest'),
value: 'latest'
});
list.push({
name: globalize.translate('Favorites'),
value: 'favorites'
});
}
else if (type === 'music') {
list.push({
name: globalize.translate('Suggestions'),
value: 'suggestions',
isDefault: true
});
list.push({
name: globalize.translate('Albums'),
value: 'albums'
});
list.push({
name: globalize.translate('HeaderAlbumArtists'),
value: 'albumartists'
});
list.push({
name: globalize.translate('Artists'),
value: 'artists'
});
list.push({
name: globalize.translate('Playlists'),
value: 'playlists'
});
list.push({
name: globalize.translate('Genres'),
value: 'genres'
});
}
else if (type === 'livetv') {
list.push({
name: globalize.translate('Suggestions'),
value: 'suggestions',
isDefault: true
});
list.push({
name: globalize.translate('Guide'),
value: 'guide'
});
}
return list;
}
function getLandingScreenOptionsHtml(type, userValue) {
return getLandingScreenOptions(type).map(function (o) {
var selected = userValue === o.value || (o.isDefault && !userValue);
var selectedHtml = selected ? ' selected' : '';
var optionValue = o.isDefault ? '' : o.value;
return '<option value="' + optionValue + '"' + selectedHtml + '>' + o.name + '</option>';
}).join('');
}
function renderViewOrder(context, user, result) {
var html = '';
var index = 0;
html += result.Items.map(function (view) {
var currentHtml = '';
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('Up') + '"><i class="md-icon">&#xE316;</i></button>';
currentHtml += '<button type="button" is="paper-icon-button-light" class="btnViewItemDown btnViewItemMove autoSize" title="' + globalize.translate('Down') + '"><i class="md-icon">&#xE313;</i></button>';
currentHtml += '</div>';
index++;
return currentHtml;
}).join('');
context.querySelector('.viewOrderList').innerHTML = html;
}
function updateHomeSectionValues(context, userSettings) {
for (var i = 1; i <= 7; i++) {
var select = context.querySelector('#selectHomeSection' + i);
var defaultValue = homeSections.getDefaultSection(i - 1);
var option = select.querySelector('option[value=' + defaultValue + ']') || select.querySelector('option[value=""]');
var userValue = userSettings.get('homesection' + (i - 1));
option.value = '';
if (userValue === defaultValue || !userValue) {
select.value = '';
} else {
select.value = userValue;
}
}
context.querySelector('.selectTVHomeScreen').value = userSettings.get('tvhome') || '';
}
function getPerLibrarySettingsHtml(item, user, userSettings, apiClient) {
var html = '';
var isChecked;
if (item.Type === 'Channel' || item.CollectionType === 'boxsets' || item.CollectionType === 'playlists') {
isChecked = (user.Configuration.MyMediaExcludes || []).indexOf(item.Id) === -1;
html += '<div>';
html += '<label>';
html += '<input type="checkbox" is="emby-checkbox" class="chkIncludeInMyMedia" data-folderid="' + item.Id + '"' + (isChecked ? ' checked="checked"' : '') + '/>';
html += '<span>' + globalize.translate('DisplayInMyMedia') + '</span>';
html += '</label>';
html += '</div>';
}
var excludeFromLatest = ['playlists', 'livetv', 'boxsets', 'channels'];
if (excludeFromLatest.indexOf(item.CollectionType || '') === -1) {
isChecked = user.Configuration.LatestItemsExcludes.indexOf(item.Id) === -1;
html += '<label class="fldIncludeInLatest">';
html += '<input type="checkbox" is="emby-checkbox" class="chkIncludeInLatest" data-folderid="' + item.Id + '"' + (isChecked ? ' checked="checked"' : '') + '/>';
html += '<span>' + globalize.translate('DisplayInOtherHomeScreenSections') + '</span>';
html += '</label>';
}
if (html) {
html = '<div class="checkboxListContainer">' + html + '</div>';
}
if (item.CollectionType === 'movies' || item.CollectionType === 'tvshows' || item.CollectionType === 'music' || item.CollectionType === 'livetv') {
var idForLanding = item.CollectionType === 'livetv' ? item.CollectionType : item.Id;
html += '<div class="selectContainer">';
html += '<select is="emby-select" class="selectLanding" data-folderid="' + idForLanding + '" label="' + globalize.translate('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) {
var elem = context.querySelector('.perLibrarySettings');
var html = '';
for (var 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 || false;
updateHomeSectionValues(context, userSettings);
var promise1 = apiClient.getUserViews({ IncludeHidden: true }, user.Id);
var 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 getSibling(elem, type, className) {
var sibling = elem[type];
while (sibling != null) {
if (sibling.classList.contains(className)) {
break;
}
}
if (sibling != null) {
if (!sibling.classList.contains(className)) {
sibling = null;
}
}
return sibling;
}
function onSectionOrderListClick(e) {
var target = dom.parentWithClass(e.target, 'btnViewItemMove');
if (target) {
var viewItem = dom.parentWithClass(target, 'viewItem');
if (viewItem) {
var ul = dom.parentWithClass(viewItem, 'paperList');
if (target.classList.contains('btnViewItemDown')) {
var next = viewItem.nextSibling;
if (next) {
viewItem.parentNode.removeChild(viewItem);
next.parentNode.insertBefore(viewItem, next.nextSibling);
}
} else {
var prev = viewItem.previousSibling;
if (prev) {
viewItem.parentNode.removeChild(viewItem);
prev.parentNode.insertBefore(viewItem, prev);
}
}
}
}
}
function getCheckboxItems(selector, context, isChecked) {
var inputs = context.querySelectorAll(selector);
var list = [];
for (var i = 0, length = inputs.length; i < length; i++) {
if (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, false).map(function (i) {
return i.getAttribute('data-folderid');
});
user.Configuration.MyMediaExcludes = getCheckboxItems(".chkIncludeInMyMedia", context, false).map(function (i) {
return i.getAttribute('data-folderid');
});
user.Configuration.GroupedFolders = getCheckboxItems(".chkGroupFolder", context, true).map(function (i) {
return i.getAttribute('data-folderid');
});
var viewItems = context.querySelectorAll('.viewItem');
var orderedViews = [];
var i, length;
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();
if (enableSaveConfirmation) {
require(['toast'], function (toast) {
toast(globalize.translate('SettingsSaved'));
});
}
events.trigger(instance, 'saved');
}, function () {
loading.hide();
});
});
}
function onSubmit(e) {
var self = this;
var apiClient = connectionManager.getApiClient(self.options.serverId);
var userId = self.options.userId;
var userSettings = self.options.userSettings;
userSettings.setUserInfo(userId, apiClient).then(function () {
var enableSaveConfirmation = self.options.enableSaveConfirmation;
save(self, self.options.element, userId, userSettings, apiClient, enableSaveConfirmation);
});
// Disable default form submission
if (e) {
e.preventDefault();
}
return false;
}
function onChange(e) {
var chkIncludeInMyMedia = dom.parentWithClass(e.target, 'chkIncludeInMyMedia');
if (!chkIncludeInMyMedia) {
return;
}
var section = dom.parentWithClass(chkIncludeInMyMedia, 'verticalSection');
var fldIncludeInLatest = section.querySelector('.fldIncludeInLatest');
if (fldIncludeInLatest) {
if (chkIncludeInMyMedia.checked) {
fldIncludeInLatest.classList.remove('hide');
} else {
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('LabelHomeScreenSectionValue', i));
}
options.element.innerHTML = globalize.translateDocument(template, 'core');
options.element.querySelector('.viewOrderList').addEventListener('click', onSectionOrderListClick);
options.element.querySelector('form').addEventListener('submit', onSubmit.bind(self));
options.element.addEventListener('change', onChange);
if (options.enableSaveButton) {
options.element.querySelector('.btnSave').classList.remove('hide');
}
if (layoutManager.tv) {
options.element.querySelector('.selectTVHomeScreenContainer').classList.remove('hide');
} else {
options.element.querySelector('.selectTVHomeScreenContainer').classList.add('hide');
}
self.loadData(options.autoFocus);
});
}
function HomeScreenSettings(options) {
this.options = options;
embed(options, this);
}
HomeScreenSettings.prototype.loadData = function (autoFocus) {
var self = this;
var context = self.options.element;
loading.show();
var userId = self.options.userId;
var apiClient = connectionManager.getApiClient(self.options.serverId);
var userSettings = self.options.userSettings;
apiClient.getUser(userId).then(function (user) {
userSettings.setUserInfo(userId, apiClient).then(function () {
self.dataLoaded = true;
loadForm(context, user, userSettings, apiClient);
if (autoFocus) {
focusManager.autoFocus(context);
}
});
});
};
HomeScreenSettings.prototype.submit = function () {
onSubmit.call(this);
};
HomeScreenSettings.prototype.destroy = function () {
this.options = null;
};
return 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,88 @@
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: true,
scrollY: false
};
if (layoutManager.tv) {
dialogOptions.size = 'fullscreen';
} else {
dialogOptions.size = 'medium-tall';
}
var dlg = dialogHelper.createDialog(dialogOptions);
dlg.classList.add('formDialog');
var html = '';
var submitted = false;
html += globalize.translateDocument(template, 'core');
dlg.innerHTML = html;
if (layoutManager.tv) {
centerFocus(dlg.querySelector('.formDialogContent'), false, true);
}
var homescreenSettingsInstance = new HomescreenSettings({
serverId: options.serverId,
userId: options.userId,
element: dlg.querySelector('.settingsContent'),
userSettings: options.userSettings,
enableSaveButton: false,
enableSaveConfirmation: false
});
dialogHelper.open(dlg);
dlg.addEventListener('close', function () {
if (layoutManager.tv) {
centerFocus(dlg.querySelector('.formDialogContent'), false, false);
}
if (submitted) {
resolve();
} else {
reject();
}
});
dlg.querySelector('.btnCancel').addEventListener('click', function (e) {
dialogHelper.close(dlg);
});
dlg.querySelector('.btnSave').addEventListener('click', function (e) {
submitted = true;
homescreenSettingsInstance.submit();
});
events.on(homescreenSettingsInstance, 'saved', function () {
submitted = true;
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,11 @@
.homeLibraryButton {
min-width: 18%;
margin: .5em !important;
}
@media all and (max-width: 50em) {
.homeLibraryButton {
width: 46% !important;
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,515 @@
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({}));
});
});
}
var fadeTimeout;
function fade(instance, elem, startingVolume) {
instance._isFadingOut = true;
// Need to record the starting volume on each pass rather than querying elem.volume
// This is due to iOS safari not allowing volume changes and always returning the system volume value
var newVolume = Math.max(0, startingVolume - 0.15);
console.log('fading volume to ' + newVolume);
elem.volume = newVolume;
if (newVolume <= 0) {
instance._isFadingOut = false;
return Promise.resolve();
}
return new Promise(function (resolve, reject) {
cancelFadeTimeout();
fadeTimeout = setTimeout(function () {
fade(instance, elem, newVolume).then(resolve, reject);
}, 100);
});
}
function cancelFadeTimeout() {
var timeout = fadeTimeout;
if (timeout) {
clearTimeout(timeout);
fadeTimeout = null;
}
}
function supportsFade() {
if (browser.tv) {
// Not working on tizen.
// We could possibly enable on other tv's, but all smart tv browsers tend to be pretty primitive
return false;
}
return true;
}
function requireHlsPlayer(callback) {
require(['hlsjs'], function (hls) {
window.Hls = hls;
callback();
});
}
function enableHlsPlayer(url, item, mediaSource, mediaType) {
if (!htmlMediaHelper.enableHlsJsPlayer(mediaSource.RunTimeTicks, mediaType)) {
return Promise.reject();
}
if (url.indexOf('.m3u8') !== -1) {
return Promise.resolve();
}
// issue head request to get content type
return new Promise(function (resolve, reject) {
require(['fetchHelper'], function (fetchHelper) {
fetchHelper.ajax({
url: url,
type: 'HEAD'
}).then(function (response) {
var contentType = (response.headers.get('Content-Type') || '').toLowerCase();
if (contentType === 'application/x-mpegurl') {
resolve();
} else {
reject();
}
}, reject);
});
});
}
function HtmlAudioPlayer() {
var self = this;
self.name = 'Html Audio Player';
self.type = 'mediaplayer';
self.id = 'htmlaudioplayer';
// Let any players created by plugins take priority
self.priority = 1;
self.play = function (options) {
self._started = false;
self._timeUpdated = false;
self._currentTime = null;
var elem = createMediaElement(options);
return setCurrentSrc(elem, options);
};
function setCurrentSrc(elem, options) {
elem.removeEventListener('error', onError);
unBindEvents(elem);
bindEvents(elem);
var val = options.url;
console.log('playing url: ' + val);
// Convert to seconds
var seconds = (options.playerStartPositionTicks || 0) / 10000000;
if (seconds) {
val += '#t=' + seconds;
}
htmlMediaHelper.destroyHlsPlayer(self);
self._currentPlayOptions = options;
var crossOrigin = htmlMediaHelper.getCrossOriginValue(options.mediaSource);
if (crossOrigin) {
elem.crossOrigin = crossOrigin;
}
return enableHlsPlayer(val, options.item, options.mediaSource, 'Audio').then(function () {
return new Promise(function (resolve, reject) {
requireHlsPlayer(function () {
var hls = new Hls({
manifestLoadingTimeOut: 20000
//appendErrorMaxRetry: 6,
//debug: true
});
hls.loadSource(val);
hls.attachMedia(elem);
htmlMediaHelper.bindEventsToHlsPlayer(self, hls, elem, onError, resolve, reject);
self._hlsPlayer = hls;
self._currentSrc = val;
});
});
}, function () {
elem.autoplay = true;
return htmlMediaHelper.applySrc(elem, val, options).then(function () {
self._currentSrc = val;
return 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);
}
self.stop = function (destroyPlayer) {
cancelFadeTimeout();
var elem = self._mediaElement;
var src = self._currentSrc;
if (elem && src) {
if (!destroyPlayer || !supportsFade()) {
elem.pause();
htmlMediaHelper.onEndedInternal(self, elem, onError);
if (destroyPlayer) {
self.destroy();
}
return Promise.resolve();
}
var originalVolume = elem.volume;
return fade(self, elem, elem.volume).then(function () {
elem.pause();
elem.volume = originalVolume;
htmlMediaHelper.onEndedInternal(self, elem, onError);
if (destroyPlayer) {
self.destroy();
}
});
}
return Promise.resolve();
};
self.destroy = function () {
unBindEvents(self._mediaElement);
};
function createMediaElement() {
var elem = self._mediaElement;
if (elem) {
return elem;
}
elem = document.querySelector('.mediaPlayerAudio');
if (!elem) {
elem = document.createElement('audio');
elem.classList.add('mediaPlayerAudio');
elem.classList.add('hide');
document.body.appendChild(elem);
}
elem.volume = htmlMediaHelper.getSavedVolume();
self._mediaElement = elem;
return elem;
}
function onEnded() {
htmlMediaHelper.onEndedInternal(self, this, onError);
}
function onTimeUpdate() {
// Get the player position + the transcoding offset
var time = this.currentTime;
// Don't trigger events after user stop
if (!self._isFadingOut) {
self._currentTime = time;
events.trigger(self, 'timeupdate');
}
}
function onVolumeChange() {
if (!self._isFadingOut) {
htmlMediaHelper.saveVolume(this.volume);
events.trigger(self, 'volumechange');
}
}
function onPlaying(e) {
if (!self._started) {
self._started = true;
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;
var errorMessage = this.error ? (this.error.message || '') : '';
console.log('Media element error: ' + errorCode.toString() + ' ' + errorMessage);
var type;
switch (errorCode) {
case 1:
// MEDIA_ERR_ABORTED
// This will trigger when changing media while something is playing
return;
case 2:
// MEDIA_ERR_NETWORK
type = 'network';
break;
case 3:
// MEDIA_ERR_DECODE
if (self._hlsPlayer) {
htmlMediaHelper.handleHlsJsMediaError(self);
return;
} else {
type = 'mediadecodeerror';
}
break;
case 4:
// MEDIA_ERR_SRC_NOT_SUPPORTED
type = 'medianotsupported';
break;
default:
// seeing cases where Edge is firing error events with no error code
// example is start playing something, then immediately change src to something else
return;
}
htmlMediaHelper.onErrorInternal(self, type);
}
}
HtmlAudioPlayer.prototype.currentSrc = function () {
return this._currentSrc;
};
HtmlAudioPlayer.prototype.canPlayMediaType = function (mediaType) {
return (mediaType || '').toLowerCase() === 'audio';
};
HtmlAudioPlayer.prototype.getDeviceProfile = function (item) {
if (appHost.getDeviceProfile) {
return appHost.getDeviceProfile(item);
}
return getDefaultProfile();
};
// Save this for when playback stops, because querying the time at that point might return 0
HtmlAudioPlayer.prototype.currentTime = function (val) {
var mediaElement = this._mediaElement;
if (mediaElement) {
if (val != null) {
mediaElement.currentTime = val / 1000;
return;
}
var currentTime = this._currentTime;
if (currentTime) {
return currentTime * 1000;
}
return (mediaElement.currentTime || 0) * 1000;
}
};
HtmlAudioPlayer.prototype.duration = function (val) {
var mediaElement = this._mediaElement;
if (mediaElement) {
var duration = mediaElement.duration;
if (htmlMediaHelper.isValidDuration(duration)) {
return duration * 1000;
}
}
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);
var end = seekable.end(0);
if (!htmlMediaHelper.isValidDuration(start)) {
start = 0;
}
if (!htmlMediaHelper.isValidDuration(end)) {
end = 0;
}
return (end - start) > 0;
}
return false;
}
};
HtmlAudioPlayer.prototype.getBufferedRanges = function () {
var mediaElement = this._mediaElement;
if (mediaElement) {
return htmlMediaHelper.getBufferedRanges(this, mediaElement);
}
return [];
};
HtmlAudioPlayer.prototype.pause = function () {
var mediaElement = this._mediaElement;
if (mediaElement) {
mediaElement.pause();
}
};
// This is a retry after error
HtmlAudioPlayer.prototype.resume = function () {
var mediaElement = this._mediaElement;
if (mediaElement) {
mediaElement.play();
}
};
HtmlAudioPlayer.prototype.unpause = function () {
var mediaElement = this._mediaElement;
if (mediaElement) {
mediaElement.play();
}
};
HtmlAudioPlayer.prototype.paused = function () {
var mediaElement = this._mediaElement;
if (mediaElement) {
return mediaElement.paused;
}
return false;
};
HtmlAudioPlayer.prototype.setVolume = function (val) {
var mediaElement = this._mediaElement;
if (mediaElement) {
mediaElement.volume = val / 100;
}
};
HtmlAudioPlayer.prototype.getVolume = function () {
var mediaElement = this._mediaElement;
if (mediaElement) {
return Math.min(Math.round(mediaElement.volume * 100), 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;
if (mediaElement) {
mediaElement.muted = mute;
}
};
HtmlAudioPlayer.prototype.isMuted = function () {
var mediaElement = this._mediaElement;
if (mediaElement) {
return mediaElement.muted;
}
return false;
};
HtmlAudioPlayer.prototype.destroy = function () {
};
return HtmlAudioPlayer;
});

View file

@ -0,0 +1,468 @@
define(['appSettings', 'browser', 'events'], function (appSettings, browser, events) {
'use strict';
function getSavedVolume() {
return appSettings.get("volume") || 1;
}
function saveVolume(value) {
if (value) {
appSettings.set("volume", value);
}
}
function getCrossOriginValue(mediaSource) {
if (mediaSource.IsRemote) {
return null;
}
return 'anonymous';
}
function canPlayNativeHls() {
var media = document.createElement('video');
if (media.canPlayType('application/x-mpegURL').replace(/no/, '') ||
media.canPlayType('application/vnd.apple.mpegURL').replace(/no/, '')) {
return true;
}
return false;
}
function enableHlsShakaPlayer(item, mediaSource, mediaType) {
if (!!window.MediaSource && !!MediaSource.isTypeSupported) {
if (canPlayNativeHls()) {
if (browser.edge && mediaType === 'Video') {
return true;
}
// simple playback should use the native support
if (mediaSource.RunTimeTicks) {
//if (!browser.edge) {
//return false;
//}
}
//return false;
}
return true;
}
return false;
}
function enableHlsJsPlayer(runTimeTicks, mediaType) {
if (window.MediaSource == null) {
return false;
}
// hls.js is only in beta. needs more testing.
if (browser.iOS) {
return false;
}
// The native players on these devices support seeking live streams, no need to use hls.js here
if (browser.tizen || browser.web0s) {
return false;
}
if (canPlayNativeHls()) {
// Having trouble with chrome's native support and transcoded music
if (browser.android && mediaType === 'Audio') {
return true;
}
if (browser.edge && mediaType === 'Video') {
//return true;
}
// simple playback should use the native support
if (runTimeTicks) {
//if (!browser.edge) {
return false;
//}
}
//return false;
}
return true;
}
var recoverDecodingErrorDate, recoverSwapAudioCodecDate;
function handleHlsJsMediaError(instance, reject) {
var hlsPlayer = instance._hlsPlayer;
if (!hlsPlayer) {
return;
}
var now = Date.now();
if (window.performance && window.performance.now) {
now = performance.now();
}
if (!recoverDecodingErrorDate || (now - recoverDecodingErrorDate) > 3000) {
recoverDecodingErrorDate = now;
console.log('try to recover media Error ...');
hlsPlayer.recoverMediaError();
} else {
if (!recoverSwapAudioCodecDate || (now - recoverSwapAudioCodecDate) > 3000) {
recoverSwapAudioCodecDate = now;
console.log('try to swap Audio Codec and recover media Error ...');
hlsPlayer.swapAudioCodec();
hlsPlayer.recoverMediaError();
} else {
console.error('cannot recover, last media error recovery failed ...');
if (reject) {
reject();
} else {
onErrorInternal(instance, 'mediadecodeerror');
}
}
}
}
function onErrorInternal(instance, type) {
// Needed for video
if (instance.destroyCustomTrack) {
instance.destroyCustomTrack(instance._mediaElement);
}
events.trigger(instance, 'error', [
{
type: type
}]);
}
function isValidDuration(duration) {
if (duration && !isNaN(duration) && duration !== Number.POSITIVE_INFINITY && duration !== Number.NEGATIVE_INFINITY) {
return true;
}
return false;
}
function setCurrentTimeIfNeeded(element, seconds) {
if (Math.abs(element.currentTime || 0, seconds) <= 1) {
element.currentTime = seconds;
}
}
function seekOnPlaybackStart(instance, element, ticks) {
var seconds = (ticks || 0) / 10000000;
if (seconds) {
var src = (instance.currentSrc() || '').toLowerCase();
// Appending #t=xxx to the query string doesn't seem to work with HLS
// For plain video files, not all browsers support it either
var delay = browser.safari ? 2500 : 0;
if (delay) {
setTimeout(function () {
setCurrentTimeIfNeeded(element, seconds);
}, delay);
} else {
setCurrentTimeIfNeeded(element, seconds);
}
}
}
function applySrc(elem, src, options) {
if (window.Windows && options.mediaSource && options.mediaSource.IsLocal) {
return Windows.Storage.StorageFile.getFileFromPathAsync(options.url).then(function (file) {
var playlist = new Windows.Media.Playback.MediaPlaybackList();
var source1 = Windows.Media.Core.MediaSource.createFromStorageFile(file);
var startTime = (options.playerStartPositionTicks || 0) / 10000;
playlist.items.append(new Windows.Media.Playback.MediaPlaybackItem(source1, startTime));
elem.src = URL.createObjectURL(playlist, { oneTimeOnly: true });
return Promise.resolve();
});
} else {
elem.src = src;
}
return Promise.resolve();
}
function onSuccessfulPlay(elem, onErrorFn) {
elem.addEventListener('error', onErrorFn);
}
function playWithPromise(elem, onErrorFn) {
try {
var promise = elem.play();
if (promise && promise.then) {
// Chrome now returns a promise
return promise.catch(function (e) {
var errorName = (e.name || '').toLowerCase();
// safari uses aborterror
if (errorName === 'notallowederror' ||
errorName === 'aborterror') {
// swallow this error because the user can still click the play button on the video element
onSuccessfulPlay(elem, onErrorFn);
return Promise.resolve();
}
return Promise.reject();
});
} else {
onSuccessfulPlay(elem, onErrorFn);
return Promise.resolve();
}
} catch (err) {
console.log('error calling video.play: ' + err);
return 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 () {
if (reject) {
reject();
reject = null;
}
});
});
hls.on(Hls.Events.ERROR, function (event, data) {
console.log('HLS Error: Type: ' + data.type + ' Details: ' + (data.details || '') + ' Fatal: ' + (data.fatal || false));
switch (data.type) {
case Hls.ErrorTypes.NETWORK_ERROR:
// try to recover network error
if (data.response && data.response.code && data.response.code >= 400) {
console.log('hls.js response error code: ' + data.response.code);
// Trigger failure differently depending on whether this is prior to start of playback, or after
hls.destroy();
if (reject) {
reject('servererror');
reject = null;
} else {
onErrorInternal(instance, 'servererror');
}
return;
}
break;
default:
break;
}
if (data.fatal) {
switch (data.type) {
case Hls.ErrorTypes.NETWORK_ERROR:
if (data.response && data.response.code === 0) {
// This could be a CORS error related to access control response headers
console.log('hls.js response error code: ' + data.response.code);
// Trigger failure differently depending on whether this is prior to start of playback, or after
hls.destroy();
if (reject) {
reject('network');
reject = null;
} else {
onErrorInternal(instance, 'network');
}
}
else {
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');
// cannot recover
// Trigger failure differently depending on whether this is prior to start of playback, or after
hls.destroy();
if (reject) {
reject();
reject = null;
} else {
onErrorInternal(instance, 'mediadecodeerror');
}
break;
}
}
});
}
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 ranges = [];
var seekable = elem.buffered || [];
var offset;
var currentPlayOptions = instance._currentPlayOptions;
if (currentPlayOptions) {
offset = currentPlayOptions.transcodingOffsetTicks;
}
offset = offset || 0;
for (var i = 0, length = seekable.length; i < length; i++) {
var start = seekable.start(i);
var end = seekable.end(i);
if (!isValidDuration(start)) {
start = 0;
}
if (!isValidDuration(end)) {
end = 0;
continue;
}
ranges.push({
start: (start * 10000000) + offset,
end: (end * 10000000) + offset
});
}
return ranges;
}
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
};
});

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,74 @@
.videoPlayerContainer {
position: fixed !important;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: flex;
align-items: center;
}
.videoPlayerContainer:not(.videoPlayerContainer-withBackdrop) {
background: #000 !important;
}
.videoPlayerContainer-withBackdrop {
background-repeat: no-repeat;
background-position: center center;
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 {
/*Style the text itself*/
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;
}
@keyframes htmlvideoplayer-zoomin {
from {
transform: scale3d(.2, .2, .2);
opacity: .6;
}
to {
transform: none;
opacity: initial;
}
}

View file

@ -0,0 +1,416 @@
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';
var currentItemId;
var currentItemType;
var currentResolve;
var currentReject;
var hasChanges = false;
// These images can be large and we're seeing memory problems in safari
var browsableImagePageSize = browser.slow ? 6 : 30;
var browsableImageStartIndex = 0;
var browsableImageType = 'Primary';
var selectedProvider;
function getBaseRemoteOptions() {
var options = {};
options.itemId = currentItemId;
return 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 || '';
if (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>';
});
var selectImageProvider = page.querySelector('#selectImageProvider');
selectImageProvider.innerHTML = '<option value="">' + globalize.translate('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);
var html = '';
for (var 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');
var btnPreviousPage = page.querySelector('.btnPreviousPage');
if (btnNextPage) {
btnNextPage.addEventListener('click', function () {
browsableImageStartIndex += browsableImagePageSize;
reloadBrowsableImages(page, apiClient);
});
}
if (btnPreviousPage) {
btnPreviousPage.addEventListener('click', function () {
browsableImageStartIndex -= browsableImagePageSize;
reloadBrowsableImages(page, apiClient);
});
}
}
function getPagingHtml(startIndex, limit, totalRecordCount) {
var html = '';
var recordsEnd = Math.min(startIndex + limit, totalRecordCount);
// 20 is the minimum page size
var showControls = totalRecordCount > limit;
html += '<div class="listPaging">';
html += '<span style="margin-right: 10px;">';
var startAtDisplay = totalRecordCount ? startIndex + 1 : 0;
html += startAtDisplay + '-' + recordsEnd + ' of ' + totalRecordCount;
html += '</span>';
if (showControls) {
html += '<div data-role="controlgroup" data-type="horizontal" style="display:inline-block;">';
html += '<button is="paper-icon-button-light" title="' + globalize.translate('Previous') + '" class="btnPreviousPage autoSize" ' + (startIndex ? '' : 'disabled') + '><i class="md-icon">&#xE5C4;</i></button>';
html += '<button is="paper-icon-button-light" title="' + globalize.translate('Next') + '" class="btnNextPage autoSize" ' + (startIndex + limit >= totalRecordCount ? 'disabled' : '') + '><i class="md-icon">arrow_forward</i></button>';
html += '</div>';
}
html += '</div>';
return html;
}
function parentWithClass(elem, className) {
while (!elem.classList || !elem.classList.contains(className)) {
elem = elem.parentNode;
if (!elem) {
return null;
}
}
return elem;
}
function downloadRemoteImage(page, apiClient, url, type, provider) {
var options = getBaseRemoteOptions();
options.Type = type;
options.ImageUrl = url;
options.ProviderName = provider;
loading.show();
apiClient.downloadRemoteImage(options).then(function () {
hasChanges = true;
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';
var enableFooterButtons = !layoutManager.tv;
var html = '';
var cssClass = "card scalableCard imageEditorCard";
var cardBoxCssClass = 'cardBox visualCardBox';
var shape = 'backdrop';
if (imageType === "Backdrop" || imageType === "Art" || imageType === "Thumb" || imageType === "Logo") {
shape = 'backdrop';
}
else if (imageType === "Banner") {
shape = 'banner';
}
else if (imageType === "Disc") {
shape = 'square';
}
else {
if (currentItemType === "Episode") {
shape = 'backdrop';
}
else if (currentItemType === "MusicAlbum" || currentItemType === "MusicArtist") {
shape = 'square';
}
else {
shape = 'portrait';
}
}
cssClass += ' ' + shape + 'Card ' + shape + 'Card-scalable';
if (tagName === 'button') {
cssClass += ' btnImageCard';
if (layoutManager.tv && !browser.slow) {
cardBoxCssClass += ' cardBox-focustransform';
}
if (layoutManager.tv) {
cardBoxCssClass += ' card-focuscontent cardBox-withfocuscontent';
}
html += '<button type="button" class="' + cssClass + '"';
} else {
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">';
if (layoutManager.tv || !appHost.supports('externallinks')) {
html += '<div class="cardImageContainer lazy" data-src="' + getDisplayUrl(image.Url, apiClient) + '" style="background-position:center bottom;"></div>';
}
else {
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>';
// begin footer
html += '<div class="cardFooter visualCardBox-cardFooter">';
html += '<div class="cardText cardTextCentered">' + image.ProviderName + '</div>';
if (image.Width || image.Height || image.Language) {
html += '<div class="cardText cardText-secondary cardTextCentered">';
if (image.Width && image.Height) {
html += image.Width + ' x ' + image.Height;
if (image.Language) {
html += ' • ' + image.Language;
}
} else {
if (image.Language) {
html += image.Language;
}
}
html += '</div>';
}
if (image.CommunityRating != null) {
html += '<div class="cardText cardText-secondary cardTextCentered">';
if (image.RatingType === "Likes") {
html += image.CommunityRating + (image.CommunityRating === 1 ? " like" : " likes");
} else {
if (image.CommunityRating) {
html += image.CommunityRating.toFixed(1);
if (image.VoteCount) {
html += ' • ' + image.VoteCount + (image.VoteCount === 1 ? " vote" : " votes");
}
} else {
html += "Unrated";
}
}
html += '</div>';
}
if (enableFooterButtons) {
html += '<div class="cardText cardTextCentered">';
html += '<button is="paper-icon-button-light" class="btnDownloadRemoteImage autoSize" raised" title="' + globalize.translate('Download') + '"><i class="md-icon">&#xE2C0;</i></button>';
html += '</div>';
}
html += '</div>';
// end footer
html += '</div>';
//html += '</div>';
html += '</' + tagName + '>';
return html;
}
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');
downloadRemoteImage(page, apiClient, card.getAttribute('data-imageurl'), card.getAttribute('data-imagetype'), card.getAttribute('data-imageprovider'));
return;
}
var btnImageCard = parentWithClass(e.target, 'btnImageCard');
if (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: true
};
if (layoutManager.tv) {
dialogOptions.size = 'fullscreen';
} else {
dialogOptions.size = 'fullscreen-border';
}
var dlg = dialogHelper.createDialog(dialogOptions);
dlg.innerHTML = globalize.translateDocument(template, 'core');
if (layoutManager.tv) {
scrollHelper.centerFocus.on(dlg, false);
}
// Has to be assigned a z-index after the call to .open()
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;
if (layoutManager.tv) {
scrollHelper.centerFocus.off(dlg, false);
}
loading.hide();
if (hasChanges) {
currentResolve();
} else {
currentReject();
}
}
return {
show: function (itemId, serverId, itemType, imageType) {
return new Promise(function (resolve, reject) {
currentResolve = resolve;
currentReject = reject;
hasChanges = false;
browsableImageStartIndex = 0;
browsableImageType = imageType || 'Primary';
selectedProvider = null;
showEditor(itemId, serverId, itemType);
});
}
};
});

View file

@ -0,0 +1,44 @@
<div class="formDialogHeader">
<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><i class="md-icon">&#xE5C4;</i></button>
<h3 class="formDialogHeaderTitle">
${Search}
</h3>
</div>
<div class="formDialogContent">
<div class="dialogContentInner">
<div class="flex align-items-center justify-content-center flex-wrap-wrap" style="margin: 2em 0;">
<div style="margin: 0;">
<label for="selectImageProvider" style="display: inline-block;">${LabelSource}</label>
<select id="selectImageProvider" style="padding-left:.5em;padding-right:0;display: inline-block;width:auto!important;">
<option value="">${All}</option>
</select>
</div>
<div style="margin-left:1em;">
<label for="selectBrowsableImageType" style="display: inline-block;">${LabelType}</label>
<select id="selectBrowsableImageType" style="padding-left:.5em;padding-right:0;display: inline-block;width:auto!important;">
<option value="Primary">${Primary}</option>
<option value="Art">${Art}</option>
<option value="Backdrop">${Backdrop}</option>
<option value="Banner">${Banner}</option>
<option value="Box">${Box}</option>
<option value="BoxRear">${BoxRear}</option>
<option value="Disc">${Disc}</option>
<option value="Logo">${Logo}</option>
<option value="Menu">${Menu}</option>
<option value="Screenshot">${Screenshot}</option>
<option value="Thumb">${Thumb}</option>
</select>
</div>
<div class="availableImagesPaging" style="margin-left:1em;"></div>
<label style="margin: 0 0 0 1em;width:auto;">
<input id="chkAllLanguages" type="checkbox" is="emby-checkbox" />
<span>${AllLanguages}</span>
</label>
</div>
<div class="availableImagesList vertical-wrap centered"></div>
</div>
</div>

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