diff --git a/src/components/accessSchedule/accessSchedule.js b/src/components/accessSchedule/accessSchedule.js index bbfb35ebca..91b994c2ef 100644 --- a/src/components/accessSchedule/accessSchedule.js +++ b/src/components/accessSchedule/accessSchedule.js @@ -1,6 +1,3 @@ - -/* eslint-disable indent */ - /** * Module for controlling user parental control from. * @module components/accessSchedule/accessSchedule @@ -14,84 +11,82 @@ import '../../elements/emby-button/paper-icon-button-light'; import '../formdialog.scss'; import template from './accessSchedule.template.html'; - function getDisplayTime(hours) { - let minutes = 0; - const pct = hours % 1; +function getDisplayTime(hours) { + let minutes = 0; + const pct = hours % 1; - if (pct) { - minutes = parseInt(60 * pct, 10); - } - - return datetime.getDisplayTime(new Date(2000, 1, 1, hours, minutes, 0, 0)); + if (pct) { + minutes = parseInt(60 * pct, 10); } - function populateHours(context) { - let html = ''; + return datetime.getDisplayTime(new Date(2000, 1, 1, hours, minutes, 0, 0)); +} - for (let i = 0; i < 24; i += 0.5) { - html += ``; - } +function populateHours(context) { + let html = ''; - html += ``; - context.querySelector('#selectStart').innerHTML = html; - context.querySelector('#selectEnd').innerHTML = html; + for (let i = 0; i < 24; i += 0.5) { + html += ``; } - function loadSchedule(context, { DayOfWeek, StartHour, EndHour }) { - context.querySelector('#selectDay').value = DayOfWeek || 'Sunday'; - context.querySelector('#selectStart').value = StartHour || 0; - context.querySelector('#selectEnd').value = EndHour || 0; + html += ``; + context.querySelector('#selectStart').innerHTML = html; + context.querySelector('#selectEnd').innerHTML = html; +} + +function loadSchedule(context, { DayOfWeek, StartHour, EndHour }) { + context.querySelector('#selectDay').value = DayOfWeek || 'Sunday'; + context.querySelector('#selectStart').value = StartHour || 0; + context.querySelector('#selectEnd').value = EndHour || 0; +} + +function submitSchedule(context, options) { + const updatedSchedule = { + DayOfWeek: context.querySelector('#selectDay').value, + StartHour: context.querySelector('#selectStart').value, + EndHour: context.querySelector('#selectEnd').value + }; + + if (parseFloat(updatedSchedule.StartHour) >= parseFloat(updatedSchedule.EndHour)) { + alert(globalize.translate('ErrorStartHourGreaterThanEnd')); + return; } - function submitSchedule(context, options) { - const updatedSchedule = { - DayOfWeek: context.querySelector('#selectDay').value, - StartHour: context.querySelector('#selectStart').value, - EndHour: context.querySelector('#selectEnd').value - }; + context.submitted = true; + options.schedule = Object.assign(options.schedule, updatedSchedule); + dialogHelper.close(context); +} - if (parseFloat(updatedSchedule.StartHour) >= parseFloat(updatedSchedule.EndHour)) { - alert(globalize.translate('ErrorStartHourGreaterThanEnd')); - return; - } - - context.submitted = true; - options.schedule = Object.assign(options.schedule, updatedSchedule); - dialogHelper.close(context); - } - - export function show(options) { - return new Promise((resolve, reject) => { - const dlg = dialogHelper.createDialog({ - removeOnClose: true, - size: 'small' - }); - dlg.classList.add('formDialog'); - let html = ''; - html += globalize.translateHtml(template); - dlg.innerHTML = html; - populateHours(dlg); - loadSchedule(dlg, options.schedule); - dialogHelper.open(dlg); - dlg.addEventListener('close', () => { - if (dlg.submitted) { - resolve(options.schedule); - } else { - reject(); - } - }); - dlg.querySelector('.btnCancel').addEventListener('click', () => { - dialogHelper.close(dlg); - }); - dlg.querySelector('form').addEventListener('submit', event => { - submitSchedule(dlg, options); - event.preventDefault(); - return false; - }); +export function show(options) { + return new Promise((resolve, reject) => { + const dlg = dialogHelper.createDialog({ + removeOnClose: true, + size: 'small' }); - } - -/* eslint-enable indent */ + dlg.classList.add('formDialog'); + let html = ''; + html += globalize.translateHtml(template); + dlg.innerHTML = html; + populateHours(dlg); + loadSchedule(dlg, options.schedule); + dialogHelper.open(dlg); + dlg.addEventListener('close', () => { + if (dlg.submitted) { + resolve(options.schedule); + } else { + reject(); + } + }); + dlg.querySelector('.btnCancel').addEventListener('click', () => { + dialogHelper.close(dlg); + }); + dlg.querySelector('form').addEventListener('submit', event => { + submitSchedule(dlg, options); + event.preventDefault(); + return false; + }); + }); +} export default { show: show diff --git a/src/components/activitylog.js b/src/components/activitylog.js index 54de02b97c..fc9a5a9fe6 100644 --- a/src/components/activitylog.js +++ b/src/components/activitylog.js @@ -11,130 +11,128 @@ import alert from './alert'; import { getLocale } from '../utils/dateFnsLocale.ts'; import { toBoolean } from '../utils/string.ts'; -/* eslint-disable indent */ +function getEntryHtml(entry, apiClient) { + let html = ''; + html += '
'; + let color = '#00a4dc'; + let icon = 'notifications'; - function getEntryHtml(entry, apiClient) { - let html = ''; - html += '
'; - let color = '#00a4dc'; - let icon = 'notifications'; + if (entry.Severity == 'Error' || entry.Severity == 'Fatal' || entry.Severity == 'Warn') { + color = '#cc0000'; + icon = 'notification_important'; + } - if (entry.Severity == 'Error' || entry.Severity == 'Fatal' || entry.Severity == 'Warn') { - color = '#cc0000'; - icon = 'notification_important'; - } + if (entry.UserId && entry.UserPrimaryImageTag) { + html += '"; + } else { + html += ''; + } - if (entry.UserId && entry.UserPrimaryImageTag) { - html += '"; - } else { - html += ''; - } + html += '
'; + html += '
'; + html += escapeHtml(entry.Name); + html += '
'; + html += '
'; + html += formatRelative(Date.parse(entry.Date), Date.now(), { locale: getLocale() }); + html += '
'; + html += '
'; + html += escapeHtml(entry.ShortOverview || ''); + html += '
'; + html += '
'; - html += '
'; - html += '
'; - html += escapeHtml(entry.Name); - html += '
'; - html += '
'; - html += formatRelative(Date.parse(entry.Date), Date.now(), { locale: getLocale() }); - html += '
'; - html += '
'; - html += escapeHtml(entry.ShortOverview || ''); - html += '
'; - html += '
'; - - if (entry.Overview) { - html += ``; - } - - html += '
'; - - return html; } - function renderList(elem, apiClient, result) { - elem.innerHTML = result.Items.map(function (i) { - return getEntryHtml(i, apiClient); - }).join(''); + html += '
'; + + return html; +} + +function renderList(elem, apiClient, result) { + elem.innerHTML = result.Items.map(function (i) { + return getEntryHtml(i, apiClient); + }).join(''); +} + +function reloadData(instance, elem, apiClient, startIndex, limit) { + if (startIndex == null) { + startIndex = parseInt(elem.getAttribute('data-activitystartindex') || '0', 10); } - function reloadData(instance, elem, apiClient, startIndex, limit) { - if (startIndex == null) { - startIndex = parseInt(elem.getAttribute('data-activitystartindex') || '0', 10); - } + limit = limit || parseInt(elem.getAttribute('data-activitylimit') || '7', 10); + const minDate = new Date(); + const hasUserId = toBoolean(elem.getAttribute('data-useractivity'), true); - limit = limit || parseInt(elem.getAttribute('data-activitylimit') || '7', 10); - const minDate = new Date(); - const hasUserId = toBoolean(elem.getAttribute('data-useractivity'), true); - - // TODO: Use date-fns - if (hasUserId) { - minDate.setTime(minDate.getTime() - 24 * 60 * 60 * 1000); // one day back - } else { - minDate.setTime(minDate.getTime() - 7 * 24 * 60 * 60 * 1000); // one week back - } - - ApiClient.getJSON(ApiClient.getUrl('System/ActivityLog/Entries', { - startIndex: startIndex, - limit: limit, - minDate: minDate.toISOString(), - hasUserId: hasUserId - })).then(function (result) { - elem.setAttribute('data-activitystartindex', startIndex); - elem.setAttribute('data-activitylimit', limit); - if (!startIndex) { - const activityContainer = dom.parentWithClass(elem, 'activityContainer'); - - if (activityContainer) { - if (result.Items.length) { - activityContainer.classList.remove('hide'); - } else { - activityContainer.classList.add('hide'); - } - } - } - - instance.items = result.Items; - renderList(elem, apiClient, result); - }); + // TODO: Use date-fns + if (hasUserId) { + minDate.setTime(minDate.getTime() - 24 * 60 * 60 * 1000); // one day back + } else { + minDate.setTime(minDate.getTime() - 7 * 24 * 60 * 60 * 1000); // one week back } - function onActivityLogUpdate(e, apiClient) { - const options = this.options; + ApiClient.getJSON(ApiClient.getUrl('System/ActivityLog/Entries', { + startIndex: startIndex, + limit: limit, + minDate: minDate.toISOString(), + hasUserId: hasUserId + })).then(function (result) { + elem.setAttribute('data-activitystartindex', startIndex); + elem.setAttribute('data-activitylimit', limit); + if (!startIndex) { + const activityContainer = dom.parentWithClass(elem, 'activityContainer'); - if (options && options.serverId === apiClient.serverId()) { - reloadData(this, options.element, apiClient); - } - } - - function onListClick(e) { - const btnEntryInfo = dom.parentWithClass(e.target, 'btnEntryInfo'); - - if (btnEntryInfo) { - const id = btnEntryInfo.getAttribute('data-id'); - const items = this.items; - - if (items) { - const item = items.filter(function (i) { - return i.Id.toString() === id; - })[0]; - - if (item) { - showItemOverview(item); + if (activityContainer) { + if (result.Items.length) { + activityContainer.classList.remove('hide'); + } else { + activityContainer.classList.add('hide'); } } } - } - function showItemOverview(item) { - alert({ - text: item.Overview - }); + instance.items = result.Items; + renderList(elem, apiClient, result); + }); +} + +function onActivityLogUpdate(e, apiClient) { + const options = this.options; + + if (options && options.serverId === apiClient.serverId()) { + reloadData(this, options.element, apiClient); } +} + +function onListClick(e) { + const btnEntryInfo = dom.parentWithClass(e.target, 'btnEntryInfo'); + + if (btnEntryInfo) { + const id = btnEntryInfo.getAttribute('data-id'); + const items = this.items; + + if (items) { + const item = items.filter(function (i) { + return i.Id.toString() === id; + })[0]; + + if (item) { + showItemOverview(item); + } + } + } +} + +function showItemOverview(item) { + alert({ + text: item.Overview + }); +} class ActivityLog { constructor(options) { @@ -169,5 +167,3 @@ class ActivityLog { } export default ActivityLog; - -/* eslint-enable indent */ diff --git a/src/components/alert.js b/src/components/alert.js index f8c4209f6d..bd6ff7187c 100644 --- a/src/components/alert.js +++ b/src/components/alert.js @@ -3,45 +3,41 @@ import browser from '../scripts/browser'; import dialog from './dialog/dialog'; import globalize from '../scripts/globalize'; -/* eslint-disable indent */ - - function useNativeAlert() { - // webOS seems to block modals - // Tizen 2.x seems to block modals - return !browser.web0s +function useNativeAlert() { + // webOS seems to block modals + // Tizen 2.x seems to block modals + return !browser.web0s && !(browser.tizenVersion && browser.tizenVersion < 3) && browser.tv && window.alert; +} + +export default async function (text, title) { + let options; + if (typeof text === 'string') { + options = { + title: title, + text: text + }; + } else { + options = text; } - export default async function (text, title) { - let options; - if (typeof text === 'string') { - options = { - title: title, - text: text - }; - } else { - options = text; - } + await appRouter.ready(); - await appRouter.ready(); + if (useNativeAlert()) { + alert((options.text || '').replaceAll('
', '\n')); + return Promise.resolve(); + } else { + const items = []; - if (useNativeAlert()) { - alert((options.text || '').replaceAll('
', '\n')); - return Promise.resolve(); - } else { - const items = []; + items.push({ + name: globalize.translate('ButtonGotIt'), + id: 'ok', + type: 'submit' + }); - items.push({ - name: globalize.translate('ButtonGotIt'), - id: 'ok', - type: 'submit' - }); - - options.buttons = items; - return dialog.show(options); - } + options.buttons = items; + return dialog.show(options); } - -/* eslint-enable indent */ +} diff --git a/src/components/alphaPicker/alphaPicker.js b/src/components/alphaPicker/alphaPicker.js index ac484bbf1c..dd97c59074 100644 --- a/src/components/alphaPicker/alphaPicker.js +++ b/src/components/alphaPicker/alphaPicker.js @@ -1,5 +1,3 @@ -/* eslint-disable indent */ - /** * Module alphaPicker. * @module components/alphaPicker/alphaPicker @@ -13,312 +11,311 @@ import './style.scss'; import '../../elements/emby-button/paper-icon-button-light'; import 'material-design-icons-iconfont'; - const selectedButtonClass = 'alphaPickerButton-selected'; +const selectedButtonClass = 'alphaPickerButton-selected'; - function focus() { - const scope = this; - const selected = scope.querySelector(`.${selectedButtonClass}`); +function focus() { + const scope = this; + const selected = scope.querySelector(`.${selectedButtonClass}`); - if (selected) { - focusManager.focus(selected); - } else { - focusManager.autoFocus(scope, true); - } + if (selected) { + focusManager.focus(selected); + } else { + focusManager.autoFocus(scope, true); + } +} + +function getAlphaPickerButtonClassName(vertical) { + let alphaPickerButtonClassName = 'alphaPickerButton'; + + if (layoutManager.tv) { + alphaPickerButtonClassName += ' alphaPickerButton-tv'; } - function getAlphaPickerButtonClassName(vertical) { - let alphaPickerButtonClassName = 'alphaPickerButton'; - - if (layoutManager.tv) { - alphaPickerButtonClassName += ' alphaPickerButton-tv'; - } - - if (vertical) { - alphaPickerButtonClassName += ' alphaPickerButton-vertical'; - } - - return alphaPickerButtonClassName; + if (vertical) { + alphaPickerButtonClassName += ' alphaPickerButton-vertical'; } - function getLetterButton(l, vertical) { - return ``; + return alphaPickerButtonClassName; +} + +function getLetterButton(l, vertical) { + return ``; +} + +function mapLetters(letters, vertical) { + return letters.map(l => { + return getLetterButton(l, vertical); + }); +} + +function render(element, options) { + element.classList.add('alphaPicker'); + + if (layoutManager.tv) { + element.classList.add('alphaPicker-tv'); } - function mapLetters(letters, vertical) { - return letters.map(l => { - return getLetterButton(l, vertical); - }); + const vertical = element.classList.contains('alphaPicker-vertical'); + + if (!vertical) { + element.classList.add('focuscontainer-x'); } - function render(element, options) { - element.classList.add('alphaPicker'); + let html = ''; + let letters; - if (layoutManager.tv) { - element.classList.add('alphaPicker-tv'); - } + const alphaPickerButtonClassName = getAlphaPickerButtonClassName(vertical); - const vertical = element.classList.contains('alphaPicker-vertical'); + let rowClassName = 'alphaPickerRow'; - if (!vertical) { - element.classList.add('focuscontainer-x'); - } + if (vertical) { + rowClassName += ' alphaPickerRow-vertical'; + } - let html = ''; - let letters; - - const alphaPickerButtonClassName = getAlphaPickerButtonClassName(vertical); - - let rowClassName = 'alphaPickerRow'; - - if (vertical) { - rowClassName += ' alphaPickerRow-vertical'; - } - - html += `
`; - if (options.mode === 'keyboard') { - html += ``; - } 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 += `
`; + if (options.mode === 'keyboard') { + html += ``; + } else { + letters = ['#']; html += mapLetters(letters, vertical).join(''); - - if (options.mode === 'keyboard') { - html += ``; - html += '
'; - - letters = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; - html += `
`; - html += '
'; - html += mapLetters(letters, vertical).join(''); - html += '
'; - } else { - html += '
'; - } - - element.innerHTML = html; - - element.classList.add('focusable'); - element.focus = focus; } - export class AlphaPicker { - constructor(options) { - const self = this; + 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(''); - this.options = options; + if (options.mode === 'keyboard') { + html += ``; + html += ''; - const element = options.element; - const itemsContainer = options.itemsContainer; - const itemClass = options.itemClass; + letters = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; + html += `
`; + html += '
'; + html += mapLetters(letters, vertical).join(''); + html += '
'; + } else { + html += ''; + } - let itemFocusValue; - let itemFocusTimeout; + element.innerHTML = html; - function onItemFocusTimeout() { - itemFocusTimeout = null; - self.value(itemFocusValue); - } + element.classList.add('focusable'); + element.focus = focus; +} - let alphaFocusedElement; - let alphaFocusTimeout; +export class AlphaPicker { + constructor(options) { + const self = this; - function onAlphaFocusTimeout() { - alphaFocusTimeout = null; + this.options = options; - if (document.activeElement === alphaFocusedElement) { - const value = alphaFocusedElement.getAttribute('data-value'); - self.value(value, true); - } - } + const element = options.element; + const itemsContainer = options.itemsContainer; + const itemClass = options.itemClass; - function onAlphaPickerInKeyboardModeClick(e) { - const alphaPickerButton = dom.parentWithClass(e.target, 'alphaPickerButton'); + let itemFocusValue; + let itemFocusTimeout; - if (alphaPickerButton) { - const value = alphaPickerButton.getAttribute('data-value'); - - element.dispatchEvent(new CustomEvent('alphavalueclicked', { - cancelable: false, - detail: { - value - } - })); - } - } - - function onAlphaPickerClick(e) { - const alphaPickerButton = dom.parentWithClass(e.target, 'alphaPickerButton'); - - if (alphaPickerButton) { - const value = alphaPickerButton.getAttribute('data-value'); - if ((this._currentValue || '').toUpperCase() === value.toUpperCase()) { - this.value(null, true); - } else { - this.value(value, true); - } - } - } - - function onAlphaPickerFocusIn(e) { - if (alphaFocusTimeout) { - clearTimeout(alphaFocusTimeout); - alphaFocusTimeout = null; - } - - const alphaPickerButton = dom.parentWithClass(e.target, 'alphaPickerButton'); - - if (alphaPickerButton) { - alphaFocusedElement = alphaPickerButton; - alphaFocusTimeout = setTimeout(onAlphaFocusTimeout, 600); - } - } - - function onItemsFocusIn(e) { - const item = dom.parentWithClass(e.target, itemClass); - - if (item) { - const prefix = item.getAttribute('data-prefix'); - if (prefix && prefix.length) { - itemFocusValue = prefix[0]; - if (itemFocusTimeout) { - clearTimeout(itemFocusTimeout); - } - itemFocusTimeout = setTimeout(onItemFocusTimeout, 100); - } - } - } - - this.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); + function onItemFocusTimeout() { + itemFocusTimeout = null; + self.value(itemFocusValue); } - value(value, applyValue) { - const element = this.options.element; - let btn; - let selected; + let alphaFocusedElement; + let alphaFocusTimeout; - if (value !== undefined) { - if (value != null) { - value = value.toUpperCase(); - this._currentValue = value; + function onAlphaFocusTimeout() { + alphaFocusTimeout = null; - if (this.options.mode !== 'keyboard') { - selected = element.querySelector(`.${selectedButtonClass}`); - - try { - btn = element.querySelector(`.alphaPickerButton[data-value='${value}']`); - } catch (err) { - console.error('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 (document.activeElement === alphaFocusedElement) { + const value = alphaFocusedElement.getAttribute('data-value'); + self.value(value, true); } + } - if (applyValue) { - element.dispatchEvent(new CustomEvent('alphavaluechanged', { + function onAlphaPickerInKeyboardModeClick(e) { + const alphaPickerButton = dom.parentWithClass(e.target, 'alphaPickerButton'); + + if (alphaPickerButton) { + const value = alphaPickerButton.getAttribute('data-value'); + + element.dispatchEvent(new CustomEvent('alphavalueclicked', { cancelable: false, detail: { value } })); } - - return this._currentValue; } - on(name, fn) { - const element = this.options.element; - element.addEventListener(name, fn); + function onAlphaPickerClick(e) { + const alphaPickerButton = dom.parentWithClass(e.target, 'alphaPickerButton'); + + if (alphaPickerButton) { + const value = alphaPickerButton.getAttribute('data-value'); + if ((this._currentValue || '').toUpperCase() === value.toUpperCase()) { + this.value(null, true); + } else { + this.value(value, true); + } + } } - off(name, fn) { - const element = this.options.element; - element.removeEventListener(name, fn); + function onAlphaPickerFocusIn(e) { + if (alphaFocusTimeout) { + clearTimeout(alphaFocusTimeout); + alphaFocusTimeout = null; + } + + const alphaPickerButton = dom.parentWithClass(e.target, 'alphaPickerButton'); + + if (alphaPickerButton) { + alphaFocusedElement = alphaPickerButton; + alphaFocusTimeout = setTimeout(onAlphaFocusTimeout, 600); + } } - updateControls(query) { - if (query.NameLessThan) { - this.value('#'); + function onItemsFocusIn(e) { + const item = dom.parentWithClass(e.target, itemClass); + + if (item) { + const prefix = item.getAttribute('data-prefix'); + if (prefix && prefix.length) { + itemFocusValue = prefix[0]; + if (itemFocusTimeout) { + clearTimeout(itemFocusTimeout); + } + itemFocusTimeout = setTimeout(onItemFocusTimeout, 100); + } + } + } + + this.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 { - this.value(query.NameStartsWith); + if (itemsContainer) { + itemsContainer.removeEventListener('focus', onItemsFocusIn, true); + } + + element.removeEventListener('click', onAlphaPickerInKeyboardModeClick); + element.removeEventListener('focus', onAlphaPickerFocusIn, true); + element.removeEventListener('click', onAlphaPickerClick.bind(this)); } + }; - this.visible(query.SortBy.indexOf('SortName') !== -1); - } + render(element, options); - visible(visible) { - const element = this.options.element; - element.style.visibility = visible ? 'visible' : 'hidden'; - } - - values() { - const element = this.options.element; - const elems = element.querySelectorAll('.alphaPickerButton'); - const values = []; - for (let i = 0, length = elems.length; i < length; i++) { - values.push(elems[i].getAttribute('data-value')); - } - - return values; - } - - focus() { - const element = this.options.element; - focusManager.autoFocus(element, true); - } - - destroy() { - const element = this.options.element; - this.enabled(false); - element.classList.remove('focuscontainer-x'); - this.options = null; - } + this.enabled(true); + this.visible(true); } -/* eslint-enable indent */ + value(value, applyValue) { + const element = this.options.element; + let btn; + let 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.error('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 + } + })); + } + + return this._currentValue; + } + + on(name, fn) { + const element = this.options.element; + element.addEventListener(name, fn); + } + + off(name, fn) { + const element = this.options.element; + element.removeEventListener(name, fn); + } + + updateControls(query) { + if (query.NameLessThan) { + this.value('#'); + } else { + this.value(query.NameStartsWith); + } + + this.visible(query.SortBy.indexOf('SortName') !== -1); + } + + visible(visible) { + const element = this.options.element; + element.style.visibility = visible ? 'visible' : 'hidden'; + } + + values() { + const element = this.options.element; + const elems = element.querySelectorAll('.alphaPickerButton'); + const values = []; + for (let i = 0, length = elems.length; i < length; i++) { + values.push(elems[i].getAttribute('data-value')); + } + + return values; + } + + focus() { + const element = this.options.element; + focusManager.autoFocus(element, true); + } + + destroy() { + const element = this.options.element; + this.enabled(false); + element.classList.remove('focuscontainer-x'); + this.options = null; + } +} + export default AlphaPicker; diff --git a/src/components/autoFocuser.js b/src/components/autoFocuser.js index 59b6bca809..347b6dd4f2 100644 --- a/src/components/autoFocuser.js +++ b/src/components/autoFocuser.js @@ -1,5 +1,3 @@ -/* eslint-disable indent */ - /** * Module for performing auto-focus. * @module components/autoFocuser @@ -8,93 +6,91 @@ import focusManager from './focusManager'; import layoutManager from './layoutManager'; - /** +/** * Previously selected element. */ - let activeElement; +let activeElement; - /** +/** * Returns _true_ if AutoFocuser is enabled. */ - export function isEnabled() { - return layoutManager.tv; - } +export function isEnabled() { + return layoutManager.tv; +} - /** +/** * Start AutoFocuser. */ - export function enable() { - if (!isEnabled()) { - return; - } - - window.addEventListener('focusin', function (e) { - activeElement = e.target; - }); - - console.debug('AutoFocuser enabled'); +export function enable() { + if (!isEnabled()) { + return; } - /** + window.addEventListener('focusin', function (e) { + activeElement = e.target; + }); + + console.debug('AutoFocuser enabled'); +} + +/** * Set focus on a suitable element, taking into account the previously selected. * @param {HTMLElement} [container] - Element to limit scope. * @returns {HTMLElement} Focused element. */ - export function autoFocus(container) { - if (!isEnabled()) { - return null; - } - - container = container || document.body; - - let candidates = []; - - if (activeElement) { - // These elements are recreated - if (activeElement.classList.contains('btnPreviousPage')) { - candidates.push(container.querySelector('.btnPreviousPage')); - candidates.push(container.querySelector('.btnNextPage')); - } else if (activeElement.classList.contains('btnNextPage')) { - candidates.push(container.querySelector('.btnNextPage')); - candidates.push(container.querySelector('.btnPreviousPage')); - } else if (activeElement.classList.contains('btnSelectView')) { - candidates.push(container.querySelector('.btnSelectView')); - } - - candidates.push(activeElement); - } - - candidates = candidates.concat(Array.from(container.querySelectorAll('.btnPlay'))); - - let focusedElement; - - candidates.every(function (element) { - if (focusManager.isCurrentlyFocusable(element)) { - focusManager.focus(element); - focusedElement = element; - return false; - } - - return true; - }); - - if (!focusedElement) { - // FIXME: Multiple itemsContainers - const itemsContainer = container.querySelector('.itemsContainer'); - - if (itemsContainer) { - focusedElement = focusManager.autoFocus(itemsContainer); - } - } - - if (!focusedElement) { - focusedElement = focusManager.autoFocus(container); - } - - return focusedElement; +export function autoFocus(container) { + if (!isEnabled()) { + return null; } -/* eslint-enable indent */ + container = container || document.body; + + let candidates = []; + + if (activeElement) { + // These elements are recreated + if (activeElement.classList.contains('btnPreviousPage')) { + candidates.push(container.querySelector('.btnPreviousPage')); + candidates.push(container.querySelector('.btnNextPage')); + } else if (activeElement.classList.contains('btnNextPage')) { + candidates.push(container.querySelector('.btnNextPage')); + candidates.push(container.querySelector('.btnPreviousPage')); + } else if (activeElement.classList.contains('btnSelectView')) { + candidates.push(container.querySelector('.btnSelectView')); + } + + candidates.push(activeElement); + } + + candidates = candidates.concat(Array.from(container.querySelectorAll('.btnPlay'))); + + let focusedElement; + + candidates.every(function (element) { + if (focusManager.isCurrentlyFocusable(element)) { + focusManager.focus(element); + focusedElement = element; + return false; + } + + return true; + }); + + if (!focusedElement) { + // FIXME: Multiple itemsContainers + const itemsContainer = container.querySelector('.itemsContainer'); + + if (itemsContainer) { + focusedElement = focusManager.autoFocus(itemsContainer); + } + } + + if (!focusedElement) { + focusedElement = focusManager.autoFocus(container); + } + + return focusedElement; +} export default { isEnabled: isEnabled, diff --git a/src/components/backdrop/backdrop.js b/src/components/backdrop/backdrop.js index 027f727812..4cd481827f 100644 --- a/src/components/backdrop/backdrop.js +++ b/src/components/backdrop/backdrop.js @@ -7,282 +7,278 @@ import ServerConnections from '../ServerConnections'; import './backdrop.scss'; -/* eslint-disable indent */ +function enableAnimation() { + return !browser.slow; +} - function enableAnimation() { - return !browser.slow; - } - - function enableRotation() { - return !browser.tv +function enableRotation() { + return !browser.tv // Causes high cpu usage && !browser.firefox; - } +} - class Backdrop { - load(url, parent, existingBackdropImage) { - const img = new Image(); - const self = this; +class Backdrop { + load(url, parent, existingBackdropImage) { + const img = new Image(); + const self = this; - img.onload = () => { - if (self.isDestroyed) { - return; - } - - const 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()) { - if (existingBackdropImage && existingBackdropImage.parentNode) { - existingBackdropImage.parentNode.removeChild(existingBackdropImage); - } - internalBackdrop(true); - return; - } - - const onAnimationComplete = () => { - 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; - } - - cancelAnimation() { - const elem = this.currentAnimatingElement; - if (elem) { - elem.classList.remove('backdropImageFadeIn'); - this.currentAnimatingElement = null; - } - } - - destroy() { - this.isDestroyed = true; - this.cancelAnimation(); - } - } - - let 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; - } - - export function clearBackdrop(clearAll) { - clearRotation(); - - if (currentLoadingBackdrop) { - currentLoadingBackdrop.destroy(); - currentLoadingBackdrop = null; - } - - const elem = getBackdropContainer(); - elem.innerHTML = ''; - - if (clearAll) { - hasExternalBackdrop = false; - } - - internalBackdrop(false); - } - - let 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'); - } - } - - let hasInternalBackdrop; - function internalBackdrop(isEnabled) { - hasInternalBackdrop = isEnabled; - setBackgroundContainerBackgroundEnabled(); - } - - let hasExternalBackdrop; - export function externalBackdrop(isEnabled) { - hasExternalBackdrop = isEnabled; - setBackgroundContainerBackgroundEnabled(); - } - - let currentLoadingBackdrop; - function setBackdropImage(url) { - if (currentLoadingBackdrop) { - currentLoadingBackdrop.destroy(); - currentLoadingBackdrop = null; - } - - const elem = getBackdropContainer(); - const existingBackdropImage = elem.querySelector('.displayingBackdropImage'); - - if (existingBackdropImage && existingBackdropImage.getAttribute('data-url') === url) { - if (existingBackdropImage.getAttribute('data-url') === url) { + img.onload = () => { + if (self.isDestroyed) { return; } - existingBackdropImage.classList.remove('displayingBackdropImage'); - } - const instance = new Backdrop(); - instance.load(url, elem, existingBackdropImage); - currentLoadingBackdrop = instance; - } + const backdropImage = document.createElement('div'); + backdropImage.classList.add('backdropImage'); + backdropImage.classList.add('displayingBackdropImage'); + backdropImage.style.backgroundImage = `url('${url}')`; + backdropImage.setAttribute('data-url', url); - function getItemImageUrls(item, imageOptions) { - imageOptions = imageOptions || {}; + backdropImage.classList.add('backdropImageFadeIn'); + parent.appendChild(backdropImage); - const apiClient = ServerConnections.getApiClient(item.ServerId); - if (item.BackdropImageTags && item.BackdropImageTags.length > 0) { - return item.BackdropImageTags.map((imgTag, index) => { - return apiClient.getScaledImageUrl(item.BackdropItemId || item.Id, Object.assign(imageOptions, { - type: 'Backdrop', - tag: imgTag, - maxWidth: dom.getScreenWidth(), - index: index - })); + if (!enableAnimation()) { + if (existingBackdropImage && existingBackdropImage.parentNode) { + existingBackdropImage.parentNode.removeChild(existingBackdropImage); + } + internalBackdrop(true); + return; + } + + const onAnimationComplete = () => { + 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 }); - } - if (item.ParentBackdropItemId && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length) { - return item.ParentBackdropImageTags.map((imgTag, index) => { - return apiClient.getScaledImageUrl(item.ParentBackdropItemId, Object.assign(imageOptions, { - type: 'Backdrop', - tag: imgTag, - maxWidth: dom.getScreenWidth(), - index: index - })); - }); - } - - return []; - } - - function getImageUrls(items, imageOptions) { - const list = []; - const onImg = img => { - list.push(img); + internalBackdrop(true); }; - for (let i = 0, length = items.length; i < length; i++) { - const itemImages = getItemImageUrls(items[i], imageOptions); - itemImages.forEach(onImg); - } - - return list; + img.src = url; } - function enabled() { - return userSettings.enableBackdrops(); - } - - let rotationInterval; - let currentRotatingImages = []; - let currentRotationIndex = -1; - export function setBackdrops(items, imageOptions, enableImageRotation) { - if (enabled()) { - const images = getImageUrls(items, imageOptions); - - if (images.length) { - startRotation(images, enableImageRotation); - } else { - clearBackdrop(); - } + cancelAnimation() { + const elem = this.currentAnimatingElement; + if (elem) { + elem.classList.remove('backdropImageFadeIn'); + this.currentAnimatingElement = null; } } - function startRotation(images, enableImageRotation) { - if (isEqual(images, currentRotatingImages)) { + destroy() { + this.isDestroyed = true; + this.cancelAnimation(); + } +} + +let 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; +} + +export function clearBackdrop(clearAll) { + clearRotation(); + + if (currentLoadingBackdrop) { + currentLoadingBackdrop.destroy(); + currentLoadingBackdrop = null; + } + + const elem = getBackdropContainer(); + elem.innerHTML = ''; + + if (clearAll) { + hasExternalBackdrop = false; + } + + internalBackdrop(false); +} + +let 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'); + } +} + +let hasInternalBackdrop; +function internalBackdrop(isEnabled) { + hasInternalBackdrop = isEnabled; + setBackgroundContainerBackgroundEnabled(); +} + +let hasExternalBackdrop; +export function externalBackdrop(isEnabled) { + hasExternalBackdrop = isEnabled; + setBackgroundContainerBackgroundEnabled(); +} + +let currentLoadingBackdrop; +function setBackdropImage(url) { + if (currentLoadingBackdrop) { + currentLoadingBackdrop.destroy(); + currentLoadingBackdrop = null; + } + + const elem = getBackdropContainer(); + const existingBackdropImage = elem.querySelector('.displayingBackdropImage'); + + if (existingBackdropImage && existingBackdropImage.getAttribute('data-url') === url) { + if (existingBackdropImage.getAttribute('data-url') === url) { return; } - - clearRotation(); - - currentRotatingImages = images; - currentRotationIndex = -1; - - if (images.length > 1 && enableImageRotation !== false && enableRotation()) { - rotationInterval = setInterval(onRotationInterval, 24000); - } - - onRotationInterval(); + existingBackdropImage.classList.remove('displayingBackdropImage'); } - function onRotationInterval() { - if (playbackManager.isPlayingLocally(['Video'])) { - return; - } + const instance = new Backdrop(); + instance.load(url, elem, existingBackdropImage); + currentLoadingBackdrop = instance; +} - let newIndex = currentRotationIndex + 1; - if (newIndex >= currentRotatingImages.length) { - newIndex = 0; - } +function getItemImageUrls(item, imageOptions) { + imageOptions = imageOptions || {}; - currentRotationIndex = newIndex; - setBackdropImage(currentRotatingImages[newIndex]); + const apiClient = ServerConnections.getApiClient(item.ServerId); + if (item.BackdropImageTags && item.BackdropImageTags.length > 0) { + return item.BackdropImageTags.map((imgTag, index) => { + return apiClient.getScaledImageUrl(item.BackdropItemId || item.Id, Object.assign(imageOptions, { + type: 'Backdrop', + tag: imgTag, + maxWidth: dom.getScreenWidth(), + index: index + })); + }); } - function clearRotation() { - const interval = rotationInterval; - if (interval) { - clearInterval(interval); - } - - rotationInterval = null; - currentRotatingImages = []; - currentRotationIndex = -1; + if (item.ParentBackdropItemId && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length) { + return item.ParentBackdropImageTags.map((imgTag, index) => { + return apiClient.getScaledImageUrl(item.ParentBackdropItemId, Object.assign(imageOptions, { + type: 'Backdrop', + tag: imgTag, + maxWidth: dom.getScreenWidth(), + index: index + })); + }); } - export function setBackdrop(url, imageOptions) { - if (url && typeof url !== 'string') { - url = getImageUrls([url], imageOptions)[0]; - } + return []; +} - if (url) { - clearRotation(); - setBackdropImage(url); +function getImageUrls(items, imageOptions) { + const list = []; + const onImg = img => { + list.push(img); + }; + + for (let i = 0, length = items.length; i < length; i++) { + const itemImages = getItemImageUrls(items[i], imageOptions); + itemImages.forEach(onImg); + } + + return list; +} + +function enabled() { + return userSettings.enableBackdrops(); +} + +let rotationInterval; +let currentRotatingImages = []; +let currentRotationIndex = -1; +export function setBackdrops(items, imageOptions, enableImageRotation) { + if (enabled()) { + const images = getImageUrls(items, imageOptions); + + if (images.length) { + startRotation(images, enableImageRotation); } else { clearBackdrop(); } } +} -/* eslint-enable indent */ +function startRotation(images, enableImageRotation) { + if (isEqual(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; + } + + let newIndex = currentRotationIndex + 1; + if (newIndex >= currentRotatingImages.length) { + newIndex = 0; + } + + currentRotationIndex = newIndex; + setBackdropImage(currentRotatingImages[newIndex]); +} + +function clearRotation() { + const interval = rotationInterval; + if (interval) { + clearInterval(interval); + } + + rotationInterval = null; + currentRotatingImages = []; + currentRotationIndex = -1; +} + +export function setBackdrop(url, imageOptions) { + if (url && typeof url !== 'string') { + url = getImageUrls([url], imageOptions)[0]; + } + + if (url) { + clearRotation(); + setBackdropImage(url); + } else { + clearBackdrop(); + } +} /** * @enum TransparencyLevel diff --git a/src/components/cardbuilder/cardBuilder.js b/src/components/cardbuilder/cardBuilder.js index 04c03c6605..d4f3aafa49 100644 --- a/src/components/cardbuilder/cardBuilder.js +++ b/src/components/cardbuilder/cardBuilder.js @@ -1,4 +1,3 @@ -/* eslint-disable indent */ /** * Module for building cards from item data. @@ -25,467 +24,467 @@ import '../guide/programs.scss'; import ServerConnections from '../ServerConnections'; import { appRouter } from '../appRouter'; - const enableFocusTransform = !browser.slow && !browser.edge; +const enableFocusTransform = !browser.slow && !browser.edge; - /** +/** * Generate the HTML markup for cards for a set of items. * @param items - The items used to generate cards. * @param options - The options of the cards. * @returns {string} The HTML markup for the cards. */ - export function getCardsHtml(items, options) { - if (arguments.length === 1) { - options = arguments[0]; - items = options.items; - } +export function getCardsHtml(items, options) { + if (arguments.length === 1) { + options = arguments[0]; + items = options.items; + } - return buildCardsHtmlInternal(items, options); - } + return buildCardsHtmlInternal(items, options); +} - /** +/** * Computes the number of posters per row. * @param {string} shape - Shape of the cards. * @param {number} screenWidth - Width of the screen. * @param {boolean} isOrientationLandscape - Flag for the orientation of the screen. * @returns {number} Number of cards per row for an itemsContainer. */ - function getPostersPerRow(shape, screenWidth, isOrientationLandscape) { - switch (shape) { - case 'portrait': - if (layoutManager.tv) { - return 100 / 16.66666667; - } - if (screenWidth >= 2200) { - return 100 / 10; - } - if (screenWidth >= 1920) { - return 100 / 11.1111111111; - } - if (screenWidth >= 1600) { - return 100 / 12.5; - } - if (screenWidth >= 1400) { - return 100 / 14.28571428571; - } - if (screenWidth >= 1200) { - return 100 / 16.66666667; - } - if (screenWidth >= 800) { - return 5; - } - if (screenWidth >= 700) { - return 4; - } - if (screenWidth >= 500) { - return 100 / 33.33333333; - } - return 100 / 33.33333333; - case 'square': - if (layoutManager.tv) { - return 100 / 16.66666667; - } - if (screenWidth >= 2200) { - return 100 / 10; - } - if (screenWidth >= 1920) { - return 100 / 11.1111111111; - } - if (screenWidth >= 1600) { - return 100 / 12.5; - } - if (screenWidth >= 1400) { - return 100 / 14.28571428571; - } - if (screenWidth >= 1200) { - return 100 / 16.66666667; - } - if (screenWidth >= 800) { - return 5; - } - if (screenWidth >= 700) { - return 4; - } - if (screenWidth >= 500) { - return 100 / 33.33333333; - } - return 2; - case 'banner': - if (screenWidth >= 2200) { - return 100 / 25; - } - if (screenWidth >= 1200) { - return 100 / 33.33333333; - } - if (screenWidth >= 800) { - return 2; - } - return 1; - case 'backdrop': - if (layoutManager.tv) { - return 100 / 25; - } - if (screenWidth >= 2500) { - return 6; - } - if (screenWidth >= 1600) { - return 5; - } - if (screenWidth >= 1200) { - return 4; - } - if (screenWidth >= 770) { - return 3; - } - if (screenWidth >= 420) { - return 2; - } - return 1; - case 'smallBackdrop': - if (screenWidth >= 1600) { - return 100 / 12.5; - } - if (screenWidth >= 1400) { - return 100 / 14.2857142857; - } - if (screenWidth >= 1200) { - return 100 / 16.66666667; - } - if (screenWidth >= 1000) { - return 5; - } - if (screenWidth >= 800) { - return 4; - } - if (screenWidth >= 500) { - return 100 / 33.33333333; - } - return 2; - case 'overflowSmallBackdrop': - if (layoutManager.tv) { - return 100 / 18.9; - } - if (isOrientationLandscape) { - if (screenWidth >= 800) { - return 100 / 15.5; - } - return 100 / 23.3; - } else { - if (screenWidth >= 540) { - return 100 / 30; - } - return 100 / 72; - } - case 'overflowPortrait': - - if (layoutManager.tv) { - return 100 / 15.5; - } - if (isOrientationLandscape) { - if (screenWidth >= 1700) { - return 100 / 11.6; - } - return 100 / 15.5; - } else { - if (screenWidth >= 1400) { - return 100 / 15; - } - if (screenWidth >= 1200) { - return 100 / 18; - } - if (screenWidth >= 760) { - return 100 / 23; - } - if (screenWidth >= 400) { - return 100 / 31.5; - } - return 100 / 42; - } - case 'overflowSquare': - if (layoutManager.tv) { - return 100 / 15.5; - } - if (isOrientationLandscape) { - if (screenWidth >= 1700) { - return 100 / 11.6; - } - return 100 / 15.5; - } else { - if (screenWidth >= 1400) { - return 100 / 15; - } - if (screenWidth >= 1200) { - return 100 / 18; - } - if (screenWidth >= 760) { - return 100 / 23; - } - if (screenWidth >= 540) { - return 100 / 31.5; - } - return 100 / 42; - } - case 'overflowBackdrop': - if (layoutManager.tv) { - return 100 / 23.3; - } - if (isOrientationLandscape) { - if (screenWidth >= 1700) { - return 100 / 18.5; - } - return 100 / 23.3; - } else { - if (screenWidth >= 1800) { - return 100 / 23.5; - } - if (screenWidth >= 1400) { - return 100 / 30; - } - if (screenWidth >= 760) { - return 100 / 40; - } - if (screenWidth >= 640) { - return 100 / 56; - } - return 100 / 72; - } - default: - return 4; +function getPostersPerRow(shape, screenWidth, isOrientationLandscape) { + switch (shape) { + case 'portrait': + if (layoutManager.tv) { + return 100 / 16.66666667; } - } + if (screenWidth >= 2200) { + return 100 / 10; + } + if (screenWidth >= 1920) { + return 100 / 11.1111111111; + } + if (screenWidth >= 1600) { + return 100 / 12.5; + } + if (screenWidth >= 1400) { + return 100 / 14.28571428571; + } + if (screenWidth >= 1200) { + return 100 / 16.66666667; + } + if (screenWidth >= 800) { + return 5; + } + if (screenWidth >= 700) { + return 4; + } + if (screenWidth >= 500) { + return 100 / 33.33333333; + } + return 100 / 33.33333333; + case 'square': + if (layoutManager.tv) { + return 100 / 16.66666667; + } + if (screenWidth >= 2200) { + return 100 / 10; + } + if (screenWidth >= 1920) { + return 100 / 11.1111111111; + } + if (screenWidth >= 1600) { + return 100 / 12.5; + } + if (screenWidth >= 1400) { + return 100 / 14.28571428571; + } + if (screenWidth >= 1200) { + return 100 / 16.66666667; + } + if (screenWidth >= 800) { + return 5; + } + if (screenWidth >= 700) { + return 4; + } + if (screenWidth >= 500) { + return 100 / 33.33333333; + } + return 2; + case 'banner': + if (screenWidth >= 2200) { + return 100 / 25; + } + if (screenWidth >= 1200) { + return 100 / 33.33333333; + } + if (screenWidth >= 800) { + return 2; + } + return 1; + case 'backdrop': + if (layoutManager.tv) { + return 100 / 25; + } + if (screenWidth >= 2500) { + return 6; + } + if (screenWidth >= 1600) { + return 5; + } + if (screenWidth >= 1200) { + return 4; + } + if (screenWidth >= 770) { + return 3; + } + if (screenWidth >= 420) { + return 2; + } + return 1; + case 'smallBackdrop': + if (screenWidth >= 1600) { + return 100 / 12.5; + } + if (screenWidth >= 1400) { + return 100 / 14.2857142857; + } + if (screenWidth >= 1200) { + return 100 / 16.66666667; + } + if (screenWidth >= 1000) { + return 5; + } + if (screenWidth >= 800) { + return 4; + } + if (screenWidth >= 500) { + return 100 / 33.33333333; + } + return 2; + case 'overflowSmallBackdrop': + if (layoutManager.tv) { + return 100 / 18.9; + } + if (isOrientationLandscape) { + if (screenWidth >= 800) { + return 100 / 15.5; + } + return 100 / 23.3; + } else { + if (screenWidth >= 540) { + return 100 / 30; + } + return 100 / 72; + } + case 'overflowPortrait': - /** + if (layoutManager.tv) { + return 100 / 15.5; + } + if (isOrientationLandscape) { + if (screenWidth >= 1700) { + return 100 / 11.6; + } + return 100 / 15.5; + } else { + if (screenWidth >= 1400) { + return 100 / 15; + } + if (screenWidth >= 1200) { + return 100 / 18; + } + if (screenWidth >= 760) { + return 100 / 23; + } + if (screenWidth >= 400) { + return 100 / 31.5; + } + return 100 / 42; + } + case 'overflowSquare': + if (layoutManager.tv) { + return 100 / 15.5; + } + if (isOrientationLandscape) { + if (screenWidth >= 1700) { + return 100 / 11.6; + } + return 100 / 15.5; + } else { + if (screenWidth >= 1400) { + return 100 / 15; + } + if (screenWidth >= 1200) { + return 100 / 18; + } + if (screenWidth >= 760) { + return 100 / 23; + } + if (screenWidth >= 540) { + return 100 / 31.5; + } + return 100 / 42; + } + case 'overflowBackdrop': + if (layoutManager.tv) { + return 100 / 23.3; + } + if (isOrientationLandscape) { + if (screenWidth >= 1700) { + return 100 / 18.5; + } + return 100 / 23.3; + } else { + if (screenWidth >= 1800) { + return 100 / 23.5; + } + if (screenWidth >= 1400) { + return 100 / 30; + } + if (screenWidth >= 760) { + return 100 / 40; + } + if (screenWidth >= 640) { + return 100 / 56; + } + return 100 / 72; + } + default: + return 4; + } +} + +/** * Checks if the window is resizable. * @param {number} windowWidth - Width of the device's screen. * @returns {boolean} - Result of the check. */ - function isResizable(windowWidth) { - const screen = window.screen; - if (screen) { - const screenWidth = screen.availWidth; +function isResizable(windowWidth) { + const screen = window.screen; + if (screen) { + const screenWidth = screen.availWidth; - if ((screenWidth - windowWidth) > 20) { - return true; - } - } - - return false; + if ((screenWidth - windowWidth) > 20) { + return true; } + } - /** + return false; +} + +/** * Gets the width of a card's image according to the shape and amount of cards per row. * @param {string} shape - Shape of the card. * @param {number} screenWidth - Width of the screen. * @param {boolean} isOrientationLandscape - Flag for the orientation of the screen. * @returns {number} Width of the image for a card. */ - function getImageWidth(shape, screenWidth, isOrientationLandscape) { - const imagesPerRow = getPostersPerRow(shape, screenWidth, isOrientationLandscape); - return Math.round(screenWidth / imagesPerRow); - } +function getImageWidth(shape, screenWidth, isOrientationLandscape) { + const imagesPerRow = getPostersPerRow(shape, screenWidth, isOrientationLandscape); + return Math.round(screenWidth / imagesPerRow); +} - /** +/** * Normalizes the options for a card. * @param {Object} items - A set of items. * @param {Object} options - Options for handling the items. */ - function setCardData(items, options) { - options.shape = options.shape || 'auto'; +function setCardData(items, options) { + options.shape = options.shape || 'auto'; - const primaryImageAspectRatio = imageLoader.getPrimaryImageAspectRatio(items); + const primaryImageAspectRatio = imageLoader.getPrimaryImageAspectRatio(items); - if (['auto', 'autohome', 'autooverflow', 'autoVertical'].includes(options.shape)) { - const requestedShape = options.shape; - options.shape = null; + if (['auto', 'autohome', 'autooverflow', 'autoVertical'].includes(options.shape)) { + const requestedShape = options.shape; + options.shape = null; - if (primaryImageAspectRatio) { - if (primaryImageAspectRatio >= 3) { - options.shape = 'banner'; - options.coverImage = true; - } else if (primaryImageAspectRatio >= 1.33) { - options.shape = requestedShape === 'autooverflow' ? 'overflowBackdrop' : 'backdrop'; - } else if (primaryImageAspectRatio > 0.71) { - options.shape = requestedShape === 'autooverflow' ? 'overflowSquare' : 'square'; - } else { - options.shape = requestedShape === 'autooverflow' ? 'overflowPortrait' : 'portrait'; - } - } - - if (!options.shape) { - options.shape = options.defaultShape || (requestedShape === 'autooverflow' ? 'overflowSquare' : 'square'); - } - } - - if (options.preferThumb === 'auto') { - options.preferThumb = options.shape === 'backdrop' || options.shape === 'overflowBackdrop'; - } - - options.uiAspect = getDesiredAspect(options.shape); - options.primaryImageAspectRatio = primaryImageAspectRatio; - - if (!options.width && options.widths) { - options.width = options.widths[options.shape]; - } - - if (options.rows && typeof (options.rows) !== 'number') { - options.rows = options.rows[options.shape]; - } - - if (!options.width) { - let screenWidth = dom.getWindowSize().innerWidth; - const screenHeight = dom.getWindowSize().innerHeight; - - if (isResizable(screenWidth)) { - const roundScreenTo = 100; - screenWidth = Math.floor(screenWidth / roundScreenTo) * roundScreenTo; - } - - options.width = getImageWidth(options.shape, screenWidth, screenWidth > (screenHeight * 1.3)); + if (primaryImageAspectRatio) { + if (primaryImageAspectRatio >= 3) { + options.shape = 'banner'; + options.coverImage = true; + } else if (primaryImageAspectRatio >= 1.33) { + options.shape = requestedShape === 'autooverflow' ? 'overflowBackdrop' : 'backdrop'; + } else if (primaryImageAspectRatio > 0.71) { + options.shape = requestedShape === 'autooverflow' ? 'overflowSquare' : 'square'; + } else { + options.shape = requestedShape === 'autooverflow' ? 'overflowPortrait' : 'portrait'; } } - /** + if (!options.shape) { + options.shape = options.defaultShape || (requestedShape === 'autooverflow' ? 'overflowSquare' : 'square'); + } + } + + if (options.preferThumb === 'auto') { + options.preferThumb = options.shape === 'backdrop' || options.shape === 'overflowBackdrop'; + } + + options.uiAspect = getDesiredAspect(options.shape); + options.primaryImageAspectRatio = primaryImageAspectRatio; + + if (!options.width && options.widths) { + options.width = options.widths[options.shape]; + } + + if (options.rows && typeof (options.rows) !== 'number') { + options.rows = options.rows[options.shape]; + } + + if (!options.width) { + let screenWidth = dom.getWindowSize().innerWidth; + const screenHeight = dom.getWindowSize().innerHeight; + + if (isResizable(screenWidth)) { + const roundScreenTo = 100; + screenWidth = Math.floor(screenWidth / roundScreenTo) * roundScreenTo; + } + + options.width = getImageWidth(options.shape, screenWidth, screenWidth > (screenHeight * 1.3)); + } +} + +/** * Generates the internal HTML markup for cards. * @param {Object} items - Items for which to generate the markup. * @param {Object} options - Options for generating the markup. * @returns {string} The internal HTML markup of the cards. */ - function buildCardsHtmlInternal(items, options) { - let isVertical = false; +function buildCardsHtmlInternal(items, options) { + let isVertical = false; - if (options.shape === 'autoVertical') { - isVertical = true; + if (options.shape === 'autoVertical') { + isVertical = true; + } + + setCardData(items, options); + + let html = ''; + let itemsInRow = 0; + + let currentIndexValue; + let hasOpenRow; + let hasOpenSection; + + const sectionTitleTagName = options.sectionTitleTagName || 'div'; + let apiClient; + let lastServerId; + + for (const [i, item] of items.entries()) { + const serverId = item.ServerId || options.serverId; + + if (serverId !== lastServerId) { + lastServerId = serverId; + apiClient = ServerConnections.getApiClient(lastServerId); + } + + if (options.indexBy) { + let newIndexValue = ''; + + if (options.indexBy === 'PremiereDate') { + if (item.PremiereDate) { + try { + newIndexValue = datetime.toLocaleDateString(datetime.parseISO8601Date(item.PremiereDate), { weekday: 'long', month: 'long', day: 'numeric' }); + } catch (error) { + console.error('error parsing timestamp for premiere date', error); + } + } + } else if (options.indexBy === 'ProductionYear') { + newIndexValue = item.ProductionYear; + } else if (options.indexBy === 'CommunityRating') { + const roundedRatingDecimal = item.CommunityRating % 1 >= 0.5 ? 0.5 : 0; + newIndexValue = item.CommunityRating ? (Math.floor(item.CommunityRating) + roundedRatingDecimal) + '+' : null; } - setCardData(items, options); - - let html = ''; - let itemsInRow = 0; - - let currentIndexValue; - let hasOpenRow; - let hasOpenSection; - - const sectionTitleTagName = options.sectionTitleTagName || 'div'; - let apiClient; - let lastServerId; - - for (const [i, item] of items.entries()) { - const serverId = item.ServerId || options.serverId; - - if (serverId !== lastServerId) { - lastServerId = serverId; - apiClient = ServerConnections.getApiClient(lastServerId); - } - - if (options.indexBy) { - let newIndexValue = ''; - - if (options.indexBy === 'PremiereDate') { - if (item.PremiereDate) { - try { - newIndexValue = datetime.toLocaleDateString(datetime.parseISO8601Date(item.PremiereDate), { weekday: 'long', month: 'long', day: 'numeric' }); - } catch (error) { - console.error('error parsing timestamp for premiere date', error); - } - } - } else if (options.indexBy === 'ProductionYear') { - newIndexValue = item.ProductionYear; - } else if (options.indexBy === 'CommunityRating') { - const roundedRatingDecimal = item.CommunityRating % 1 >= 0.5 ? 0.5 : 0; - newIndexValue = item.CommunityRating ? (Math.floor(item.CommunityRating) + roundedRatingDecimal) + '+' : null; - } - - if (newIndexValue !== currentIndexValue) { - if (hasOpenRow) { - html += ''; - hasOpenRow = false; - itemsInRow = 0; - } - - if (hasOpenSection) { - html += ''; - - if (isVertical) { - html += ''; - } - hasOpenSection = false; - } - - if (isVertical) { - html += '
'; - } else { - html += '
'; - } - html += '<' + sectionTitleTagName + ' class="sectionTitle">' + newIndexValue + ''; - if (isVertical) { - html += '
'; - } - currentIndexValue = newIndexValue; - hasOpenSection = true; - } - } - - if (options.rows && itemsInRow === 0) { - if (hasOpenRow) { - html += '
'; - hasOpenRow = false; - } - - html += '
'; - hasOpenRow = true; - } - - html += buildCard(i, item, apiClient, options); - - itemsInRow++; - - if (options.rows && itemsInRow >= options.rows) { + if (newIndexValue !== currentIndexValue) { + if (hasOpenRow) { html += '
'; hasOpenRow = false; itemsInRow = 0; } - } - if (hasOpenRow) { - html += '
'; - } + if (hasOpenSection) { + html += '
'; - if (hasOpenSection) { - html += ''; + if (isVertical) { + html += ''; + } + hasOpenSection = false; + } if (isVertical) { - html += ''; + html += '
'; + } else { + html += '
'; } + html += '<' + sectionTitleTagName + ' class="sectionTitle">' + newIndexValue + ''; + if (isVertical) { + html += '
'; + } + currentIndexValue = newIndexValue; + hasOpenSection = true; } - - return html; } - /** + if (options.rows && itemsInRow === 0) { + if (hasOpenRow) { + html += '
'; + hasOpenRow = false; + } + + html += '
'; + hasOpenRow = true; + } + + html += buildCard(i, item, apiClient, options); + + itemsInRow++; + + if (options.rows && itemsInRow >= options.rows) { + html += '
'; + hasOpenRow = false; + itemsInRow = 0; + } + } + + if (hasOpenRow) { + html += '
'; + } + + if (hasOpenSection) { + html += '
'; + + if (isVertical) { + html += ''; + } + } + + return html; +} + +/** * Computes the aspect ratio for a card given its shape. * @param {string} shape - Shape for which to get the aspect ratio. * @returns {null|number} Ratio of the shape. */ - function getDesiredAspect(shape) { - if (shape) { - shape = shape.toLowerCase(); - if (shape.indexOf('portrait') !== -1) { - return (2 / 3); - } - if (shape.indexOf('backdrop') !== -1) { - return (16 / 9); - } - if (shape.indexOf('square') !== -1) { - return 1; - } - if (shape.indexOf('banner') !== -1) { - return (1000 / 185); - } - } - return null; +function getDesiredAspect(shape) { + if (shape) { + shape = shape.toLowerCase(); + if (shape.indexOf('portrait') !== -1) { + return (2 / 3); } + if (shape.indexOf('backdrop') !== -1) { + return (16 / 9); + } + if (shape.indexOf('square') !== -1) { + return 1; + } + if (shape.indexOf('banner') !== -1) { + return (1000 / 185); + } + } + return null; +} - /** +/** * @typedef {Object} CardImageUrl * @property {string} imgUrl - Image URL. * @property {string} blurhash - Image blurhash. @@ -493,178 +492,178 @@ import { appRouter } from '../appRouter'; * @property {boolean} coverImage - Use cover style. */ - /** Get the URL of the card's image. +/** Get the URL of the card's image. * @param {Object} item - Item for which to generate a card. * @param {Object} apiClient - API client object. * @param {Object} options - Options of the card. * @param {string} shape - Shape of the desired image. * @returns {CardImageUrl} Object representing the URL of the card's image. */ - function getCardImageUrl(item, apiClient, options, shape) { - item = item.ProgramInfo || item; +function getCardImageUrl(item, apiClient, options, shape) { + item = item.ProgramInfo || item; - const width = options.width; - let height = null; - const primaryImageAspectRatio = item.PrimaryImageAspectRatio; - let forceName = false; - let imgUrl = null; - let imgTag = null; - let coverImage = false; - const uiAspect = getDesiredAspect(shape); - let imgType = null; - let itemId = null; + const width = options.width; + let height = null; + const primaryImageAspectRatio = item.PrimaryImageAspectRatio; + let forceName = false; + let imgUrl = null; + let imgTag = null; + let coverImage = false; + const uiAspect = getDesiredAspect(shape); + let imgType = null; + let itemId = null; - /* eslint-disable sonarjs/no-duplicated-branches */ - if (options.preferThumb && item.ImageTags && item.ImageTags.Thumb) { - imgType = 'Thumb'; - imgTag = item.ImageTags.Thumb; - } else if ((options.preferBanner || shape === 'banner') && item.ImageTags && item.ImageTags.Banner) { - imgType = 'Banner'; - imgTag = item.ImageTags.Banner; - } else if (options.preferDisc && item.ImageTags && item.ImageTags.Disc) { - imgType = 'Disc'; - imgTag = item.ImageTags.Disc; - } else if (options.preferLogo && item.ImageTags && item.ImageTags.Logo) { - imgType = 'Logo'; - imgTag = item.ImageTags.Logo; - } else if (options.preferLogo && item.ParentLogoImageTag && item.ParentLogoItemId) { - imgType = 'Logo'; - imgTag = item.ParentLogoImageTag; - itemId = item.ParentLogoItemId; - } else if (options.preferThumb && item.SeriesThumbImageTag && options.inheritThumb !== false) { - imgType = 'Thumb'; - imgTag = item.SeriesThumbImageTag; - itemId = item.SeriesId; - } else if (options.preferThumb && item.ParentThumbItemId && options.inheritThumb !== false && item.MediaType !== 'Photo') { - imgType = 'Thumb'; - imgTag = item.ParentThumbImageTag; - itemId = item.ParentThumbItemId; - } else if (options.preferThumb && item.BackdropImageTags && item.BackdropImageTags.length) { - imgType = 'Backdrop'; - imgTag = item.BackdropImageTags[0]; - forceName = true; - } else if (options.preferThumb && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length && options.inheritThumb !== false && item.Type === 'Episode') { - imgType = 'Backdrop'; - imgTag = item.ParentBackdropImageTags[0]; - itemId = item.ParentBackdropItemId; - } else if (item.ImageTags && item.ImageTags.Primary && (item.Type !== 'Episode' || item.ChildCount !== 0)) { - imgType = 'Primary'; - imgTag = item.ImageTags.Primary; - height = width && primaryImageAspectRatio ? Math.round(width / primaryImageAspectRatio) : null; + /* eslint-disable sonarjs/no-duplicated-branches */ + if (options.preferThumb && item.ImageTags && item.ImageTags.Thumb) { + imgType = 'Thumb'; + imgTag = item.ImageTags.Thumb; + } else if ((options.preferBanner || shape === 'banner') && item.ImageTags && item.ImageTags.Banner) { + imgType = 'Banner'; + imgTag = item.ImageTags.Banner; + } else if (options.preferDisc && item.ImageTags && item.ImageTags.Disc) { + imgType = 'Disc'; + imgTag = item.ImageTags.Disc; + } else if (options.preferLogo && item.ImageTags && item.ImageTags.Logo) { + imgType = 'Logo'; + imgTag = item.ImageTags.Logo; + } else if (options.preferLogo && item.ParentLogoImageTag && item.ParentLogoItemId) { + imgType = 'Logo'; + imgTag = item.ParentLogoImageTag; + itemId = item.ParentLogoItemId; + } else if (options.preferThumb && item.SeriesThumbImageTag && options.inheritThumb !== false) { + imgType = 'Thumb'; + imgTag = item.SeriesThumbImageTag; + itemId = item.SeriesId; + } else if (options.preferThumb && item.ParentThumbItemId && options.inheritThumb !== false && item.MediaType !== 'Photo') { + imgType = 'Thumb'; + imgTag = item.ParentThumbImageTag; + itemId = item.ParentThumbItemId; + } else if (options.preferThumb && item.BackdropImageTags && item.BackdropImageTags.length) { + imgType = 'Backdrop'; + imgTag = item.BackdropImageTags[0]; + forceName = true; + } else if (options.preferThumb && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length && options.inheritThumb !== false && item.Type === 'Episode') { + imgType = 'Backdrop'; + imgTag = item.ParentBackdropImageTags[0]; + itemId = item.ParentBackdropItemId; + } else if (item.ImageTags && item.ImageTags.Primary && (item.Type !== 'Episode' || item.ChildCount !== 0)) { + imgType = 'Primary'; + imgTag = item.ImageTags.Primary; + height = width && primaryImageAspectRatio ? Math.round(width / primaryImageAspectRatio) : null; - if (options.preferThumb && options.showTitle !== false) { - forceName = true; - } - - if (primaryImageAspectRatio && uiAspect) { - coverImage = (Math.abs(primaryImageAspectRatio - uiAspect) / uiAspect) <= 0.2; - } - } else if (item.SeriesPrimaryImageTag) { - imgType = 'Primary'; - imgTag = item.SeriesPrimaryImageTag; - itemId = item.SeriesId; - } else if (item.PrimaryImageTag) { - imgType = 'Primary'; - imgTag = item.PrimaryImageTag; - itemId = item.PrimaryImageItemId; - height = width && primaryImageAspectRatio ? Math.round(width / primaryImageAspectRatio) : null; - - if (options.preferThumb && options.showTitle !== false) { - forceName = true; - } - - if (primaryImageAspectRatio && uiAspect) { - coverImage = (Math.abs(primaryImageAspectRatio - uiAspect) / uiAspect) <= 0.2; - } - } else if (item.ParentPrimaryImageTag) { - imgType = 'Primary'; - imgTag = item.ParentPrimaryImageTag; - itemId = item.ParentPrimaryImageItemId; - } else if (item.AlbumId && item.AlbumPrimaryImageTag) { - imgType = 'Primary'; - imgTag = item.AlbumPrimaryImageTag; - itemId = item.AlbumId; - height = width && primaryImageAspectRatio ? Math.round(width / primaryImageAspectRatio) : null; - - if (primaryImageAspectRatio && uiAspect) { - coverImage = (Math.abs(primaryImageAspectRatio - uiAspect) / uiAspect) <= 0.2; - } - } else if (item.Type === 'Season' && item.ImageTags && item.ImageTags.Thumb) { - imgType = 'Thumb'; - imgTag = item.ImageTags.Thumb; - } else if (item.BackdropImageTags && item.BackdropImageTags.length) { - imgType = 'Backdrop'; - imgTag = item.BackdropImageTags[0]; - } else if (item.ImageTags && item.ImageTags.Thumb) { - imgType = 'Thumb'; - imgTag = item.ImageTags.Thumb; - } else if (item.SeriesThumbImageTag && options.inheritThumb !== false) { - imgType = 'Thumb'; - imgTag = item.SeriesThumbImageTag; - itemId = item.SeriesId; - } else if (item.ParentThumbItemId && options.inheritThumb !== false) { - imgType = 'Thumb'; - imgTag = item.ParentThumbImageTag; - itemId = item.ParentThumbItemId; - } else if (item.ParentBackdropImageTags && item.ParentBackdropImageTags.length && options.inheritThumb !== false) { - imgType = 'Backdrop'; - imgTag = item.ParentBackdropImageTags[0]; - itemId = item.ParentBackdropItemId; - } - /* eslint-enable sonarjs/no-duplicated-branches */ - - if (!itemId) { - itemId = item.Id; - } - - if (imgTag && imgType) { - // TODO: This place is a mess. Could do with a good spring cleaning. - if (!height && width && uiAspect) { - height = width / uiAspect; - } - imgUrl = apiClient.getScaledImageUrl(itemId, { - type: imgType, - fillHeight: height, - fillWidth: width, - quality: 96, - tag: imgTag - }); - } - - const blurHashes = options.imageBlurhashes || item.ImageBlurHashes || {}; - - return { - imgUrl: imgUrl, - blurhash: (blurHashes[imgType] || {})[imgTag], - forceName: forceName, - coverImage: coverImage - }; + if (options.preferThumb && options.showTitle !== false) { + forceName = true; } - /** + if (primaryImageAspectRatio && uiAspect) { + coverImage = (Math.abs(primaryImageAspectRatio - uiAspect) / uiAspect) <= 0.2; + } + } else if (item.SeriesPrimaryImageTag) { + imgType = 'Primary'; + imgTag = item.SeriesPrimaryImageTag; + itemId = item.SeriesId; + } else if (item.PrimaryImageTag) { + imgType = 'Primary'; + imgTag = item.PrimaryImageTag; + itemId = item.PrimaryImageItemId; + height = width && primaryImageAspectRatio ? Math.round(width / primaryImageAspectRatio) : null; + + if (options.preferThumb && options.showTitle !== false) { + forceName = true; + } + + if (primaryImageAspectRatio && uiAspect) { + coverImage = (Math.abs(primaryImageAspectRatio - uiAspect) / uiAspect) <= 0.2; + } + } else if (item.ParentPrimaryImageTag) { + imgType = 'Primary'; + imgTag = item.ParentPrimaryImageTag; + itemId = item.ParentPrimaryImageItemId; + } else if (item.AlbumId && item.AlbumPrimaryImageTag) { + imgType = 'Primary'; + imgTag = item.AlbumPrimaryImageTag; + itemId = item.AlbumId; + height = width && primaryImageAspectRatio ? Math.round(width / primaryImageAspectRatio) : null; + + if (primaryImageAspectRatio && uiAspect) { + coverImage = (Math.abs(primaryImageAspectRatio - uiAspect) / uiAspect) <= 0.2; + } + } else if (item.Type === 'Season' && item.ImageTags && item.ImageTags.Thumb) { + imgType = 'Thumb'; + imgTag = item.ImageTags.Thumb; + } else if (item.BackdropImageTags && item.BackdropImageTags.length) { + imgType = 'Backdrop'; + imgTag = item.BackdropImageTags[0]; + } else if (item.ImageTags && item.ImageTags.Thumb) { + imgType = 'Thumb'; + imgTag = item.ImageTags.Thumb; + } else if (item.SeriesThumbImageTag && options.inheritThumb !== false) { + imgType = 'Thumb'; + imgTag = item.SeriesThumbImageTag; + itemId = item.SeriesId; + } else if (item.ParentThumbItemId && options.inheritThumb !== false) { + imgType = 'Thumb'; + imgTag = item.ParentThumbImageTag; + itemId = item.ParentThumbItemId; + } else if (item.ParentBackdropImageTags && item.ParentBackdropImageTags.length && options.inheritThumb !== false) { + imgType = 'Backdrop'; + imgTag = item.ParentBackdropImageTags[0]; + itemId = item.ParentBackdropItemId; + } + /* eslint-enable sonarjs/no-duplicated-branches */ + + if (!itemId) { + itemId = item.Id; + } + + if (imgTag && imgType) { + // TODO: This place is a mess. Could do with a good spring cleaning. + if (!height && width && uiAspect) { + height = width / uiAspect; + } + imgUrl = apiClient.getScaledImageUrl(itemId, { + type: imgType, + fillHeight: height, + fillWidth: width, + quality: 96, + tag: imgTag + }); + } + + const blurHashes = options.imageBlurhashes || item.ImageBlurHashes || {}; + + return { + imgUrl: imgUrl, + blurhash: (blurHashes[imgType] || {})[imgTag], + forceName: forceName, + coverImage: coverImage + }; +} + +/** * Generates an index used to select the default color of a card based on a string. * @param {?string} [str] - String to use for generating the index. * @returns {number} Index of the color. */ - function getDefaultColorIndex(str) { - const numRandomColors = 5; +function getDefaultColorIndex(str) { + const numRandomColors = 5; - if (str) { - const charIndex = Math.floor(str.length / 2); - const character = String(str.slice(charIndex, charIndex + 1).charCodeAt()); - let sum = 0; - for (let i = 0; i < character.length; i++) { - sum += parseInt(character.charAt(i), 10); - } - const index = String(sum).slice(-1); - - return (index % numRandomColors) + 1; - } else { - return randomInt(1, numRandomColors); - } + if (str) { + const charIndex = Math.floor(str.length / 2); + const character = String(str.slice(charIndex, charIndex + 1).charCodeAt()); + let sum = 0; + for (let i = 0; i < character.length; i++) { + sum += parseInt(character.charAt(i), 10); } + const index = String(sum).slice(-1); - /** + return (index % numRandomColors) + 1; + } else { + return randomInt(1, numRandomColors); + } +} + +/** * Generates the HTML markup for a card's text. * @param {Array} lines - Array containing the text lines. * @param {string} cssClass - Base CSS class to use for the lines. @@ -675,91 +674,91 @@ import { appRouter } from '../appRouter'; * @param {number} maxLines - Maximum number of lines to render. * @returns {string} HTML markup for the card's text. */ - function getCardTextLines(lines, cssClass, forceLines, isOuterFooter, cardLayout, addRightMargin, maxLines) { - let html = ''; +function getCardTextLines(lines, cssClass, forceLines, isOuterFooter, cardLayout, addRightMargin, maxLines) { + let html = ''; - let valid = 0; + let valid = 0; - for (let i = 0; i < lines.length; i++) { - let currentCssClass = cssClass; - const text = lines[i]; + for (let i = 0; i < lines.length; i++) { + let currentCssClass = cssClass; + const text = lines[i]; - if (valid > 0 && isOuterFooter) { - currentCssClass += ' cardText-secondary'; - } else if (valid === 0 && isOuterFooter) { - currentCssClass += ' cardText-first'; - } - - if (addRightMargin) { - currentCssClass += ' cardText-rightmargin'; - } - - if (text) { - html += "
"; - html += '' + text + ''; - html += '
'; - valid++; - - if (maxLines && valid >= maxLines) { - break; - } - } - } - - if (forceLines) { - const linesLength = maxLines || Math.min(lines.length, maxLines || lines.length); - - while (valid < linesLength) { - html += "
 
"; - valid++; - } - } - - return html; + if (valid > 0 && isOuterFooter) { + currentCssClass += ' cardText-secondary'; + } else if (valid === 0 && isOuterFooter) { + currentCssClass += ' cardText-first'; } - /** + if (addRightMargin) { + currentCssClass += ' cardText-rightmargin'; + } + + if (text) { + html += "
"; + html += '' + text + ''; + html += '
'; + valid++; + + if (maxLines && valid >= maxLines) { + break; + } + } + } + + if (forceLines) { + const linesLength = maxLines || Math.min(lines.length, maxLines || lines.length); + + while (valid < linesLength) { + html += "
 
"; + valid++; + } + } + + return html; +} + +/** * Determines if the item is live TV. * @param {Object} item - Item to use for the check. * @returns {boolean} Flag showing if the item is live TV. */ - function isUsingLiveTvNaming(item) { - return item.Type === 'Program' || item.Type === 'Timer' || item.Type === 'Recording'; - } +function isUsingLiveTvNaming(item) { + return item.Type === 'Program' || item.Type === 'Timer' || item.Type === 'Recording'; +} - /** +/** * Returns the air time text for the item based on the given times. * @param {object} item - Item used to generate the air time text. * @param {boolean} showAirDateTime - ISO8601 date for the start of the show. * @param {boolean} showAirEndTime - ISO8601 date for the end of the show. * @returns {string} The air time text for the item based on the given dates. */ - function getAirTimeText(item, showAirDateTime, showAirEndTime) { - let airTimeText = ''; +function getAirTimeText(item, showAirDateTime, showAirEndTime) { + let airTimeText = ''; - if (item.StartDate) { - try { - let date = datetime.parseISO8601Date(item.StartDate); + if (item.StartDate) { + try { + let date = datetime.parseISO8601Date(item.StartDate); - if (showAirDateTime) { - airTimeText += datetime.toLocaleDateString(date, { weekday: 'short', month: 'short', day: 'numeric' }) + ' '; - } - - airTimeText += datetime.getDisplayTime(date); - - if (item.EndDate && showAirEndTime) { - date = datetime.parseISO8601Date(item.EndDate); - airTimeText += ' - ' + datetime.getDisplayTime(date); - } - } catch (e) { - console.error('error parsing date: ' + item.StartDate); - } + if (showAirDateTime) { + airTimeText += datetime.toLocaleDateString(date, { weekday: 'short', month: 'short', day: 'numeric' }) + ' '; } - return airTimeText; - } + airTimeText += datetime.getDisplayTime(date); - /** + if (item.EndDate && showAirEndTime) { + date = datetime.parseISO8601Date(item.EndDate); + airTimeText += ' - ' + datetime.getDisplayTime(date); + } + } catch (e) { + console.error('error parsing date: ' + item.StartDate); + } + } + + return airTimeText; +} + +/** * Generates the HTML markup for the card's footer text. * @param {Object} item - Item used to generate the footer text. * @param {Object} apiClient - API client instance. @@ -770,370 +769,370 @@ import { appRouter } from '../appRouter'; * @param {Object} urls - Various urls for the footer * @returns {string} HTML markup of the card's footer text element. */ - function getCardFooterText(item, apiClient, options, footerClass, progressHtml, flags, urls) { - item = item.ProgramInfo || item; - let html = ''; +function getCardFooterText(item, apiClient, options, footerClass, progressHtml, flags, urls) { + item = item.ProgramInfo || item; + let html = ''; - if (urls.logoUrl) { - html += ''; - } + if (urls.logoUrl) { + html += ''; + } - const showTitle = options.showTitle === 'auto' ? true : (options.showTitle || item.Type === 'PhotoAlbum' || item.Type === 'Folder'); - const showOtherText = flags.isOuterFooter ? !flags.overlayText : flags.overlayText; + const showTitle = options.showTitle === 'auto' ? true : (options.showTitle || item.Type === 'PhotoAlbum' || item.Type === 'Folder'); + const showOtherText = flags.isOuterFooter ? !flags.overlayText : flags.overlayText; - if (flags.isOuterFooter && options.cardLayout && layoutManager.mobile && options.cardFooterAside !== 'none') { - html += ``; - } + if (flags.isOuterFooter && options.cardLayout && layoutManager.mobile && options.cardFooterAside !== 'none') { + html += ``; + } - const cssClass = options.centerText ? 'cardText cardTextCentered' : 'cardText'; - const serverId = item.ServerId || options.serverId; + const cssClass = options.centerText ? 'cardText cardTextCentered' : 'cardText'; + const serverId = item.ServerId || options.serverId; - let lines = []; - const parentTitleUnderneath = item.Type === 'MusicAlbum' || item.Type === 'Audio' || item.Type === 'MusicVideo'; - let titleAdded; - - if (showOtherText && (options.showParentTitle || options.showParentTitleOrTitle) && !parentTitleUnderneath) { - if (flags.isOuterFooter && item.Type === 'Episode' && item.SeriesName) { - if (item.SeriesId) { - lines.push(getTextActionButton({ - Id: item.SeriesId, - ServerId: serverId, - Name: item.SeriesName, - Type: 'Series', - IsFolder: true - })); - } else { - lines.push(escapeHtml(item.SeriesName)); - } - } else { - if (isUsingLiveTvNaming(item)) { - lines.push(escapeHtml(item.Name)); - - if (!item.EpisodeTitle && !item.IndexNumber) { - titleAdded = true; - } - } else { - const parentTitle = item.SeriesName || item.Series || item.Album || item.AlbumArtist || ''; - - if (parentTitle || showTitle) { - lines.push(escapeHtml(parentTitle)); - } - } - } - } - - let showMediaTitle = (showTitle && !titleAdded) || (options.showParentTitleOrTitle && !lines.length); - if (!showMediaTitle && !titleAdded && (showTitle || flags.forceName)) { - showMediaTitle = true; - } - - if (showMediaTitle) { - const name = options.showTitle === 'auto' && !item.IsFolder && item.MediaType === 'Photo' ? '' : itemHelper.getDisplayName(item, { - includeParentInfo: options.includeParentInfoInTitle - }); + let lines = []; + const parentTitleUnderneath = item.Type === 'MusicAlbum' || item.Type === 'Audio' || item.Type === 'MusicVideo'; + let titleAdded; + if (showOtherText && (options.showParentTitle || options.showParentTitleOrTitle) && !parentTitleUnderneath) { + if (flags.isOuterFooter && item.Type === 'Episode' && item.SeriesName) { + if (item.SeriesId) { lines.push(getTextActionButton({ - Id: item.Id, + Id: item.SeriesId, ServerId: serverId, - Name: name, - Type: item.Type, - CollectionType: item.CollectionType, - IsFolder: item.IsFolder + Name: item.SeriesName, + Type: 'Series', + IsFolder: true })); + } else { + lines.push(escapeHtml(item.SeriesName)); + } + } else { + if (isUsingLiveTvNaming(item)) { + lines.push(escapeHtml(item.Name)); + + if (!item.EpisodeTitle && !item.IndexNumber) { + titleAdded = true; + } + } else { + const parentTitle = item.SeriesName || item.Series || item.Album || item.AlbumArtist || ''; + + if (parentTitle || showTitle) { + lines.push(escapeHtml(parentTitle)); + } + } + } + } + + let showMediaTitle = (showTitle && !titleAdded) || (options.showParentTitleOrTitle && !lines.length); + if (!showMediaTitle && !titleAdded && (showTitle || flags.forceName)) { + showMediaTitle = true; + } + + if (showMediaTitle) { + const name = options.showTitle === 'auto' && !item.IsFolder && item.MediaType === 'Photo' ? '' : itemHelper.getDisplayName(item, { + includeParentInfo: options.includeParentInfoInTitle + }); + + lines.push(getTextActionButton({ + Id: item.Id, + ServerId: serverId, + Name: name, + Type: item.Type, + CollectionType: item.CollectionType, + IsFolder: item.IsFolder + })); + } + + if (showOtherText) { + if (options.showParentTitle && parentTitleUnderneath) { + if (flags.isOuterFooter && item.AlbumArtists && item.AlbumArtists.length) { + item.AlbumArtists[0].Type = 'MusicArtist'; + item.AlbumArtists[0].IsFolder = true; + lines.push(getTextActionButton(item.AlbumArtists[0], null, serverId)); + } else { + lines.push(escapeHtml(isUsingLiveTvNaming(item) ? item.Name : (item.SeriesName || item.Series || item.Album || item.AlbumArtist || ''))); + } + } + + if (item.ExtraType && item.ExtraType !== 'Unknown') { + lines.push(globalize.translate(item.ExtraType)); + } + + if (options.showItemCounts) { + lines.push(getItemCountsHtml(options, item)); + } + + if (options.textLines) { + const additionalLines = options.textLines(item); + for (let i = 0; i < additionalLines.length; i++) { + lines.push(additionalLines[i]); + } + } + + if (options.showSongCount) { + let songLine = ''; + + if (item.SongCount) { + songLine = item.SongCount === 1 ? + globalize.translate('ValueOneSong') : + globalize.translate('ValueSongCount', item.SongCount); } - if (showOtherText) { - if (options.showParentTitle && parentTitleUnderneath) { - if (flags.isOuterFooter && item.AlbumArtists && item.AlbumArtists.length) { - item.AlbumArtists[0].Type = 'MusicArtist'; - item.AlbumArtists[0].IsFolder = true; - lines.push(getTextActionButton(item.AlbumArtists[0], null, serverId)); - } else { - lines.push(escapeHtml(isUsingLiveTvNaming(item) ? item.Name : (item.SeriesName || item.Series || item.Album || item.AlbumArtist || ''))); - } + lines.push(songLine); + } + + if (options.showPremiereDate) { + if (item.PremiereDate) { + try { + lines.push(datetime.toLocaleDateString( + datetime.parseISO8601Date(item.PremiereDate), + { weekday: 'long', month: 'long', day: 'numeric' } + )); + } catch (err) { + lines.push(''); } + } else { + lines.push(''); + } + } - if (item.ExtraType && item.ExtraType !== 'Unknown') { - lines.push(globalize.translate(item.ExtraType)); - } - - if (options.showItemCounts) { - lines.push(getItemCountsHtml(options, item)); - } - - if (options.textLines) { - const additionalLines = options.textLines(item); - for (let i = 0; i < additionalLines.length; i++) { - lines.push(additionalLines[i]); - } - } - - if (options.showSongCount) { - let songLine = ''; - - if (item.SongCount) { - songLine = item.SongCount === 1 ? - globalize.translate('ValueOneSong') : - globalize.translate('ValueSongCount', item.SongCount); - } - - lines.push(songLine); - } - - if (options.showPremiereDate) { - if (item.PremiereDate) { - try { - lines.push(datetime.toLocaleDateString( - datetime.parseISO8601Date(item.PremiereDate), - { weekday: 'long', month: 'long', day: 'numeric' } - )); - } catch (err) { - lines.push(''); - } - } else { - lines.push(''); - } - } - - if (options.showYear || options.showSeriesYear) { - const productionYear = item.ProductionYear && datetime.toLocaleString(item.ProductionYear, { useGrouping: false }); - if (item.Type === 'Series') { - if (item.Status === 'Continuing') { - lines.push(globalize.translate('SeriesYearToPresent', productionYear || '')); - } else { - if (item.EndDate && item.ProductionYear) { - const endYear = datetime.toLocaleString(datetime.parseISO8601Date(item.EndDate).getFullYear(), { useGrouping: false }); - lines.push(productionYear + ((endYear === item.ProductionYear) ? '' : (' - ' + endYear))); - } else { - lines.push(productionYear || ''); - } - } + if (options.showYear || options.showSeriesYear) { + const productionYear = item.ProductionYear && datetime.toLocaleString(item.ProductionYear, { useGrouping: false }); + if (item.Type === 'Series') { + if (item.Status === 'Continuing') { + lines.push(globalize.translate('SeriesYearToPresent', productionYear || '')); + } else { + if (item.EndDate && item.ProductionYear) { + const endYear = datetime.toLocaleString(datetime.parseISO8601Date(item.EndDate).getFullYear(), { useGrouping: false }); + lines.push(productionYear + ((endYear === item.ProductionYear) ? '' : (' - ' + endYear))); } else { lines.push(productionYear || ''); } } - - if (options.showRuntime) { - if (item.RunTimeTicks) { - lines.push(datetime.getDisplayRunningTime(item.RunTimeTicks)); - } else { - lines.push(''); - } - } - - if (options.showAirTime) { - lines.push(getAirTimeText(item, options.showAirDateTime, options.showAirEndTime) || ''); - } - - if (options.showChannelName) { - if (item.ChannelId) { - lines.push(getTextActionButton({ - - Id: item.ChannelId, - ServerId: serverId, - Name: item.ChannelName, - Type: 'TvChannel', - MediaType: item.MediaType, - IsFolder: false - - }, item.ChannelName)); - } else { - lines.push(escapeHtml(item.ChannelName || '') || ' '); - } - } - - if (options.showCurrentProgram && item.Type === 'TvChannel') { - if (item.CurrentProgram) { - lines.push(escapeHtml(item.CurrentProgram.Name)); - } else { - lines.push(''); - } - } - - if (options.showCurrentProgramTime && item.Type === 'TvChannel') { - if (item.CurrentProgram) { - lines.push(getAirTimeText(item.CurrentProgram, false, true) || ''); - } else { - lines.push(''); - } - } - - if (options.showSeriesTimerTime) { - if (item.RecordAnyTime) { - lines.push(globalize.translate('Anytime')); - } else { - lines.push(datetime.getDisplayTime(item.StartDate)); - } - } - - if (options.showSeriesTimerChannel) { - if (item.RecordAnyChannel) { - lines.push(globalize.translate('AllChannels')); - } else { - lines.push(escapeHtml(item.ChannelName || '') || globalize.translate('OneChannel')); - } - } - - if (options.showPersonRoleOrType && item.Role) { - lines.push(globalize.translate('PersonRole', escapeHtml(item.Role))); - } + } else { + lines.push(productionYear || ''); } - - if ((showTitle || !urls.imgUrl) && flags.forceName && flags.overlayText && lines.length === 1) { - lines = []; - } - - if (flags.overlayText && showTitle) { - lines = [escapeHtml(item.Name)]; - } - - const addRightTextMargin = flags.isOuterFooter && options.cardLayout && !options.centerText && options.cardFooterAside !== 'none' && layoutManager.mobile; - - html += getCardTextLines(lines, cssClass, !options.overlayText, flags.isOuterFooter, options.cardLayout, addRightTextMargin, options.lines); - - if (progressHtml) { - html += progressHtml; - } - - if (html && (!flags.isOuterFooter || urls.logoUrl || options.cardLayout)) { - html = '
' + html; - - //cardFooter - html += '
'; - } - - return html; } - /** + if (options.showRuntime) { + if (item.RunTimeTicks) { + lines.push(datetime.getDisplayRunningTime(item.RunTimeTicks)); + } else { + lines.push(''); + } + } + + if (options.showAirTime) { + lines.push(getAirTimeText(item, options.showAirDateTime, options.showAirEndTime) || ''); + } + + if (options.showChannelName) { + if (item.ChannelId) { + lines.push(getTextActionButton({ + + Id: item.ChannelId, + ServerId: serverId, + Name: item.ChannelName, + Type: 'TvChannel', + MediaType: item.MediaType, + IsFolder: false + + }, item.ChannelName)); + } else { + lines.push(escapeHtml(item.ChannelName || '') || ' '); + } + } + + if (options.showCurrentProgram && item.Type === 'TvChannel') { + if (item.CurrentProgram) { + lines.push(escapeHtml(item.CurrentProgram.Name)); + } else { + lines.push(''); + } + } + + if (options.showCurrentProgramTime && item.Type === 'TvChannel') { + if (item.CurrentProgram) { + lines.push(getAirTimeText(item.CurrentProgram, false, true) || ''); + } else { + lines.push(''); + } + } + + if (options.showSeriesTimerTime) { + if (item.RecordAnyTime) { + lines.push(globalize.translate('Anytime')); + } else { + lines.push(datetime.getDisplayTime(item.StartDate)); + } + } + + if (options.showSeriesTimerChannel) { + if (item.RecordAnyChannel) { + lines.push(globalize.translate('AllChannels')); + } else { + lines.push(escapeHtml(item.ChannelName || '') || globalize.translate('OneChannel')); + } + } + + if (options.showPersonRoleOrType && item.Role) { + lines.push(globalize.translate('PersonRole', escapeHtml(item.Role))); + } + } + + if ((showTitle || !urls.imgUrl) && flags.forceName && flags.overlayText && lines.length === 1) { + lines = []; + } + + if (flags.overlayText && showTitle) { + lines = [escapeHtml(item.Name)]; + } + + const addRightTextMargin = flags.isOuterFooter && options.cardLayout && !options.centerText && options.cardFooterAside !== 'none' && layoutManager.mobile; + + html += getCardTextLines(lines, cssClass, !options.overlayText, flags.isOuterFooter, options.cardLayout, addRightTextMargin, options.lines); + + if (progressHtml) { + html += progressHtml; + } + + if (html && (!flags.isOuterFooter || urls.logoUrl || options.cardLayout)) { + html = '
' + html; + + //cardFooter + html += '
'; + } + + return html; +} + +/** * Generates the HTML markup for the action button. * @param {Object} item - Item used to generate the action button. * @param {string} text - Text of the action button. * @param {string} serverId - ID of the server. * @returns {string} HTML markup of the action button. */ - function getTextActionButton(item, text, serverId) { - if (!text) { - text = itemHelper.getDisplayName(item); - } +function getTextActionButton(item, text, serverId) { + if (!text) { + text = itemHelper.getDisplayName(item); + } - text = escapeHtml(text); + text = escapeHtml(text); - if (layoutManager.tv) { - return text; - } + if (layoutManager.tv) { + return text; + } - const url = appRouter.getRouteUrl(item); - let html = ''; - html += text; - html += ''; + const url = appRouter.getRouteUrl(item); + let html = ''; + html += text; + html += ''; - return html; - } + return html; +} - /** +/** * Generates HTML markup for the item count indicator. * @param {Object} options - Options used to generate the item count. * @param {Object} item - Item used to generate the item count. * @returns {string} HTML markup for the item count indicator. */ - function getItemCountsHtml(options, item) { - const counts = []; - let childText; +function getItemCountsHtml(options, item) { + const counts = []; + let childText; - if (item.Type === 'Playlist') { - childText = ''; + if (item.Type === 'Playlist') { + childText = ''; - if (item.RunTimeTicks) { - let minutes = item.RunTimeTicks / 600000000; + if (item.RunTimeTicks) { + let minutes = item.RunTimeTicks / 600000000; - minutes = minutes || 1; + minutes = minutes || 1; - childText += globalize.translate('ValueMinutes', Math.round(minutes)); - } else { - childText += globalize.translate('ValueMinutes', 0); - } - - counts.push(childText); - } else if (item.Type === 'Genre' || item.Type === 'Studio') { - if (item.MovieCount) { - childText = item.MovieCount === 1 ? - globalize.translate('ValueOneMovie') : - globalize.translate('ValueMovieCount', item.MovieCount); - - counts.push(childText); - } - - if (item.SeriesCount) { - childText = item.SeriesCount === 1 ? - globalize.translate('ValueOneSeries') : - globalize.translate('ValueSeriesCount', item.SeriesCount); - - counts.push(childText); - } - if (item.EpisodeCount) { - childText = item.EpisodeCount === 1 ? - globalize.translate('ValueOneEpisode') : - globalize.translate('ValueEpisodeCount', item.EpisodeCount); - - counts.push(childText); - } - } else if (item.Type === 'MusicGenre' || options.context === 'MusicArtist') { - if (item.AlbumCount) { - childText = item.AlbumCount === 1 ? - globalize.translate('ValueOneAlbum') : - globalize.translate('ValueAlbumCount', item.AlbumCount); - - counts.push(childText); - } - if (item.SongCount) { - childText = item.SongCount === 1 ? - globalize.translate('ValueOneSong') : - globalize.translate('ValueSongCount', item.SongCount); - - counts.push(childText); - } - if (item.MusicVideoCount) { - childText = item.MusicVideoCount === 1 ? - globalize.translate('ValueOneMusicVideo') : - globalize.translate('ValueMusicVideoCount', item.MusicVideoCount); - - counts.push(childText); - } - } else if (item.Type === 'Series') { - childText = item.RecursiveItemCount === 1 ? - globalize.translate('ValueOneEpisode') : - globalize.translate('ValueEpisodeCount', item.RecursiveItemCount); - - counts.push(childText); - } - - return counts.join(', '); + childText += globalize.translate('ValueMinutes', Math.round(minutes)); + } else { + childText += globalize.translate('ValueMinutes', 0); } - let refreshIndicatorLoaded; + counts.push(childText); + } else if (item.Type === 'Genre' || item.Type === 'Studio') { + if (item.MovieCount) { + childText = item.MovieCount === 1 ? + globalize.translate('ValueOneMovie') : + globalize.translate('ValueMovieCount', item.MovieCount); - /** + counts.push(childText); + } + + if (item.SeriesCount) { + childText = item.SeriesCount === 1 ? + globalize.translate('ValueOneSeries') : + globalize.translate('ValueSeriesCount', item.SeriesCount); + + counts.push(childText); + } + if (item.EpisodeCount) { + childText = item.EpisodeCount === 1 ? + globalize.translate('ValueOneEpisode') : + globalize.translate('ValueEpisodeCount', item.EpisodeCount); + + counts.push(childText); + } + } else if (item.Type === 'MusicGenre' || options.context === 'MusicArtist') { + if (item.AlbumCount) { + childText = item.AlbumCount === 1 ? + globalize.translate('ValueOneAlbum') : + globalize.translate('ValueAlbumCount', item.AlbumCount); + + counts.push(childText); + } + if (item.SongCount) { + childText = item.SongCount === 1 ? + globalize.translate('ValueOneSong') : + globalize.translate('ValueSongCount', item.SongCount); + + counts.push(childText); + } + if (item.MusicVideoCount) { + childText = item.MusicVideoCount === 1 ? + globalize.translate('ValueOneMusicVideo') : + globalize.translate('ValueMusicVideoCount', item.MusicVideoCount); + + counts.push(childText); + } + } else if (item.Type === 'Series') { + childText = item.RecursiveItemCount === 1 ? + globalize.translate('ValueOneEpisode') : + globalize.translate('ValueEpisodeCount', item.RecursiveItemCount); + + counts.push(childText); + } + + return counts.join(', '); +} + +let refreshIndicatorLoaded; + +/** * Imports the refresh indicator element. */ - function importRefreshIndicator() { - if (!refreshIndicatorLoaded) { - refreshIndicatorLoaded = true; - /* eslint-disable-next-line @babel/no-unused-expressions */ - import('../../elements/emby-itemrefreshindicator/emby-itemrefreshindicator'); - } - } +function importRefreshIndicator() { + if (!refreshIndicatorLoaded) { + refreshIndicatorLoaded = true; + /* eslint-disable-next-line @babel/no-unused-expressions */ + import('../../elements/emby-itemrefreshindicator/emby-itemrefreshindicator'); + } +} - /** +/** * Returns the default background class for a card based on a string. * @param {?string} [str] - Text used to generate the background class. * @returns {string} CSS classes for default card backgrounds. */ - export function getDefaultBackgroundClass(str) { - return 'defaultCardBackground defaultCardBackground' + getDefaultColorIndex(str); - } +export function getDefaultBackgroundClass(str) { + return 'defaultCardBackground defaultCardBackground' + getDefaultColorIndex(str); +} - /** +/** * Builds the HTML markup for an individual card. * @param {number} index - Index of the card * @param {object} item - Item used to generate the card. @@ -1141,619 +1140,617 @@ import { appRouter } from '../appRouter'; * @param {object} options - Options used to generate the card. * @returns {string} HTML markup for the generated card. */ - function buildCard(index, item, apiClient, options) { - let action = options.action || 'link'; +function buildCard(index, item, apiClient, options) { + let action = options.action || 'link'; - if (action === 'play' && item.IsFolder) { - // If this hard-coding is ever removed make sure to test nested photo albums - action = 'link'; - } else if (item.MediaType === 'Photo') { - action = 'play'; - } + if (action === 'play' && item.IsFolder) { + // If this hard-coding is ever removed make sure to test nested photo albums + action = 'link'; + } else if (item.MediaType === 'Photo') { + action = 'play'; + } - let shape = options.shape; + let shape = options.shape; - if (shape === 'mixed') { - shape = null; + if (shape === 'mixed') { + shape = null; - const primaryImageAspectRatio = item.PrimaryImageAspectRatio; + const primaryImageAspectRatio = item.PrimaryImageAspectRatio; - if (primaryImageAspectRatio) { - if (primaryImageAspectRatio >= 1.33) { - shape = 'mixedBackdrop'; - } else if (primaryImageAspectRatio > 0.71) { - shape = 'mixedSquare'; - } else { - shape = 'mixedPortrait'; - } - } - - shape = shape || 'mixedSquare'; - } - - // TODO move card creation code to Card component - - let className = 'card'; - - if (shape) { - className += ' ' + shape + 'Card'; - } - - if (options.cardCssClass) { - className += ' ' + options.cardCssClass; - } - - if (options.cardClass) { - className += ' ' + options.cardClass; - } - - if (layoutManager.desktop) { - className += ' card-hoverable'; - } - - if (layoutManager.tv) { - className += ' show-focus'; - - if (enableFocusTransform) { - className += ' show-animation'; - } - } - - const imgInfo = getCardImageUrl(item, apiClient, options, shape); - const imgUrl = imgInfo.imgUrl; - const blurhash = imgInfo.blurhash; - - const forceName = imgInfo.forceName; - - const overlayText = options.overlayText; - - let cardImageContainerClass = 'cardImageContainer'; - const coveredImage = options.coverImage || imgInfo.coverImage; - - if (coveredImage) { - cardImageContainerClass += ' coveredImage'; - - if (item.Type === 'TvChannel') { - cardImageContainerClass += ' coveredImage-contain'; - } - } - - if (!imgUrl) { - cardImageContainerClass += ' ' + getDefaultBackgroundClass(item.Name); - } - - let cardBoxClass = options.cardLayout ? 'cardBox visualCardBox' : 'cardBox'; - - let footerCssClass; - let progressHtml = indicators.getProgressBarHtml(item); - - let innerCardFooter = ''; - - let footerOverlayed = false; - - let logoUrl; - const logoHeight = 40; - - if (options.showChannelLogo && item.ChannelPrimaryImageTag) { - logoUrl = apiClient.getScaledImageUrl(item.ChannelId, { - type: 'Primary', - height: logoHeight, - tag: item.ChannelPrimaryImageTag - }); - } else if (options.showLogo && item.ParentLogoImageTag) { - logoUrl = apiClient.getScaledImageUrl(item.ParentLogoItemId, { - type: 'Logo', - height: logoHeight, - tag: item.ParentLogoImageTag - }); - } - - if (overlayText) { - logoUrl = null; - - footerCssClass = progressHtml ? 'innerCardFooter fullInnerCardFooter' : 'innerCardFooter'; - innerCardFooter += getCardFooterText(item, apiClient, options, footerCssClass, progressHtml, { forceName, overlayText, isOuterFooter: false }, { imgUrl, logoUrl }); - footerOverlayed = true; - } else if (progressHtml) { - innerCardFooter += '
'; - innerCardFooter += progressHtml; - innerCardFooter += '
'; - - progressHtml = ''; - } - - const mediaSourceCount = item.MediaSourceCount || 1; - if (mediaSourceCount > 1 && options.disableIndicators !== true) { - innerCardFooter += '
' + mediaSourceCount + '
'; - } - - let outerCardFooter = ''; - if (!overlayText && !footerOverlayed) { - footerCssClass = options.cardLayout ? 'cardFooter' : 'cardFooter cardFooter-transparent'; - - if (logoUrl) { - footerCssClass += ' cardFooter-withlogo'; - } - - if (!options.cardLayout) { - logoUrl = null; - } - - outerCardFooter = getCardFooterText(item, apiClient, options, footerCssClass, progressHtml, { forceName, overlayText, isOuterFooter: true }, { imgUrl, logoUrl }); - } - - if (outerCardFooter && !options.cardLayout) { - cardBoxClass += ' cardBox-bottompadded'; - } - - let overlayButtons = ''; - if (layoutManager.mobile) { - let overlayPlayButton = options.overlayPlayButton; - - if (overlayPlayButton == null && !options.overlayMoreButton && !options.overlayInfoButton && !options.cardLayout) { - overlayPlayButton = item.MediaType === 'Video'; - } - - const btnCssClass = 'cardOverlayButton cardOverlayButton-br itemAction'; - - if (options.centerPlayButton) { - overlayButtons += ``; - } - - if (overlayPlayButton && !item.IsPlaceHolder && (item.LocationType !== 'Virtual' || !item.MediaType || item.Type === 'Program') && item.Type !== 'Person') { - overlayButtons += ``; - } - - if (options.overlayMoreButton) { - overlayButtons += ``; - } - } - - if (options.showChildCountIndicator && item.ChildCount) { - className += ' groupedCard'; - } - - // cardBox can be it's own separate element if an outer footer is ever needed - let cardImageContainerOpen; - let cardImageContainerClose = ''; - let cardBoxClose = ''; - let cardScalableClose = ''; - - const cardContentClass = 'cardContent'; - - let blurhashAttrib = ''; - if (blurhash && blurhash.length > 0) { - blurhashAttrib = 'data-blurhash="' + blurhash + '"'; - } - - if (layoutManager.tv) { - // Don't use the IMG tag with safari because it puts a white border around it - cardImageContainerOpen = imgUrl ? ('
') : ('
'); - - cardImageContainerClose = '
'; + if (primaryImageAspectRatio) { + if (primaryImageAspectRatio >= 1.33) { + shape = 'mixedBackdrop'; + } else if (primaryImageAspectRatio > 0.71) { + shape = 'mixedSquare'; } else { - const cardImageContainerAriaLabelAttribute = ` aria-label="${escapeHtml(item.Name)}"`; - - const url = appRouter.getRouteUrl(item); - // Don't use the IMG tag with safari because it puts a white border around it - cardImageContainerOpen = imgUrl ? ('') : (''); - - cardImageContainerClose = ''; + shape = 'mixedPortrait'; } - - const cardScalableClass = 'cardScalable'; - - let cardPadderIcon = ''; - - // TV Channel logos are transparent so skip the placeholder to avoid overlapping - if (imgUrl && item.Type !== 'TvChannel') { - cardPadderIcon = getDefaultText(item, { - // Always use an icon - defaultCardImageIcon: 'folder', - ...options - }); - } - - cardImageContainerOpen = `
${cardPadderIcon}
${cardImageContainerOpen}`; - cardBoxClose = '
'; - cardScalableClose = '
'; - - if (options.disableIndicators !== true) { - let indicatorsHtml = ''; - - if (options.missingIndicator !== false) { - indicatorsHtml += indicators.getMissingIndicator(item); - } - - indicatorsHtml += indicators.getSyncIndicator(item); - indicatorsHtml += indicators.getTimerIndicator(item); - - indicatorsHtml += indicators.getTypeIndicator(item); - - if (options.showGroupCount) { - indicatorsHtml += indicators.getChildCountIndicatorHtml(item, { - minCount: 1 - }); - } else { - indicatorsHtml += indicators.getPlayedIndicatorHtml(item); - } - - if (item.Type === 'CollectionFolder' || item.CollectionType) { - const refreshClass = item.RefreshProgress ? '' : ' class="hide"'; - indicatorsHtml += '
'; - importRefreshIndicator(); - } - - if (indicatorsHtml) { - cardImageContainerOpen += '
' + indicatorsHtml + '
'; - } - } - - if (!imgUrl) { - cardImageContainerOpen += getDefaultText(item, options); - } - - const tagName = (layoutManager.tv) && !overlayButtons ? 'button' : 'div'; - - const nameWithPrefix = (item.SortName || item.Name || ''); - let prefix = nameWithPrefix.substring(0, Math.min(3, nameWithPrefix.length)); - - if (prefix) { - prefix = prefix.toUpperCase(); - } - - let timerAttributes = ''; - if (item.TimerId) { - timerAttributes += ' data-timerid="' + item.TimerId + '"'; - } - if (item.SeriesTimerId) { - timerAttributes += ' data-seriestimerid="' + item.SeriesTimerId + '"'; - } - - let actionAttribute; - let ariaLabelAttribute = ''; - - if (tagName === 'button') { - className += ' itemAction'; - actionAttribute = ' data-action="' + action + '"'; - ariaLabelAttribute = ` aria-label="${escapeHtml(item.Name)}"`; - } else { - actionAttribute = ''; - } - - if (item.Type !== 'MusicAlbum' && item.Type !== 'MusicArtist' && item.Type !== 'Audio') { - className += ' card-withuserdata'; - } - - const positionTicksData = item.UserData && item.UserData.PlaybackPositionTicks ? (' data-positionticks="' + item.UserData.PlaybackPositionTicks + '"') : ''; - const collectionIdData = options.collectionId ? (' data-collectionid="' + options.collectionId + '"') : ''; - const playlistIdData = options.playlistId ? (' data-playlistid="' + options.playlistId + '"') : ''; - const mediaTypeData = item.MediaType ? (' data-mediatype="' + item.MediaType + '"') : ''; - const collectionTypeData = item.CollectionType ? (' data-collectiontype="' + item.CollectionType + '"') : ''; - const channelIdData = item.ChannelId ? (' data-channelid="' + item.ChannelId + '"') : ''; - const pathData = item.Path ? (' data-path="' + escapeHtml(item.Path) + '"') : ''; - const contextData = options.context ? (' data-context="' + options.context + '"') : ''; - const parentIdData = options.parentId ? (' data-parentid="' + options.parentId + '"') : ''; - const startDate = item.StartDate ? (' data-startdate="' + item.StartDate.toString() + '"') : ''; - const endDate = item.EndDate ? (' data-enddate="' + item.EndDate.toString() + '"') : ''; - - let additionalCardContent = ''; - - if (layoutManager.desktop && !options.disableHoverMenu) { - additionalCardContent += getHoverMenuHtml(item, action); - } - - return '<' + tagName + ' data-index="' + index + '"' + timerAttributes + actionAttribute + ' data-isfolder="' + (item.IsFolder || false) + '" data-serverid="' + (item.ServerId || options.serverId) + '" data-id="' + (item.Id || item.ItemId) + '" data-type="' + item.Type + '"' + mediaTypeData + collectionTypeData + channelIdData + pathData + positionTicksData + collectionIdData + playlistIdData + contextData + parentIdData + startDate + endDate + ' data-prefix="' + escapeHtml(prefix) + '" class="' + className + '"' + ariaLabelAttribute + '>' + cardImageContainerOpen + innerCardFooter + cardImageContainerClose + overlayButtons + additionalCardContent + cardScalableClose + outerCardFooter + cardBoxClose + ''; } - /** + shape = shape || 'mixedSquare'; + } + + // TODO move card creation code to Card component + + let className = 'card'; + + if (shape) { + className += ' ' + shape + 'Card'; + } + + if (options.cardCssClass) { + className += ' ' + options.cardCssClass; + } + + if (options.cardClass) { + className += ' ' + options.cardClass; + } + + if (layoutManager.desktop) { + className += ' card-hoverable'; + } + + if (layoutManager.tv) { + className += ' show-focus'; + + if (enableFocusTransform) { + className += ' show-animation'; + } + } + + const imgInfo = getCardImageUrl(item, apiClient, options, shape); + const imgUrl = imgInfo.imgUrl; + const blurhash = imgInfo.blurhash; + + const forceName = imgInfo.forceName; + + const overlayText = options.overlayText; + + let cardImageContainerClass = 'cardImageContainer'; + const coveredImage = options.coverImage || imgInfo.coverImage; + + if (coveredImage) { + cardImageContainerClass += ' coveredImage'; + + if (item.Type === 'TvChannel') { + cardImageContainerClass += ' coveredImage-contain'; + } + } + + if (!imgUrl) { + cardImageContainerClass += ' ' + getDefaultBackgroundClass(item.Name); + } + + let cardBoxClass = options.cardLayout ? 'cardBox visualCardBox' : 'cardBox'; + + let footerCssClass; + let progressHtml = indicators.getProgressBarHtml(item); + + let innerCardFooter = ''; + + let footerOverlayed = false; + + let logoUrl; + const logoHeight = 40; + + if (options.showChannelLogo && item.ChannelPrimaryImageTag) { + logoUrl = apiClient.getScaledImageUrl(item.ChannelId, { + type: 'Primary', + height: logoHeight, + tag: item.ChannelPrimaryImageTag + }); + } else if (options.showLogo && item.ParentLogoImageTag) { + logoUrl = apiClient.getScaledImageUrl(item.ParentLogoItemId, { + type: 'Logo', + height: logoHeight, + tag: item.ParentLogoImageTag + }); + } + + if (overlayText) { + logoUrl = null; + + footerCssClass = progressHtml ? 'innerCardFooter fullInnerCardFooter' : 'innerCardFooter'; + innerCardFooter += getCardFooterText(item, apiClient, options, footerCssClass, progressHtml, { forceName, overlayText, isOuterFooter: false }, { imgUrl, logoUrl }); + footerOverlayed = true; + } else if (progressHtml) { + innerCardFooter += '
'; + innerCardFooter += progressHtml; + innerCardFooter += '
'; + + progressHtml = ''; + } + + const mediaSourceCount = item.MediaSourceCount || 1; + if (mediaSourceCount > 1 && options.disableIndicators !== true) { + innerCardFooter += '
' + mediaSourceCount + '
'; + } + + let outerCardFooter = ''; + if (!overlayText && !footerOverlayed) { + footerCssClass = options.cardLayout ? 'cardFooter' : 'cardFooter cardFooter-transparent'; + + if (logoUrl) { + footerCssClass += ' cardFooter-withlogo'; + } + + if (!options.cardLayout) { + logoUrl = null; + } + + outerCardFooter = getCardFooterText(item, apiClient, options, footerCssClass, progressHtml, { forceName, overlayText, isOuterFooter: true }, { imgUrl, logoUrl }); + } + + if (outerCardFooter && !options.cardLayout) { + cardBoxClass += ' cardBox-bottompadded'; + } + + let overlayButtons = ''; + if (layoutManager.mobile) { + let overlayPlayButton = options.overlayPlayButton; + + if (overlayPlayButton == null && !options.overlayMoreButton && !options.overlayInfoButton && !options.cardLayout) { + overlayPlayButton = item.MediaType === 'Video'; + } + + const btnCssClass = 'cardOverlayButton cardOverlayButton-br itemAction'; + + if (options.centerPlayButton) { + overlayButtons += ``; + } + + if (overlayPlayButton && !item.IsPlaceHolder && (item.LocationType !== 'Virtual' || !item.MediaType || item.Type === 'Program') && item.Type !== 'Person') { + overlayButtons += ``; + } + + if (options.overlayMoreButton) { + overlayButtons += ``; + } + } + + if (options.showChildCountIndicator && item.ChildCount) { + className += ' groupedCard'; + } + + // cardBox can be it's own separate element if an outer footer is ever needed + let cardImageContainerOpen; + let cardImageContainerClose = ''; + let cardBoxClose = ''; + let cardScalableClose = ''; + + const cardContentClass = 'cardContent'; + + let blurhashAttrib = ''; + if (blurhash && blurhash.length > 0) { + blurhashAttrib = 'data-blurhash="' + blurhash + '"'; + } + + if (layoutManager.tv) { + // Don't use the IMG tag with safari because it puts a white border around it + cardImageContainerOpen = imgUrl ? ('
') : ('
'); + + cardImageContainerClose = '
'; + } else { + const cardImageContainerAriaLabelAttribute = ` aria-label="${escapeHtml(item.Name)}"`; + + const url = appRouter.getRouteUrl(item); + // Don't use the IMG tag with safari because it puts a white border around it + cardImageContainerOpen = imgUrl ? ('') : (''); + + cardImageContainerClose = ''; + } + + const cardScalableClass = 'cardScalable'; + + let cardPadderIcon = ''; + + // TV Channel logos are transparent so skip the placeholder to avoid overlapping + if (imgUrl && item.Type !== 'TvChannel') { + cardPadderIcon = getDefaultText(item, { + // Always use an icon + defaultCardImageIcon: 'folder', + ...options + }); + } + + cardImageContainerOpen = `
${cardPadderIcon}
${cardImageContainerOpen}`; + cardBoxClose = '
'; + cardScalableClose = '
'; + + if (options.disableIndicators !== true) { + let indicatorsHtml = ''; + + if (options.missingIndicator !== false) { + indicatorsHtml += indicators.getMissingIndicator(item); + } + + indicatorsHtml += indicators.getSyncIndicator(item); + indicatorsHtml += indicators.getTimerIndicator(item); + + indicatorsHtml += indicators.getTypeIndicator(item); + + if (options.showGroupCount) { + indicatorsHtml += indicators.getChildCountIndicatorHtml(item, { + minCount: 1 + }); + } else { + indicatorsHtml += indicators.getPlayedIndicatorHtml(item); + } + + if (item.Type === 'CollectionFolder' || item.CollectionType) { + const refreshClass = item.RefreshProgress ? '' : ' class="hide"'; + indicatorsHtml += '
'; + importRefreshIndicator(); + } + + if (indicatorsHtml) { + cardImageContainerOpen += '
' + indicatorsHtml + '
'; + } + } + + if (!imgUrl) { + cardImageContainerOpen += getDefaultText(item, options); + } + + const tagName = (layoutManager.tv) && !overlayButtons ? 'button' : 'div'; + + const nameWithPrefix = (item.SortName || item.Name || ''); + let prefix = nameWithPrefix.substring(0, Math.min(3, nameWithPrefix.length)); + + if (prefix) { + prefix = prefix.toUpperCase(); + } + + let timerAttributes = ''; + if (item.TimerId) { + timerAttributes += ' data-timerid="' + item.TimerId + '"'; + } + if (item.SeriesTimerId) { + timerAttributes += ' data-seriestimerid="' + item.SeriesTimerId + '"'; + } + + let actionAttribute; + let ariaLabelAttribute = ''; + + if (tagName === 'button') { + className += ' itemAction'; + actionAttribute = ' data-action="' + action + '"'; + ariaLabelAttribute = ` aria-label="${escapeHtml(item.Name)}"`; + } else { + actionAttribute = ''; + } + + if (item.Type !== 'MusicAlbum' && item.Type !== 'MusicArtist' && item.Type !== 'Audio') { + className += ' card-withuserdata'; + } + + const positionTicksData = item.UserData && item.UserData.PlaybackPositionTicks ? (' data-positionticks="' + item.UserData.PlaybackPositionTicks + '"') : ''; + const collectionIdData = options.collectionId ? (' data-collectionid="' + options.collectionId + '"') : ''; + const playlistIdData = options.playlistId ? (' data-playlistid="' + options.playlistId + '"') : ''; + const mediaTypeData = item.MediaType ? (' data-mediatype="' + item.MediaType + '"') : ''; + const collectionTypeData = item.CollectionType ? (' data-collectiontype="' + item.CollectionType + '"') : ''; + const channelIdData = item.ChannelId ? (' data-channelid="' + item.ChannelId + '"') : ''; + const pathData = item.Path ? (' data-path="' + escapeHtml(item.Path) + '"') : ''; + const contextData = options.context ? (' data-context="' + options.context + '"') : ''; + const parentIdData = options.parentId ? (' data-parentid="' + options.parentId + '"') : ''; + const startDate = item.StartDate ? (' data-startdate="' + item.StartDate.toString() + '"') : ''; + const endDate = item.EndDate ? (' data-enddate="' + item.EndDate.toString() + '"') : ''; + + let additionalCardContent = ''; + + if (layoutManager.desktop && !options.disableHoverMenu) { + additionalCardContent += getHoverMenuHtml(item, action); + } + + return '<' + tagName + ' data-index="' + index + '"' + timerAttributes + actionAttribute + ' data-isfolder="' + (item.IsFolder || false) + '" data-serverid="' + (item.ServerId || options.serverId) + '" data-id="' + (item.Id || item.ItemId) + '" data-type="' + item.Type + '"' + mediaTypeData + collectionTypeData + channelIdData + pathData + positionTicksData + collectionIdData + playlistIdData + contextData + parentIdData + startDate + endDate + ' data-prefix="' + escapeHtml(prefix) + '" class="' + className + '"' + ariaLabelAttribute + '>' + cardImageContainerOpen + innerCardFooter + cardImageContainerClose + overlayButtons + additionalCardContent + cardScalableClose + outerCardFooter + cardBoxClose + ''; +} + +/** * Generates HTML markup for the card overlay. * @param {object} item - Item used to generate the card overlay. * @param {string} action - Action assigned to the overlay. * @returns {string} HTML markup of the card overlay. */ - function getHoverMenuHtml(item, action) { - let html = ''; +function getHoverMenuHtml(item, action) { + let html = ''; - html += '
'; - const url = appRouter.getRouteUrl(item); - html += ''; + html += '
'; + const url = appRouter.getRouteUrl(item); + html += ''; - const btnCssClass = 'cardOverlayButton cardOverlayButton-hover itemAction paper-icon-button-light'; + const btnCssClass = 'cardOverlayButton cardOverlayButton-hover itemAction paper-icon-button-light'; - if (playbackManager.canPlay(item)) { - html += ''; - } + if (playbackManager.canPlay(item)) { + html += ''; + } - html += '
'; + html += '
'; - const userData = item.UserData || {}; + const userData = item.UserData || {}; - if (itemHelper.canMarkPlayed(item)) { - /* eslint-disable-next-line @babel/no-unused-expressions */ - import('../../elements/emby-playstatebutton/emby-playstatebutton'); - html += ''; - } + if (itemHelper.canMarkPlayed(item)) { + /* eslint-disable-next-line @babel/no-unused-expressions */ + import('../../elements/emby-playstatebutton/emby-playstatebutton'); + html += ''; + } - if (itemHelper.canRate(item)) { - const likes = userData.Likes == null ? '' : userData.Likes; + if (itemHelper.canRate(item)) { + const likes = userData.Likes == null ? '' : userData.Likes; - /* eslint-disable-next-line @babel/no-unused-expressions */ - import('../../elements/emby-ratingbutton/emby-ratingbutton'); - html += ''; - } + /* eslint-disable-next-line @babel/no-unused-expressions */ + import('../../elements/emby-ratingbutton/emby-ratingbutton'); + html += ''; + } - html += ``; - html += '
'; - html += '
'; + html += ``; + html += '
'; + html += '
'; - return html; - } + return html; +} - /** +/** * Generates the text or icon used for default card backgrounds. * @param {object} item - Item used to generate the card overlay. * @param {object} options - Options used to generate the card overlay. * @returns {string} HTML markup of the card overlay. */ - export function getDefaultText(item, options) { - if (item.CollectionType) { - return ''; - } +export function getDefaultText(item, options) { + if (item.CollectionType) { + return ''; + } - switch (item.Type) { - case 'MusicAlbum': - return ''; - case 'MusicArtist': - case 'Person': - return ''; - case 'Audio': - return ''; - case 'Movie': - return ''; - case 'Episode': - case 'Series': - return ''; - case 'Program': - return ''; - case 'Book': - return ''; - case 'Folder': - return ''; - case 'BoxSet': - return ''; - case 'Playlist': - return ''; - case 'Photo': - return ''; - case 'PhotoAlbum': - return ''; - } + switch (item.Type) { + case 'MusicAlbum': + return ''; + case 'MusicArtist': + case 'Person': + return ''; + case 'Audio': + return ''; + case 'Movie': + return ''; + case 'Episode': + case 'Series': + return ''; + case 'Program': + return ''; + case 'Book': + return ''; + case 'Folder': + return ''; + case 'BoxSet': + return ''; + case 'Playlist': + return ''; + case 'Photo': + return ''; + case 'PhotoAlbum': + return ''; + } - if (options?.defaultCardImageIcon) { - return ''; - } + if (options?.defaultCardImageIcon) { + return ''; + } - const defaultName = isUsingLiveTvNaming(item) ? item.Name : itemHelper.getDisplayName(item); - return '
' + escapeHtml(defaultName) + '
'; - } + const defaultName = isUsingLiveTvNaming(item) ? item.Name : itemHelper.getDisplayName(item); + return '
' + escapeHtml(defaultName) + '
'; +} - /** +/** * Builds a set of cards and inserts them into the page. * @param {Array} items - Array of items used to build the cards. * @param {options} options - Options of the cards to build. */ - export function buildCards(items, options) { - // Abort if the container has been disposed - if (!document.body.contains(options.itemsContainer)) { - return; - } +export function buildCards(items, options) { + // Abort if the container has been disposed + if (!document.body.contains(options.itemsContainer)) { + return; + } - if (options.parentContainer) { - if (items.length) { - options.parentContainer.classList.remove('hide'); - } else { - options.parentContainer.classList.add('hide'); - return; - } - } + if (options.parentContainer) { + if (items.length) { + options.parentContainer.classList.remove('hide'); + } else { + options.parentContainer.classList.add('hide'); + return; + } + } - const html = buildCardsHtmlInternal(items, options); + const html = buildCardsHtmlInternal(items, options); - if (html) { - if (options.itemsContainer.cardBuilderHtml !== html) { - options.itemsContainer.innerHTML = html; + if (html) { + if (options.itemsContainer.cardBuilderHtml !== html) { + options.itemsContainer.innerHTML = html; - if (items.length < 50) { - options.itemsContainer.cardBuilderHtml = html; - } else { - options.itemsContainer.cardBuilderHtml = null; - } - } - - imageLoader.lazyChildren(options.itemsContainer); + if (items.length < 50) { + options.itemsContainer.cardBuilderHtml = html; } else { - options.itemsContainer.innerHTML = html; options.itemsContainer.cardBuilderHtml = null; } - - if (options.autoFocus) { - focusManager.autoFocus(options.itemsContainer, true); - } } - /** + imageLoader.lazyChildren(options.itemsContainer); + } else { + options.itemsContainer.innerHTML = html; + options.itemsContainer.cardBuilderHtml = null; + } + + if (options.autoFocus) { + focusManager.autoFocus(options.itemsContainer, true); + } +} + +/** * Ensures the indicators for a card exist and creates them if they don't exist. * @param {HTMLDivElement} card - DOM element of the card. * @param {HTMLDivElement} indicatorsElem - DOM element of the indicators. * @returns {HTMLDivElement} - DOM element of the indicators. */ - function ensureIndicators(card, indicatorsElem) { - if (indicatorsElem) { - return indicatorsElem; - } +function ensureIndicators(card, indicatorsElem) { + if (indicatorsElem) { + return indicatorsElem; + } - indicatorsElem = card.querySelector('.cardIndicators'); + indicatorsElem = card.querySelector('.cardIndicators'); - if (!indicatorsElem) { - const cardImageContainer = card.querySelector('.cardImageContainer'); - indicatorsElem = document.createElement('div'); - indicatorsElem.classList.add('cardIndicators'); - cardImageContainer.appendChild(indicatorsElem); - } + if (!indicatorsElem) { + const cardImageContainer = card.querySelector('.cardImageContainer'); + indicatorsElem = document.createElement('div'); + indicatorsElem.classList.add('cardIndicators'); + cardImageContainer.appendChild(indicatorsElem); + } - return indicatorsElem; - } + return indicatorsElem; +} - /** +/** * Adds user data to the card such as progress indicators and played status. * @param {HTMLDivElement} card - DOM element of the card. * @param {Object} userData - User data to apply to the card. */ - function updateUserData(card, userData) { - const type = card.getAttribute('data-type'); - const enableCountIndicator = type === 'Series' || type === 'BoxSet' || type === 'Season'; - let indicatorsElem = null; - let playedIndicator = null; - let countIndicator = null; - let itemProgressBar = null; +function updateUserData(card, userData) { + const type = card.getAttribute('data-type'); + const enableCountIndicator = type === 'Series' || type === 'BoxSet' || type === 'Season'; + let indicatorsElem = null; + let playedIndicator = null; + let countIndicator = null; + let itemProgressBar = null; - if (userData.Played) { - playedIndicator = card.querySelector('.playedIndicator'); + if (userData.Played) { + playedIndicator = card.querySelector('.playedIndicator'); - if (!playedIndicator) { - playedIndicator = document.createElement('div'); - playedIndicator.classList.add('playedIndicator'); - playedIndicator.classList.add('indicator'); - indicatorsElem = ensureIndicators(card, indicatorsElem); - indicatorsElem.appendChild(playedIndicator); - } - playedIndicator.innerHTML = ''; - } else { - playedIndicator = card.querySelector('.playedIndicator'); - if (playedIndicator) { - playedIndicator.parentNode.removeChild(playedIndicator); - } - } - if (userData.UnplayedItemCount) { - countIndicator = card.querySelector('.countIndicator'); - - if (!countIndicator) { - countIndicator = document.createElement('div'); - countIndicator.classList.add('countIndicator'); - indicatorsElem = ensureIndicators(card, indicatorsElem); - indicatorsElem.appendChild(countIndicator); - } - countIndicator.innerHTML = userData.UnplayedItemCount; - } else if (enableCountIndicator) { - countIndicator = card.querySelector('.countIndicator'); - if (countIndicator) { - countIndicator.parentNode.removeChild(countIndicator); - } - } - - const progressHtml = indicators.getProgressBarHtml({ - Type: type, - UserData: userData, - MediaType: 'Video' - }); - - if (progressHtml) { - itemProgressBar = card.querySelector('.itemProgressBar'); - - if (!itemProgressBar) { - itemProgressBar = document.createElement('div'); - itemProgressBar.classList.add('itemProgressBar'); - - let innerCardFooter = card.querySelector('.innerCardFooter'); - if (!innerCardFooter) { - innerCardFooter = document.createElement('div'); - innerCardFooter.classList.add('innerCardFooter'); - const cardImageContainer = card.querySelector('.cardImageContainer'); - cardImageContainer.appendChild(innerCardFooter); - } - innerCardFooter.appendChild(itemProgressBar); - } - - itemProgressBar.innerHTML = progressHtml; - } else { - itemProgressBar = card.querySelector('.itemProgressBar'); - if (itemProgressBar) { - itemProgressBar.parentNode.removeChild(itemProgressBar); - } + if (!playedIndicator) { + playedIndicator = document.createElement('div'); + playedIndicator.classList.add('playedIndicator'); + playedIndicator.classList.add('indicator'); + indicatorsElem = ensureIndicators(card, indicatorsElem); + indicatorsElem.appendChild(playedIndicator); + } + playedIndicator.innerHTML = ''; + } else { + playedIndicator = card.querySelector('.playedIndicator'); + if (playedIndicator) { + playedIndicator.parentNode.removeChild(playedIndicator); + } + } + if (userData.UnplayedItemCount) { + countIndicator = card.querySelector('.countIndicator'); + + if (!countIndicator) { + countIndicator = document.createElement('div'); + countIndicator.classList.add('countIndicator'); + indicatorsElem = ensureIndicators(card, indicatorsElem); + indicatorsElem.appendChild(countIndicator); + } + countIndicator.innerHTML = userData.UnplayedItemCount; + } else if (enableCountIndicator) { + countIndicator = card.querySelector('.countIndicator'); + if (countIndicator) { + countIndicator.parentNode.removeChild(countIndicator); + } + } + + const progressHtml = indicators.getProgressBarHtml({ + Type: type, + UserData: userData, + MediaType: 'Video' + }); + + if (progressHtml) { + itemProgressBar = card.querySelector('.itemProgressBar'); + + if (!itemProgressBar) { + itemProgressBar = document.createElement('div'); + itemProgressBar.classList.add('itemProgressBar'); + + let innerCardFooter = card.querySelector('.innerCardFooter'); + if (!innerCardFooter) { + innerCardFooter = document.createElement('div'); + innerCardFooter.classList.add('innerCardFooter'); + const cardImageContainer = card.querySelector('.cardImageContainer'); + cardImageContainer.appendChild(innerCardFooter); } + innerCardFooter.appendChild(itemProgressBar); } - /** + itemProgressBar.innerHTML = progressHtml; + } else { + itemProgressBar = card.querySelector('.itemProgressBar'); + if (itemProgressBar) { + itemProgressBar.parentNode.removeChild(itemProgressBar); + } + } +} + +/** * Handles when user data has changed. * @param {Object} userData - User data to apply to the card. * @param {HTMLElement} scope - DOM element to use as a scope when selecting cards. */ - export function onUserDataChanged(userData, scope) { - const cards = (scope || document.body).querySelectorAll('.card-withuserdata[data-id="' + userData.ItemId + '"]'); +export function onUserDataChanged(userData, scope) { + const cards = (scope || document.body).querySelectorAll('.card-withuserdata[data-id="' + userData.ItemId + '"]'); - for (let i = 0, length = cards.length; i < length; i++) { - updateUserData(cards[i], userData); - } - } + for (let i = 0, length = cards.length; i < length; i++) { + updateUserData(cards[i], userData); + } +} - /** +/** * Handles when a timer has been created. * @param {string} programId - ID of the program. * @param {string} newTimerId - ID of the new timer. * @param {HTMLElement} itemsContainer - DOM element of the itemsContainer. */ - export function onTimerCreated(programId, newTimerId, itemsContainer) { - const cells = itemsContainer.querySelectorAll('.card[data-id="' + programId + '"]'); +export function onTimerCreated(programId, newTimerId, itemsContainer) { + const cells = itemsContainer.querySelectorAll('.card[data-id="' + programId + '"]'); - for (let i = 0, length = cells.length; i < length; i++) { - const cell = cells[i]; - const icon = cell.querySelector('.timerIndicator'); - if (!icon) { - const indicatorsElem = ensureIndicators(cell); - indicatorsElem.insertAdjacentHTML('beforeend', ''); - } - cell.setAttribute('data-timerid', newTimerId); - } + for (let i = 0, length = cells.length; i < length; i++) { + const cell = cells[i]; + const icon = cell.querySelector('.timerIndicator'); + if (!icon) { + const indicatorsElem = ensureIndicators(cell); + indicatorsElem.insertAdjacentHTML('beforeend', ''); } + cell.setAttribute('data-timerid', newTimerId); + } +} - /** +/** * Handles when a timer has been cancelled. * @param {string} timerId - ID of the cancelled timer. * @param {HTMLElement} itemsContainer - DOM element of the itemsContainer. */ - export function onTimerCancelled(timerId, itemsContainer) { - const cells = itemsContainer.querySelectorAll('.card[data-timerid="' + timerId + '"]'); +export function onTimerCancelled(timerId, itemsContainer) { + const cells = itemsContainer.querySelectorAll('.card[data-timerid="' + timerId + '"]'); - for (let i = 0; i < cells.length; i++) { - const cell = cells[i]; - const icon = cell.querySelector('.timerIndicator'); - if (icon) { - icon.parentNode.removeChild(icon); - } - cell.removeAttribute('data-timerid'); - } + for (let i = 0; i < cells.length; i++) { + const cell = cells[i]; + const icon = cell.querySelector('.timerIndicator'); + if (icon) { + icon.parentNode.removeChild(icon); } + cell.removeAttribute('data-timerid'); + } +} - /** +/** * Handles when a series timer has been cancelled. * @param {string} cancelledTimerId - ID of the cancelled timer. * @param {HTMLElement} itemsContainer - DOM element of the itemsContainer. */ - export function onSeriesTimerCancelled(cancelledTimerId, itemsContainer) { - const cells = itemsContainer.querySelectorAll('.card[data-seriestimerid="' + cancelledTimerId + '"]'); +export function onSeriesTimerCancelled(cancelledTimerId, itemsContainer) { + const cells = itemsContainer.querySelectorAll('.card[data-seriestimerid="' + cancelledTimerId + '"]'); - for (let i = 0; i < cells.length; i++) { - const cell = cells[i]; - const icon = cell.querySelector('.timerIndicator'); - if (icon) { - icon.parentNode.removeChild(icon); - } - cell.removeAttribute('data-seriestimerid'); - } + for (let i = 0; i < cells.length; i++) { + const cell = cells[i]; + const icon = cell.querySelector('.timerIndicator'); + if (icon) { + icon.parentNode.removeChild(icon); } - -/* eslint-enable indent */ + cell.removeAttribute('data-seriestimerid'); + } +} export default { getCardsHtml: getCardsHtml, diff --git a/src/components/cardbuilder/chaptercardbuilder.js b/src/components/cardbuilder/chaptercardbuilder.js index 794e845f8f..4a359cd9f4 100644 --- a/src/components/cardbuilder/chaptercardbuilder.js +++ b/src/components/cardbuilder/chaptercardbuilder.js @@ -1,4 +1,3 @@ -/* eslint-disable indent */ /** * Module for building cards from item data. @@ -12,123 +11,121 @@ import layoutManager from '../layoutManager'; import browser from '../../scripts/browser'; import ServerConnections from '../ServerConnections'; - const enableFocusTransform = !browser.slow && !browser.edge; +const enableFocusTransform = !browser.slow && !browser.edge; - function buildChapterCardsHtml(item, chapters, options) { - // TODO move card creation code to Card component +function buildChapterCardsHtml(item, chapters, options) { + // TODO move card creation code to Card component - let className = 'card itemAction chapterCard'; + let className = 'card itemAction chapterCard'; - if (layoutManager.tv) { - className += ' show-focus'; + if (layoutManager.tv) { + className += ' show-focus'; - if (enableFocusTransform) { - className += ' show-animation'; - } + if (enableFocusTransform) { + className += ' show-animation'; } - - const mediaStreams = ((item.MediaSources || [])[0] || {}).MediaStreams || []; - const videoStream = mediaStreams.filter(({ Type }) => { - return Type === 'Video'; - })[0] || {}; - - let shape = (options.backdropShape || 'backdrop'); - - if (videoStream.Width && videoStream.Height && (videoStream.Width / videoStream.Height) <= 1.2) { - shape = (options.squareShape || 'square'); - } - - className += ` ${shape}Card`; - - if (options.block || options.rows) { - className += ' block'; - } - - let html = ''; - let itemsInRow = 0; - - const apiClient = ServerConnections.getApiClient(item.ServerId); - - for (let i = 0, length = chapters.length; i < length; i++) { - if (options.rows && itemsInRow === 0) { - html += '
'; - } - - const chapter = chapters[i]; - - html += buildChapterCard(item, apiClient, chapter, i, options, className, shape); - itemsInRow++; - - if (options.rows && itemsInRow >= options.rows) { - itemsInRow = 0; - html += '
'; - } - } - - return html; } - function getImgUrl({ Id }, { ImageTag }, index, maxWidth, apiClient) { - if (ImageTag) { - return apiClient.getScaledImageUrl(Id, { + const mediaStreams = ((item.MediaSources || [])[0] || {}).MediaStreams || []; + const videoStream = mediaStreams.filter(({ Type }) => { + return Type === 'Video'; + })[0] || {}; - maxWidth: maxWidth, - tag: ImageTag, - type: 'Chapter', - index - }); - } + let shape = (options.backdropShape || 'backdrop'); - return null; + if (videoStream.Width && videoStream.Height && (videoStream.Width / videoStream.Height) <= 1.2) { + shape = (options.squareShape || 'square'); } - function buildChapterCard(item, apiClient, chapter, index, { width, coverImage }, className, shape) { - const imgUrl = getImgUrl(item, chapter, index, width || 400, apiClient); + className += ` ${shape}Card`; - let cardImageContainerClass = 'cardContent cardContent-shadow cardImageContainer chapterCardImageContainer'; - if (coverImage) { - cardImageContainerClass += ' coveredImage'; - } - const 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}"`; - let cardImageContainer = imgUrl ? (`
`) : (`
`); - - if (!imgUrl) { - cardImageContainer += ''; - } - - let nameHtml = ''; - nameHtml += `
${escapeHtml(chapter.Name)}
`; - nameHtml += `
${datetime.getDisplayRunningTime(chapter.StartPositionTicks)}
`; - - const cardBoxCssClass = 'cardBox'; - const cardScalableClass = 'cardScalable'; - - return `
`; + if (options.block || options.rows) { + className += ' block'; } - export function buildChapterCards(item, chapters, options) { - if (options.parentContainer) { - // Abort if the container has been disposed - if (!document.body.contains(options.parentContainer)) { - return; - } + let html = ''; + let itemsInRow = 0; - if (chapters.length) { - options.parentContainer.classList.remove('hide'); - } else { - options.parentContainer.classList.add('hide'); - return; - } + const apiClient = ServerConnections.getApiClient(item.ServerId); + + for (let i = 0, length = chapters.length; i < length; i++) { + if (options.rows && itemsInRow === 0) { + html += '
'; } - const html = buildChapterCardsHtml(item, chapters, options); + const chapter = chapters[i]; - options.itemsContainer.innerHTML = html; + html += buildChapterCard(item, apiClient, chapter, i, options, className, shape); + itemsInRow++; - imageLoader.lazyChildren(options.itemsContainer); + if (options.rows && itemsInRow >= options.rows) { + itemsInRow = 0; + html += '
'; + } } -/* eslint-enable indent */ + return html; +} + +function getImgUrl({ Id }, { ImageTag }, index, maxWidth, apiClient) { + if (ImageTag) { + return apiClient.getScaledImageUrl(Id, { + + maxWidth: maxWidth, + tag: ImageTag, + type: 'Chapter', + index + }); + } + + return null; +} + +function buildChapterCard(item, apiClient, chapter, index, { width, coverImage }, className, shape) { + const imgUrl = getImgUrl(item, chapter, index, width || 400, apiClient); + + let cardImageContainerClass = 'cardContent cardContent-shadow cardImageContainer chapterCardImageContainer'; + if (coverImage) { + cardImageContainerClass += ' coveredImage'; + } + const 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}"`; + let cardImageContainer = imgUrl ? (`
`) : (`
`); + + if (!imgUrl) { + cardImageContainer += ''; + } + + let nameHtml = ''; + nameHtml += `
${escapeHtml(chapter.Name)}
`; + nameHtml += `
${datetime.getDisplayRunningTime(chapter.StartPositionTicks)}
`; + + const cardBoxCssClass = 'cardBox'; + const cardScalableClass = 'cardScalable'; + + return `
`; +} + +export 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; + } + } + + const html = buildChapterCardsHtml(item, chapters, options); + + options.itemsContainer.innerHTML = html; + + imageLoader.lazyChildren(options.itemsContainer); +} export default { buildChapterCards: buildChapterCards diff --git a/src/components/cardbuilder/peoplecardbuilder.js b/src/components/cardbuilder/peoplecardbuilder.js index 00b8f0fb89..a991953704 100644 --- a/src/components/cardbuilder/peoplecardbuilder.js +++ b/src/components/cardbuilder/peoplecardbuilder.js @@ -1,4 +1,3 @@ -/* eslint-disable indent */ /** * Module for building cards from item data. @@ -7,20 +6,18 @@ import cardBuilder from './cardBuilder'; - export function buildPeopleCards(items, options) { - options = Object.assign(options || {}, { - cardLayout: false, - centerText: true, - showTitle: true, - cardFooterAside: 'none', - showPersonRoleOrType: true, - cardCssClass: 'personCard', - defaultCardImageIcon: 'person' - }); - cardBuilder.buildCards(items, options); - } - - /* eslint-enable indent */ +export function buildPeopleCards(items, options) { + options = Object.assign(options || {}, { + cardLayout: false, + centerText: true, + showTitle: true, + cardFooterAside: 'none', + showPersonRoleOrType: true, + cardCssClass: 'personCard', + defaultCardImageIcon: 'person' + }); + cardBuilder.buildCards(items, options); +} export default { buildPeopleCards: buildPeopleCards diff --git a/src/components/collectionEditor/collectionEditor.js b/src/components/collectionEditor/collectionEditor.js index 89862725a5..efcd7f86e7 100644 --- a/src/components/collectionEditor/collectionEditor.js +++ b/src/components/collectionEditor/collectionEditor.js @@ -16,254 +16,251 @@ import '../../styles/flexstyles.scss'; import ServerConnections from '../ServerConnections'; import toast from '../toast/toast'; -/* eslint-disable indent */ +let currentServerId; - let currentServerId; +function onSubmit(e) { + loading.show(); - function onSubmit(e) { - loading.show(); + const panel = dom.parentWithClass(this, 'dialog'); - const panel = dom.parentWithClass(this, 'dialog'); + const collectionId = panel.querySelector('#selectCollectionToAddTo').value; - const collectionId = panel.querySelector('#selectCollectionToAddTo').value; + const apiClient = ServerConnections.getApiClient(currentServerId); - const apiClient = ServerConnections.getApiClient(currentServerId); - - if (collectionId) { - addToCollection(apiClient, panel, collectionId); - } else { - createCollection(apiClient, panel); - } - - e.preventDefault(); - return false; + if (collectionId) { + addToCollection(apiClient, panel, collectionId); + } else { + createCollection(apiClient, panel); } - function createCollection(apiClient, dlg) { - const url = apiClient.getUrl('Collections', { + e.preventDefault(); + return false; +} - Name: dlg.querySelector('#txtNewCollectionName').value, - IsLocked: !dlg.querySelector('#chkEnableInternetMetadata').checked, - Ids: dlg.querySelector('.fldSelectedItemIds').value || '' - }); +function createCollection(apiClient, dlg) { + const url = apiClient.getUrl('Collections', { - apiClient.ajax({ - type: 'POST', - url: url, - dataType: 'json' + Name: dlg.querySelector('#txtNewCollectionName').value, + IsLocked: !dlg.querySelector('#chkEnableInternetMetadata').checked, + Ids: dlg.querySelector('.fldSelectedItemIds').value || '' + }); - }).then(result => { - loading.hide(); + apiClient.ajax({ + type: 'POST', + url: url, + dataType: 'json' - const id = result.Id; + }).then(result => { + loading.hide(); - dlg.submitted = true; - dialogHelper.close(dlg); - redirectToCollection(apiClient, id); - }); - } + const id = result.Id; - function redirectToCollection(apiClient, id) { - appRouter.showItem(id, apiClient.serverId()); - } + dlg.submitted = true; + dialogHelper.close(dlg); + redirectToCollection(apiClient, id); + }); +} - function addToCollection(apiClient, dlg, id) { - const url = apiClient.getUrl(`Collections/${id}/Items`, { +function redirectToCollection(apiClient, id) { + appRouter.showItem(id, apiClient.serverId()); +} - Ids: dlg.querySelector('.fldSelectedItemIds').value || '' - }); +function addToCollection(apiClient, dlg, id) { + const url = apiClient.getUrl(`Collections/${id}/Items`, { - apiClient.ajax({ - type: 'POST', - url: url + Ids: dlg.querySelector('.fldSelectedItemIds').value || '' + }); - }).then(() => { - loading.hide(); + apiClient.ajax({ + type: 'POST', + url: url - dlg.submitted = true; - dialogHelper.close(dlg); + }).then(() => { + loading.hide(); - toast(globalize.translate('MessageItemsAdded')); - }); - } + dlg.submitted = true; + dialogHelper.close(dlg); - function triggerChange(select) { - select.dispatchEvent(new CustomEvent('change', {})); - } + toast(globalize.translate('MessageItemsAdded')); + }); +} - function populateCollections(panel) { - loading.show(); +function triggerChange(select) { + select.dispatchEvent(new CustomEvent('change', {})); +} - const select = panel.querySelector('#selectCollectionToAddTo'); +function populateCollections(panel) { + loading.show(); - panel.querySelector('.newCollectionInfo').classList.add('hide'); + const select = panel.querySelector('#selectCollectionToAddTo'); - const options = { + panel.querySelector('.newCollectionInfo').classList.add('hide'); - Recursive: true, - IncludeItemTypes: 'BoxSet', - SortBy: 'SortName', - EnableTotalRecordCount: false - }; + const options = { - const apiClient = ServerConnections.getApiClient(currentServerId); - apiClient.getItems(apiClient.getCurrentUserId(), options).then(result => { - let html = ''; + Recursive: true, + IncludeItemTypes: 'BoxSet', + SortBy: 'SortName', + EnableTotalRecordCount: false + }; - html += ``; - - html += result.Items.map(i => { - return ``; - }); - - select.innerHTML = html; - select.value = ''; - triggerChange(select); - - loading.hide(); - }); - } - - function getEditorHtml() { + const apiClient = ServerConnections.getApiClient(currentServerId); + apiClient.getItems(apiClient.getCurrentUserId(), options).then(result => { let html = ''; - html += '
'; - html += '
'; - html += '
'; + html += ``; - html += '
'; - html += globalize.translate('NewCollectionHelp'); - html += '
'; - - html += '
'; - html += '
'; - html += '
'; - html += '
'; - html += ``; - html += '
'; - html += '
'; - - html += '
'; - - html += '
'; - html += ``; - html += `
${globalize.translate('NewCollectionNameExample')}
`; - html += '
'; - - html += ''; - - // newCollectionInfo - html += '
'; - - html += '
'; - html += ``; - html += '
'; - - html += ''; - - html += '
'; - html += '
'; - html += '
'; - - 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'); - } + html += result.Items.map(i => { + return ``; }); - content.querySelector('form').addEventListener('submit', onSubmit); + select.innerHTML = html; + select.value = ''; + triggerChange(select); - content.querySelector('.fldSelectedItemIds', content).value = items.join(','); + loading.hide(); + }); +} - if (items.length) { - content.querySelector('.fldSelectCollection').classList.remove('hide'); - populateCollections(content); +function getEditorHtml() { + let html = ''; + + html += '
'; + html += '
'; + html += '
'; + + html += '
'; + html += globalize.translate('NewCollectionHelp'); + html += '
'; + + html += '
'; + html += '
'; + html += '
'; + html += '
'; + html += ``; + html += '
'; + html += '
'; + + html += '
'; + + html += '
'; + html += ``; + html += `
${globalize.translate('NewCollectionNameExample')}
`; + html += '
'; + + html += ''; + + // newCollectionInfo + html += '
'; + + html += '
'; + html += ``; + html += '
'; + + html += ''; + + html += '
'; + html += '
'; + html += '
'; + + 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('.fldSelectCollection').classList.add('hide'); - - const selectCollectionToAddTo = content.querySelector('#selectCollectionToAddTo'); - selectCollectionToAddTo.innerHTML = ''; - selectCollectionToAddTo.value = ''; - triggerChange(selectCollectionToAddTo); + content.querySelector('.newCollectionInfo').classList.remove('hide'); + content.querySelector('#txtNewCollectionName').setAttribute('required', 'required'); } - } + }); - function centerFocus(elem, horiz, on) { - import('../../scripts/scrollHelper').then((scrollHelper) => { - const fn = on ? 'on' : 'off'; - scrollHelper.centerFocus[fn](elem, horiz); + 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'); + + const selectCollectionToAddTo = content.querySelector('#selectCollectionToAddTo'); + selectCollectionToAddTo.innerHTML = ''; + selectCollectionToAddTo.value = ''; + triggerChange(selectCollectionToAddTo); + } +} + +function centerFocus(elem, horiz, on) { + import('../../scripts/scrollHelper').then((scrollHelper) => { + const fn = on ? 'on' : 'off'; + scrollHelper.centerFocus[fn](elem, horiz); + }); +} + +class CollectionEditor { + show(options) { + const items = options.items || {}; + currentServerId = options.serverId; + + const dialogOptions = { + removeOnClose: true, + scrollY: false + }; + + if (layoutManager.tv) { + dialogOptions.size = 'fullscreen'; + } else { + dialogOptions.size = 'small'; + } + + const dlg = dialogHelper.createDialog(dialogOptions); + + dlg.classList.add('formDialog'); + + let html = ''; + const title = items.length ? globalize.translate('HeaderAddToCollection') : globalize.translate('NewCollection'); + + html += '
'; + html += ``; + html += '

'; + html += title; + html += '

'; + + html += '
'; + + html += getEditorHtml(); + + dlg.innerHTML = html; + + initEditor(dlg, items); + + dlg.querySelector('.btnCancel').addEventListener('click', () => { + dialogHelper.close(dlg); + }); + + if (layoutManager.tv) { + centerFocus(dlg.querySelector('.formDialogContent'), false, true); + } + + return dialogHelper.open(dlg).then(() => { + if (layoutManager.tv) { + centerFocus(dlg.querySelector('.formDialogContent'), false, false); + } + + if (dlg.submitted) { + return Promise.resolve(); + } + + return Promise.reject(); }); } +} - class CollectionEditor { - show(options) { - const items = options.items || {}; - currentServerId = options.serverId; - - const dialogOptions = { - removeOnClose: true, - scrollY: false - }; - - if (layoutManager.tv) { - dialogOptions.size = 'fullscreen'; - } else { - dialogOptions.size = 'small'; - } - - const dlg = dialogHelper.createDialog(dialogOptions); - - dlg.classList.add('formDialog'); - - let html = ''; - const title = items.length ? globalize.translate('HeaderAddToCollection') : globalize.translate('NewCollection'); - - html += '
'; - html += ``; - html += '

'; - html += title; - html += '

'; - - html += '
'; - - html += getEditorHtml(); - - dlg.innerHTML = html; - - initEditor(dlg, items); - - dlg.querySelector('.btnCancel').addEventListener('click', () => { - dialogHelper.close(dlg); - }); - - if (layoutManager.tv) { - centerFocus(dlg.querySelector('.formDialogContent'), false, true); - } - - return dialogHelper.open(dlg).then(() => { - if (layoutManager.tv) { - centerFocus(dlg.querySelector('.formDialogContent'), false, false); - } - - if (dlg.submitted) { - return Promise.resolve(); - } - - return Promise.reject(); - }); - } - } - -/* eslint-enable indent */ export default CollectionEditor; diff --git a/src/components/dialog/dialog.js b/src/components/dialog/dialog.js index 85e81abb02..3e285514e1 100644 --- a/src/components/dialog/dialog.js +++ b/src/components/dialog/dialog.js @@ -13,129 +13,126 @@ import '../formdialog.scss'; import '../../styles/flexstyles.scss'; import template from './dialog.template.html'; -/* eslint-disable indent */ +function showDialog(options = { dialogOptions: {}, buttons: [] }) { + const dialogOptions = { + removeOnClose: true, + scrollY: false, + ...options.dialogOptions + }; - function showDialog(options = { dialogOptions: {}, buttons: [] }) { - const dialogOptions = { - removeOnClose: true, - scrollY: false, - ...options.dialogOptions - }; + const enableTvLayout = layoutManager.tv; - const enableTvLayout = layoutManager.tv; + if (enableTvLayout) { + dialogOptions.size = 'fullscreen'; + } - if (enableTvLayout) { - dialogOptions.size = 'fullscreen'; + const 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'); + const 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'); + } + + if (options.title) { + dlg.querySelector('.formDialogHeaderTitle').innerText = options.title || ''; + } else { + dlg.querySelector('.formDialogHeaderTitle').classList.add('hide'); + } + + const displayText = options.html || options.text || ''; + dlg.querySelector('.text').innerHTML = DOMPurify.sanitize(displayText); + + if (!displayText) { + dlg.querySelector('.dialogContentInner').classList.add('hide'); + } + + let i; + let length; + let html = ''; + let hasDescriptions = false; + + for (i = 0, length = options.buttons.length; i < length; i++) { + const item = options.buttons[i]; + const autoFocus = i === 0 ? ' autofocus' : ''; + + let buttonClass = 'btnOption raised formDialogFooterItem formDialogFooterItem-autosize'; + + if (item.type) { + buttonClass += ` button-${item.type}`; } - const 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'); - const 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'); + if (item.description) { + hasDescriptions = true; } - if (options.title) { - dlg.querySelector('.formDialogHeaderTitle').innerText = options.title || ''; - } else { - dlg.querySelector('.formDialogHeaderTitle').classList.add('hide'); - } - - const displayText = options.html || options.text || ''; - dlg.querySelector('.text').innerHTML = DOMPurify.sanitize(displayText); - - if (!displayText) { - dlg.querySelector('.dialogContentInner').classList.add('hide'); - } - - let i; - let length; - let html = ''; - let hasDescriptions = false; - - for (i = 0, length = options.buttons.length; i < length; i++) { - const item = options.buttons[i]; - const autoFocus = i === 0 ? ' autofocus' : ''; - - let 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 += ``; - - if (item.description) { - html += `
${item.description}
`; - } - } - - dlg.querySelector('.formDialogFooter').innerHTML = html; - if (hasDescriptions) { - dlg.querySelector('.formDialogFooter').classList.add('formDialogFooter-vertical'); + buttonClass += ' formDialogFooterItem-vertical formDialogFooterItem-nomarginbottom'; } - let dialogResult; - function onButtonClick() { - dialogResult = this.getAttribute('data-id'); - dialogHelper.close(dlg); + html += ``; + + if (item.description) { + html += `
${item.description}
`; } - - const buttons = dlg.querySelectorAll('.btnOption'); - for (i = 0, length = buttons.length; i < length; i++) { - buttons[i].addEventListener('click', onButtonClick); - } - - return dialogHelper.open(dlg).then(() => { - if (enableTvLayout) { - scrollHelper.centerFocus.off(dlg.querySelector('.formDialogContent'), false); - } - - if (dialogResult) { - return dialogResult; - } else { - return Promise.reject(); - } - }); } - export function show(text, title) { - let options; - if (typeof text === 'string') { - options = { - title: title, - text: text - }; + dlg.querySelector('.formDialogFooter').innerHTML = html; + + if (hasDescriptions) { + dlg.querySelector('.formDialogFooter').classList.add('formDialogFooter-vertical'); + } + + let dialogResult; + function onButtonClick() { + dialogResult = this.getAttribute('data-id'); + dialogHelper.close(dlg); + } + + const buttons = dlg.querySelectorAll('.btnOption'); + for (i = 0, length = buttons.length; i < length; i++) { + buttons[i].addEventListener('click', onButtonClick); + } + + return dialogHelper.open(dlg).then(() => { + if (enableTvLayout) { + scrollHelper.centerFocus.off(dlg.querySelector('.formDialogContent'), false); + } + + if (dialogResult) { + return dialogResult; } else { - options = text; + return Promise.reject(); } + }); +} - return showDialog(options); +export function show(text, title) { + let options; + if (typeof text === 'string') { + options = { + title: title, + text: text + }; + } else { + options = text; } -/* eslint-enable indent */ + return showDialog(options); +} + export default { show: show }; diff --git a/src/components/dialogHelper/dialogHelper.js b/src/components/dialogHelper/dialogHelper.js index 32fa105041..5819f0a755 100644 --- a/src/components/dialogHelper/dialogHelper.js +++ b/src/components/dialogHelper/dialogHelper.js @@ -9,505 +9,501 @@ import dom from '../../scripts/dom'; import './dialoghelper.scss'; import '../../styles/scrollstyles.scss'; -/* eslint-disable indent */ +let globalOnOpenCallback; - let globalOnOpenCallback; - - function enableAnimation() { - // too slow - if (browser.tv) { - return false; - } - - return browser.supportsCssAnimation(); +function enableAnimation() { + // too slow + if (browser.tv) { + return false; } - 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); - } + 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) { + const parentNode = elem.parentNode; + if (parentNode) { + // Seeing crashes in edge webview + try { + parentNode.removeChild(elem); + } catch (err) { + console.error('[dialogHelper] error removing dialog element: ' + err); + } + } +} + +function DialogHashHandler(dlg, hash, resolve) { + const self = this; + self.originalUrl = window.location.href; + const activeElement = document.activeElement; + let removeScrollLockOnClose = false; + let unlisten; + + function onHashChange({ location }) { + const dialogs = location.state?.dialogs || []; + const shouldClose = !dialogs.includes(hash); + + if ((shouldClose || !isOpened(dlg)) && unlisten) { + unlisten(); + unlisten = null; + } + + if (shouldClose) { + close(dlg); } } - function tryRemoveElement(elem) { - const parentNode = elem.parentNode; - if (parentNode) { - // Seeing crashes in edge webview - try { - parentNode.removeChild(elem); - } catch (err) { - console.error('[dialogHelper] error removing dialog element: ' + err); - } - } - } - - function DialogHashHandler(dlg, hash, resolve) { - const self = this; - self.originalUrl = window.location.href; - const activeElement = document.activeElement; - let removeScrollLockOnClose = false; - let unlisten; - - function onHashChange({ location }) { - const dialogs = location.state?.dialogs || []; - const shouldClose = !dialogs.includes(hash); - - if ((shouldClose || !isOpened(dlg)) && unlisten) { - unlisten(); - unlisten = null; - } - - if (shouldClose) { - close(dlg); - } + function finishClose() { + if (unlisten) { + unlisten(); + unlisten = null; } - function finishClose() { - if (unlisten) { - unlisten(); - unlisten = null; - } - - dlg.dispatchEvent(new CustomEvent('close', { - bubbles: false, - cancelable: false - })); - - resolve({ - element: dlg - }); - } - - function onBackCommand(e) { - if (e.detail.command === 'back') { - e.preventDefault(); - e.stopPropagation(); - close(dlg); - } - } - - function onDialogClosed() { - if (!isHistoryEnabled(dlg)) { - inputManager.off(dlg, onBackCommand); - } - - if (unlisten) { - unlisten(); - unlisten = null; - } - - removeBackdrop(dlg); - dlg.classList.remove('opened'); - - if (removeScrollLockOnClose) { - document.body.classList.remove('noScroll'); - } - - if (isHistoryEnabled(dlg)) { - const state = history.location.state || {}; - if (state.dialogs?.length > 0) { - if (state.dialogs[state.dialogs.length - 1] === hash) { - unlisten = history.listen(finishClose); - history.back(); - } else if (state.dialogs.includes(hash)) { - console.warn('[dialogHelper] dialog "%s" was closed, but is not the last dialog opened', hash); - - unlisten = history.listen(finishClose); - - // Remove the closed dialog hash from the history state - history.replace( - `${history.location.pathname}${history.location.search}`, - { - ...state, - dialogs: state.dialogs.filter(dialog => dialog !== hash) - } - ); - } - } - } - - if (layoutManager.tv) { - focusManager.focus(activeElement); - } - - if (toBoolean(dlg.getAttribute('data-removeonclose'), true)) { - removeCenterFocus(dlg); - - const dialogContainer = dlg.dialogContainer; - if (dialogContainer) { - tryRemoveElement(dialogContainer); - dlg.dialogContainer = null; - } else { - tryRemoveElement(dlg); - } - } - - if (!unlisten) { - finishClose(); - } - } - - dlg.addEventListener('_close', onDialogClosed); - - const 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', { + dlg.dispatchEvent(new CustomEvent('close', { bubbles: false, cancelable: false })); - if (dlg.getAttribute('data-lockscroll') === 'true' && !document.body.classList.contains('noScroll')) { - document.body.classList.add('noScroll'); - removeScrollLockOnClose = true; + resolve({ + element: dlg + }); + } + + function onBackCommand(e) { + if (e.detail.command === 'back') { + e.preventDefault(); + e.stopPropagation(); + close(dlg); + } + } + + function onDialogClosed() { + if (!isHistoryEnabled(dlg)) { + inputManager.off(dlg, onBackCommand); } - animateDialogOpen(dlg); + if (unlisten) { + unlisten(); + unlisten = null; + } + + removeBackdrop(dlg); + dlg.classList.remove('opened'); + + if (removeScrollLockOnClose) { + document.body.classList.remove('noScroll'); + } if (isHistoryEnabled(dlg)) { const state = history.location.state || {}; - const dialogs = state.dialogs || []; - // Add new dialog to the list of open dialogs - dialogs.push(hash); + if (state.dialogs?.length > 0) { + if (state.dialogs[state.dialogs.length - 1] === hash) { + unlisten = history.listen(finishClose); + history.back(); + } else if (state.dialogs.includes(hash)) { + console.warn('[dialogHelper] dialog "%s" was closed, but is not the last dialog opened', hash); - history.push( - `${history.location.pathname}${history.location.search}`, - { - ...state, - dialogs + unlisten = history.listen(finishClose); + + // Remove the closed dialog hash from the history state + history.replace( + `${history.location.pathname}${history.location.search}`, + { + ...state, + dialogs: state.dialogs.filter(dialog => dialog !== hash) + } + ); } - ); - - unlisten = history.listen(onHashChange); - } else { - inputManager.on(dlg, onBackCommand); - } - } - - function addBackdropOverlay(dlg) { - const backdrop = document.createElement('div'); - backdrop.classList.add('dialogBackdrop'); - - const 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', e => { - if (e.target === dlg.dialogContainer) { - close(dlg); } - }, { - passive: true - }); + } - dom.addEventListener((dlg.dialogContainer || backdrop), 'contextmenu', e => { - if (e.target === dlg.dialogContainer) { - // Close the application dialog menu - close(dlg); - // Prevent the default browser context menu from appearing - e.preventDefault(); + if (layoutManager.tv) { + focusManager.focus(activeElement); + } + + if (toBoolean(dlg.getAttribute('data-removeonclose'), true)) { + removeCenterFocus(dlg); + + const dialogContainer = dlg.dialogContainer; + if (dialogContainer) { + tryRemoveElement(dialogContainer); + dlg.dialogContainer = null; + } else { + tryRemoveElement(dlg); } - }); - } - - function isHistoryEnabled(dlg) { - return dlg.getAttribute('data-history') === 'true'; - } - - export function open(dlg) { - if (globalOnOpenCallback) { - globalOnOpenCallback(dlg); } - const parent = dlg.parentNode; - if (parent) { - parent.removeChild(dlg); + if (!unlisten) { + finishClose(); } - - const dialogContainer = document.createElement('div'); - dialogContainer.classList.add('dialogContainer'); - dialogContainer.appendChild(dlg); - dlg.dialogContainer = dialogContainer; - document.body.appendChild(dialogContainer); - - return new Promise((resolve) => { - new DialogHashHandler(dlg, `dlg${new Date().getTime()}`, resolve); - }); } - function isOpened(dlg) { - return !dlg.classList.contains('hide'); + dlg.addEventListener('_close', onDialogClosed); + + const center = !dlg.classList.contains('dialog-fixedSize'); + if (center) { + dlg.classList.add('centeredDialog'); } - export function close(dlg) { - if (!dlg.classList.contains('hide')) { - dlg.dispatchEvent(new CustomEvent('closing', { + 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)) { + const state = history.location.state || {}; + const dialogs = state.dialogs || []; + // Add new dialog to the list of open dialogs + dialogs.push(hash); + + history.push( + `${history.location.pathname}${history.location.search}`, + { + ...state, + dialogs + } + ); + + unlisten = history.listen(onHashChange); + } else { + inputManager.on(dlg, onBackCommand); + } +} + +function addBackdropOverlay(dlg) { + const backdrop = document.createElement('div'); + backdrop.classList.add('dialogBackdrop'); + + const 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', e => { + if (e.target === dlg.dialogContainer) { + close(dlg); + } + }, { + passive: true + }); + + dom.addEventListener((dlg.dialogContainer || backdrop), 'contextmenu', e => { + if (e.target === dlg.dialogContainer) { + // Close the application dialog menu + close(dlg); + // Prevent the default browser context menu from appearing + e.preventDefault(); + } + }); +} + +function isHistoryEnabled(dlg) { + return dlg.getAttribute('data-history') === 'true'; +} + +export function open(dlg) { + if (globalOnOpenCallback) { + globalOnOpenCallback(dlg); + } + + const parent = dlg.parentNode; + if (parent) { + parent.removeChild(dlg); + } + + const dialogContainer = document.createElement('div'); + dialogContainer.classList.add('dialogContainer'); + dialogContainer.appendChild(dlg); + dlg.dialogContainer = dialogContainer; + document.body.appendChild(dialogContainer); + + return new Promise((resolve) => { + new DialogHashHandler(dlg, `dlg${new Date().getTime()}`, resolve); + }); +} + +function isOpened(dlg) { + return !dlg.classList.contains('hide'); +} + +export function close(dlg) { + if (!dlg.classList.contains('hide')) { + dlg.dispatchEvent(new CustomEvent('closing', { + bubbles: false, + cancelable: false + })); + + const onAnimationFinish = () => { + focusManager.popScope(dlg); + + dlg.classList.add('hide'); + dlg.dispatchEvent(new CustomEvent('_close', { bubbles: false, cancelable: false })); + }; - const onAnimationFinish = () => { - focusManager.popScope(dlg); - - dlg.classList.add('hide'); - dlg.dispatchEvent(new CustomEvent('_close', { - bubbles: false, - cancelable: false - })); - }; - - animateDialogClose(dlg, onAnimationFinish); - } + animateDialogClose(dlg, onAnimationFinish); } +} - const getAnimationEndHandler = (dlg, callback) => function handler() { - dom.removeEventListener(dlg, dom.whichAnimationEvent(), handler, { once: true }); - callback(); +const getAnimationEndHandler = (dlg, callback) => function handler() { + dom.removeEventListener(dlg, dom.whichAnimationEvent(), handler, { once: true }); + callback(); +}; + +function animateDialogOpen(dlg) { + const onAnimationFinish = () => { + focusManager.pushScope(dlg); + + if (dlg.getAttribute('data-autofocus') === 'true') { + focusManager.autoFocus(dlg); + } + + if (document.activeElement && !dlg.contains(document.activeElement)) { + // Blur foreign element to prevent triggering of an action from the previous scope + document.activeElement.blur(); + } }; - function animateDialogOpen(dlg) { - const onAnimationFinish = () => { - focusManager.pushScope(dlg); + if (enableAnimation()) { + dom.addEventListener( + dlg, + dom.whichAnimationEvent(), + getAnimationEndHandler(dlg, onAnimationFinish), + { once: true }); - if (dlg.getAttribute('data-autofocus') === 'true') { - focusManager.autoFocus(dlg); - } + return; + } - if (document.activeElement && !dlg.contains(document.activeElement)) { - // Blur foreign element to prevent triggering of an action from the previous scope - document.activeElement.blur(); - } - }; + onAnimationFinish(); +} - if (enableAnimation()) { - dom.addEventListener( - dlg, - dom.whichAnimationEvent(), - getAnimationEndHandler(dlg, onAnimationFinish), - { once: true }); +function animateDialogClose(dlg, onAnimationFinish) { + if (enableAnimation()) { + let 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; + } + + dom.addEventListener( + dlg, + dom.whichAnimationEvent(), + getAnimationEndHandler(dlg, onAnimationFinish), + { once: true }); + + if (animated) { return; } - - onAnimationFinish(); } - function animateDialogClose(dlg, onAnimationFinish) { - if (enableAnimation()) { - let animated = true; + onAnimationFinish(); +} - 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; +const supportsOverscrollBehavior = 'overscroll-behavior-y' in document.body.style; + +function shouldLockDocumentScroll(options) { + if (options.lockScroll != null) { + return options.lockScroll; + } + + if (options.size === 'fullscreen') { + return true; + } + + if (supportsOverscrollBehavior && (options.size || !browser.touch)) { + return false; + } + + if (options.size) { + return true; + } + + return browser.touch; +} + +function removeBackdrop(dlg) { + const backdrop = dlg.backdrop; + + if (!backdrop) { + return; + } + + dlg.backdrop = null; + + const onAnimationFinish = () => { + tryRemoveElement(backdrop); + }; + + if (enableAnimation()) { + backdrop.classList.remove('dialogBackdropOpened'); + + // this is not firing animationend + setTimeout(onAnimationFinish, 300); + return; + } + + onAnimationFinish(); +} + +function centerFocus(elem, horiz, on) { + import('../../scripts/scrollHelper').then((scrollHelper) => { + const fn = on ? 'on' : 'off'; + scrollHelper.centerFocus[fn](elem, horiz); + }); +} + +export function createDialog(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 + const dlg = document.createElement('div'); + + // Add an id so we can access the dialog element + if (options.id) { + dlg.id = options.id; + } + + dlg.classList.add('focuscontainer'); + dlg.classList.add('hide'); + + if (shouldLockDocumentScroll(options)) { + dlg.setAttribute('data-lockscroll', 'true'); + } + + if (options.enableHistory !== false) { + 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'); + } + + const defaultEntryAnimation = 'scaleup'; + const defaultExitAnimation = 'scaledown'; + const entryAnimation = options.entryAnimation || defaultEntryAnimation; + const exitAnimation = options.exitAnimation || defaultExitAnimation; + + // If it's not fullscreen then lower the default animation speed to make it open really fast + const entryAnimationDuration = options.entryAnimationDuration || (options.size !== 'fullscreen' ? 180 : 280); + const exitAnimationDuration = options.exitAnimationDuration || (options.size !== 'fullscreen' ? 120 : 220); + + dlg.animationConfig = { + // scale up + 'entry': { + name: entryAnimation, + timing: { + duration: entryAnimationDuration, + easing: 'ease-out' } - - dom.addEventListener( - dlg, - dom.whichAnimationEvent(), - getAnimationEndHandler(dlg, onAnimationFinish), - { once: true }); - - if (animated) { - return; + }, + // fade out + 'exit': { + name: exitAnimation, + timing: { + duration: exitAnimationDuration, + easing: 'ease-out', + fill: 'both' } } + }; - onAnimationFinish(); + 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); + } } - const supportsOverscrollBehavior = 'overscroll-behavior-y' in document.body.style; - - function shouldLockDocumentScroll(options) { - if (options.lockScroll != null) { - return options.lockScroll; - } - - if (options.size === 'fullscreen') { - return true; - } - - if (supportsOverscrollBehavior && (options.size || !browser.touch)) { - return false; - } - - if (options.size) { - return true; - } - - return browser.touch; + if (options.removeOnClose) { + dlg.setAttribute('data-removeonclose', 'true'); } - function removeBackdrop(dlg) { - const backdrop = dlg.backdrop; - - if (!backdrop) { - return; - } - - dlg.backdrop = null; - - const onAnimationFinish = () => { - tryRemoveElement(backdrop); - }; - - if (enableAnimation()) { - backdrop.classList.remove('dialogBackdropOpened'); - - // this is not firing animationend - setTimeout(onAnimationFinish, 300); - return; - } - - onAnimationFinish(); + if (options.size) { + dlg.classList.add('dialog-fixedSize'); + dlg.classList.add(`dialog-${options.size}`); } - function centerFocus(elem, horiz, on) { - import('../../scripts/scrollHelper').then((scrollHelper) => { - const fn = on ? 'on' : 'off'; - scrollHelper.centerFocus[fn](elem, horiz); - }); + 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; + } } - export function createDialog(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 - const dlg = document.createElement('div'); + return dlg; +} - // Add an id so we can access the dialog element - if (options.id) { - dlg.id = options.id; - } - - dlg.classList.add('focuscontainer'); - dlg.classList.add('hide'); - - if (shouldLockDocumentScroll(options)) { - dlg.setAttribute('data-lockscroll', 'true'); - } - - if (options.enableHistory !== false) { - 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'); - } - - const defaultEntryAnimation = 'scaleup'; - const defaultExitAnimation = 'scaledown'; - const entryAnimation = options.entryAnimation || defaultEntryAnimation; - const exitAnimation = options.exitAnimation || defaultExitAnimation; - - // If it's not fullscreen then lower the default animation speed to make it open really fast - const entryAnimationDuration = options.entryAnimationDuration || (options.size !== 'fullscreen' ? 180 : 280); - const 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; - } - - export function setOnOpen(val) { - globalOnOpenCallback = val; - } - -/* eslint-enable indent */ +export function setOnOpen(val) { + globalOnOpenCallback = val; +} export default { open: open, diff --git a/src/components/displaySettings/displaySettings.js b/src/components/displaySettings/displaySettings.js index 195c919e9b..c0ede8aec5 100644 --- a/src/components/displaySettings/displaySettings.js +++ b/src/components/displaySettings/displaySettings.js @@ -18,238 +18,235 @@ import ServerConnections from '../ServerConnections'; import toast from '../toast/toast'; import template from './displaySettings.template.html'; -/* eslint-disable indent */ - - function fillThemes(select, selectedTheme) { - skinManager.getThemes().then(themes => { - select.innerHTML = themes.map(t => { - return ``; - }).join(''); - - // get default theme - const defaultTheme = themes.find(theme => theme.default); - - // set the current theme - select.value = selectedTheme || defaultTheme.id; - }); - } - - function loadScreensavers(context, userSettings) { - const selectScreensaver = context.querySelector('.selectScreensaver'); - const options = pluginManager.ofType(PluginType.Screensaver).map(plugin => { - return { - name: plugin.name, - value: plugin.id - }; - }); - - options.unshift({ - name: globalize.translate('None'), - value: 'none' - }); - - selectScreensaver.innerHTML = options.map(o => { - return ``; +function fillThemes(select, selectedTheme) { + skinManager.getThemes().then(themes => { + select.innerHTML = themes.map(t => { + return ``; }).join(''); - selectScreensaver.value = userSettings.screensaver(); + // get default theme + const defaultTheme = themes.find(theme => theme.default); - if (!selectScreensaver.value) { - // TODO: set the default instead of none - selectScreensaver.value = 'none'; - } + // set the current theme + select.value = selectedTheme || defaultTheme.id; + }); +} + +function loadScreensavers(context, userSettings) { + const selectScreensaver = context.querySelector('.selectScreensaver'); + const options = pluginManager.ofType(PluginType.Screensaver).map(plugin => { + return { + name: plugin.name, + value: plugin.id + }; + }); + + options.unshift({ + name: globalize.translate('None'), + value: 'none' + }); + + selectScreensaver.innerHTML = options.map(o => { + return ``; + }).join(''); + + selectScreensaver.value = userSettings.screensaver(); + + if (!selectScreensaver.value) { + // TODO: set the default instead of none + selectScreensaver.value = 'none'; + } +} + +function showOrHideMissingEpisodesField(context) { + if (browser.tizen || browser.web0s) { + context.querySelector('.fldDisplayMissingEpisodes').classList.add('hide'); + return; } - function showOrHideMissingEpisodesField(context) { - if (browser.tizen || browser.web0s) { - context.querySelector('.fldDisplayMissingEpisodes').classList.add('hide'); - return; - } + context.querySelector('.fldDisplayMissingEpisodes').classList.remove('hide'); +} - context.querySelector('.fldDisplayMissingEpisodes').classList.remove('hide'); +function loadForm(context, user, userSettings) { + if (appHost.supports('displaylanguage')) { + context.querySelector('.languageSection').classList.remove('hide'); + } else { + context.querySelector('.languageSection').classList.add('hide'); } - function loadForm(context, user, userSettings) { - 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'); - } - - context.querySelector('.selectDashboardThemeContainer').classList.toggle('hide', !user.Policy.IsAdministrator); - - 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'); - } - - fillThemes(context.querySelector('#selectTheme'), userSettings.theme()); - fillThemes(context.querySelector('#selectDashboardTheme'), userSettings.dashboardTheme()); - - loadScreensavers(context, userSettings); - - context.querySelector('.chkDisplayMissingEpisodes').checked = user.Configuration.DisplayMissingEpisodes || false; - - context.querySelector('#chkThemeSong').checked = userSettings.enableThemeSongs(); - context.querySelector('#chkThemeVideo').checked = userSettings.enableThemeVideos(); - context.querySelector('#chkFadein').checked = userSettings.enableFastFadein(); - context.querySelector('#chkBlurhash').checked = userSettings.enableBlurhash(); - context.querySelector('#chkBackdrops').checked = userSettings.enableBackdrops(); - context.querySelector('#chkDetailsBanner').checked = userSettings.detailsBanner(); - - context.querySelector('#chkDisableCustomCss').checked = userSettings.disableCustomCss(); - context.querySelector('#txtLocalCustomCss').value = userSettings.customCss(); - - context.querySelector('#selectLanguage').value = userSettings.language() || ''; - context.querySelector('.selectDateTimeLocale').value = userSettings.dateTimeLocale() || ''; - - context.querySelector('#txtLibraryPageSize').value = userSettings.libraryPageSize(); - - context.querySelector('#txtMaxDaysForNextUp').value = userSettings.maxDaysForNextUp(); - context.querySelector('#chkRewatchingNextUp').checked = userSettings.enableRewatchingInNextUp(); - context.querySelector('#chkUseEpisodeImagesInNextUp').checked = userSettings.useEpisodeImagesInNextUpAndResume(); - - context.querySelector('.selectLayout').value = layoutManager.getSavedLayout() || ''; - - showOrHideMissingEpisodesField(context); - - loading.hide(); + if (appHost.supports('displaymode')) { + context.querySelector('.fldDisplayMode').classList.remove('hide'); + } else { + context.querySelector('.fldDisplayMode').classList.add('hide'); } - function saveUser(context, user, userSettingsInstance, apiClient) { - 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.theme(context.querySelector('#selectTheme').value); - userSettingsInstance.dashboardTheme(context.querySelector('#selectDashboardTheme').value); - userSettingsInstance.screensaver(context.querySelector('.selectScreensaver').value); - - userSettingsInstance.libraryPageSize(context.querySelector('#txtLibraryPageSize').value); - - userSettingsInstance.maxDaysForNextUp(context.querySelector('#txtMaxDaysForNextUp').value); - userSettingsInstance.enableRewatchingInNextUp(context.querySelector('#chkRewatchingNextUp').checked); - userSettingsInstance.useEpisodeImagesInNextUpAndResume(context.querySelector('#chkUseEpisodeImagesInNextUp').checked); - - userSettingsInstance.enableFastFadein(context.querySelector('#chkFadein').checked); - userSettingsInstance.enableBlurhash(context.querySelector('#chkBlurhash').checked); - userSettingsInstance.enableBackdrops(context.querySelector('#chkBackdrops').checked); - userSettingsInstance.detailsBanner(context.querySelector('#chkDetailsBanner').checked); - - userSettingsInstance.disableCustomCss(context.querySelector('#chkDisableCustomCss').checked); - userSettingsInstance.customCss(context.querySelector('#txtLocalCustomCss').value); - - if (user.Id === apiClient.getCurrentUserId()) { - skinManager.setTheme(userSettingsInstance.theme()); - } - - layoutManager.setLayout(context.querySelector('.selectLayout').value); - return apiClient.updateUserConfiguration(user.Id, user.Configuration); + if (appHost.supports('externallinks')) { + context.querySelector('.learnHowToContributeContainer').classList.remove('hide'); + } else { + context.querySelector('.learnHowToContributeContainer').classList.add('hide'); } - function save(instance, context, userId, userSettings, apiClient, enableSaveConfirmation) { + context.querySelector('.selectDashboardThemeContainer').classList.toggle('hide', !user.Policy.IsAdministrator); + + 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'); + } + + fillThemes(context.querySelector('#selectTheme'), userSettings.theme()); + fillThemes(context.querySelector('#selectDashboardTheme'), userSettings.dashboardTheme()); + + loadScreensavers(context, userSettings); + + context.querySelector('.chkDisplayMissingEpisodes').checked = user.Configuration.DisplayMissingEpisodes || false; + + context.querySelector('#chkThemeSong').checked = userSettings.enableThemeSongs(); + context.querySelector('#chkThemeVideo').checked = userSettings.enableThemeVideos(); + context.querySelector('#chkFadein').checked = userSettings.enableFastFadein(); + context.querySelector('#chkBlurhash').checked = userSettings.enableBlurhash(); + context.querySelector('#chkBackdrops').checked = userSettings.enableBackdrops(); + context.querySelector('#chkDetailsBanner').checked = userSettings.detailsBanner(); + + context.querySelector('#chkDisableCustomCss').checked = userSettings.disableCustomCss(); + context.querySelector('#txtLocalCustomCss').value = userSettings.customCss(); + + context.querySelector('#selectLanguage').value = userSettings.language() || ''; + context.querySelector('.selectDateTimeLocale').value = userSettings.dateTimeLocale() || ''; + + context.querySelector('#txtLibraryPageSize').value = userSettings.libraryPageSize(); + + context.querySelector('#txtMaxDaysForNextUp').value = userSettings.maxDaysForNextUp(); + context.querySelector('#chkRewatchingNextUp').checked = userSettings.enableRewatchingInNextUp(); + context.querySelector('#chkUseEpisodeImagesInNextUp').checked = userSettings.useEpisodeImagesInNextUpAndResume(); + + context.querySelector('.selectLayout').value = layoutManager.getSavedLayout() || ''; + + showOrHideMissingEpisodesField(context); + + loading.hide(); +} + +function saveUser(context, user, userSettingsInstance, apiClient) { + 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.theme(context.querySelector('#selectTheme').value); + userSettingsInstance.dashboardTheme(context.querySelector('#selectDashboardTheme').value); + userSettingsInstance.screensaver(context.querySelector('.selectScreensaver').value); + + userSettingsInstance.libraryPageSize(context.querySelector('#txtLibraryPageSize').value); + + userSettingsInstance.maxDaysForNextUp(context.querySelector('#txtMaxDaysForNextUp').value); + userSettingsInstance.enableRewatchingInNextUp(context.querySelector('#chkRewatchingNextUp').checked); + userSettingsInstance.useEpisodeImagesInNextUpAndResume(context.querySelector('#chkUseEpisodeImagesInNextUp').checked); + + userSettingsInstance.enableFastFadein(context.querySelector('#chkFadein').checked); + userSettingsInstance.enableBlurhash(context.querySelector('#chkBlurhash').checked); + userSettingsInstance.enableBackdrops(context.querySelector('#chkBackdrops').checked); + userSettingsInstance.detailsBanner(context.querySelector('#chkDetailsBanner').checked); + + userSettingsInstance.disableCustomCss(context.querySelector('#chkDisableCustomCss').checked); + userSettingsInstance.customCss(context.querySelector('#txtLocalCustomCss').value); + + 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(user => { + saveUser(context, user, userSettings, apiClient).then(() => { + loading.hide(); + if (enableSaveConfirmation) { + toast(globalize.translate('SettingsSaved')); + } + Events.trigger(instance, 'saved'); + }, () => { + loading.hide(); + }); + }); +} + +function onSubmit(e) { + const self = this; + const apiClient = ServerConnections.getApiClient(self.options.serverId); + const userId = self.options.userId; + const userSettings = self.options.userSettings; + + userSettings.setUserInfo(userId, apiClient).then(() => { + const 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) { + options.element.innerHTML = globalize.translateHtml(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); +} + +class DisplaySettings { + constructor(options) { + this.options = options; + embed(options, this); + } + + loadData(autoFocus) { + const self = this; + const context = self.options.element; + loading.show(); - apiClient.getUser(userId).then(user => { - saveUser(context, user, userSettings, apiClient).then(() => { - loading.hide(); - if (enableSaveConfirmation) { - toast(globalize.translate('SettingsSaved')); - } - Events.trigger(instance, 'saved'); - }, () => { - loading.hide(); - }); - }); - } - - function onSubmit(e) { - const self = this; - const apiClient = ServerConnections.getApiClient(self.options.serverId); const userId = self.options.userId; + const apiClient = ServerConnections.getApiClient(self.options.serverId); const userSettings = self.options.userSettings; - userSettings.setUserInfo(userId, apiClient).then(() => { - const 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) { - options.element.innerHTML = globalize.translateHtml(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); - } - - class DisplaySettings { - constructor(options) { - this.options = options; - embed(options, this); - } - - loadData(autoFocus) { - const self = this; - const context = self.options.element; - - loading.show(); - - const userId = self.options.userId; - const apiClient = ServerConnections.getApiClient(self.options.serverId); - const userSettings = self.options.userSettings; - - return apiClient.getUser(userId).then(user => { - return userSettings.setUserInfo(userId, apiClient).then(() => { - self.dataLoaded = true; - loadForm(context, user, userSettings); - if (autoFocus) { - focusManager.autoFocus(context); - } - }); + return apiClient.getUser(userId).then(user => { + return userSettings.setUserInfo(userId, apiClient).then(() => { + self.dataLoaded = true; + loadForm(context, user, userSettings); + if (autoFocus) { + focusManager.autoFocus(context); + } }); - } - - submit() { - onSubmit.call(this); - } - - destroy() { - this.options = null; - } + }); } -/* eslint-enable indent */ + submit() { + onSubmit.call(this); + } + + destroy() { + this.options = null; + } +} + export default DisplaySettings; diff --git a/src/components/favoriteitems.js b/src/components/favoriteitems.js index 6bba5c3bdc..ac0f3c0de0 100644 --- a/src/components/favoriteitems.js +++ b/src/components/favoriteitems.js @@ -9,236 +9,232 @@ import { getParameterByName } from '../utils/url.ts'; import '../styles/scrollstyles.scss'; import '../elements/emby-itemscontainer/emby-itemscontainer'; -/* eslint-disable indent */ +function enableScrollX() { + return !layoutManager.desktop; +} - function enableScrollX() { - return !layoutManager.desktop; +function getThumbShape() { + return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; +} + +function getPosterShape() { + return enableScrollX() ? 'overflowPortrait' : 'portrait'; +} + +function getSquareShape() { + return enableScrollX() ? 'overflowSquare' : 'square'; +} + +function getSections() { + return [{ + name: 'Movies', + types: 'Movie', + id: 'favoriteMovies', + shape: getPosterShape(), + showTitle: false, + overlayPlayButton: true + }, { + name: 'Shows', + types: 'Series', + id: 'favoriteShows', + shape: getPosterShape(), + showTitle: false, + overlayPlayButton: true + }, { + name: 'Episodes', + types: 'Episode', + id: 'favoriteEpisode', + shape: getThumbShape(), + preferThumb: false, + showTitle: true, + showParentTitle: true, + overlayPlayButton: true, + overlayText: false, + centerText: true + }, { + name: 'Videos', + types: 'Video,MusicVideo', + id: 'favoriteVideos', + shape: getThumbShape(), + preferThumb: true, + showTitle: true, + overlayPlayButton: true, + overlayText: false, + centerText: true + }, { + name: 'Artists', + types: 'MusicArtist', + id: 'favoriteArtists', + shape: getSquareShape(), + preferThumb: false, + showTitle: true, + overlayText: false, + showParentTitle: false, + centerText: true, + overlayPlayButton: true, + coverImage: true + }, { + name: 'Albums', + types: 'MusicAlbum', + id: 'favoriteAlbums', + shape: getSquareShape(), + preferThumb: false, + showTitle: true, + overlayText: false, + showParentTitle: true, + centerText: true, + overlayPlayButton: true, + coverImage: true + }, { + name: 'Songs', + types: 'Audio', + id: 'favoriteSongs', + shape: getSquareShape(), + preferThumb: false, + showTitle: true, + overlayText: false, + showParentTitle: true, + centerText: true, + overlayMoreButton: true, + action: 'instantmix', + coverImage: true + }]; +} + +function loadSection(elem, userId, topParentId, section, isSingleSection) { + const screenWidth = dom.getWindowSize().innerWidth; + const options = { + SortBy: 'SortName', + SortOrder: 'Ascending', + Filters: 'IsFavorite', + Recursive: true, + Fields: 'PrimaryImageAspectRatio,BasicSyncInfo', + CollapseBoxSetItems: false, + ExcludeLocationTypes: 'Virtual', + EnableTotalRecordCount: false + }; + + if (topParentId) { + options.ParentId = topParentId; } - function getThumbShape() { - return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; - } + if (!isSingleSection) { + options.Limit = 6; - function getPosterShape() { - return enableScrollX() ? 'overflowPortrait' : 'portrait'; - } - - function getSquareShape() { - return enableScrollX() ? 'overflowSquare' : 'square'; - } - - function getSections() { - return [{ - name: 'Movies', - types: 'Movie', - id: 'favoriteMovies', - shape: getPosterShape(), - showTitle: false, - overlayPlayButton: true - }, { - name: 'Shows', - types: 'Series', - id: 'favoriteShows', - shape: getPosterShape(), - showTitle: false, - overlayPlayButton: true - }, { - name: 'Episodes', - types: 'Episode', - id: 'favoriteEpisode', - shape: getThumbShape(), - preferThumb: false, - showTitle: true, - showParentTitle: true, - overlayPlayButton: true, - overlayText: false, - centerText: true - }, { - name: 'Videos', - types: 'Video,MusicVideo', - id: 'favoriteVideos', - shape: getThumbShape(), - preferThumb: true, - showTitle: true, - overlayPlayButton: true, - overlayText: false, - centerText: true - }, { - name: 'Artists', - types: 'MusicArtist', - id: 'favoriteArtists', - shape: getSquareShape(), - preferThumb: false, - showTitle: true, - overlayText: false, - showParentTitle: false, - centerText: true, - overlayPlayButton: true, - coverImage: true - }, { - name: 'Albums', - types: 'MusicAlbum', - id: 'favoriteAlbums', - shape: getSquareShape(), - preferThumb: false, - showTitle: true, - overlayText: false, - showParentTitle: true, - centerText: true, - overlayPlayButton: true, - coverImage: true - }, { - name: 'Songs', - types: 'Audio', - id: 'favoriteSongs', - shape: getSquareShape(), - preferThumb: false, - showTitle: true, - overlayText: false, - showParentTitle: true, - centerText: true, - overlayMoreButton: true, - action: 'instantmix', - coverImage: true - }]; - } - - function loadSection(elem, userId, topParentId, section, isSingleSection) { - const screenWidth = dom.getWindowSize().innerWidth; - const options = { - SortBy: 'SortName', - SortOrder: 'Ascending', - Filters: 'IsFavorite', - Recursive: true, - Fields: 'PrimaryImageAspectRatio,BasicSyncInfo', - CollapseBoxSetItems: false, - ExcludeLocationTypes: 'Virtual', - EnableTotalRecordCount: false - }; - - if (topParentId) { - options.ParentId = topParentId; + if (enableScrollX()) { + options.Limit = 20; + } else if (screenWidth >= 1920) { + options.Limit = 10; + } else if (screenWidth >= 1440) { + options.Limit = 8; } + } - if (!isSingleSection) { - options.Limit = 6; + let promise; + if (section.types === 'MusicArtist') { + promise = ApiClient.getArtists(userId, options); + } else { + options.IncludeItemTypes = section.types; + promise = ApiClient.getItems(userId, options); + } + + return promise.then(function (result) { + let html = ''; + + if (result.Items.length) { + html += '
'; + + if (!layoutManager.tv && options.Limit && result.Items.length >= options.Limit) { + html += ''; + html += '

'; + html += globalize.translate(section.name); + html += '

'; + html += ''; + html += '
'; + } else { + html += '

' + globalize.translate(section.name) + '

'; + } + + html += '
'; if (enableScrollX()) { - options.Limit = 20; - } else if (screenWidth >= 1920) { - options.Limit = 10; - } else if (screenWidth >= 1440) { - options.Limit = 8; - } - } - - let promise; - - if (section.types === 'MusicArtist') { - promise = ApiClient.getArtists(userId, options); - } else { - options.IncludeItemTypes = section.types; - promise = ApiClient.getItems(userId, options); - } - - return promise.then(function (result) { - let html = ''; - - if (result.Items.length) { - html += '
'; - - if (!layoutManager.tv && options.Limit && result.Items.length >= options.Limit) { - html += ''; - html += '

'; - html += globalize.translate(section.name); - html += '

'; - html += ''; - html += '
'; - } else { - html += '

' + globalize.translate(section.name) + '

'; + let scrollXClass = 'scrollX hiddenScrollX'; + if (layoutManager.tv) { + scrollXClass += ' smoothScrollX'; } - html += '
'; - if (enableScrollX()) { - let scrollXClass = 'scrollX hiddenScrollX'; - if (layoutManager.tv) { - scrollXClass += ' smoothScrollX'; - } - - html += '
'; - } else { - html += '
'; - } - - let cardLayout = appHost.preferVisualCards && section.autoCardLayout && section.showTitle; - cardLayout = false; - html += cardBuilder.getCardsHtml(result.Items, { - preferThumb: section.preferThumb, - shape: section.shape, - centerText: section.centerText && !cardLayout, - overlayText: section.overlayText !== false, - showTitle: section.showTitle, - showParentTitle: section.showParentTitle, - scalable: true, - coverImage: section.coverImage, - overlayPlayButton: section.overlayPlayButton, - overlayMoreButton: section.overlayMoreButton && !cardLayout, - action: section.action, - allowBottomPadding: !enableScrollX(), - cardLayout: cardLayout - }); - html += '
'; + html += '
'; + } else { + html += '
'; } - elem.innerHTML = html; - imageLoader.lazyChildren(elem); + let cardLayout = appHost.preferVisualCards && section.autoCardLayout && section.showTitle; + cardLayout = false; + html += cardBuilder.getCardsHtml(result.Items, { + preferThumb: section.preferThumb, + shape: section.shape, + centerText: section.centerText && !cardLayout, + overlayText: section.overlayText !== false, + showTitle: section.showTitle, + showParentTitle: section.showParentTitle, + scalable: true, + coverImage: section.coverImage, + overlayPlayButton: section.overlayPlayButton, + overlayMoreButton: section.overlayMoreButton && !cardLayout, + action: section.action, + allowBottomPadding: !enableScrollX(), + cardLayout: cardLayout + }); + html += '
'; + } + + elem.innerHTML = html; + imageLoader.lazyChildren(elem); + }); +} + +export function loadSections(page, userId, topParentId, types) { + loading.show(); + let sections = getSections(); + const sectionid = getParameterByName('sectionid'); + + if (sectionid) { + sections = sections.filter(function (s) { + return s.id === sectionid; }); } - export function loadSections(page, userId, topParentId, types) { - loading.show(); - let sections = getSections(); - const sectionid = getParameterByName('sectionid'); + if (types) { + sections = sections.filter(function (s) { + return types.indexOf(s.id) !== -1; + }); + } - if (sectionid) { - sections = sections.filter(function (s) { - return s.id === sectionid; - }); - } + let elem = page.querySelector('.favoriteSections'); - if (types) { - sections = sections.filter(function (s) { - return types.indexOf(s.id) !== -1; - }); - } - - let elem = page.querySelector('.favoriteSections'); - - if (!elem.innerHTML) { - let html = ''; - - for (let i = 0, length = sections.length; i < length; i++) { - html += '
'; - } - - elem.innerHTML = html; - } - - const promises = []; + if (!elem.innerHTML) { + let html = ''; for (let i = 0, length = sections.length; i < length; i++) { - const section = sections[i]; - elem = page.querySelector('.section' + section.id); - promises.push(loadSection(elem, userId, topParentId, section, sections.length === 1)); + html += '
'; } - Promise.all(promises).then(function () { - loading.hide(); - }); + elem.innerHTML = html; } + const promises = []; + + for (let i = 0, length = sections.length; i < length; i++) { + const section = sections[i]; + elem = page.querySelector('.section' + section.id); + promises.push(loadSection(elem, userId, topParentId, section, sections.length === 1)); + } + + Promise.all(promises).then(function () { + loading.hide(); + }); +} + export default { render: loadSections }; - -/* eslint-enable indent */ diff --git a/src/components/fetchhelper.js b/src/components/fetchhelper.js index efc490fc66..f63fa62ced 100644 --- a/src/components/fetchhelper.js +++ b/src/components/fetchhelper.js @@ -1,110 +1,108 @@ -/* eslint-disable indent */ - export function getFetchPromise(request) { - const headers = request.headers || {}; +export function getFetchPromise(request) { + const headers = request.headers || {}; - if (request.dataType === 'json') { - headers.accept = 'application/json'; - } - - const fetchRequest = { - headers: headers, - method: request.type, - credentials: 'same-origin' - }; - - let 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; - } - - let url = request.url; - - if (request.query) { - const paramString = paramsToString(request.query); - if (paramString) { - url += `?${paramString}`; - } - } - - if (!request.timeout) { - return fetch(url, fetchRequest); - } - - return fetchWithTimeout(url, fetchRequest, request.timeout); + if (request.dataType === 'json') { + headers.accept = 'application/json'; } - function fetchWithTimeout(url, options, timeoutMs) { - console.debug(`fetchWithTimeout: timeoutMs: ${timeoutMs}, url: ${url}`); + const fetchRequest = { + headers: headers, + method: request.type, + credentials: 'same-origin' + }; - return new Promise(function (resolve, reject) { - const timeout = setTimeout(reject, timeoutMs); + let contentType = request.contentType; - options = options || {}; - options.credentials = 'same-origin'; + if (request.data) { + if (typeof request.data === 'string') { + fetchRequest.body = request.data; + } else { + fetchRequest.body = paramsToString(request.data); - fetch(url, options).then(function (response) { - clearTimeout(timeout); + contentType = contentType || 'application/x-www-form-urlencoded; charset=UTF-8'; + } + } - console.debug(`fetchWithTimeout: succeeded connecting to url: ${url}`); + if (contentType) { + headers['Content-Type'] = contentType; + } - resolve(response); - }, function (error) { - clearTimeout(timeout); + let url = request.url; - console.debug(`fetchWithTimeout: timed out connecting to url: ${url}`); + if (request.query) { + const paramString = paramsToString(request.query); + if (paramString) { + url += `?${paramString}`; + } + } - reject(error); - }); + if (!request.timeout) { + return fetch(url, fetchRequest); + } + + return fetchWithTimeout(url, fetchRequest, request.timeout); +} + +function fetchWithTimeout(url, options, timeoutMs) { + console.debug(`fetchWithTimeout: timeoutMs: ${timeoutMs}, url: ${url}`); + + return new Promise(function (resolve, reject) { + const timeout = setTimeout(reject, timeoutMs); + + options = options || {}; + options.credentials = 'same-origin'; + + fetch(url, options).then(function (response) { + clearTimeout(timeout); + + console.debug(`fetchWithTimeout: succeeded connecting to url: ${url}`); + + resolve(response); + }, function (error) { + clearTimeout(timeout); + + console.debug(`fetchWithTimeout: timed out connecting to url: ${url}`); + + reject(error); }); - } + }); +} - /** +/** * @param params {Record} * @returns {string} Query string */ - function paramsToString(params) { - return Object.entries(params) - .filter(([, v]) => v !== null && v !== undefined && v !== '') - .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`) - .join('&'); +function paramsToString(params) { + return Object.entries(params) + .filter(([, v]) => v !== null && v !== undefined && v !== '') + .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`) + .join('&'); +} + +export function ajax(request) { + if (!request) { + throw new Error('Request cannot be null'); } - export function ajax(request) { - if (!request) { - throw new Error('Request cannot be null'); - } + request.headers = request.headers || {}; - request.headers = request.headers || {}; + console.debug(`requesting url: ${request.url}`); - console.debug(`requesting url: ${request.url}`); - - return getFetchPromise(request).then(function (response) { - console.debug(`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().startsWith('text/')) { - return response.text(); - } else { - return response; - } + return getFetchPromise(request).then(function (response) { + console.debug(`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().startsWith('text/')) { + return response.text(); } else { - return Promise.reject(response); + return response; } - }, function (err) { - console.error(`request failed to url: ${request.url}`); - throw err; - }); - } -/* eslint-enable indent */ + } else { + return Promise.reject(response); + } + }, function (err) { + console.error(`request failed to url: ${request.url}`); + throw err; + }); +} diff --git a/src/components/filterdialog/filterdialog.js b/src/components/filterdialog/filterdialog.js index 83d7ccbd2e..2301dcd404 100644 --- a/src/components/filterdialog/filterdialog.js +++ b/src/components/filterdialog/filterdialog.js @@ -8,417 +8,414 @@ import './style.scss'; import ServerConnections from '../ServerConnections'; import template from './filterdialog.template.html'; -/* eslint-disable indent */ - function renderOptions(context, selector, cssClass, items, isCheckedFn) { - const elem = context.querySelector(selector); - if (items.length) { - elem.classList.remove('hide'); - } else { - elem.classList.add('hide'); - } - let html = ''; - html += '
'; - html += items.map(function (filter) { - let itemHtml = ''; - const checkedHtml = isCheckedFn(filter) ? 'checked' : ''; - itemHtml += ''; - return itemHtml; - }).join(''); - html += '
'; - elem.querySelector('.filterOptions').innerHTML = html; +function renderOptions(context, selector, cssClass, items, isCheckedFn) { + const elem = context.querySelector(selector); + if (items.length) { + elem.classList.remove('hide'); + } else { + elem.classList.add('hide'); } + let html = ''; + html += '
'; + html += items.map(function (filter) { + let itemHtml = ''; + const checkedHtml = isCheckedFn(filter) ? 'checked' : ''; + itemHtml += ''; + return itemHtml; + }).join(''); + html += '
'; + elem.querySelector('.filterOptions').innerHTML = html; +} - function renderFilters(context, result, query) { - renderOptions(context, '.genreFilters', 'chkGenreFilter', result.Genres, function (i) { - const delimeter = '|'; - return (delimeter + (query.Genres || '') + delimeter).includes(delimeter + i + delimeter); - }); - renderOptions(context, '.officialRatingFilters', 'chkOfficialRatingFilter', result.OfficialRatings, function (i) { - const delimeter = '|'; - return (delimeter + (query.OfficialRatings || '') + delimeter).includes(delimeter + i + delimeter); - }); - renderOptions(context, '.tagFilters', 'chkTagFilter', result.Tags, function (i) { - const delimeter = '|'; - return (delimeter + (query.Tags || '') + delimeter).includes(delimeter + i + delimeter); - }); - renderOptions(context, '.yearFilters', 'chkYearFilter', result.Years, function (i) { - const delimeter = ','; - return (delimeter + (query.Years || '') + delimeter).includes(delimeter + i + delimeter); - }); - } +function renderFilters(context, result, query) { + renderOptions(context, '.genreFilters', 'chkGenreFilter', result.Genres, function (i) { + const delimeter = '|'; + return (delimeter + (query.Genres || '') + delimeter).includes(delimeter + i + delimeter); + }); + renderOptions(context, '.officialRatingFilters', 'chkOfficialRatingFilter', result.OfficialRatings, function (i) { + const delimeter = '|'; + return (delimeter + (query.OfficialRatings || '') + delimeter).includes(delimeter + i + delimeter); + }); + renderOptions(context, '.tagFilters', 'chkTagFilter', result.Tags, function (i) { + const delimeter = '|'; + return (delimeter + (query.Tags || '') + delimeter).includes(delimeter + i + delimeter); + }); + renderOptions(context, '.yearFilters', 'chkYearFilter', result.Years, function (i) { + const delimeter = ','; + return (delimeter + (query.Years || '') + delimeter).includes(delimeter + i + delimeter); + }); +} - function loadDynamicFilters(context, apiClient, userId, itemQuery) { - return apiClient.getJSON(apiClient.getUrl('Items/Filters', { - UserId: userId, - ParentId: itemQuery.ParentId, - IncludeItemTypes: itemQuery.IncludeItemTypes - })).then(function (result) { - renderFilters(context, result, itemQuery); - }); - } +function loadDynamicFilters(context, apiClient, userId, itemQuery) { + return apiClient.getJSON(apiClient.getUrl('Items/Filters', { + UserId: userId, + ParentId: itemQuery.ParentId, + IncludeItemTypes: itemQuery.IncludeItemTypes + })).then(function (result) { + renderFilters(context, result, itemQuery); + }); +} - /** +/** * @param context {HTMLDivElement} Dialog * @param options {any} Options */ - function updateFilterControls(context, options) { - const query = options.query; +function updateFilterControls(context, options) { + const query = options.query; - if (options.mode === 'livetvchannels') { - context.querySelector('.chkFavorite').checked = query.IsFavorite === true; + if (options.mode === 'livetvchannels') { + context.querySelector('.chkFavorite').checked = query.IsFavorite === true; + } else { + for (const elem of context.querySelectorAll('.chkStandardFilter')) { + const filters = `,${query.Filters || ''}`; + const filterName = elem.getAttribute('data-filter'); + elem.checked = filters.includes(`,${filterName}`); + } + } + + for (const elem of context.querySelectorAll('.chkVideoTypeFilter')) { + const filters = `,${query.VideoTypes || ''}`; + const filterName = elem.getAttribute('data-filter'); + elem.checked = filters.includes(`,${filterName}`); + } + context.querySelector('.chk3DFilter').checked = query.Is3D === true; + context.querySelector('.chkHDFilter').checked = query.IsHD === true; + context.querySelector('.chk4KFilter').checked = query.Is4K === true; + context.querySelector('.chkSDFilter').checked = query.IsHD === false; + context.querySelector('#chkSubtitle').checked = query.HasSubtitles === true; + context.querySelector('#chkTrailer').checked = query.HasTrailer === true; + context.querySelector('#chkThemeSong').checked = query.HasThemeSong === true; + context.querySelector('#chkThemeVideo').checked = query.HasThemeVideo === true; + context.querySelector('#chkSpecialFeature').checked = query.HasSpecialFeature === true; + context.querySelector('#chkSpecialEpisode').checked = query.ParentIndexNumber === 0; + context.querySelector('#chkMissingEpisode').checked = query.IsMissing === true; + context.querySelector('#chkFutureEpisode').checked = query.IsUnaired === true; + for (const elem of context.querySelectorAll('.chkStatus')) { + const filters = `,${query.SeriesStatus || ''}`; + const filterName = elem.getAttribute('data-filter'); + elem.checked = filters.includes(`,${filterName}`); + } +} + +/** + * @param instance {FilterDialog} An instance of FilterDialog + */ +function triggerChange(instance) { + Events.trigger(instance, 'filterchange'); +} + +function setVisibility(context, options) { + if (options.mode === 'livetvchannels' || options.mode === 'albums' || options.mode === 'artists' || options.mode === 'albumartists' || options.mode === 'songs') { + hideByClass(context, 'videoStandard'); + } + + if (enableDynamicFilters(options.mode)) { + context.querySelector('.genreFilters').classList.remove('hide'); + context.querySelector('.officialRatingFilters').classList.remove('hide'); + context.querySelector('.tagFilters').classList.remove('hide'); + context.querySelector('.yearFilters').classList.remove('hide'); + } + + if (options.mode === 'movies' || options.mode === 'episodes') { + context.querySelector('.videoTypeFilters').classList.remove('hide'); + } + + if (options.mode === 'movies' || options.mode === 'series' || options.mode === 'episodes') { + context.querySelector('.features').classList.remove('hide'); + } + + if (options.mode === 'series') { + context.querySelector('.seriesStatus').classList.remove('hide'); + } + + if (options.mode === 'episodes') { + showByClass(context, 'episodeFilter'); + } +} + +function showByClass(context, className) { + for (const elem of context.querySelectorAll(`.${className}`)) { + elem.classList.remove('hide'); + } +} + +function hideByClass(context, className) { + for (const elem of context.querySelectorAll(`.${className}`)) { + elem.classList.add('hide'); + } +} + +function enableDynamicFilters(mode) { + return mode === 'movies' || mode === 'series' || mode === 'albums' || mode === 'albumartists' || mode === 'artists' || mode === 'songs' || mode === 'episodes'; +} + +class FilterDialog { + constructor(options) { + /** + * @private + */ + this.options = options; + } + + /** + * @private + */ + onFavoriteChange(elem) { + const query = this.options.query; + query.StartIndex = 0; + query.IsFavorite = !!elem.checked || null; + triggerChange(this); + } + + /** + * @private + */ + onStandardFilterChange(elem) { + const query = this.options.query; + const filterName = elem.getAttribute('data-filter'); + let filters = query.Filters || ''; + filters = (`,${filters}`).replace(`,${filterName}`, '').substring(1); + + if (elem.checked) { + filters = filters ? `${filters},${filterName}` : filterName; + } + + query.StartIndex = 0; + query.Filters = filters; + triggerChange(this); + } + + /** + * @private + */ + onVideoTypeFilterChange(elem) { + const query = this.options.query; + const filterName = elem.getAttribute('data-filter'); + let filters = query.VideoTypes || ''; + filters = (`,${filters}`).replace(`,${filterName}`, '').substring(1); + + if (elem.checked) { + filters = filters ? `${filters},${filterName}` : filterName; + } + + query.StartIndex = 0; + query.VideoTypes = filters; + triggerChange(this); + } + + /** + * @private + */ + onStatusChange(elem) { + const query = this.options.query; + const filterName = elem.getAttribute('data-filter'); + let filters = query.SeriesStatus || ''; + filters = (`,${filters}`).replace(`,${filterName}`, '').substring(1); + + if (elem.checked) { + filters = filters ? `${filters},${filterName}` : filterName; + } + + query.SeriesStatus = filters; + query.StartIndex = 0; + triggerChange(this); + } + + /** + * @param context {HTMLDivElement} The dialog + */ + bindEvents(context) { + const query = this.options.query; + + if (this.options.mode === 'livetvchannels') { + for (const elem of context.querySelectorAll('.chkFavorite')) { + elem.addEventListener('change', () => this.onFavoriteChange(elem)); + } } else { for (const elem of context.querySelectorAll('.chkStandardFilter')) { - const filters = `,${query.Filters || ''}`; - const filterName = elem.getAttribute('data-filter'); - elem.checked = filters.includes(`,${filterName}`); + elem.addEventListener('change', () => this.onStandardFilterChange(elem)); } } for (const elem of context.querySelectorAll('.chkVideoTypeFilter')) { - const filters = `,${query.VideoTypes || ''}`; - const filterName = elem.getAttribute('data-filter'); - elem.checked = filters.includes(`,${filterName}`); + elem.addEventListener('change', () => this.onVideoTypeFilterChange(elem)); } - context.querySelector('.chk3DFilter').checked = query.Is3D === true; - context.querySelector('.chkHDFilter').checked = query.IsHD === true; - context.querySelector('.chk4KFilter').checked = query.Is4K === true; - context.querySelector('.chkSDFilter').checked = query.IsHD === false; - context.querySelector('#chkSubtitle').checked = query.HasSubtitles === true; - context.querySelector('#chkTrailer').checked = query.HasTrailer === true; - context.querySelector('#chkThemeSong').checked = query.HasThemeSong === true; - context.querySelector('#chkThemeVideo').checked = query.HasThemeVideo === true; - context.querySelector('#chkSpecialFeature').checked = query.HasSpecialFeature === true; - context.querySelector('#chkSpecialEpisode').checked = query.ParentIndexNumber === 0; - context.querySelector('#chkMissingEpisode').checked = query.IsMissing === true; - context.querySelector('#chkFutureEpisode').checked = query.IsUnaired === true; - for (const elem of context.querySelectorAll('.chkStatus')) { - const filters = `,${query.SeriesStatus || ''}`; - const filterName = elem.getAttribute('data-filter'); - elem.checked = filters.includes(`,${filterName}`); - } - } - - /** - * @param instance {FilterDialog} An instance of FilterDialog - */ - function triggerChange(instance) { - Events.trigger(instance, 'filterchange'); - } - - function setVisibility(context, options) { - if (options.mode === 'livetvchannels' || options.mode === 'albums' || options.mode === 'artists' || options.mode === 'albumartists' || options.mode === 'songs') { - hideByClass(context, 'videoStandard'); - } - - if (enableDynamicFilters(options.mode)) { - context.querySelector('.genreFilters').classList.remove('hide'); - context.querySelector('.officialRatingFilters').classList.remove('hide'); - context.querySelector('.tagFilters').classList.remove('hide'); - context.querySelector('.yearFilters').classList.remove('hide'); - } - - if (options.mode === 'movies' || options.mode === 'episodes') { - context.querySelector('.videoTypeFilters').classList.remove('hide'); - } - - if (options.mode === 'movies' || options.mode === 'series' || options.mode === 'episodes') { - context.querySelector('.features').classList.remove('hide'); - } - - if (options.mode === 'series') { - context.querySelector('.seriesStatus').classList.remove('hide'); - } - - if (options.mode === 'episodes') { - showByClass(context, 'episodeFilter'); - } - } - - function showByClass(context, className) { - for (const elem of context.querySelectorAll(`.${className}`)) { - elem.classList.remove('hide'); - } - } - - function hideByClass(context, className) { - for (const elem of context.querySelectorAll(`.${className}`)) { - elem.classList.add('hide'); - } - } - - function enableDynamicFilters(mode) { - return mode === 'movies' || mode === 'series' || mode === 'albums' || mode === 'albumartists' || mode === 'artists' || mode === 'songs' || mode === 'episodes'; - } - - class FilterDialog { - constructor(options) { - /** - * @private - */ - this.options = options; - } - - /** - * @private - */ - onFavoriteChange(elem) { - const query = this.options.query; + const chk3DFilter = context.querySelector('.chk3DFilter'); + chk3DFilter.addEventListener('change', () => { query.StartIndex = 0; - query.IsFavorite = !!elem.checked || null; + query.Is3D = chk3DFilter.checked ? true : null; triggerChange(this); - } - - /** - * @private - */ - onStandardFilterChange(elem) { - const query = this.options.query; - const filterName = elem.getAttribute('data-filter'); - let filters = query.Filters || ''; - filters = (`,${filters}`).replace(`,${filterName}`, '').substring(1); - - if (elem.checked) { - filters = filters ? `${filters},${filterName}` : filterName; - } - + }); + const chk4KFilter = context.querySelector('.chk4KFilter'); + chk4KFilter.addEventListener('change', () => { query.StartIndex = 0; - query.Filters = filters; + query.Is4K = chk4KFilter.checked ? true : null; triggerChange(this); - } - - /** - * @private - */ - onVideoTypeFilterChange(elem) { - const query = this.options.query; - const filterName = elem.getAttribute('data-filter'); - let filters = query.VideoTypes || ''; - filters = (`,${filters}`).replace(`,${filterName}`, '').substring(1); - - if (elem.checked) { - filters = filters ? `${filters},${filterName}` : filterName; - } - + }); + const chkHDFilter = context.querySelector('.chkHDFilter'); + const chkSDFilter = context.querySelector('.chkSDFilter'); + chkHDFilter.addEventListener('change', () => { query.StartIndex = 0; - query.VideoTypes = filters; - triggerChange(this); - } - - /** - * @private - */ - onStatusChange(elem) { - const query = this.options.query; - const filterName = elem.getAttribute('data-filter'); - let filters = query.SeriesStatus || ''; - filters = (`,${filters}`).replace(`,${filterName}`, '').substring(1); - - if (elem.checked) { - filters = filters ? `${filters},${filterName}` : filterName; - } - - query.SeriesStatus = filters; - query.StartIndex = 0; - triggerChange(this); - } - - /** - * @param context {HTMLDivElement} The dialog - */ - bindEvents(context) { - const query = this.options.query; - - if (this.options.mode === 'livetvchannels') { - for (const elem of context.querySelectorAll('.chkFavorite')) { - elem.addEventListener('change', () => this.onFavoriteChange(elem)); - } + if (chkHDFilter.checked) { + chkSDFilter.checked = false; + query.IsHD = true; } else { - for (const elem of context.querySelectorAll('.chkStandardFilter')) { - elem.addEventListener('change', () => this.onStandardFilterChange(elem)); - } + query.IsHD = null; } - - for (const elem of context.querySelectorAll('.chkVideoTypeFilter')) { - elem.addEventListener('change', () => this.onVideoTypeFilterChange(elem)); + triggerChange(this); + }); + chkSDFilter.addEventListener('change', () => { + query.StartIndex = 0; + if (chkSDFilter.checked) { + chkHDFilter.checked = false; + query.IsHD = false; + } else { + query.IsHD = null; } - const chk3DFilter = context.querySelector('.chk3DFilter'); - chk3DFilter.addEventListener('change', () => { - query.StartIndex = 0; - query.Is3D = chk3DFilter.checked ? true : null; - triggerChange(this); - }); - const chk4KFilter = context.querySelector('.chk4KFilter'); - chk4KFilter.addEventListener('change', () => { - query.StartIndex = 0; - query.Is4K = chk4KFilter.checked ? true : null; - triggerChange(this); - }); - const chkHDFilter = context.querySelector('.chkHDFilter'); - const chkSDFilter = context.querySelector('.chkSDFilter'); - chkHDFilter.addEventListener('change', () => { - query.StartIndex = 0; - if (chkHDFilter.checked) { - chkSDFilter.checked = false; - query.IsHD = true; - } else { - query.IsHD = null; - } - triggerChange(this); - }); - chkSDFilter.addEventListener('change', () => { - query.StartIndex = 0; - if (chkSDFilter.checked) { - chkHDFilter.checked = false; - query.IsHD = false; - } else { - query.IsHD = null; - } - triggerChange(this); - }); - for (const elem of context.querySelectorAll('.chkStatus')) { - elem.addEventListener('change', () => this.onStatusChange(elem)); - } - const chkTrailer = context.querySelector('#chkTrailer'); - chkTrailer.addEventListener('change', () => { - query.StartIndex = 0; - query.HasTrailer = chkTrailer.checked ? true : null; - triggerChange(this); - }); - const chkThemeSong = context.querySelector('#chkThemeSong'); - chkThemeSong.addEventListener('change', () => { - query.StartIndex = 0; - query.HasThemeSong = chkThemeSong.checked ? true : null; - triggerChange(this); - }); - const chkSpecialFeature = context.querySelector('#chkSpecialFeature'); - chkSpecialFeature.addEventListener('change', () => { - query.StartIndex = 0; - query.HasSpecialFeature = chkSpecialFeature.checked ? true : null; - triggerChange(this); - }); - const chkThemeVideo = context.querySelector('#chkThemeVideo'); - chkThemeVideo.addEventListener('change', () => { - query.StartIndex = 0; - query.HasThemeVideo = chkThemeVideo.checked ? true : null; - triggerChange(this); - }); - const chkMissingEpisode = context.querySelector('#chkMissingEpisode'); - chkMissingEpisode.addEventListener('change', () => { - query.StartIndex = 0; - query.IsMissing = !!chkMissingEpisode.checked; - triggerChange(this); - }); - const chkSpecialEpisode = context.querySelector('#chkSpecialEpisode'); - chkSpecialEpisode.addEventListener('change', () => { - query.StartIndex = 0; - query.ParentIndexNumber = chkSpecialEpisode.checked ? 0 : null; - triggerChange(this); - }); - const chkFutureEpisode = context.querySelector('#chkFutureEpisode'); - chkFutureEpisode.addEventListener('change', () => { - query.StartIndex = 0; - if (chkFutureEpisode.checked) { - query.IsUnaired = true; - query.IsVirtualUnaired = null; - } else { - query.IsUnaired = null; - query.IsVirtualUnaired = false; - } - triggerChange(this); - }); - const chkSubtitle = context.querySelector('#chkSubtitle'); - chkSubtitle.addEventListener('change', () => { - query.StartIndex = 0; - query.HasSubtitles = chkSubtitle.checked ? true : null; - triggerChange(this); - }); - context.addEventListener('change', (e) => { - const chkGenreFilter = dom.parentWithClass(e.target, 'chkGenreFilter'); - if (chkGenreFilter) { - const filterName = chkGenreFilter.getAttribute('data-filter'); - let filters = query.Genres || ''; - const delimiter = '|'; - filters = (delimiter + filters).replace(delimiter + filterName, '').substring(1); - if (chkGenreFilter.checked) { - filters = filters ? (filters + delimiter + filterName) : filterName; - } - query.StartIndex = 0; - query.Genres = filters; - triggerChange(this); - return; - } - const chkTagFilter = dom.parentWithClass(e.target, 'chkTagFilter'); - if (chkTagFilter) { - const filterName = chkTagFilter.getAttribute('data-filter'); - let filters = query.Tags || ''; - const delimiter = '|'; - filters = (delimiter + filters).replace(delimiter + filterName, '').substring(1); - if (chkTagFilter.checked) { - filters = filters ? (filters + delimiter + filterName) : filterName; - } - query.StartIndex = 0; - query.Tags = filters; - triggerChange(this); - return; - } - const chkYearFilter = dom.parentWithClass(e.target, 'chkYearFilter'); - if (chkYearFilter) { - const filterName = chkYearFilter.getAttribute('data-filter'); - let filters = query.Years || ''; - const delimiter = ','; - filters = (delimiter + filters).replace(delimiter + filterName, '').substring(1); - if (chkYearFilter.checked) { - filters = filters ? (filters + delimiter + filterName) : filterName; - } - query.StartIndex = 0; - query.Years = filters; - triggerChange(this); - return; - } - const chkOfficialRatingFilter = dom.parentWithClass(e.target, 'chkOfficialRatingFilter'); - if (chkOfficialRatingFilter) { - const filterName = chkOfficialRatingFilter.getAttribute('data-filter'); - let filters = query.OfficialRatings || ''; - const delimiter = '|'; - filters = (delimiter + filters).replace(delimiter + filterName, '').substring(1); - if (chkOfficialRatingFilter.checked) { - filters = filters ? (filters + delimiter + filterName) : filterName; - } - query.StartIndex = 0; - query.OfficialRatings = filters; - triggerChange(this); - } - }); + triggerChange(this); + }); + for (const elem of context.querySelectorAll('.chkStatus')) { + elem.addEventListener('change', () => this.onStatusChange(elem)); } - - show() { - return new Promise((resolve) => { - const dlg = dialogHelper.createDialog({ - removeOnClose: true, - modal: false - }); - dlg.classList.add('ui-body-a'); - dlg.classList.add('background-theme-a'); - dlg.classList.add('formDialog'); - dlg.classList.add('filterDialog'); - dlg.innerHTML = globalize.translateHtml(template); - setVisibility(dlg, this.options); - dialogHelper.open(dlg); - dlg.addEventListener('close', resolve); - updateFilterControls(dlg, this.options); - this.bindEvents(dlg); - if (enableDynamicFilters(this.options.mode)) { - dlg.classList.add('dynamicFilterDialog'); - const apiClient = ServerConnections.getApiClient(this.options.serverId); - loadDynamicFilters(dlg, apiClient, apiClient.getCurrentUserId(), this.options.query); + const chkTrailer = context.querySelector('#chkTrailer'); + chkTrailer.addEventListener('change', () => { + query.StartIndex = 0; + query.HasTrailer = chkTrailer.checked ? true : null; + triggerChange(this); + }); + const chkThemeSong = context.querySelector('#chkThemeSong'); + chkThemeSong.addEventListener('change', () => { + query.StartIndex = 0; + query.HasThemeSong = chkThemeSong.checked ? true : null; + triggerChange(this); + }); + const chkSpecialFeature = context.querySelector('#chkSpecialFeature'); + chkSpecialFeature.addEventListener('change', () => { + query.StartIndex = 0; + query.HasSpecialFeature = chkSpecialFeature.checked ? true : null; + triggerChange(this); + }); + const chkThemeVideo = context.querySelector('#chkThemeVideo'); + chkThemeVideo.addEventListener('change', () => { + query.StartIndex = 0; + query.HasThemeVideo = chkThemeVideo.checked ? true : null; + triggerChange(this); + }); + const chkMissingEpisode = context.querySelector('#chkMissingEpisode'); + chkMissingEpisode.addEventListener('change', () => { + query.StartIndex = 0; + query.IsMissing = !!chkMissingEpisode.checked; + triggerChange(this); + }); + const chkSpecialEpisode = context.querySelector('#chkSpecialEpisode'); + chkSpecialEpisode.addEventListener('change', () => { + query.StartIndex = 0; + query.ParentIndexNumber = chkSpecialEpisode.checked ? 0 : null; + triggerChange(this); + }); + const chkFutureEpisode = context.querySelector('#chkFutureEpisode'); + chkFutureEpisode.addEventListener('change', () => { + query.StartIndex = 0; + if (chkFutureEpisode.checked) { + query.IsUnaired = true; + query.IsVirtualUnaired = null; + } else { + query.IsUnaired = null; + query.IsVirtualUnaired = false; + } + triggerChange(this); + }); + const chkSubtitle = context.querySelector('#chkSubtitle'); + chkSubtitle.addEventListener('change', () => { + query.StartIndex = 0; + query.HasSubtitles = chkSubtitle.checked ? true : null; + triggerChange(this); + }); + context.addEventListener('change', (e) => { + const chkGenreFilter = dom.parentWithClass(e.target, 'chkGenreFilter'); + if (chkGenreFilter) { + const filterName = chkGenreFilter.getAttribute('data-filter'); + let filters = query.Genres || ''; + const delimiter = '|'; + filters = (delimiter + filters).replace(delimiter + filterName, '').substring(1); + if (chkGenreFilter.checked) { + filters = filters ? (filters + delimiter + filterName) : filterName; } - }); - } + query.StartIndex = 0; + query.Genres = filters; + triggerChange(this); + return; + } + const chkTagFilter = dom.parentWithClass(e.target, 'chkTagFilter'); + if (chkTagFilter) { + const filterName = chkTagFilter.getAttribute('data-filter'); + let filters = query.Tags || ''; + const delimiter = '|'; + filters = (delimiter + filters).replace(delimiter + filterName, '').substring(1); + if (chkTagFilter.checked) { + filters = filters ? (filters + delimiter + filterName) : filterName; + } + query.StartIndex = 0; + query.Tags = filters; + triggerChange(this); + return; + } + const chkYearFilter = dom.parentWithClass(e.target, 'chkYearFilter'); + if (chkYearFilter) { + const filterName = chkYearFilter.getAttribute('data-filter'); + let filters = query.Years || ''; + const delimiter = ','; + filters = (delimiter + filters).replace(delimiter + filterName, '').substring(1); + if (chkYearFilter.checked) { + filters = filters ? (filters + delimiter + filterName) : filterName; + } + query.StartIndex = 0; + query.Years = filters; + triggerChange(this); + return; + } + const chkOfficialRatingFilter = dom.parentWithClass(e.target, 'chkOfficialRatingFilter'); + if (chkOfficialRatingFilter) { + const filterName = chkOfficialRatingFilter.getAttribute('data-filter'); + let filters = query.OfficialRatings || ''; + const delimiter = '|'; + filters = (delimiter + filters).replace(delimiter + filterName, '').substring(1); + if (chkOfficialRatingFilter.checked) { + filters = filters ? (filters + delimiter + filterName) : filterName; + } + query.StartIndex = 0; + query.OfficialRatings = filters; + triggerChange(this); + } + }); } -/* eslint-enable indent */ + show() { + return new Promise((resolve) => { + const dlg = dialogHelper.createDialog({ + removeOnClose: true, + modal: false + }); + dlg.classList.add('ui-body-a'); + dlg.classList.add('background-theme-a'); + dlg.classList.add('formDialog'); + dlg.classList.add('filterDialog'); + dlg.innerHTML = globalize.translateHtml(template); + setVisibility(dlg, this.options); + dialogHelper.open(dlg); + dlg.addEventListener('close', resolve); + updateFilterControls(dlg, this.options); + this.bindEvents(dlg); + if (enableDynamicFilters(this.options.mode)) { + dlg.classList.add('dynamicFilterDialog'); + const apiClient = ServerConnections.getApiClient(this.options.serverId); + loadDynamicFilters(dlg, apiClient, apiClient.getCurrentUserId(), this.options.query); + } + }); + } +} export default FilterDialog; diff --git a/src/components/focusManager.js b/src/components/focusManager.js index 1eacc49e77..48ccd6b92c 100644 --- a/src/components/focusManager.js +++ b/src/components/focusManager.js @@ -1,470 +1,466 @@ -/* eslint-disable indent */ - import dom from '../scripts/dom'; import scrollManager from './scrollManager'; - const scopes = []; - function pushScope(elem) { - scopes.push(elem); - } +const scopes = []; +function pushScope(elem) { + scopes.push(elem); +} - function popScope() { - if (scopes.length) { - scopes.length -= 1; +function popScope() { + if (scopes.length) { + scopes.length -= 1; + } +} + +function autoFocus(view, defaultToFirst, findAutoFocusElement) { + let element; + if (findAutoFocusElement !== false) { + element = view.querySelector('*[autofocus]'); + if (element) { + focus(element); + return element; } } - function autoFocus(view, defaultToFirst, findAutoFocusElement) { - let element; - if (findAutoFocusElement !== false) { - element = view.querySelector('*[autofocus]'); - if (element) { - focus(element); - return element; - } - } + if (defaultToFirst !== false) { + element = getFocusableElements(view, 1, 'noautofocus')[0]; - if (defaultToFirst !== false) { - element = getFocusableElements(view, 1, 'noautofocus')[0]; - - if (element) { - focus(element); - return element; - } - } - - return null; - } - - function focus(element) { - try { - element.focus({ - preventScroll: scrollManager.isEnabled() - }); - } catch (err) { - console.error('Error in focusManager.autoFocus: ' + err); + if (element) { + focus(element); + return element; } } - const focusableTagNames = ['INPUT', 'TEXTAREA', 'SELECT', 'BUTTON', 'A']; - const focusableContainerTagNames = ['BODY', 'DIALOG']; - const focusableQuery = focusableTagNames.map(function (t) { - if (t === 'INPUT') { - t += ':not([type="range"]):not([type="file"])'; - } - return t + ':not([tabindex="-1"]):not(:disabled)'; - }).join(',') + ',.focusable'; + return null; +} - function isFocusable(elem) { - return focusableTagNames.indexOf(elem.tagName) !== -1 +function focus(element) { + try { + element.focus({ + preventScroll: scrollManager.isEnabled() + }); + } catch (err) { + console.error('Error in focusManager.autoFocus: ' + err); + } +} + +const focusableTagNames = ['INPUT', 'TEXTAREA', 'SELECT', 'BUTTON', 'A']; +const focusableContainerTagNames = ['BODY', 'DIALOG']; +const 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) { + return focusableTagNames.indexOf(elem.tagName) !== -1 || (elem.classList?.contains('focusable')); +} + +function normalizeFocusable(elem, originalElement) { + if (elem) { + const tagName = elem.tagName; + if (!tagName || tagName === 'HTML' || tagName === 'BODY') { + elem = originalElement; + } } - function normalizeFocusable(elem, originalElement) { - if (elem) { - const tagName = elem.tagName; - if (!tagName || tagName === 'HTML' || tagName === 'BODY') { - elem = originalElement; - } + return elem; +} + +function focusableParent(elem) { + const originalElement = elem; + + while (!isFocusable(elem)) { + const parent = elem.parentNode; + + if (!parent) { + return normalizeFocusable(elem, originalElement); } - return elem; + elem = parent; } - function focusableParent(elem) { - const originalElement = elem; + return normalizeFocusable(elem, originalElement); +} - while (!isFocusable(elem)) { - const 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 - return elem.offsetParent !== null; - } - - // 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') { - const 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) { - const elems = (parent || getDefaultScope()).querySelectorAll(focusableQuery); - const focusableElements = []; - - for (let i = 0, length = elems.length; i < length; i++) { - const 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; - } - - const 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; - } - } +// 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 + return elem.offsetParent !== null; +} +// Determines if a focusable element can be focused at a given point in time +function isCurrentlyFocusable(elem) { + if (elem.disabled) { return false; } - function getFocusContainer(elem, direction) { - while (!isFocusContainer(elem, direction)) { - elem = elem.parentNode; + if (elem.getAttribute('tabindex') === '-1') { + return false; + } - if (!elem) { - return getDefaultScope(); + if (elem.tagName === 'INPUT') { + const 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) { + const elems = (parent || getDefaultScope()).querySelectorAll(focusableQuery); + const focusableElements = []; + + for (let i = 0, length = elems.length; i < length; i++) { + const elem = elems[i]; + + if (excludeClass && elem.classList.contains(excludeClass)) { + continue; + } + + if (isCurrentlyFocusableInternal(elem)) { + focusableElements.push(elem); + + if (limit && focusableElements.length >= limit) { + break; } } - - return elem; } - function getOffset(elem) { - let box; + return focusableElements; +} - // 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 - const 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 isFocusContainer(elem, direction) { + if (focusableContainerTagNames.indexOf(elem.tagName) !== -1) { + return true; } - function nav(activeElement, direction, container, focusableElements) { - activeElement = activeElement || document.activeElement; + const 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) { + let 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 + const 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; + } + + const focusableContainer = dom.parentWithClass(activeElement, 'focusable'); + + const rect = getOffset(activeElement); + + // Get elements and work out x/y points + const point1x = parseFloat(rect.left) || 0; + const point1y = parseFloat(rect.top) || 0; + const point2x = parseFloat(point1x + rect.width - 1) || point1x; + const point2y = parseFloat(point1y + rect.height - 1) || point1y; + + const sourceMidX = rect.left + (rect.width / 2); + const sourceMidY = rect.top + (rect.height / 2); + + const focusable = focusableElements || container.querySelectorAll(focusableQuery); + + const maxDistance = Infinity; + let minDistance = maxDistance; + let nearestElement; + + for (let i = 0, length = focusable.length; i < length; i++) { + const curr = focusable[i]; + + if (curr === activeElement) { + continue; + } + // Don't refocus into the same container + if (curr === focusableContainer) { + continue; + } + + const 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; + } + + const x = elementRect.left; + const y = elementRect.top; + const x2 = x + elementRect.width - 1; + const y2 = y + elementRect.height - 1; + + const intersectX = intersects(point1x, point2x, x, x2); + const intersectY = intersects(point1y, point2y, y, y2); + + const midX = elementRect.left + (elementRect.width / 2); + const midY = elementRect.top + (elementRect.height / 2); + + let distX; + let 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; + } + + const 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) { - activeElement = focusableParent(activeElement); - } - - container = container || (activeElement ? getFocusContainer(activeElement, direction) : getDefaultScope()); - - if (!activeElement) { - autoFocus(container, true, false); - return; - } - - const focusableContainer = dom.parentWithClass(activeElement, 'focusable'); - - const rect = getOffset(activeElement); - - // Get elements and work out x/y points - const point1x = parseFloat(rect.left) || 0; - const point1y = parseFloat(rect.top) || 0; - const point2x = parseFloat(point1x + rect.width - 1) || point1x; - const point2y = parseFloat(point1y + rect.height - 1) || point1y; - - const sourceMidX = rect.left + (rect.width / 2); - const sourceMidY = rect.top + (rect.height / 2); - - const focusable = focusableElements || container.querySelectorAll(focusableQuery); - - const maxDistance = Infinity; - let minDistance = maxDistance; - let nearestElement; - - for (let i = 0, length = focusable.length; i < length; i++) { - const curr = focusable[i]; - - if (curr === activeElement) { - continue; - } - // Don't refocus into the same container - if (curr === focusableContainer) { - continue; - } - - const 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; - } - - const x = elementRect.left; - const y = elementRect.top; - const x2 = x + elementRect.width - 1; - const y2 = y + elementRect.height - 1; - - const intersectX = intersects(point1x, point2x, x, x2); - const intersectY = intersects(point1y, point2y, y, y2); - - const midX = elementRect.left + (elementRect.width / 2); - const midY = elementRect.top + (elementRect.height / 2); - - let distX; - let 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; - } - - const 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) { - const nearestElementFocusableParent = dom.parentWithClass(nearestElement, 'focusable'); - if (nearestElementFocusableParent + const nearestElementFocusableParent = dom.parentWithClass(nearestElement, 'focusable'); + if (nearestElementFocusableParent && nearestElementFocusableParent !== nearestElement && focusableContainer !== nearestElementFocusableParent - ) { - nearestElement = nearestElementFocusableParent; - } - } - focus(nearestElement); - } - } - - function intersectsInternal(a1, a2, b1, b2) { - return (b1 >= a1 && b1 <= a2) || (b2 >= a1 && b2 <= a2); - } - - function intersects(a1, a2, b1, b2) { - return intersectsInternal(a1, a2, b1, b2) || intersectsInternal(b1, b2, a1, a2); - } - - function sendText(text) { - const elem = document.activeElement; - - elem.value = text; - } - - function focusFirst(container, focusableSelector) { - const elems = container.querySelectorAll(focusableSelector); - - for (let i = 0, length = elems.length; i < length; i++) { - const elem = elems[i]; - - if (isCurrentlyFocusableInternal(elem)) { - focus(elem); - break; + ) { + nearestElement = nearestElementFocusableParent; } } + focus(nearestElement); } +} - function focusLast(container, focusableSelector) { - const elems = [].slice.call(container.querySelectorAll(focusableSelector), 0).reverse(); +function intersectsInternal(a1, a2, b1, b2) { + return (b1 >= a1 && b1 <= a2) || (b2 >= a1 && b2 <= a2); +} - for (let i = 0, length = elems.length; i < length; i++) { - const elem = elems[i]; +function intersects(a1, a2, b1, b2) { + return intersectsInternal(a1, a2, b1, b2) || intersectsInternal(b1, b2, a1, a2); +} - if (isCurrentlyFocusableInternal(elem)) { - focus(elem); - break; - } +function sendText(text) { + const elem = document.activeElement; + + elem.value = text; +} + +function focusFirst(container, focusableSelector) { + const elems = container.querySelectorAll(focusableSelector); + + for (let i = 0, length = elems.length; i < length; i++) { + const elem = elems[i]; + + if (isCurrentlyFocusableInternal(elem)) { + focus(elem); + break; + } + } +} + +function focusLast(container, focusableSelector) { + const elems = [].slice.call(container.querySelectorAll(focusableSelector), 0).reverse(); + + for (let i = 0, length = elems.length; i < length; i++) { + const elem = elems[i]; + + if (isCurrentlyFocusableInternal(elem)) { + focus(elem); + break; + } + } +} + +function moveFocus(sourceElement, container, focusableSelector, offset) { + const elems = container.querySelectorAll(focusableSelector); + const list = []; + let i; + let length; + let elem; + + for (i = 0, length = elems.length; i < length; i++) { + elem = elems[i]; + + if (isCurrentlyFocusableInternal(elem)) { + list.push(elem); } } - function moveFocus(sourceElement, container, focusableSelector, offset) { - const elems = container.querySelectorAll(focusableSelector); - const list = []; - let i; - let length; - let elem; + let currentIndex = -1; - for (i = 0, length = elems.length; i < length; i++) { - elem = elems[i]; + for (i = 0, length = list.length; i < length; i++) { + elem = list[i]; - if (isCurrentlyFocusableInternal(elem)) { - list.push(elem); - } - } - - let 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; - } - - let newIndex = currentIndex + offset; - newIndex = Math.max(0, newIndex); - newIndex = Math.min(newIndex, list.length - 1); - - const newElem = list[newIndex]; - if (newElem) { - focus(newElem); + if (sourceElement === elem || elem.contains(sourceElement)) { + currentIndex = i; + break; } } -/* eslint-enable indent */ + if (currentIndex === -1) { + return; + } + + let newIndex = currentIndex + offset; + newIndex = Math.max(0, newIndex); + newIndex = Math.min(newIndex, list.length - 1); + + const newElem = list[newIndex]; + if (newElem) { + focus(newElem); + } +} export default { autoFocus: autoFocus, diff --git a/src/components/groupedcards.js b/src/components/groupedcards.js index 295c3170cc..a2f7b757e0 100644 --- a/src/components/groupedcards.js +++ b/src/components/groupedcards.js @@ -1,47 +1,43 @@ -/* eslint-disable indent */ - import dom from '../scripts/dom'; import { appRouter } from './appRouter'; import Dashboard from '../utils/dashboard'; import ServerConnections from './ServerConnections'; - function onGroupedCardClick(e, card) { - const itemId = card.getAttribute('data-id'); - const serverId = card.getAttribute('data-serverid'); - const apiClient = ServerConnections.getApiClient(serverId); - const userId = apiClient.getCurrentUserId(); - const playedIndicator = card.querySelector('.playedIndicator'); - const playedIndicatorHtml = playedIndicator ? playedIndicator.innerHTML : null; - const options = { - Limit: parseInt(playedIndicatorHtml || '10', 10), - Fields: 'PrimaryImageAspectRatio,DateCreated', - ParentId: itemId, - GroupItems: false - }; - const actionableParent = dom.parentWithTag(e.target, ['A', 'BUTTON', 'INPUT']); +function onGroupedCardClick(e, card) { + const itemId = card.getAttribute('data-id'); + const serverId = card.getAttribute('data-serverid'); + const apiClient = ServerConnections.getApiClient(serverId); + const userId = apiClient.getCurrentUserId(); + const playedIndicator = card.querySelector('.playedIndicator'); + const playedIndicatorHtml = playedIndicator ? playedIndicator.innerHTML : null; + const options = { + Limit: parseInt(playedIndicatorHtml || '10', 10), + Fields: 'PrimaryImageAspectRatio,DateCreated', + ParentId: itemId, + GroupItems: false + }; + const actionableParent = dom.parentWithTag(e.target, ['A', 'BUTTON', 'INPUT']); - if (!actionableParent || actionableParent.classList.contains('cardContent')) { - apiClient.getJSON(apiClient.getUrl('Users/' + userId + '/Items/Latest', options)).then(function (items) { - if (items.length === 1) { - appRouter.showItem(items[0]); - return; - } + if (!actionableParent || actionableParent.classList.contains('cardContent')) { + apiClient.getJSON(apiClient.getUrl('Users/' + userId + '/Items/Latest', options)).then(function (items) { + if (items.length === 1) { + appRouter.showItem(items[0]); + return; + } - const url = 'details?id=' + itemId + '&serverId=' + serverId; - Dashboard.navigate(url); - }); - e.stopPropagation(); - e.preventDefault(); - return false; - } + const url = 'details?id=' + itemId + '&serverId=' + serverId; + Dashboard.navigate(url); + }); + e.stopPropagation(); + e.preventDefault(); + return false; } +} - export default function onItemsContainerClick(e) { - const groupedCard = dom.parentWithClass(e.target, 'groupedCard'); +export default function onItemsContainerClick(e) { + const groupedCard = dom.parentWithClass(e.target, 'groupedCard'); - if (groupedCard) { - onGroupedCardClick(e, groupedCard); - } + if (groupedCard) { + onGroupedCardClick(e, groupedCard); } - -/* eslint-enable indent */ +} diff --git a/src/components/homeScreenSettings/homeScreenSettings.js b/src/components/homeScreenSettings/homeScreenSettings.js index a28a18438c..64bc69a810 100644 --- a/src/components/homeScreenSettings/homeScreenSettings.js +++ b/src/components/homeScreenSettings/homeScreenSettings.js @@ -14,498 +14,494 @@ import ServerConnections from '../ServerConnections'; import toast from '../toast/toast'; import template from './homeScreenSettings.template.html'; -/* eslint-disable indent */ +const numConfigurableSections = 7; - const numConfigurableSections = 7; +function renderViews(page, user, result) { + let folderHtml = ''; - function renderViews(page, user, result) { - let folderHtml = ''; + folderHtml += '
'; + folderHtml += result.map(i => { + let currentHtml = ''; - folderHtml += '
'; - folderHtml += result.map(i => { - let currentHtml = ''; + const id = `chkGroupFolder${i.Id}`; - const id = `chkGroupFolder${i.Id}`; + const isChecked = user.Configuration.GroupedFolders.includes(i.Id); - const isChecked = user.Configuration.GroupedFolders.includes(i.Id); + const checkedHtml = isChecked ? ' checked="checked"' : ''; - const checkedHtml = isChecked ? ' checked="checked"' : ''; + currentHtml += ''; - currentHtml += ''; + return currentHtml; + }).join(''); - return currentHtml; - }).join(''); + folderHtml += '
'; - folderHtml += '
'; + page.querySelector('.folderGroupList').innerHTML = folderHtml; +} - page.querySelector('.folderGroupList').innerHTML = folderHtml; +function getLandingScreenOptions(type) { + const 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('Trailers'), + value: 'trailers' + }); + list.push({ + name: globalize.translate('Favorites'), + value: 'favorites' + }); + list.push({ + name: globalize.translate('Collections'), + value: 'collections' + }); + list.push({ + name: globalize.translate('Genres'), + value: 'genres' + }); + } 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('TabUpcoming'), + value: 'upcoming' + }); + list.push({ + name: globalize.translate('Genres'), + value: 'genres' + }); + list.push({ + name: globalize.translate('TabNetworks'), + value: 'networks' + }); + list.push({ + name: globalize.translate('Episodes'), + value: 'episodes' + }); + } else if (type === 'music') { + list.push({ + name: globalize.translate('Albums'), + value: 'albums', + isDefault: true + }); + list.push({ + name: globalize.translate('Suggestions'), + value: 'suggestions' + }); + 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('Songs'), + value: 'songs' + }); + list.push({ + name: globalize.translate('Genres'), + value: 'genres' + }); + } else if (type === 'livetv') { + list.push({ + name: globalize.translate('Programs'), + value: 'programs', + isDefault: true + }); + list.push({ + name: globalize.translate('Guide'), + value: 'guide' + }); + list.push({ + name: globalize.translate('Channels'), + value: 'channels' + }); + list.push({ + name: globalize.translate('Recordings'), + value: 'recordings' + }); + list.push({ + name: globalize.translate('Schedule'), + value: 'schedule' + }); + list.push({ + name: globalize.translate('Series'), + value: 'series' + }); } - function getLandingScreenOptions(type) { - const list = []; + return 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('Trailers'), - value: 'trailers' - }); - list.push({ - name: globalize.translate('Favorites'), - value: 'favorites' - }); - list.push({ - name: globalize.translate('Collections'), - value: 'collections' - }); - list.push({ - name: globalize.translate('Genres'), - value: 'genres' - }); - } 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('TabUpcoming'), - value: 'upcoming' - }); - list.push({ - name: globalize.translate('Genres'), - value: 'genres' - }); - list.push({ - name: globalize.translate('TabNetworks'), - value: 'networks' - }); - list.push({ - name: globalize.translate('Episodes'), - value: 'episodes' - }); - } else if (type === 'music') { - list.push({ - name: globalize.translate('Albums'), - value: 'albums', - isDefault: true - }); - list.push({ - name: globalize.translate('Suggestions'), - value: 'suggestions' - }); - 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('Songs'), - value: 'songs' - }); - list.push({ - name: globalize.translate('Genres'), - value: 'genres' - }); - } else if (type === 'livetv') { - list.push({ - name: globalize.translate('Programs'), - value: 'programs', - isDefault: true - }); - list.push({ - name: globalize.translate('Guide'), - value: 'guide' - }); - list.push({ - name: globalize.translate('Channels'), - value: 'channels' - }); - list.push({ - name: globalize.translate('Recordings'), - value: 'recordings' - }); - list.push({ - name: globalize.translate('Schedule'), - value: 'schedule' - }); - list.push({ - name: globalize.translate('Series'), - value: 'series' - }); +function getLandingScreenOptionsHtml(type, userValue) { + return getLandingScreenOptions(type).map(o => { + const selected = userValue === o.value || (o.isDefault && !userValue); + const selectedHtml = selected ? ' selected' : ''; + const optionValue = o.isDefault ? '' : o.value; + + return ``; + }).join(''); +} + +function renderViewOrder(context, user, result) { + let html = ''; + + html += result.Items.map((view) => { + let currentHtml = ''; + + currentHtml += `
`; + + currentHtml += ''; + + currentHtml += '
'; + + currentHtml += '
'; + currentHtml += escapeHtml(view.Name); + currentHtml += '
'; + + currentHtml += '
'; + + currentHtml += ``; + currentHtml += ``; + + currentHtml += '
'; + + return currentHtml; + }).join(''); + + context.querySelector('.viewOrderList').innerHTML = html; +} + +function updateHomeSectionValues(context, userSettings) { + for (let i = 1; i <= 7; i++) { + const select = context.querySelector(`#selectHomeSection${i}`); + const defaultValue = homeSections.getDefaultSection(i - 1); + + const option = select.querySelector(`option[value=${defaultValue}]`) || select.querySelector('option[value=""]'); + + const userValue = userSettings.get(`homesection${i - 1}`); + + option.value = ''; + + if (userValue === defaultValue || !userValue) { + select.value = ''; + } else { + select.value = userValue; } - - return list; } - function getLandingScreenOptionsHtml(type, userValue) { - return getLandingScreenOptions(type).map(o => { - const selected = userValue === o.value || (o.isDefault && !userValue); - const selectedHtml = selected ? ' selected' : ''; - const optionValue = o.isDefault ? '' : o.value; + context.querySelector('.selectTVHomeScreen').value = userSettings.get('tvhome') || ''; +} - return ``; - }).join(''); +function getPerLibrarySettingsHtml(item, user, userSettings) { + let html = ''; + + let isChecked; + + if (item.Type === 'Channel' || item.CollectionType === 'boxsets' || item.CollectionType === 'playlists') { + isChecked = !(user.Configuration.MyMediaExcludes || []).includes(item.Id); + html += '
'; + html += ''; + html += '
'; } - function renderViewOrder(context, user, result) { - let html = ''; - - html += result.Items.map((view) => { - let currentHtml = ''; - - currentHtml += `
`; - - currentHtml += ''; - - currentHtml += '
'; - - currentHtml += '
'; - currentHtml += escapeHtml(view.Name); - currentHtml += '
'; - - currentHtml += '
'; - - currentHtml += ``; - currentHtml += ``; - - currentHtml += '
'; - - return currentHtml; - }).join(''); - - context.querySelector('.viewOrderList').innerHTML = html; + const excludeFromLatest = ['playlists', 'livetv', 'boxsets', 'channels']; + if (!excludeFromLatest.includes(item.CollectionType || '')) { + isChecked = !user.Configuration.LatestItemsExcludes.includes(item.Id); + html += ''; } - function updateHomeSectionValues(context, userSettings) { - for (let i = 1; i <= 7; i++) { - const select = context.querySelector(`#selectHomeSection${i}`); - const defaultValue = homeSections.getDefaultSection(i - 1); + if (html) { + html = `
${html}
`; + } - const option = select.querySelector(`option[value=${defaultValue}]`) || select.querySelector('option[value=""]'); + if (item.CollectionType === 'movies' || item.CollectionType === 'tvshows' || item.CollectionType === 'music' || item.CollectionType === 'livetv') { + const idForLanding = item.CollectionType === 'livetv' ? item.CollectionType : item.Id; + html += '
'; + html += `'; + html += '
'; + } + + if (html) { + let prefix = ''; + prefix += '
'; + + prefix += '

'; + prefix += escapeHtml(item.Name); + prefix += '

'; + + html = prefix + html; + html += '
'; + } + + return html; +} + +function renderPerLibrarySettings(context, user, userViews, userSettings) { + const elem = context.querySelector('.perLibrarySettings'); + let html = ''; + + for (let i = 0, length = userViews.length; i < length; i++) { + html += getPerLibrarySettingsHtml(userViews[i], user, userSettings); + } + + elem.innerHTML = html; +} + +function loadForm(context, user, userSettings, apiClient) { + context.querySelector('.chkHidePlayedFromLatest').checked = user.Configuration.HidePlayedInLatest || false; + + updateHomeSectionValues(context, userSettings); + + const promise1 = apiClient.getUserViews({ IncludeHidden: true }, user.Id); + const promise2 = apiClient.getJSON(apiClient.getUrl(`Users/${user.Id}/GroupingOptions`)); + + Promise.all([promise1, promise2]).then(responses => { + renderViewOrder(context, user, responses[0]); + + renderPerLibrarySettings(context, user, responses[0].Items, userSettings); + + renderViews(context, user, responses[1]); + + loading.hide(); + }); +} + +function onSectionOrderListClick(e) { + const target = dom.parentWithClass(e.target, 'btnViewItemMove'); + + if (target) { + const viewItem = dom.parentWithClass(target, 'viewItem'); + + if (viewItem) { + if (target.classList.contains('btnViewItemDown')) { + const next = viewItem.nextSibling; + + if (next) { + viewItem.parentNode.removeChild(viewItem); + next.parentNode.insertBefore(viewItem, next.nextSibling); + focusManager.focus(e.target); + } } else { - select.value = userValue; + const prev = viewItem.previousSibling; + + if (prev) { + viewItem.parentNode.removeChild(viewItem); + prev.parentNode.insertBefore(viewItem, prev); + focusManager.focus(e.target); + } } } + } +} - context.querySelector('.selectTVHomeScreen').value = userSettings.get('tvhome') || ''; +function getCheckboxItems(selector, context, isChecked) { + const inputs = context.querySelectorAll(selector); + const list = []; + + for (let i = 0, length = inputs.length; i < length; i++) { + if (inputs[i].checked === isChecked) { + list.push(inputs[i]); + } } - function getPerLibrarySettingsHtml(item, user, userSettings) { - let html = ''; + return list; +} - let isChecked; +function saveUser(context, user, userSettingsInstance, apiClient) { + user.Configuration.HidePlayedInLatest = context.querySelector('.chkHidePlayedFromLatest').checked; - if (item.Type === 'Channel' || item.CollectionType === 'boxsets' || item.CollectionType === 'playlists') { - isChecked = !(user.Configuration.MyMediaExcludes || []).includes(item.Id); - html += '
'; - html += ''; - html += '
'; - } + user.Configuration.LatestItemsExcludes = getCheckboxItems('.chkIncludeInLatest', context, false).map(i => { + return i.getAttribute('data-folderid'); + }); - const excludeFromLatest = ['playlists', 'livetv', 'boxsets', 'channels']; - if (!excludeFromLatest.includes(item.CollectionType || '')) { - isChecked = !user.Configuration.LatestItemsExcludes.includes(item.Id); - html += ''; - } + user.Configuration.MyMediaExcludes = getCheckboxItems('.chkIncludeInMyMedia', context, false).map(i => { + return i.getAttribute('data-folderid'); + }); - if (html) { - html = `
${html}
`; - } + user.Configuration.GroupedFolders = getCheckboxItems('.chkGroupFolder', context, true).map(i => { + return i.getAttribute('data-folderid'); + }); - if (item.CollectionType === 'movies' || item.CollectionType === 'tvshows' || item.CollectionType === 'music' || item.CollectionType === 'livetv') { - const idForLanding = item.CollectionType === 'livetv' ? item.CollectionType : item.Id; - html += '
'; - html += `'; - html += '
'; - } - - if (html) { - let prefix = ''; - prefix += '
'; - - prefix += '

'; - prefix += escapeHtml(item.Name); - prefix += '

'; - - html = prefix + html; - html += '
'; - } - - return html; + const viewItems = context.querySelectorAll('.viewItem'); + const orderedViews = []; + let i; + let length; + for (i = 0, length = viewItems.length; i < length; i++) { + orderedViews.push(viewItems[i].getAttribute('data-viewid')); } - function renderPerLibrarySettings(context, user, userViews, userSettings) { - const elem = context.querySelector('.perLibrarySettings'); - let html = ''; + user.Configuration.OrderedViews = orderedViews; - for (let i = 0, length = userViews.length; i < length; i++) { - html += getPerLibrarySettingsHtml(userViews[i], user, userSettings); - } + userSettingsInstance.set('tvhome', context.querySelector('.selectTVHomeScreen').value); - elem.innerHTML = html; + 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); + + const selectLandings = context.querySelectorAll('.selectLanding'); + for (i = 0, length = selectLandings.length; i < length; i++) { + const selectLanding = selectLandings[i]; + userSettingsInstance.set(`landing-${selectLanding.getAttribute('data-folderid')}`, selectLanding.value); } - function loadForm(context, user, userSettings, apiClient) { - context.querySelector('.chkHidePlayedFromLatest').checked = user.Configuration.HidePlayedInLatest || false; + return apiClient.updateUserConfiguration(user.Id, user.Configuration); +} - updateHomeSectionValues(context, userSettings); +function save(instance, context, userId, userSettings, apiClient, enableSaveConfirmation) { + loading.show(); - const promise1 = apiClient.getUserViews({ IncludeHidden: true }, user.Id); - const promise2 = apiClient.getJSON(apiClient.getUrl(`Users/${user.Id}/GroupingOptions`)); - - Promise.all([promise1, promise2]).then(responses => { - renderViewOrder(context, user, responses[0]); - - renderPerLibrarySettings(context, user, responses[0].Items, userSettings); - - renderViews(context, user, responses[1]); + apiClient.getUser(userId).then(user => { + saveUser(context, user, userSettings, apiClient).then(() => { + loading.hide(); + if (enableSaveConfirmation) { + toast(globalize.translate('SettingsSaved')); + } + Events.trigger(instance, 'saved'); + }, () => { loading.hide(); }); + }); +} + +function onSubmit(e) { + const self = this; + const apiClient = ServerConnections.getApiClient(self.options.serverId); + const userId = self.options.userId; + const userSettings = self.options.userSettings; + + userSettings.setUserInfo(userId, apiClient).then(() => { + const 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) { + const chkIncludeInMyMedia = dom.parentWithClass(e.target, 'chkIncludeInMyMedia'); + if (!chkIncludeInMyMedia) { + return; } - function onSectionOrderListClick(e) { - const target = dom.parentWithClass(e.target, 'btnViewItemMove'); - - if (target) { - const viewItem = dom.parentWithClass(target, 'viewItem'); - - if (viewItem) { - if (target.classList.contains('btnViewItemDown')) { - const next = viewItem.nextSibling; - - if (next) { - viewItem.parentNode.removeChild(viewItem); - next.parentNode.insertBefore(viewItem, next.nextSibling); - focusManager.focus(e.target); - } - } else { - const prev = viewItem.previousSibling; - - if (prev) { - viewItem.parentNode.removeChild(viewItem); - prev.parentNode.insertBefore(viewItem, prev); - focusManager.focus(e.target); - } - } - } + const section = dom.parentWithClass(chkIncludeInMyMedia, 'verticalSection'); + const fldIncludeInLatest = section.querySelector('.fldIncludeInLatest'); + if (fldIncludeInLatest) { + if (chkIncludeInMyMedia.checked) { + fldIncludeInLatest.classList.remove('hide'); + } else { + fldIncludeInLatest.classList.add('hide'); } } +} - function getCheckboxItems(selector, context, isChecked) { - const inputs = context.querySelectorAll(selector); - const list = []; - - for (let i = 0, length = inputs.length; i < length; i++) { - if (inputs[i].checked === isChecked) { - list.push(inputs[i]); - } - } - - return list; +function embed(options, self) { + let workingTemplate = template; + for (let i = 1; i <= numConfigurableSections; i++) { + workingTemplate = workingTemplate.replace(`{section${i}label}`, globalize.translate('LabelHomeScreenSectionValue', i)); } - function saveUser(context, user, userSettingsInstance, apiClient) { - user.Configuration.HidePlayedInLatest = context.querySelector('.chkHidePlayedFromLatest').checked; + options.element.innerHTML = globalize.translateHtml(workingTemplate, 'core'); - user.Configuration.LatestItemsExcludes = getCheckboxItems('.chkIncludeInLatest', context, false).map(i => { - return i.getAttribute('data-folderid'); - }); + options.element.querySelector('.viewOrderList').addEventListener('click', onSectionOrderListClick); + options.element.querySelector('form').addEventListener('submit', onSubmit.bind(self)); + options.element.addEventListener('change', onChange); - user.Configuration.MyMediaExcludes = getCheckboxItems('.chkIncludeInMyMedia', context, false).map(i => { - return i.getAttribute('data-folderid'); - }); - - user.Configuration.GroupedFolders = getCheckboxItems('.chkGroupFolder', context, true).map(i => { - return i.getAttribute('data-folderid'); - }); - - const viewItems = context.querySelectorAll('.viewItem'); - const orderedViews = []; - let i; - let 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); - - const selectLandings = context.querySelectorAll('.selectLanding'); - for (i = 0, length = selectLandings.length; i < length; i++) { - const selectLanding = selectLandings[i]; - userSettingsInstance.set(`landing-${selectLanding.getAttribute('data-folderid')}`, selectLanding.value); - } - - return apiClient.updateUserConfiguration(user.Id, user.Configuration); + if (options.enableSaveButton) { + options.element.querySelector('.btnSave').classList.remove('hide'); } - function save(instance, context, userId, userSettings, apiClient, enableSaveConfirmation) { + if (layoutManager.tv) { + options.element.querySelector('.selectTVHomeScreenContainer').classList.remove('hide'); + } else { + options.element.querySelector('.selectTVHomeScreenContainer').classList.add('hide'); + } + + self.loadData(options.autoFocus); +} + +class HomeScreenSettings { + constructor(options) { + this.options = options; + embed(options, this); + } + + loadData(autoFocus) { + const self = this; + const context = self.options.element; + loading.show(); - apiClient.getUser(userId).then(user => { - saveUser(context, user, userSettings, apiClient).then(() => { - loading.hide(); - if (enableSaveConfirmation) { - toast(globalize.translate('SettingsSaved')); - } - - Events.trigger(instance, 'saved'); - }, () => { - loading.hide(); - }); - }); - } - - function onSubmit(e) { - const self = this; - const apiClient = ServerConnections.getApiClient(self.options.serverId); const userId = self.options.userId; + const apiClient = ServerConnections.getApiClient(self.options.serverId); const userSettings = self.options.userSettings; - userSettings.setUserInfo(userId, apiClient).then(() => { - const enableSaveConfirmation = self.options.enableSaveConfirmation; - save(self, self.options.element, userId, userSettings, apiClient, enableSaveConfirmation); - }); + apiClient.getUser(userId).then(user => { + userSettings.setUserInfo(userId, apiClient).then(() => { + self.dataLoaded = true; - // Disable default form submission - if (e) { - e.preventDefault(); - } - return false; - } + loadForm(context, user, userSettings, apiClient); - function onChange(e) { - const chkIncludeInMyMedia = dom.parentWithClass(e.target, 'chkIncludeInMyMedia'); - if (!chkIncludeInMyMedia) { - return; - } - - const section = dom.parentWithClass(chkIncludeInMyMedia, 'verticalSection'); - const fldIncludeInLatest = section.querySelector('.fldIncludeInLatest'); - if (fldIncludeInLatest) { - if (chkIncludeInMyMedia.checked) { - fldIncludeInLatest.classList.remove('hide'); - } else { - fldIncludeInLatest.classList.add('hide'); - } - } - } - - function embed(options, self) { - let workingTemplate = template; - for (let i = 1; i <= numConfigurableSections; i++) { - workingTemplate = workingTemplate.replace(`{section${i}label}`, globalize.translate('LabelHomeScreenSectionValue', i)); - } - - options.element.innerHTML = globalize.translateHtml(workingTemplate, '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); - } - - class HomeScreenSettings { - constructor(options) { - this.options = options; - embed(options, this); - } - - loadData(autoFocus) { - const self = this; - const context = self.options.element; - - loading.show(); - - const userId = self.options.userId; - const apiClient = ServerConnections.getApiClient(self.options.serverId); - const userSettings = self.options.userSettings; - - apiClient.getUser(userId).then(user => { - userSettings.setUserInfo(userId, apiClient).then(() => { - self.dataLoaded = true; - - loadForm(context, user, userSettings, apiClient); - - if (autoFocus) { - focusManager.autoFocus(context); - } - }); + if (autoFocus) { + focusManager.autoFocus(context); + } }); - } - - submit() { - onSubmit.call(this); - } - - destroy() { - this.options = null; - } + }); } -/* eslint-enable indent */ + submit() { + onSubmit.call(this); + } + + destroy() { + this.options = null; + } +} export default HomeScreenSettings; diff --git a/src/components/homesections/homesections.js b/src/components/homesections/homesections.js index 86e154f7b6..5cf83ced94 100644 --- a/src/components/homesections/homesections.js +++ b/src/components/homesections/homesections.js @@ -13,733 +13,731 @@ import './homesections.scss'; import Dashboard from '../../utils/dashboard'; import ServerConnections from '../ServerConnections'; -/* eslint-disable indent */ +export function getDefaultSection(index) { + switch (index) { + case 0: + return 'smalllibrarytiles'; + case 1: + return 'resume'; + case 2: + return 'resumeaudio'; + case 3: + return 'resumebook'; + case 4: + return 'livetv'; + case 5: + return 'nextup'; + case 6: + return 'latestmedia'; + case 7: + return 'none'; + default: + return ''; + } +} - export function getDefaultSection(index) { - switch (index) { - case 0: - return 'smalllibrarytiles'; - case 1: - return 'resume'; - case 2: - return 'resumeaudio'; - case 3: - return 'resumebook'; - case 4: - return 'livetv'; - case 5: - return 'nextup'; - case 6: - return 'latestmedia'; - case 7: - return 'none'; - default: - return ''; +function getAllSectionsToShow(userSettings, sectionCount) { + const sections = []; + for (let i = 0, length = sectionCount; i < length; i++) { + let section = userSettings.get('homesection' + i) || getDefaultSection(i); + if (section === 'folders') { + section = getDefaultSection(0); } + + sections.push(section); } - function getAllSectionsToShow(userSettings, sectionCount) { - const sections = []; - for (let i = 0, length = sectionCount; i < length; i++) { - let section = userSettings.get('homesection' + i) || getDefaultSection(i); - if (section === 'folders') { - section = getDefaultSection(0); + return sections; +} + +export function loadSections(elem, apiClient, user, userSettings) { + return getUserViews(apiClient, user.Id).then(function (userViews) { + let html = ''; + + if (userViews.length) { + const sectionCount = 7; + for (let i = 0; i < sectionCount; i++) { + html += '
'; } - sections.push(section); - } + elem.innerHTML = html; + elem.classList.add('homeSectionsContainer'); - return sections; - } + const promises = []; + const sections = getAllSectionsToShow(userSettings, sectionCount); + for (let i = 0; i < sections.length; i++) { + promises.push(loadSection(elem, apiClient, user, userSettings, userViews, sections, i)); + } - export function loadSections(elem, apiClient, user, userSettings) { - return getUserViews(apiClient, user.Id).then(function (userViews) { - let html = ''; - - if (userViews.length) { - const sectionCount = 7; - for (let i = 0; i < sectionCount; i++) { - html += '
'; - } - - elem.innerHTML = html; - elem.classList.add('homeSectionsContainer'); - - const promises = []; - const sections = getAllSectionsToShow(userSettings, sectionCount); - for (let i = 0; i < sections.length; i++) { - promises.push(loadSection(elem, apiClient, user, userSettings, userViews, sections, i)); - } - - return Promise.all(promises).then(function () { - return resume(elem, { - refresh: true - }); + return Promise.all(promises).then(function () { + return resume(elem, { + refresh: true }); - } else { - let noLibDescription; - if (user['Policy'] && user['Policy']['IsAdministrator']) { - noLibDescription = globalize.translate('NoCreatedLibraries', '
', ''); - } else { - noLibDescription = globalize.translate('AskAdminToCreateLibrary'); - } - - html += '
'; - html += '

' + globalize.translate('MessageNothingHere') + '

'; - html += '

' + noLibDescription + '

'; - html += '
'; - elem.innerHTML = html; - - const createNowLink = elem.querySelector('#button-createLibrary'); - if (createNowLink) { - createNowLink.addEventListener('click', function () { - Dashboard.navigate('library.html'); - }); - } - } - }); - } - - export function destroySections(elem) { - const elems = elem.querySelectorAll('.itemsContainer'); - for (let i = 0; i < elems.length; i++) { - elems[i].fetchData = null; - elems[i].parentContainer = null; - elems[i].getItemsHtml = null; - } - - elem.innerHTML = ''; - } - - export function pause(elem) { - const elems = elem.querySelectorAll('.itemsContainer'); - for (let i = 0; i < elems.length; i++) { - elems[i].pause(); - } - } - - export function resume(elem, options) { - const elems = elem.querySelectorAll('.itemsContainer'); - const promises = []; - - for (let i = 0, length = elems.length; i < length; i++) { - promises.push(elems[i].resume(options)); - } - - return Promise.all(promises); - } - - function loadSection(page, apiClient, user, userSettings, userViews, allSections, index) { - const section = allSections[index]; - const elem = page.querySelector('.section' + index); - - if (section === 'latestmedia') { - loadRecentlyAdded(elem, apiClient, user, userViews); - } else if (section === 'librarytiles' || section === 'smalllibrarytiles' || section === 'smalllibrarytiles-automobile' || section === 'librarytiles-automobile') { - loadLibraryTiles(elem, apiClient, user, userSettings, 'smallBackdrop', userViews); - } else if (section === 'librarybuttons') { - loadlibraryButtons(elem, apiClient, user, userSettings, userViews); - } else if (section === 'resume') { - return loadResume(elem, apiClient, 'HeaderContinueWatching', 'Video', userSettings); - } else if (section === 'resumeaudio') { - return loadResume(elem, apiClient, 'HeaderContinueListening', 'Audio', userSettings); - } else if (section === 'activerecordings') { - loadLatestLiveTvRecordings(elem, true, apiClient); - } else if (section === 'nextup') { - loadNextUp(elem, apiClient, userSettings); - } else if (section === 'onnow' || section === 'livetv') { - return loadOnNow(elem, apiClient, user); - } else if (section === 'resumebook') { - return loadResume(elem, apiClient, 'HeaderContinueReading', 'Book', userSettings); + }); } else { - elem.innerHTML = ''; - return Promise.resolve(); + let noLibDescription; + if (user['Policy'] && user['Policy']['IsAdministrator']) { + noLibDescription = globalize.translate('NoCreatedLibraries', '
', ''); + } else { + noLibDescription = globalize.translate('AskAdminToCreateLibrary'); + } + + html += '
'; + html += '

' + globalize.translate('MessageNothingHere') + '

'; + html += '

' + noLibDescription + '

'; + html += '
'; + elem.innerHTML = html; + + const createNowLink = elem.querySelector('#button-createLibrary'); + if (createNowLink) { + createNowLink.addEventListener('click', function () { + Dashboard.navigate('library.html'); + }); + } } + }); +} + +export function destroySections(elem) { + const elems = elem.querySelectorAll('.itemsContainer'); + for (let i = 0; i < elems.length; i++) { + elems[i].fetchData = null; + elems[i].parentContainer = null; + elems[i].getItemsHtml = null; + } + + elem.innerHTML = ''; +} + +export function pause(elem) { + const elems = elem.querySelectorAll('.itemsContainer'); + for (let i = 0; i < elems.length; i++) { + elems[i].pause(); + } +} + +export function resume(elem, options) { + const elems = elem.querySelectorAll('.itemsContainer'); + const promises = []; + + for (let i = 0, length = elems.length; i < length; i++) { + promises.push(elems[i].resume(options)); + } + + return Promise.all(promises); +} + +function loadSection(page, apiClient, user, userSettings, userViews, allSections, index) { + const section = allSections[index]; + const elem = page.querySelector('.section' + index); + + if (section === 'latestmedia') { + loadRecentlyAdded(elem, apiClient, user, userViews); + } else if (section === 'librarytiles' || section === 'smalllibrarytiles' || section === 'smalllibrarytiles-automobile' || section === 'librarytiles-automobile') { + loadLibraryTiles(elem, apiClient, user, userSettings, 'smallBackdrop', userViews); + } else if (section === 'librarybuttons') { + loadlibraryButtons(elem, apiClient, user, userSettings, userViews); + } else if (section === 'resume') { + return loadResume(elem, apiClient, 'HeaderContinueWatching', 'Video', userSettings); + } else if (section === 'resumeaudio') { + return loadResume(elem, apiClient, 'HeaderContinueListening', 'Audio', userSettings); + } else if (section === 'activerecordings') { + loadLatestLiveTvRecordings(elem, true, apiClient); + } else if (section === 'nextup') { + loadNextUp(elem, apiClient, userSettings); + } else if (section === 'onnow' || section === 'livetv') { + return loadOnNow(elem, apiClient, user); + } else if (section === 'resumebook') { + return loadResume(elem, apiClient, 'HeaderContinueReading', 'Book', userSettings); + } else { + elem.innerHTML = ''; return Promise.resolve(); } + return Promise.resolve(); +} - function getUserViews(apiClient, userId) { - return apiClient.getUserViews({}, userId || apiClient.getCurrentUserId()).then(function (result) { - return result.Items; - }); +function getUserViews(apiClient, userId) { + return apiClient.getUserViews({}, userId || apiClient.getCurrentUserId()).then(function (result) { + return result.Items; + }); +} + +function enableScrollX() { + return true; +} + +function getSquareShape() { + return enableScrollX() ? 'overflowSquare' : 'square'; +} + +function getThumbShape() { + return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; +} + +function getPortraitShape() { + return enableScrollX() ? 'overflowPortrait' : 'portrait'; +} + +function getLibraryButtonsHtml(items) { + let html = ''; + + html += '
'; + html += '

' + globalize.translate('HeaderMyMedia') + '

'; + + html += '
'; + + // library card background images + for (let i = 0, length = items.length; i < length; i++) { + const item = items[i]; + const icon = imageHelper.getLibraryIcon(item.CollectionType); + html += '' + escapeHtml(item.Name) + ''; } - function enableScrollX() { - return true; - } + html += '
'; + html += '
'; - function getSquareShape() { - return enableScrollX() ? 'overflowSquare' : 'square'; - } + return html; +} - function getThumbShape() { - return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; - } +function loadlibraryButtons(elem, apiClient, user, userSettings, userViews) { + elem.classList.remove('verticalSection'); + const html = getLibraryButtonsHtml(userViews); - function getPortraitShape() { - return enableScrollX() ? 'overflowPortrait' : 'portrait'; - } + elem.innerHTML = html; + imageLoader.lazyChildren(elem); +} - function getLibraryButtonsHtml(items) { - let html = ''; +function getFetchLatestItemsFn(serverId, parentId, collectionType) { + return function () { + const apiClient = ServerConnections.getApiClient(serverId); + let limit = 16; - html += '
'; - html += '

' + globalize.translate('HeaderMyMedia') + '

'; - - html += '
'; - - // library card background images - for (let i = 0, length = items.length; i < length; i++) { - const item = items[i]; - const icon = imageHelper.getLibraryIcon(item.CollectionType); - html += '' + escapeHtml(item.Name) + ''; - } - - html += '
'; - html += '
'; - - return html; - } - - function loadlibraryButtons(elem, apiClient, user, userSettings, userViews) { - elem.classList.remove('verticalSection'); - const html = getLibraryButtonsHtml(userViews); - - elem.innerHTML = html; - imageLoader.lazyChildren(elem); - } - - function getFetchLatestItemsFn(serverId, parentId, collectionType) { - return function () { - const apiClient = ServerConnections.getApiClient(serverId); - let limit = 16; - - if (enableScrollX()) { - if (collectionType === 'music') { - limit = 30; - } - } else { - if (collectionType === 'tvshows') { - limit = 5; - } else if (collectionType === 'music') { - limit = 9; - } else { - limit = 8; - } + if (enableScrollX()) { + if (collectionType === 'music') { + limit = 30; } - - const options = { - Limit: limit, - Fields: 'PrimaryImageAspectRatio,BasicSyncInfo,Path', - ImageTypeLimit: 1, - EnableImageTypes: 'Primary,Backdrop,Thumb', - ParentId: parentId - }; - - return apiClient.getLatestItems(options); - }; - } - - function getLatestItemsHtmlFn(itemType, viewType) { - return function (items) { - const cardLayout = false; - let shape; - if (itemType === 'Channel' || viewType === 'movies' || viewType === 'books' || viewType === 'tvshows') { - shape = getPortraitShape(); - } else if (viewType === 'music' || viewType === 'homevideos') { - shape = getSquareShape(); - } else { - shape = getThumbShape(); - } - - return cardBuilder.getCardsHtml({ - items: items, - shape: shape, - preferThumb: viewType !== 'movies' && viewType !== 'tvshows' && itemType !== 'Channel' && viewType !== 'music' ? 'auto' : null, - showUnplayedIndicator: false, - showChildCountIndicator: true, - context: 'home', - overlayText: false, - centerText: !cardLayout, - overlayPlayButton: viewType !== 'photos', - allowBottomPadding: !enableScrollX() && !cardLayout, - cardLayout: cardLayout, - showTitle: viewType !== 'photos', - showYear: viewType === 'movies' || viewType === 'tvshows' || !viewType, - showParentTitle: viewType === 'music' || viewType === 'tvshows' || !viewType || (cardLayout && (viewType === 'tvshows')), - lines: 2 - }); - }; - } - - function renderLatestSection(elem, apiClient, user, parent) { - let html = ''; - - html += '
'; - if (!layoutManager.tv) { - html += ''; - html += '

'; - html += globalize.translate('LatestFromLibrary', escapeHtml(parent.Name)); - html += '

'; - html += ''; - html += '
'; } else { - html += '

' + globalize.translate('LatestFromLibrary', escapeHtml(parent.Name)) + '

'; + if (collectionType === 'tvshows') { + limit = 5; + } else if (collectionType === 'music') { + limit = 9; + } else { + limit = 8; + } } - html += '
'; + const options = { + Limit: limit, + Fields: 'PrimaryImageAspectRatio,BasicSyncInfo,Path', + ImageTypeLimit: 1, + EnableImageTypes: 'Primary,Backdrop,Thumb', + ParentId: parentId + }; + + return apiClient.getLatestItems(options); + }; +} + +function getLatestItemsHtmlFn(itemType, viewType) { + return function (items) { + const cardLayout = false; + let shape; + if (itemType === 'Channel' || viewType === 'movies' || viewType === 'books' || viewType === 'tvshows') { + shape = getPortraitShape(); + } else if (viewType === 'music' || viewType === 'homevideos') { + shape = getSquareShape(); + } else { + shape = getThumbShape(); + } + + return cardBuilder.getCardsHtml({ + items: items, + shape: shape, + preferThumb: viewType !== 'movies' && viewType !== 'tvshows' && itemType !== 'Channel' && viewType !== 'music' ? 'auto' : null, + showUnplayedIndicator: false, + showChildCountIndicator: true, + context: 'home', + overlayText: false, + centerText: !cardLayout, + overlayPlayButton: viewType !== 'photos', + allowBottomPadding: !enableScrollX() && !cardLayout, + cardLayout: cardLayout, + showTitle: viewType !== 'photos', + showYear: viewType === 'movies' || viewType === 'tvshows' || !viewType, + showParentTitle: viewType === 'music' || viewType === 'tvshows' || !viewType || (cardLayout && (viewType === 'tvshows')), + lines: 2 + }); + }; +} + +function renderLatestSection(elem, apiClient, user, parent) { + let html = ''; + + html += '
'; + if (!layoutManager.tv) { + html += ''; + html += '

'; + html += globalize.translate('LatestFromLibrary', escapeHtml(parent.Name)); + html += '

'; + html += ''; + html += '
'; + } else { + html += '

' + globalize.translate('LatestFromLibrary', escapeHtml(parent.Name)) + '

'; + } + html += '
'; + + if (enableScrollX()) { + html += '
'; + html += '
'; + } else { + html += '
'; + } + + if (enableScrollX()) { + html += '
'; + } + html += '
'; + + elem.innerHTML = html; + + const itemsContainer = elem.querySelector('.itemsContainer'); + itemsContainer.fetchData = getFetchLatestItemsFn(apiClient.serverId(), parent.Id, parent.CollectionType); + itemsContainer.getItemsHtml = getLatestItemsHtmlFn(parent.Type, parent.CollectionType); + itemsContainer.parentContainer = elem; +} + +function loadRecentlyAdded(elem, apiClient, user, userViews) { + elem.classList.remove('verticalSection'); + const excludeViewTypes = ['playlists', 'livetv', 'boxsets', 'channels']; + + for (let i = 0, length = userViews.length; i < length; i++) { + const item = userViews[i]; + if (user.Configuration.LatestItemsExcludes.indexOf(item.Id) !== -1) { + continue; + } + + if (excludeViewTypes.indexOf(item.CollectionType || []) !== -1) { + continue; + } + + const frag = document.createElement('div'); + frag.classList.add('verticalSection'); + frag.classList.add('hide'); + elem.appendChild(frag); + + renderLatestSection(frag, apiClient, user, item); + } +} + +export function loadLibraryTiles(elem, apiClient, user, userSettings, shape, userViews) { + let html = ''; + if (userViews.length) { + html += '

' + globalize.translate('HeaderMyMedia') + '

'; if (enableScrollX()) { html += '
'; html += '
'; } else { - html += '
'; + html += '
'; } - if (enableScrollX()) { - html += '
'; - } - html += '
'; - - elem.innerHTML = html; - - const itemsContainer = elem.querySelector('.itemsContainer'); - itemsContainer.fetchData = getFetchLatestItemsFn(apiClient.serverId(), parent.Id, parent.CollectionType); - itemsContainer.getItemsHtml = getLatestItemsHtmlFn(parent.Type, parent.CollectionType); - itemsContainer.parentContainer = elem; - } - - function loadRecentlyAdded(elem, apiClient, user, userViews) { - elem.classList.remove('verticalSection'); - const excludeViewTypes = ['playlists', 'livetv', 'boxsets', 'channels']; - - for (let i = 0, length = userViews.length; i < length; i++) { - const item = userViews[i]; - if (user.Configuration.LatestItemsExcludes.indexOf(item.Id) !== -1) { - continue; - } - - if (excludeViewTypes.indexOf(item.CollectionType || []) !== -1) { - continue; - } - - const frag = document.createElement('div'); - frag.classList.add('verticalSection'); - frag.classList.add('hide'); - elem.appendChild(frag); - - renderLatestSection(frag, apiClient, user, item); - } - } - - export function loadLibraryTiles(elem, apiClient, user, userSettings, shape, userViews) { - let html = ''; - if (userViews.length) { - html += '

' + globalize.translate('HeaderMyMedia') + '

'; - if (enableScrollX()) { - html += '
'; - html += '
'; - } else { - html += '
'; - } - - html += cardBuilder.getCardsHtml({ - items: userViews, - shape: getThumbShape(), - showTitle: true, - centerText: true, - overlayText: false, - lazy: true, - transition: false, - allowBottomPadding: !enableScrollX() - }); - - if (enableScrollX()) { - html += '
'; - } - html += '
'; - } - - elem.innerHTML = html; - imageLoader.lazyChildren(elem); - } - - const dataMonitorHints = { - 'Audio': 'audioplayback,markplayed', - 'Video': 'videoplayback,markplayed' - }; - - function loadResume(elem, apiClient, headerText, mediaType, userSettings) { - let html = ''; - - const dataMonitor = dataMonitorHints[mediaType] || 'markplayed'; - - html += '

' + globalize.translate(headerText) + '

'; - if (enableScrollX()) { - html += '
'; - html += `
`; - } else { - html += `
`; - } - - if (enableScrollX()) { - html += '
'; - } - html += '
'; - - elem.classList.add('hide'); - elem.innerHTML = html; - - const itemsContainer = elem.querySelector('.itemsContainer'); - itemsContainer.fetchData = getItemsToResumeFn(mediaType, apiClient.serverId()); - itemsContainer.getItemsHtml = getItemsToResumeHtmlFn(userSettings.useEpisodeImagesInNextUpAndResume(), mediaType); - itemsContainer.parentContainer = elem; - } - - function getItemsToResumeFn(mediaType, serverId) { - return function () { - const apiClient = ServerConnections.getApiClient(serverId); - - const limit = enableScrollX() ? 12 : 5; - - const options = { - Limit: limit, - Recursive: true, - Fields: 'PrimaryImageAspectRatio,BasicSyncInfo', - ImageTypeLimit: 1, - EnableImageTypes: 'Primary,Backdrop,Thumb', - EnableTotalRecordCount: false, - MediaTypes: mediaType - }; - - return apiClient.getResumableItems(apiClient.getCurrentUserId(), options); - }; - } - - function getItemsToResumeHtmlFn(useEpisodeImages, mediaType) { - return function (items) { - const cardLayout = false; - return cardBuilder.getCardsHtml({ - items: items, - preferThumb: true, - inheritThumb: !useEpisodeImages, - shape: (mediaType === 'Book') ? getPortraitShape() : getThumbShape(), - overlayText: false, - showTitle: true, - showParentTitle: true, - lazy: true, - showDetailsMenu: true, - overlayPlayButton: true, - context: 'home', - centerText: !cardLayout, - allowBottomPadding: false, - cardLayout: cardLayout, - showYear: true, - lines: 2 - }); - }; - } - - function getOnNowFetchFn(serverId) { - return function () { - const apiClient = ServerConnections.getApiClient(serverId); - return apiClient.getLiveTvRecommendedPrograms({ - userId: apiClient.getCurrentUserId(), - IsAiring: true, - limit: 24, - ImageTypeLimit: 1, - EnableImageTypes: 'Primary,Thumb,Backdrop', - EnableTotalRecordCount: false, - Fields: 'ChannelInfo,PrimaryImageAspectRatio' - }); - }; - } - - function getOnNowItemsHtml(items) { - return cardBuilder.getCardsHtml({ - items: items, - preferThumb: 'auto', - inheritThumb: false, - shape: (enableScrollX() ? 'autooverflow' : 'auto'), - showParentTitleOrTitle: true, + html += cardBuilder.getCardsHtml({ + items: userViews, + shape: getThumbShape(), showTitle: true, centerText: true, - coverImage: true, overlayText: false, - allowBottomPadding: !enableScrollX(), - showAirTime: true, - showChannelName: false, - showAirDateTime: false, - showAirEndTime: true, - defaultShape: getThumbShape(), - lines: 3, - overlayPlayButton: true + lazy: true, + transition: false, + allowBottomPadding: !enableScrollX() }); + + if (enableScrollX()) { + html += '
'; + } + html += '
'; } - function loadOnNow(elem, apiClient, user) { - if (!user.Policy.EnableLiveTvAccess) { - return Promise.resolve(); - } + elem.innerHTML = html; + imageLoader.lazyChildren(elem); +} +const dataMonitorHints = { + 'Audio': 'audioplayback,markplayed', + 'Video': 'videoplayback,markplayed' +}; + +function loadResume(elem, apiClient, headerText, mediaType, userSettings) { + let html = ''; + + const dataMonitor = dataMonitorHints[mediaType] || 'markplayed'; + + html += '

' + globalize.translate(headerText) + '

'; + if (enableScrollX()) { + html += '
'; + html += `
`; + } else { + html += `
`; + } + + if (enableScrollX()) { + html += '
'; + } + html += '
'; + + elem.classList.add('hide'); + elem.innerHTML = html; + + const itemsContainer = elem.querySelector('.itemsContainer'); + itemsContainer.fetchData = getItemsToResumeFn(mediaType, apiClient.serverId()); + itemsContainer.getItemsHtml = getItemsToResumeHtmlFn(userSettings.useEpisodeImagesInNextUpAndResume(), mediaType); + itemsContainer.parentContainer = elem; +} + +function getItemsToResumeFn(mediaType, serverId) { + return function () { + const apiClient = ServerConnections.getApiClient(serverId); + + const limit = enableScrollX() ? 12 : 5; + + const options = { + Limit: limit, + Recursive: true, + Fields: 'PrimaryImageAspectRatio,BasicSyncInfo', + ImageTypeLimit: 1, + EnableImageTypes: 'Primary,Backdrop,Thumb', + EnableTotalRecordCount: false, + MediaTypes: mediaType + }; + + return apiClient.getResumableItems(apiClient.getCurrentUserId(), options); + }; +} + +function getItemsToResumeHtmlFn(useEpisodeImages, mediaType) { + return function (items) { + const cardLayout = false; + return cardBuilder.getCardsHtml({ + items: items, + preferThumb: true, + inheritThumb: !useEpisodeImages, + shape: (mediaType === 'Book') ? getPortraitShape() : getThumbShape(), + overlayText: false, + showTitle: true, + showParentTitle: true, + lazy: true, + showDetailsMenu: true, + overlayPlayButton: true, + context: 'home', + centerText: !cardLayout, + allowBottomPadding: false, + cardLayout: cardLayout, + showYear: true, + lines: 2 + }); + }; +} + +function getOnNowFetchFn(serverId) { + return function () { + const apiClient = ServerConnections.getApiClient(serverId); return apiClient.getLiveTvRecommendedPrograms({ userId: apiClient.getCurrentUserId(), IsAiring: true, - limit: 1, + limit: 24, ImageTypeLimit: 1, EnableImageTypes: 'Primary,Thumb,Backdrop', EnableTotalRecordCount: false, Fields: 'ChannelInfo,PrimaryImageAspectRatio' - }).then(function (result) { - let html = ''; - if (result.Items.length) { - elem.classList.remove('padded-left'); - elem.classList.remove('padded-right'); - elem.classList.remove('padded-bottom'); - elem.classList.remove('verticalSection'); - - html += '
'; - html += '
'; - html += '

' + globalize.translate('LiveTV') + '

'; - html += '
'; - - if (enableScrollX()) { - html += '
'; - html += '
'; - } else { - html += ''; - if (enableScrollX()) { - html += '
'; - } - html += '
'; - html += '
'; - - html += '
'; - html += '
'; - - if (!layoutManager.tv) { - html += ''; - html += '

'; - html += globalize.translate('HeaderOnNow'); - html += '

'; - html += ''; - html += '
'; - } else { - html += '

' + globalize.translate('HeaderOnNow') + '

'; - } - html += '
'; - - if (enableScrollX()) { - html += '
'; - html += '
'; - } else { - html += '
'; - } - - if (enableScrollX()) { - html += '
'; - } - - html += '
'; - html += '
'; - - elem.innerHTML = html; - - const itemsContainer = elem.querySelector('.itemsContainer'); - itemsContainer.parentContainer = elem; - itemsContainer.fetchData = getOnNowFetchFn(apiClient.serverId()); - itemsContainer.getItemsHtml = getOnNowItemsHtml; - } }); + }; +} + +function getOnNowItemsHtml(items) { + return cardBuilder.getCardsHtml({ + items: items, + preferThumb: 'auto', + inheritThumb: false, + shape: (enableScrollX() ? 'autooverflow' : 'auto'), + showParentTitleOrTitle: true, + showTitle: true, + centerText: true, + coverImage: true, + overlayText: false, + allowBottomPadding: !enableScrollX(), + showAirTime: true, + showChannelName: false, + showAirDateTime: false, + showAirEndTime: true, + defaultShape: getThumbShape(), + lines: 3, + overlayPlayButton: true + }); +} + +function loadOnNow(elem, apiClient, user) { + if (!user.Policy.EnableLiveTvAccess) { + return Promise.resolve(); } - function getNextUpFetchFn(serverId, userSettings) { - return function () { - const apiClient = ServerConnections.getApiClient(serverId); - const oldestDateForNextUp = new Date(); - oldestDateForNextUp.setDate(oldestDateForNextUp.getDate() - userSettings.maxDaysForNextUp()); - return apiClient.getNextUpEpisodes({ - Limit: enableScrollX() ? 24 : 15, - Fields: 'PrimaryImageAspectRatio,DateCreated,BasicSyncInfo,Path,MediaSourceCount', - UserId: apiClient.getCurrentUserId(), - ImageTypeLimit: 1, - EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', - EnableTotalRecordCount: false, - DisableFirstEpisode: false, - NextUpDateCutoff: oldestDateForNextUp.toISOString(), - EnableRewatching: userSettings.enableRewatchingInNextUp() - }); - }; - } - - function getNextUpItemsHtmlFn(useEpisodeImages) { - return function (items) { - const cardLayout = false; - return cardBuilder.getCardsHtml({ - items: items, - preferThumb: true, - inheritThumb: !useEpisodeImages, - shape: getThumbShape(), - overlayText: false, - showTitle: true, - showParentTitle: true, - lazy: true, - overlayPlayButton: true, - context: 'home', - centerText: !cardLayout, - allowBottomPadding: !enableScrollX(), - cardLayout: cardLayout - }); - }; - } - - function loadNextUp(elem, apiClient, userSettings) { + return apiClient.getLiveTvRecommendedPrograms({ + userId: apiClient.getCurrentUserId(), + IsAiring: true, + limit: 1, + ImageTypeLimit: 1, + EnableImageTypes: 'Primary,Thumb,Backdrop', + EnableTotalRecordCount: false, + Fields: 'ChannelInfo,PrimaryImageAspectRatio' + }).then(function (result) { let html = ''; + if (result.Items.length) { + elem.classList.remove('padded-left'); + elem.classList.remove('padded-right'); + elem.classList.remove('padded-bottom'); + elem.classList.remove('verticalSection'); - html += '
'; - if (!layoutManager.tv) { - html += ''; + html += '
'; + html += '

' + globalize.translate('LiveTV') + '

'; + html += '
'; + + if (enableScrollX()) { + html += '
'; + html += '
'; + } else { + html += ''; + }) + '" class="raised">' + globalize.translate('Recordings') + ''; - if (enableScrollX()) { - html += '
'; - html += '
'; - } else { - html += '
'; - } + html += '' + globalize.translate('Schedule') + ''; + + html += '' + globalize.translate('Series') + ''; - if (enableScrollX()) { html += '
'; - } - html += '
'; - - elem.classList.add('hide'); - elem.innerHTML = html; - - const itemsContainer = elem.querySelector('.itemsContainer'); - itemsContainer.fetchData = getNextUpFetchFn(apiClient.serverId(), userSettings); - itemsContainer.getItemsHtml = getNextUpItemsHtmlFn(userSettings.useEpisodeImagesInNextUpAndResume()); - itemsContainer.parentContainer = elem; - } - - function getLatestRecordingsFetchFn(serverId, activeRecordingsOnly) { - return function () { - const apiClient = ServerConnections.getApiClient(serverId); - return apiClient.getLiveTvRecordings({ - userId: apiClient.getCurrentUserId(), - Limit: enableScrollX() ? 12 : 5, - Fields: 'PrimaryImageAspectRatio,BasicSyncInfo', - EnableTotalRecordCount: false, - IsLibraryItem: activeRecordingsOnly ? null : false, - IsInProgress: activeRecordingsOnly ? true : null - }); - }; - } - - function getLatestRecordingItemsHtml(activeRecordingsOnly) { - return function (items) { - return cardBuilder.getCardsHtml({ - items: items, - shape: enableScrollX() ? 'autooverflow' : 'auto', - showTitle: true, - showParentTitle: true, - coverImage: true, - lazy: true, - showDetailsMenu: true, - centerText: true, - overlayText: false, - showYear: true, - lines: 2, - overlayPlayButton: !activeRecordingsOnly, - allowBottomPadding: !enableScrollX(), - preferThumb: true, - cardLayout: false, - overlayMoreButton: activeRecordingsOnly, - action: activeRecordingsOnly ? 'none' : null, - centerPlayButton: activeRecordingsOnly - }); - }; - } - - function loadLatestLiveTvRecordings(elem, activeRecordingsOnly, apiClient) { - const title = activeRecordingsOnly ? - globalize.translate('HeaderActiveRecordings') : - globalize.translate('HeaderLatestRecordings'); - - let html = ''; - - html += '
'; - html += '

' + title + '

'; - html += '
'; - - if (enableScrollX()) { - html += '
'; - html += '
'; - } else { - html += '
'; - } - - if (enableScrollX()) { + if (enableScrollX()) { + html += '
'; + } html += '
'; + html += '
'; + + html += '
'; + html += '
'; + + if (!layoutManager.tv) { + html += ''; + html += '

'; + html += globalize.translate('HeaderOnNow'); + html += '

'; + html += ''; + html += '
'; + } else { + html += '

' + globalize.translate('HeaderOnNow') + '

'; + } + html += '
'; + + if (enableScrollX()) { + html += '
'; + html += '
'; + } else { + html += '
'; + } + + if (enableScrollX()) { + html += '
'; + } + + html += '
'; + html += '
'; + + elem.innerHTML = html; + + const itemsContainer = elem.querySelector('.itemsContainer'); + itemsContainer.parentContainer = elem; + itemsContainer.fetchData = getOnNowFetchFn(apiClient.serverId()); + itemsContainer.getItemsHtml = getOnNowItemsHtml; } - html += '
'; + }); +} - elem.classList.add('hide'); - elem.innerHTML = html; +function getNextUpFetchFn(serverId, userSettings) { + return function () { + const apiClient = ServerConnections.getApiClient(serverId); + const oldestDateForNextUp = new Date(); + oldestDateForNextUp.setDate(oldestDateForNextUp.getDate() - userSettings.maxDaysForNextUp()); + return apiClient.getNextUpEpisodes({ + Limit: enableScrollX() ? 24 : 15, + Fields: 'PrimaryImageAspectRatio,DateCreated,BasicSyncInfo,Path,MediaSourceCount', + UserId: apiClient.getCurrentUserId(), + ImageTypeLimit: 1, + EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', + EnableTotalRecordCount: false, + DisableFirstEpisode: false, + NextUpDateCutoff: oldestDateForNextUp.toISOString(), + EnableRewatching: userSettings.enableRewatchingInNextUp() + }); + }; +} - const itemsContainer = elem.querySelector('.itemsContainer'); - itemsContainer.fetchData = getLatestRecordingsFetchFn(apiClient.serverId(), activeRecordingsOnly); - itemsContainer.getItemsHtml = getLatestRecordingItemsHtml(activeRecordingsOnly); - itemsContainer.parentContainer = elem; +function getNextUpItemsHtmlFn(useEpisodeImages) { + return function (items) { + const cardLayout = false; + return cardBuilder.getCardsHtml({ + items: items, + preferThumb: true, + inheritThumb: !useEpisodeImages, + shape: getThumbShape(), + overlayText: false, + showTitle: true, + showParentTitle: true, + lazy: true, + overlayPlayButton: true, + context: 'home', + centerText: !cardLayout, + allowBottomPadding: !enableScrollX(), + cardLayout: cardLayout + }); + }; +} + +function loadNextUp(elem, apiClient, userSettings) { + let html = ''; + + html += '
'; + if (!layoutManager.tv) { + html += ''; + html += '

'; + html += globalize.translate('NextUp'); + html += '

'; + html += ''; + html += '
'; + } else { + html += '

'; + html += globalize.translate('NextUp'); + html += '

'; } + html += '
'; + + if (enableScrollX()) { + html += '
'; + html += '
'; + } else { + html += '
'; + } + + if (enableScrollX()) { + html += '
'; + } + html += '
'; + + elem.classList.add('hide'); + elem.innerHTML = html; + + const itemsContainer = elem.querySelector('.itemsContainer'); + itemsContainer.fetchData = getNextUpFetchFn(apiClient.serverId(), userSettings); + itemsContainer.getItemsHtml = getNextUpItemsHtmlFn(userSettings.useEpisodeImagesInNextUpAndResume()); + itemsContainer.parentContainer = elem; +} + +function getLatestRecordingsFetchFn(serverId, activeRecordingsOnly) { + return function () { + const apiClient = ServerConnections.getApiClient(serverId); + return apiClient.getLiveTvRecordings({ + userId: apiClient.getCurrentUserId(), + Limit: enableScrollX() ? 12 : 5, + Fields: 'PrimaryImageAspectRatio,BasicSyncInfo', + EnableTotalRecordCount: false, + IsLibraryItem: activeRecordingsOnly ? null : false, + IsInProgress: activeRecordingsOnly ? true : null + }); + }; +} + +function getLatestRecordingItemsHtml(activeRecordingsOnly) { + return function (items) { + return cardBuilder.getCardsHtml({ + items: items, + shape: enableScrollX() ? 'autooverflow' : 'auto', + showTitle: true, + showParentTitle: true, + coverImage: true, + lazy: true, + showDetailsMenu: true, + centerText: true, + overlayText: false, + showYear: true, + lines: 2, + overlayPlayButton: !activeRecordingsOnly, + allowBottomPadding: !enableScrollX(), + preferThumb: true, + cardLayout: false, + overlayMoreButton: activeRecordingsOnly, + action: activeRecordingsOnly ? 'none' : null, + centerPlayButton: activeRecordingsOnly + }); + }; +} + +function loadLatestLiveTvRecordings(elem, activeRecordingsOnly, apiClient) { + const title = activeRecordingsOnly ? + globalize.translate('HeaderActiveRecordings') : + globalize.translate('HeaderLatestRecordings'); + + let html = ''; + + html += '
'; + html += '

' + title + '

'; + html += '
'; + + if (enableScrollX()) { + html += '
'; + html += '
'; + } else { + html += '
'; + } + + if (enableScrollX()) { + html += '
'; + } + html += '
'; + + elem.classList.add('hide'); + elem.innerHTML = html; + + const itemsContainer = elem.querySelector('.itemsContainer'); + itemsContainer.fetchData = getLatestRecordingsFetchFn(apiClient.serverId(), activeRecordingsOnly); + itemsContainer.getItemsHtml = getLatestRecordingItemsHtml(activeRecordingsOnly); + itemsContainer.parentContainer = elem; +} export default { loadLibraryTiles: loadLibraryTiles, @@ -750,4 +748,3 @@ export default { resume: resume }; -/* eslint-enable indent */ diff --git a/src/components/htmlMediaHelper.js b/src/components/htmlMediaHelper.js index 92fb4d0135..124caa5e0a 100644 --- a/src/components/htmlMediaHelper.js +++ b/src/components/htmlMediaHelper.js @@ -1,387 +1,382 @@ - -/* eslint-disable indent */ - import appSettings from '../scripts/settings/appSettings' ; import browser from '../scripts/browser'; import Events from '../utils/events.ts'; - export function getSavedVolume() { - return appSettings.get('volume') || 1; +export function getSavedVolume() { + return appSettings.get('volume') || 1; +} + +export function saveVolume(value) { + if (value) { + appSettings.set('volume', value); + } +} + +export function getCrossOriginValue(mediaSource) { + if (mediaSource.IsRemote) { + return null; } - export function saveVolume(value) { - if (value) { - appSettings.set('volume', value); - } - } + return 'anonymous'; +} - export function getCrossOriginValue(mediaSource) { - if (mediaSource.IsRemote) { - return null; - } +function canPlayNativeHls() { + const media = document.createElement('video'); - return 'anonymous'; - } - - function canPlayNativeHls() { - const media = document.createElement('video'); - - return !!(media.canPlayType('application/x-mpegURL').replace(/no/, '') + return !!(media.canPlayType('application/x-mpegURL').replace(/no/, '') || media.canPlayType('application/vnd.apple.mpegURL').replace(/no/, '')); +} + +export function enableHlsJsPlayer(runTimeTicks, mediaType) { + if (window.MediaSource == null) { + return false; } - export 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; - } - - // simple playback should use the native support - if (runTimeTicks) { - return false; - } - } - - return true; + // hls.js is only in beta. needs more testing. + if (browser.iOS) { + return false; } - let recoverDecodingErrorDate; - let recoverSwapAudioCodecDate; - export function handleHlsJsMediaError(instance, reject) { - const hlsPlayer = instance._hlsPlayer; + // 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 (!hlsPlayer) { - return; + if (canPlayNativeHls()) { + // Having trouble with chrome's native support and transcoded music + if (browser.android && mediaType === 'Audio') { + return true; } - let now = Date.now(); - - if (window.performance && window.performance.now) { - now = performance.now(); // eslint-disable-line compat/compat + // simple playback should use the native support + if (runTimeTicks) { + return false; } + } - if (!recoverDecodingErrorDate || (now - recoverDecodingErrorDate) > 3000) { - recoverDecodingErrorDate = now; - console.debug('try to recover media Error ...'); + return true; +} + +let recoverDecodingErrorDate; +let recoverSwapAudioCodecDate; +export function handleHlsJsMediaError(instance, reject) { + const hlsPlayer = instance._hlsPlayer; + + if (!hlsPlayer) { + return; + } + + let now = Date.now(); + + if (window.performance && window.performance.now) { + now = performance.now(); // eslint-disable-line compat/compat + } + + if (!recoverDecodingErrorDate || (now - recoverDecodingErrorDate) > 3000) { + recoverDecodingErrorDate = now; + console.debug('try to recover media Error ...'); + hlsPlayer.recoverMediaError(); + } else { + if (!recoverSwapAudioCodecDate || (now - recoverSwapAudioCodecDate) > 3000) { + recoverSwapAudioCodecDate = now; + console.debug('try to swap Audio Codec and recover media Error ...'); + hlsPlayer.swapAudioCodec(); hlsPlayer.recoverMediaError(); } else { - if (!recoverSwapAudioCodecDate || (now - recoverSwapAudioCodecDate) > 3000) { - recoverSwapAudioCodecDate = now; - console.debug('try to swap Audio Codec and recover media Error ...'); - hlsPlayer.swapAudioCodec(); - hlsPlayer.recoverMediaError(); + console.error('cannot recover, last media error recovery failed ...'); + + if (reject) { + reject(); } else { - console.error('cannot recover, last media error recovery failed ...'); - - if (reject) { - reject(); - } else { - onErrorInternal(instance, 'mediadecodeerror'); - } + onErrorInternal(instance, 'mediadecodeerror'); } } } +} - export function onErrorInternal(instance, type) { - // Needed for video - if (instance.destroyCustomTrack) { - instance.destroyCustomTrack(instance._mediaElement); - } - - Events.trigger(instance, 'error', [ - { - type: type - } - ]); +export function onErrorInternal(instance, type) { + // Needed for video + if (instance.destroyCustomTrack) { + instance.destroyCustomTrack(instance._mediaElement); } - export function isValidDuration(duration) { - return duration + Events.trigger(instance, 'error', [ + { + type: type + } + ]); +} + +export function isValidDuration(duration) { + return duration && !isNaN(duration) && duration !== Number.POSITIVE_INFINITY && duration !== Number.NEGATIVE_INFINITY; - } +} - function setCurrentTimeIfNeeded(element, seconds) { - // If it's worth skipping (1 sec or less of a difference) - if (Math.abs((element.currentTime || 0) - seconds) >= 1) { - element.currentTime = seconds; +function setCurrentTimeIfNeeded(element, seconds) { + // If it's worth skipping (1 sec or less of a difference) + if (Math.abs((element.currentTime || 0) - seconds) >= 1) { + element.currentTime = seconds; + } +} + +export function seekOnPlaybackStart(instance, element, ticks, onMediaReady) { + const seconds = (ticks || 0) / 10000000; + + if (seconds) { + // Appending #t=xxx to the query string doesn't seem to work with HLS + // For plain video files, not all browsers support it either + + if (element.duration >= seconds) { + // media is ready, seek immediately + setCurrentTimeIfNeeded(element, seconds); + if (onMediaReady) onMediaReady(); + } else { + // update video player position when media is ready to be sought + const events = ['durationchange', 'loadeddata', 'play', 'loadedmetadata']; + const onMediaChange = function(e) { + if (element.currentTime === 0 && element.duration >= seconds) { + // seek only when video position is exactly zero, + // as this is true only if video hasn't started yet or + // user rewound to the very beginning + // (but rewinding cannot happen as the first event with media of non-empty duration) + console.debug(`seeking to ${seconds} on ${e.type} event`); + setCurrentTimeIfNeeded(element, seconds); + events.forEach(name => { + element.removeEventListener(name, onMediaChange); + }); + if (onMediaReady) onMediaReady(); + } + }; + events.forEach(name => { + element.addEventListener(name, onMediaChange); + }); } } +} - export function seekOnPlaybackStart(instance, element, ticks, onMediaReady) { - const seconds = (ticks || 0) / 10000000; +export function applySrc(elem, src, options) { + if (window.Windows && options.mediaSource && options.mediaSource.IsLocal) { + return Windows.Storage.StorageFile.getFileFromPathAsync(options.url).then(function (file) { + const playlist = new Windows.Media.Playback.MediaPlaybackList(); - if (seconds) { - // Appending #t=xxx to the query string doesn't seem to work with HLS - // For plain video files, not all browsers support it either - - if (element.duration >= seconds) { - // media is ready, seek immediately - setCurrentTimeIfNeeded(element, seconds); - if (onMediaReady) onMediaReady(); - } else { - // update video player position when media is ready to be sought - const events = ['durationchange', 'loadeddata', 'play', 'loadedmetadata']; - const onMediaChange = function(e) { - if (element.currentTime === 0 && element.duration >= seconds) { - // seek only when video position is exactly zero, - // as this is true only if video hasn't started yet or - // user rewound to the very beginning - // (but rewinding cannot happen as the first event with media of non-empty duration) - console.debug(`seeking to ${seconds} on ${e.type} event`); - setCurrentTimeIfNeeded(element, seconds); - events.forEach(name => { - element.removeEventListener(name, onMediaChange); - }); - if (onMediaReady) onMediaReady(); - } - }; - events.forEach(name => { - element.addEventListener(name, onMediaChange); - }); - } - } + const source1 = Windows.Media.Core.MediaSource.createFromStorageFile(file); + const 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; } - export function applySrc(elem, src, options) { - if (window.Windows && options.mediaSource && options.mediaSource.IsLocal) { - return Windows.Storage.StorageFile.getFileFromPathAsync(options.url).then(function (file) { - const playlist = new Windows.Media.Playback.MediaPlaybackList(); + return Promise.resolve(); +} - const source1 = Windows.Media.Core.MediaSource.createFromStorageFile(file); - const startTime = (options.playerStartPositionTicks || 0) / 10000; - playlist.items.append(new Windows.Media.Playback.MediaPlaybackItem(source1, startTime)); - elem.src = URL.createObjectURL(playlist, { oneTimeOnly: true }); +export function resetSrc(elem) { + elem.src = ''; + elem.innerHTML = ''; + elem.removeAttribute('src'); +} + +function onSuccessfulPlay(elem, onErrorFn) { + elem.addEventListener('error', onErrorFn); +} + +export function playWithPromise(elem, onErrorFn) { + try { + return elem.play() + .catch((e) => { + const 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 + return Promise.resolve(); + } + return Promise.reject(); + }) + .then(() => { + onSuccessfulPlay(elem, onErrorFn); return Promise.resolve(); }); - } else { - elem.src = src; - } - - return Promise.resolve(); + } catch (err) { + console.error('error calling video.play: ' + err); + return Promise.reject(); } +} - export function resetSrc(elem) { - elem.src = ''; - elem.innerHTML = ''; - elem.removeAttribute('src'); - } - - function onSuccessfulPlay(elem, onErrorFn) { - elem.addEventListener('error', onErrorFn); - } - - export function playWithPromise(elem, onErrorFn) { +export function destroyCastPlayer(instance) { + const player = instance._castPlayer; + if (player) { try { - return elem.play() - .catch((e) => { - const 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 - return Promise.resolve(); - } - return Promise.reject(); - }) - .then(() => { - onSuccessfulPlay(elem, onErrorFn); - return Promise.resolve(); - }); + player.unload(); } catch (err) { - console.error('error calling video.play: ' + err); - return Promise.reject(); + console.error(err); } - } - export function destroyCastPlayer(instance) { - const player = instance._castPlayer; - if (player) { - try { - player.unload(); - } catch (err) { - console.error(err); + instance._castPlayer = null; + } +} + +export function destroyHlsPlayer(instance) { + const player = instance._hlsPlayer; + if (player) { + try { + player.destroy(); + } catch (err) { + console.error(err); + } + + instance._hlsPlayer = null; + } +} + +export function destroyFlvPlayer(instance) { + const player = instance._flvPlayer; + if (player) { + try { + player.unload(); + player.detachMediaElement(); + player.destroy(); + } catch (err) { + console.error(err); + } + + instance._flvPlayer = null; + } +} + +export 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; } - - instance._castPlayer = null; - } - } - - export function destroyHlsPlayer(instance) { - const player = instance._hlsPlayer; - if (player) { - try { - player.destroy(); - } catch (err) { - console.error(err); - } - - instance._hlsPlayer = null; - } - } - - export function destroyFlvPlayer(instance) { - const player = instance._flvPlayer; - if (player) { - try { - player.unload(); - player.detachMediaElement(); - player.destroy(); - } catch (err) { - console.error(err); - } - - instance._flvPlayer = null; - } - } - - export 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.error('HLS Error: Type: ' + data.type + ' Details: ' + (data.details || '') + ' Fatal: ' + (data.fatal || false)); + hls.on(Hls.Events.ERROR, function (event, data) { + console.error('HLS Error: Type: ' + data.type + ' Details: ' + (data.details || '') + ' Fatal: ' + (data.fatal || false)); - // try to recover network error - if (data.type === Hls.ErrorTypes.NETWORK_ERROR + // try to recover network error + if (data.type === Hls.ErrorTypes.NETWORK_ERROR && data.response?.code && data.response.code >= 400 - ) { - console.debug('hls.js response error code: ' + data.response.code); + ) { + console.debug('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(); + // 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; + if (reject) { + reject('servererror'); + reject = null; + } else { + onErrorInternal(instance, 'servererror'); } - if (data.fatal) { - switch (data.type) { - case Hls.ErrorTypes.NETWORK_ERROR: + return; + } - if (data.response && data.response.code === 0) { - // This could be a CORS error related to access control response headers + if (data.fatal) { + switch (data.type) { + case Hls.ErrorTypes.NETWORK_ERROR: - console.debug('hls.js response error code: ' + data.response.code); + if (data.response && data.response.code === 0) { + // This could be a CORS error related to access control response headers - // Trigger failure differently depending on whether this is prior to start of playback, or after - hls.destroy(); + console.debug('hls.js response error code: ' + data.response.code); - if (reject) { - reject('network'); - reject = null; - } else { - onErrorInternal(instance, 'network'); - } - } else { - console.debug('fatal network error encountered, try to recover'); - hls.startLoad(); - } - - break; - case Hls.ErrorTypes.MEDIA_ERROR: - console.debug('fatal media error encountered, try to recover'); - handleHlsJsMediaError(instance, reject); - reject = null; - break; - default: - - console.debug('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('network'); reject = null; } else { - onErrorInternal(instance, 'mediadecodeerror'); + onErrorInternal(instance, 'network'); } - break; - } + } else { + console.debug('fatal network error encountered, try to recover'); + hls.startLoad(); + } + + break; + case Hls.ErrorTypes.MEDIA_ERROR: + console.debug('fatal media error encountered, try to recover'); + handleHlsJsMediaError(instance, reject); + reject = null; + break; + default: + + console.debug('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; } + } + }); +} + +export function onEndedInternal(instance, elem, onErrorFn) { + elem.removeEventListener('error', onErrorFn); + + resetSrc(elem); + + destroyHlsPlayer(instance); + destroyFlvPlayer(instance); + destroyCastPlayer(instance); + + const stopInfo = { + src: instance._currentSrc + }; + + Events.trigger(instance, 'stopped', [stopInfo]); + + instance._currentTime = null; + instance._currentSrc = null; + instance._currentPlayOptions = null; +} + +export function getBufferedRanges(instance, elem) { + const ranges = []; + const seekable = elem.buffered || []; + + let offset; + const currentPlayOptions = instance._currentPlayOptions; + if (currentPlayOptions) { + offset = currentPlayOptions.transcodingOffsetTicks; + } + + offset = offset || 0; + + for (let i = 0, length = seekable.length; i < length; i++) { + let start = seekable.start(i); + let 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 }); } - export function onEndedInternal(instance, elem, onErrorFn) { - elem.removeEventListener('error', onErrorFn); - - resetSrc(elem); - - destroyHlsPlayer(instance); - destroyFlvPlayer(instance); - destroyCastPlayer(instance); - - const stopInfo = { - src: instance._currentSrc - }; - - Events.trigger(instance, 'stopped', [stopInfo]); - - instance._currentTime = null; - instance._currentSrc = null; - instance._currentPlayOptions = null; - } - - export function getBufferedRanges(instance, elem) { - const ranges = []; - const seekable = elem.buffered || []; - - let offset; - const currentPlayOptions = instance._currentPlayOptions; - if (currentPlayOptions) { - offset = currentPlayOptions.transcodingOffsetTicks; - } - - offset = offset || 0; - - for (let i = 0, length = seekable.length; i < length; i++) { - let start = seekable.start(i); - let 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; - } - -/* eslint-enable indent */ + return ranges; +} diff --git a/src/components/imageDownloader/imageDownloader.js b/src/components/imageDownloader/imageDownloader.js index b88eea6688..1955896180 100644 --- a/src/components/imageDownloader/imageDownloader.js +++ b/src/components/imageDownloader/imageDownloader.js @@ -15,371 +15,369 @@ import '../cardbuilder/card.scss'; import ServerConnections from '../ServerConnections'; import template from './imageDownloader.template.html'; -/* eslint-disable indent */ +const enableFocusTransform = !browser.slow && !browser.edge; - const enableFocusTransform = !browser.slow && !browser.edge; +let currentItemId; +let currentItemType; +let currentResolve; +let currentReject; +let hasChanges = false; - let currentItemId; - let currentItemType; - let currentResolve; - let currentReject; - let hasChanges = false; +// These images can be large and we're seeing memory problems in safari +const browsableImagePageSize = browser.slow ? 6 : 30; - // These images can be large and we're seeing memory problems in safari - const browsableImagePageSize = browser.slow ? 6 : 30; +let browsableImageStartIndex = 0; +let browsableImageType = 'Primary'; +let selectedProvider; +let browsableParentId; - let browsableImageStartIndex = 0; - let browsableImageType = 'Primary'; - let selectedProvider; - let browsableParentId; +function getBaseRemoteOptions(page, forceCurrentItemId = false) { + const options = {}; - function getBaseRemoteOptions(page, forceCurrentItemId = false) { - const options = {}; - - if (!forceCurrentItemId && page.querySelector('#chkShowParentImages').checked && browsableParentId) { - options.itemId = browsableParentId; - } else { - options.itemId = currentItemId; - } - - return options; + if (!forceCurrentItemId && page.querySelector('#chkShowParentImages').checked && browsableParentId) { + options.itemId = browsableParentId; + } else { + options.itemId = currentItemId; } - function reloadBrowsableImages(page, apiClient) { - loading.show(); + return options; +} - const options = getBaseRemoteOptions(page); +function reloadBrowsableImages(page, apiClient) { + loading.show(); - options.type = browsableImageType; - options.startIndex = browsableImageStartIndex; - options.limit = browsableImagePageSize; - options.IncludeAllLanguages = page.querySelector('#chkAllLanguages').checked; + const options = getBaseRemoteOptions(page); - const provider = selectedProvider || ''; + options.type = browsableImageType; + options.startIndex = browsableImageStartIndex; + options.limit = browsableImagePageSize; + options.IncludeAllLanguages = page.querySelector('#chkAllLanguages').checked; - if (provider) { - options.ProviderName = provider; - } + const provider = selectedProvider || ''; - apiClient.getAvailableRemoteImages(options).then(function (result) { - renderRemoteImages(page, apiClient, result, browsableImageType, options.startIndex, options.limit); - - page.querySelector('#selectBrowsableImageType').value = browsableImageType; - - const providersHtml = result.Providers.map(function (p) { - return ''; - }); - - const selectImageProvider = page.querySelector('#selectImageProvider'); - selectImageProvider.innerHTML = '' + providersHtml; - selectImageProvider.value = provider; - - loading.hide(); - }); - } - - function renderRemoteImages(page, apiClient, imagesResult, imageType, startIndex, limit) { - page.querySelector('.availableImagesPaging').innerHTML = getPagingHtml(startIndex, limit, imagesResult.TotalRecordCount); - - let html = ''; - - for (let i = 0, length = imagesResult.Images.length; i < length; i++) { - html += getRemoteImageHtml(imagesResult.Images[i], imageType); - } - - const availableImagesList = page.querySelector('.availableImagesList'); - availableImagesList.innerHTML = html; - imageLoader.lazyChildren(availableImagesList); - - const btnNextPage = page.querySelector('.btnNextPage'); - const 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) { - let html = ''; - - const recordsEnd = Math.min(startIndex + limit, totalRecordCount); - - // 20 is the minimum page size - const showControls = totalRecordCount > limit; - - html += '
'; - - html += ''; - - const startAtDisplay = totalRecordCount ? startIndex + 1 : 0; - html += globalize.translate('ListPaging', startAtDisplay, recordsEnd, totalRecordCount); - - html += ''; - - if (showControls) { - html += '
'; - - html += ``; - html += ``; - html += '
'; - } - - html += '
'; - - return html; - } - - function downloadRemoteImage(page, apiClient, url, type, provider) { - const options = getBaseRemoteOptions(page, true); - - options.Type = type; - options.ImageUrl = url; + if (provider) { options.ProviderName = provider; - - loading.show(); - - apiClient.downloadRemoteImage(options).then(function () { - hasChanges = true; - const dlg = dom.parentWithClass(page, 'dialog'); - dialogHelper.close(dlg); - }); } - function getRemoteImageHtml(image, imageType) { - const tagName = layoutManager.tv ? 'button' : 'div'; - const enableFooterButtons = !layoutManager.tv; + apiClient.getAvailableRemoteImages(options).then(function (result) { + renderRemoteImages(page, apiClient, result, browsableImageType, options.startIndex, options.limit); - // TODO move card creation code to Card component + page.querySelector('#selectBrowsableImageType').value = browsableImageType; - let html = ''; - - let cssClass = 'card scalableCard imageEditorCard'; - const cardBoxCssClass = 'cardBox visualCardBox'; - - let shape; - 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) { - cssClass += ' show-focus'; - - if (enableFocusTransform) { - cssClass += ' show-animation'; - } - } - - html += '`; - html += '
'; - } - - html += '
'; - // end footer - - html += '
'; - - html += ''; - - return html; - } - - function reloadBrowsableImagesFirstPage(page, apiClient) { - browsableImageStartIndex = 0; - reloadBrowsableImages(page, apiClient); - } - - function initEditor(page, apiClient) { - page.querySelector('#selectBrowsableImageType').addEventListener('change', function () { - browsableImageType = this.value; - selectedProvider = null; - - reloadBrowsableImagesFirstPage(page, apiClient); + const providersHtml = result.Providers.map(function (p) { + return ''; }); - page.querySelector('#selectImageProvider').addEventListener('change', function () { - selectedProvider = this.value; - - reloadBrowsableImagesFirstPage(page, apiClient); - }); - - page.querySelector('#chkAllLanguages').addEventListener('change', function () { - reloadBrowsableImagesFirstPage(page, apiClient); - }); - - page.querySelector('#chkShowParentImages').addEventListener('change', function () { - reloadBrowsableImagesFirstPage(page, apiClient); - }); - - page.addEventListener('click', function (e) { - const btnDownloadRemoteImage = dom.parentWithClass(e.target, 'btnDownloadRemoteImage'); - if (btnDownloadRemoteImage) { - const card = dom.parentWithClass(btnDownloadRemoteImage, 'card'); - downloadRemoteImage(page, apiClient, card.getAttribute('data-imageurl'), card.getAttribute('data-imagetype'), card.getAttribute('data-imageprovider')); - return; - } - - const btnImageCard = dom.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(); - - const apiClient = ServerConnections.getApiClient(serverId); - - currentItemId = itemId; - currentItemType = itemType; - - const dialogOptions = { - removeOnClose: true - }; - - if (layoutManager.tv) { - dialogOptions.size = 'fullscreen'; - } else { - dialogOptions.size = 'small'; - } - - const dlg = dialogHelper.createDialog(dialogOptions); - - dlg.innerHTML = globalize.translateHtml(template, 'core'); - - if (layoutManager.tv) { - scrollHelper.centerFocus.on(dlg, false); - } - - if (browsableParentId) { - dlg.querySelector('#lblShowParentImages').classList.remove('hide'); - } - - // Has to be assigned a z-index after the call to .open() - dlg.addEventListener('close', onDialogClosed); - - dialogHelper.open(dlg); - - const editorContent = dlg.querySelector('.formDialogContent'); - initEditor(editorContent, apiClient); - - dlg.querySelector('.btnCancel').addEventListener('click', function () { - dialogHelper.close(dlg); - }); - - reloadBrowsableImages(editorContent, apiClient); - } - - function onDialogClosed() { - const dlg = this; - - if (layoutManager.tv) { - scrollHelper.centerFocus.off(dlg, false); - } + const selectImageProvider = page.querySelector('#selectImageProvider'); + selectImageProvider.innerHTML = '' + providersHtml; + selectImageProvider.value = provider; loading.hide(); - if (hasChanges) { - currentResolve(); + }); +} + +function renderRemoteImages(page, apiClient, imagesResult, imageType, startIndex, limit) { + page.querySelector('.availableImagesPaging').innerHTML = getPagingHtml(startIndex, limit, imagesResult.TotalRecordCount); + + let html = ''; + + for (let i = 0, length = imagesResult.Images.length; i < length; i++) { + html += getRemoteImageHtml(imagesResult.Images[i], imageType); + } + + const availableImagesList = page.querySelector('.availableImagesList'); + availableImagesList.innerHTML = html; + imageLoader.lazyChildren(availableImagesList); + + const btnNextPage = page.querySelector('.btnNextPage'); + const 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) { + let html = ''; + + const recordsEnd = Math.min(startIndex + limit, totalRecordCount); + + // 20 is the minimum page size + const showControls = totalRecordCount > limit; + + html += '
'; + + html += ''; + + const startAtDisplay = totalRecordCount ? startIndex + 1 : 0; + html += globalize.translate('ListPaging', startAtDisplay, recordsEnd, totalRecordCount); + + html += ''; + + if (showControls) { + html += '
'; + + html += ``; + html += ``; + html += '
'; + } + + html += '
'; + + return html; +} + +function downloadRemoteImage(page, apiClient, url, type, provider) { + const options = getBaseRemoteOptions(page, true); + + options.Type = type; + options.ImageUrl = url; + options.ProviderName = provider; + + loading.show(); + + apiClient.downloadRemoteImage(options).then(function () { + hasChanges = true; + const dlg = dom.parentWithClass(page, 'dialog'); + dialogHelper.close(dlg); + }); +} + +function getRemoteImageHtml(image, imageType) { + const tagName = layoutManager.tv ? 'button' : 'div'; + const enableFooterButtons = !layoutManager.tv; + + // TODO move card creation code to Card component + + let html = ''; + + let cssClass = 'card scalableCard imageEditorCard'; + const cardBoxCssClass = 'cardBox visualCardBox'; + + let shape; + 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 { - currentReject(); + shape = 'portrait'; } } + cssClass += ' ' + shape + 'Card ' + shape + 'Card-scalable'; + if (tagName === 'button') { + cssClass += ' btnImageCard'; + + if (layoutManager.tv) { + cssClass += ' show-focus'; + + if (enableFocusTransform) { + cssClass += ' show-animation'; + } + } + + html += '`; + html += '
'; + } + + html += '
'; + // end footer + + html += '
'; + + html += ''; + + return html; +} + +function reloadBrowsableImagesFirstPage(page, apiClient) { + browsableImageStartIndex = 0; + reloadBrowsableImages(page, apiClient); +} + +function initEditor(page, apiClient) { + page.querySelector('#selectBrowsableImageType').addEventListener('change', function () { + browsableImageType = this.value; + selectedProvider = null; + + reloadBrowsableImagesFirstPage(page, apiClient); + }); + + page.querySelector('#selectImageProvider').addEventListener('change', function () { + selectedProvider = this.value; + + reloadBrowsableImagesFirstPage(page, apiClient); + }); + + page.querySelector('#chkAllLanguages').addEventListener('change', function () { + reloadBrowsableImagesFirstPage(page, apiClient); + }); + + page.querySelector('#chkShowParentImages').addEventListener('change', function () { + reloadBrowsableImagesFirstPage(page, apiClient); + }); + + page.addEventListener('click', function (e) { + const btnDownloadRemoteImage = dom.parentWithClass(e.target, 'btnDownloadRemoteImage'); + if (btnDownloadRemoteImage) { + const card = dom.parentWithClass(btnDownloadRemoteImage, 'card'); + downloadRemoteImage(page, apiClient, card.getAttribute('data-imageurl'), card.getAttribute('data-imagetype'), card.getAttribute('data-imageprovider')); + return; + } + + const btnImageCard = dom.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(); + + const apiClient = ServerConnections.getApiClient(serverId); + + currentItemId = itemId; + currentItemType = itemType; + + const dialogOptions = { + removeOnClose: true + }; + + if (layoutManager.tv) { + dialogOptions.size = 'fullscreen'; + } else { + dialogOptions.size = 'small'; + } + + const dlg = dialogHelper.createDialog(dialogOptions); + + dlg.innerHTML = globalize.translateHtml(template, 'core'); + + if (layoutManager.tv) { + scrollHelper.centerFocus.on(dlg, false); + } + + if (browsableParentId) { + dlg.querySelector('#lblShowParentImages').classList.remove('hide'); + } + + // Has to be assigned a z-index after the call to .open() + dlg.addEventListener('close', onDialogClosed); + + dialogHelper.open(dlg); + + const editorContent = dlg.querySelector('.formDialogContent'); + initEditor(editorContent, apiClient); + + dlg.querySelector('.btnCancel').addEventListener('click', function () { + dialogHelper.close(dlg); + }); + + reloadBrowsableImages(editorContent, apiClient); +} + +function onDialogClosed() { + const dlg = this; + + if (layoutManager.tv) { + scrollHelper.centerFocus.off(dlg, false); + } + + loading.hide(); + if (hasChanges) { + currentResolve(); + } else { + currentReject(); + } +} + export function show(itemId, serverId, itemType, imageType, parentId) { return new Promise(function (resolve, reject) { currentResolve = resolve; @@ -397,4 +395,3 @@ export default { show: show }; -/* eslint-enable indent */ diff --git a/src/components/imageOptionsEditor/imageOptionsEditor.js b/src/components/imageOptionsEditor/imageOptionsEditor.js index 39d2e69df8..0b9e0ad0b7 100644 --- a/src/components/imageOptionsEditor/imageOptionsEditor.js +++ b/src/components/imageOptionsEditor/imageOptionsEditor.js @@ -1,4 +1,3 @@ -/* eslint-disable indent */ /** * Module for image Options Editor. @@ -13,95 +12,95 @@ import '../../elements/emby-select/emby-select'; import '../../elements/emby-input/emby-input'; import template from './imageOptionsEditor.template.html'; - function getDefaultImageConfig(itemType, type) { - return { - Type: type, - MinWidth: 0, - Limit: type === 'Primary' ? 1 : 0 - }; - } +function getDefaultImageConfig(itemType, type) { + return { + Type: type, + MinWidth: 0, + Limit: type === 'Primary' ? 1 : 0 + }; +} - function findImageOptions(imageOptions, type) { - return imageOptions.filter(i => { - return i.Type == type; - })[0]; - } +function findImageOptions(imageOptions, type) { + return imageOptions.filter(i => { + return i.Type == type; + })[0]; +} - function getImageConfig(options, availableOptions, imageType, itemType) { - return findImageOptions(options.ImageOptions || [], imageType) || findImageOptions(availableOptions.DefaultImageOptions || [], imageType) || getDefaultImageConfig(itemType, imageType); - } +function getImageConfig(options, availableOptions, imageType, itemType) { + return findImageOptions(options.ImageOptions || [], imageType) || findImageOptions(availableOptions.DefaultImageOptions || [], imageType) || getDefaultImageConfig(itemType, imageType); +} - function setVisibilityOfBackdrops(elem, visible) { - if (visible) { - elem.classList.remove('hide'); - elem.querySelector('input').setAttribute('required', 'required'); +function setVisibilityOfBackdrops(elem, visible) { + if (visible) { + elem.classList.remove('hide'); + elem.querySelector('input').setAttribute('required', 'required'); + } else { + elem.classList.add('hide'); + elem.querySelector('input').setAttribute('required', ''); + elem.querySelector('input').removeAttribute('required'); + } +} + +function loadValues(context, itemType, options, availableOptions) { + const supportedImageTypes = availableOptions.SupportedImageTypes || []; + setVisibilityOfBackdrops(context.querySelector('.backdropFields'), supportedImageTypes.includes('Backdrop')); + Array.prototype.forEach.call(context.querySelectorAll('.imageType'), i => { + const imageType = i.getAttribute('data-imagetype'); + const container = dom.parentWithTag(i, 'LABEL'); + + if (!supportedImageTypes.includes(imageType)) { + container.classList.add('hide'); } else { - elem.classList.add('hide'); - elem.querySelector('input').setAttribute('required', ''); - elem.querySelector('input').removeAttribute('required'); + container.classList.remove('hide'); } - } - function loadValues(context, itemType, options, availableOptions) { - const supportedImageTypes = availableOptions.SupportedImageTypes || []; - setVisibilityOfBackdrops(context.querySelector('.backdropFields'), supportedImageTypes.includes('Backdrop')); - Array.prototype.forEach.call(context.querySelectorAll('.imageType'), i => { - const imageType = i.getAttribute('data-imagetype'); - const container = dom.parentWithTag(i, 'LABEL'); + if (getImageConfig(options, availableOptions, imageType, itemType).Limit) { + i.checked = true; + } else { + i.checked = false; + } + }); + const backdropConfig = getImageConfig(options, availableOptions, 'Backdrop', itemType); + context.querySelector('#txtMaxBackdrops').value = backdropConfig.Limit; + context.querySelector('#txtMinBackdropDownloadWidth').value = backdropConfig.MinWidth; +} - if (!supportedImageTypes.includes(imageType)) { - container.classList.add('hide'); - } else { - container.classList.remove('hide'); - } +function saveValues(context, options) { + options.ImageOptions = Array.prototype.map.call(context.querySelectorAll('.imageType:not(.hide)'), c => { + return { + Type: c.getAttribute('data-imagetype'), + Limit: c.checked ? 1 : 0, + MinWidth: 0 + }; + }); + options.ImageOptions.push({ + Type: 'Backdrop', + Limit: context.querySelector('#txtMaxBackdrops').value, + MinWidth: context.querySelector('#txtMinBackdropDownloadWidth').value + }); +} - if (getImageConfig(options, availableOptions, imageType, itemType).Limit) { - i.checked = true; - } else { - i.checked = false; - } - }); - const backdropConfig = getImageConfig(options, availableOptions, 'Backdrop', itemType); - context.querySelector('#txtMaxBackdrops').value = backdropConfig.Limit; - context.querySelector('#txtMinBackdropDownloadWidth').value = backdropConfig.MinWidth; - } - - function saveValues(context, options) { - options.ImageOptions = Array.prototype.map.call(context.querySelectorAll('.imageType:not(.hide)'), c => { - return { - Type: c.getAttribute('data-imagetype'), - Limit: c.checked ? 1 : 0, - MinWidth: 0 - }; - }); - options.ImageOptions.push({ - Type: 'Backdrop', - Limit: context.querySelector('#txtMaxBackdrops').value, - MinWidth: context.querySelector('#txtMinBackdropDownloadWidth').value - }); - } - - function showEditor(itemType, options, availableOptions) { - const dlg = dialogHelper.createDialog({ - size: 'small', - removeOnClose: true, - scrollY: false - }); - dlg.classList.add('formDialog'); - dlg.innerHTML = globalize.translateHtml(template); - dlg.addEventListener('close', function () { - saveValues(dlg, options); - }); - loadValues(dlg, itemType, options, availableOptions); - dialogHelper.open(dlg).then(() => { - return; - }).catch(() => { - return; - }); - dlg.querySelector('.btnCancel').addEventListener('click', function () { - dialogHelper.close(dlg); - }); - } +function showEditor(itemType, options, availableOptions) { + const dlg = dialogHelper.createDialog({ + size: 'small', + removeOnClose: true, + scrollY: false + }); + dlg.classList.add('formDialog'); + dlg.innerHTML = globalize.translateHtml(template); + dlg.addEventListener('close', function () { + saveValues(dlg, options); + }); + loadValues(dlg, itemType, options, availableOptions); + dialogHelper.open(dlg).then(() => { + return; + }).catch(() => { + return; + }); + dlg.querySelector('.btnCancel').addEventListener('click', function () { + dialogHelper.close(dlg); + }); +} export class editor { constructor() { @@ -109,5 +108,4 @@ export class editor { } } -/* eslint-enable indent */ export default editor; diff --git a/src/components/imageUploader/imageUploader.js b/src/components/imageUploader/imageUploader.js index 246bf92f7f..c2c70d32ec 100644 --- a/src/components/imageUploader/imageUploader.js +++ b/src/components/imageUploader/imageUploader.js @@ -1,4 +1,3 @@ -/* eslint-disable indent */ /** * Module for imageUploader. @@ -19,169 +18,168 @@ import ServerConnections from '../ServerConnections'; import toast from '../toast/toast'; import template from './imageUploader.template.html'; - let currentItemId; - let currentServerId; - let currentFile; - let hasChanges = false; +let currentItemId; +let currentServerId; +let currentFile; +let hasChanges = false; - function onFileReaderError(evt) { +function onFileReaderError(evt) { + loading.hide(); + + switch (evt.target.error.code) { + case evt.target.error.NOT_FOUND_ERR: + toast(globalize.translate('MessageFileReadError')); + break; + case evt.target.error.ABORT_ERR: + break; // noop + default: + toast(globalize.translate('MessageFileReadError')); + break; + } +} + +function setFiles(page, files) { + const file = files[0]; + + if (!file || !file.type.match('image.*')) { + page.querySelector('#imageOutput').innerHTML = ''; + page.querySelector('#fldUpload').classList.add('hide'); + currentFile = null; + return; + } + + currentFile = file; + + const reader = new FileReader(); + + reader.onerror = onFileReaderError; + reader.onloadstart = () => { + page.querySelector('#fldUpload').classList.add('hide'); + }; + reader.onabort = () => { loading.hide(); + console.debug('File read cancelled'); + }; - switch (evt.target.error.code) { - case evt.target.error.NOT_FOUND_ERR: - toast(globalize.translate('MessageFileReadError')); - break; - case evt.target.error.ABORT_ERR: - break; // noop - default: - toast(globalize.translate('MessageFileReadError')); - break; - } + // Closure to capture the file information. + reader.onload = (theFile => { + return e => { + // Render thumbnail. + const html = [''].join(''); + + page.querySelector('#imageOutput').innerHTML = html; + page.querySelector('#dropImageText').classList.add('hide'); + page.querySelector('#fldUpload').classList.remove('hide'); + }; + })(file); + + // Read in the image file as a data URL. + reader.readAsDataURL(file); +} + +function onSubmit(e) { + const file = currentFile; + + if (!file) { + return false; } - function setFiles(page, files) { - const file = files[0]; - - if (!file || !file.type.match('image.*')) { - page.querySelector('#imageOutput').innerHTML = ''; - page.querySelector('#fldUpload').classList.add('hide'); - currentFile = null; - return; - } - - currentFile = file; - - const reader = new FileReader(); - - reader.onerror = onFileReaderError; - reader.onloadstart = () => { - page.querySelector('#fldUpload').classList.add('hide'); - }; - reader.onabort = () => { - loading.hide(); - console.debug('File read cancelled'); - }; - - // Closure to capture the file information. - reader.onload = (theFile => { - return e => { - // Render thumbnail. - const html = [''].join(''); - - page.querySelector('#imageOutput').innerHTML = html; - page.querySelector('#dropImageText').classList.add('hide'); - page.querySelector('#fldUpload').classList.remove('hide'); - }; - })(file); - - // Read in the image file as a data URL. - reader.readAsDataURL(file); - } - - function onSubmit(e) { - const file = currentFile; - - if (!file) { - return false; - } - - if (!file.type.startsWith('image/')) { - toast(globalize.translate('MessageImageFileTypeAllowed')); - e.preventDefault(); - return false; - } - - loading.show(); - - const dlg = dom.parentWithClass(this, 'dialog'); - - const imageType = dlg.querySelector('#selectImageType').value; - if (imageType === 'None') { - toast(globalize.translate('MessageImageTypeNotSelected')); - e.preventDefault(); - return false; - } - - ServerConnections.getApiClient(currentServerId).uploadItemImage(currentItemId, imageType, file).then(() => { - dlg.querySelector('#uploadImage').value = ''; - - loading.hide(); - hasChanges = true; - dialogHelper.close(dlg); - }); - + if (!file.type.startsWith('image/')) { + toast(globalize.translate('MessageImageFileTypeAllowed')); e.preventDefault(); return false; } - function initEditor(page) { - page.querySelector('form').addEventListener('submit', onSubmit); + loading.show(); - page.querySelector('#uploadImage').addEventListener('change', function () { - setFiles(page, this.files); - }); + const dlg = dom.parentWithClass(this, 'dialog'); - page.querySelector('.btnBrowse').addEventListener('click', () => { - page.querySelector('#uploadImage').click(); - }); + const imageType = dlg.querySelector('#selectImageType').value; + if (imageType === 'None') { + toast(globalize.translate('MessageImageTypeNotSelected')); + e.preventDefault(); + return false; } - function showEditor(options, resolve) { - options = options || {}; + ServerConnections.getApiClient(currentServerId).uploadItemImage(currentItemId, imageType, file).then(() => { + dlg.querySelector('#uploadImage').value = ''; - currentItemId = options.itemId; - currentServerId = options.serverId; + loading.hide(); + hasChanges = true; + dialogHelper.close(dlg); + }); - const dialogOptions = { - removeOnClose: true - }; + e.preventDefault(); + return false; +} +function initEditor(page) { + page.querySelector('form').addEventListener('submit', onSubmit); + + page.querySelector('#uploadImage').addEventListener('change', function () { + setFiles(page, this.files); + }); + + page.querySelector('.btnBrowse').addEventListener('click', () => { + page.querySelector('#uploadImage').click(); + }); +} + +function showEditor(options, resolve) { + options = options || {}; + + currentItemId = options.itemId; + currentServerId = options.serverId; + + const dialogOptions = { + removeOnClose: true + }; + + if (layoutManager.tv) { + dialogOptions.size = 'fullscreen'; + } else { + dialogOptions.size = 'small'; + } + + const dlg = dialogHelper.createDialog(dialogOptions); + + dlg.classList.add('formDialog'); + + dlg.innerHTML = globalize.translateHtml(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', () => { if (layoutManager.tv) { - dialogOptions.size = 'fullscreen'; - } else { - dialogOptions.size = 'small'; + scrollHelper.centerFocus.off(dlg, false); } - const dlg = dialogHelper.createDialog(dialogOptions); + loading.hide(); + resolve(hasChanges); + }); - dlg.classList.add('formDialog'); + dialogHelper.open(dlg); - dlg.innerHTML = globalize.translateHtml(template, 'core'); + initEditor(dlg); - if (layoutManager.tv) { - scrollHelper.centerFocus.on(dlg, false); - } + dlg.querySelector('#selectImageType').value = options.imageType || 'Primary'; - // Has to be assigned a z-index after the call to .open() - dlg.addEventListener('close', () => { - if (layoutManager.tv) { - scrollHelper.centerFocus.off(dlg, false); - } + dlg.querySelector('.btnCancel').addEventListener('click', () => { + dialogHelper.close(dlg); + }); +} - loading.hide(); - resolve(hasChanges); - }); +export function show(options) { + return new Promise(resolve => { + hasChanges = false; - dialogHelper.open(dlg); + showEditor(options, resolve); + }); +} - initEditor(dlg); - - dlg.querySelector('#selectImageType').value = options.imageType || 'Primary'; - - dlg.querySelector('.btnCancel').addEventListener('click', () => { - dialogHelper.close(dlg); - }); - } - - export function show(options) { - return new Promise(resolve => { - hasChanges = false; - - showEditor(options, resolve); - }); - } - -/* eslint-enable indent */ export default { show: show }; diff --git a/src/components/imageeditor/imageeditor.js b/src/components/imageeditor/imageeditor.js index b2c9033bc3..6a7982cca9 100644 --- a/src/components/imageeditor/imageeditor.js +++ b/src/components/imageeditor/imageeditor.js @@ -18,441 +18,439 @@ import alert from '../alert'; import confirm from '../confirm/confirm'; import template from './imageeditor.template.html'; -/* eslint-disable indent */ +const enableFocusTransform = !browser.slow && !browser.edge; - const enableFocusTransform = !browser.slow && !browser.edge; +let currentItem; +let hasChanges = false; - let currentItem; - let hasChanges = false; +function getBaseRemoteOptions() { + return { itemId: currentItem.Id }; +} - function getBaseRemoteOptions() { - return { itemId: currentItem.Id }; +function reload(page, item, focusContext) { + loading.show(); + + let apiClient; + + if (item) { + apiClient = ServerConnections.getApiClient(item.ServerId); + reloadItem(page, item, apiClient, focusContext); + } else { + apiClient = ServerConnections.getApiClient(currentItem.ServerId); + apiClient.getItem(apiClient.getCurrentUserId(), currentItem.Id).then(function (itemToReload) { + reloadItem(page, itemToReload, apiClient, focusContext); + }); } +} - function reload(page, item, focusContext) { - loading.show(); - - let apiClient; - - if (item) { - apiClient = ServerConnections.getApiClient(item.ServerId); - reloadItem(page, item, apiClient, focusContext); - } else { - apiClient = ServerConnections.getApiClient(currentItem.ServerId); - apiClient.getItem(apiClient.getCurrentUserId(), currentItem.Id).then(function (itemToReload) { - reloadItem(page, itemToReload, apiClient, focusContext); - }); +function addListeners(container, className, eventName, fn) { + container.addEventListener(eventName, function (e) { + const elem = dom.parentWithClass(e.target, className); + if (elem) { + fn.call(elem, e); } - } + }); +} - function addListeners(container, className, eventName, fn) { - container.addEventListener(eventName, function (e) { - const elem = dom.parentWithClass(e.target, className); - if (elem) { - fn.call(elem, e); +function reloadItem(page, item, apiClient, focusContext) { + currentItem = item; + + apiClient.getRemoteImageProviders(getBaseRemoteOptions()).then(function (providers) { + const btnBrowseAllImages = page.querySelectorAll('.btnBrowseAllImages'); + for (let i = 0, length = btnBrowseAllImages.length; i < length; i++) { + if (providers.length) { + btnBrowseAllImages[i].classList.remove('hide'); + } else { + btnBrowseAllImages[i].classList.add('hide'); + } + } + + apiClient.getItemImageInfos(currentItem.Id).then(function (imageInfos) { + renderStandardImages(page, apiClient, item, imageInfos, providers); + renderBackdrops(page, apiClient, item, imageInfos, providers); + loading.hide(); + + if (layoutManager.tv) { + focusManager.autoFocus((focusContext || page)); } }); + }); +} + +function getImageUrl(item, apiClient, type, index, options) { + options = options || {}; + options.type = type; + options.index = index; + + if (type === 'Backdrop') { + options.tag = item.BackdropImageTags[index]; + } else if (type === 'Primary') { + options.tag = item.PrimaryImageTag || item.ImageTags[type]; + } else { + options.tag = item.ImageTags[type]; } - function reloadItem(page, item, apiClient, focusContext) { - currentItem = item; + // For search hints + return apiClient.getScaledImageUrl(item.Id || item.ItemId, options); +} - apiClient.getRemoteImageProviders(getBaseRemoteOptions()).then(function (providers) { - const btnBrowseAllImages = page.querySelectorAll('.btnBrowseAllImages'); - for (let i = 0, length = btnBrowseAllImages.length; i < length; i++) { - if (providers.length) { - btnBrowseAllImages[i].classList.remove('hide'); - } else { - btnBrowseAllImages[i].classList.add('hide'); - } +function getCardHtml(image, apiClient, options) { + // TODO move card creation code to Card component + + let html = ''; + + let cssClass = 'card scalableCard imageEditorCard'; + const cardBoxCssClass = 'cardBox visualCardBox'; + + cssClass += ' backdropCard backdropCard-scalable'; + + if (options.tagName === 'button') { + cssClass += ' btnImageCard'; + + if (layoutManager.tv) { + cssClass += ' show-focus'; + + if (enableFocusTransform) { + cssClass += ' show-animation'; + } + } + + html += ''; + } else { + html += ''; } - apiClient.getItemImageInfos(currentItem.Id).then(function (imageInfos) { - renderStandardImages(page, apiClient, item, imageInfos, providers); - renderBackdrops(page, apiClient, item, imageInfos, providers); - loading.hide(); + if (options.index < options.numImages - 1) { + html += ''; + } else { + html += ''; + } + } else { + if (options.imageProviders.length) { + html += ''; + } + } - if (layoutManager.tv) { - focusManager.autoFocus((focusContext || page)); - } - }); - }); + html += ''; + html += '
'; } - function getImageUrl(item, apiClient, type, index, options) { - options = options || {}; - options.type = type; - options.index = index; + html += '
'; + html += '
'; + html += ''; + + return html; +} + +function deleteImage(context, itemId, type, index, apiClient, enableConfirmation) { + const afterConfirm = function () { + apiClient.deleteItemImage(itemId, type, index).then(function () { + hasChanges = true; + reload(context); + }); + }; + + if (!enableConfirmation) { + afterConfirm(); + return; + } + + confirm({ + text: globalize.translate('ConfirmDeleteImage'), + confirmText: globalize.translate('Delete'), + primary: 'delete' + }).then(afterConfirm); +} + +function moveImage(context, apiClient, itemId, type, index, newIndex, focusContext) { + apiClient.updateItemImageIndex(itemId, type, index, newIndex).then(function () { + hasChanges = true; + reload(context, null, focusContext); + }, function () { + alert(globalize.translate('ErrorDefault')); + }); +} + +function renderImages(page, item, apiClient, images, imageProviders, elem) { + let html = ''; + + let imageSize = 300; + const windowSize = dom.getWindowSize(); + if (windowSize.innerWidth >= 1280) { + imageSize = Math.round(windowSize.innerWidth / 4); + } + + const tagName = layoutManager.tv ? 'button' : 'div'; + const enableFooterButtons = !layoutManager.tv; + + for (let i = 0, length = images.length; i < length; i++) { + const image = images[i]; + const options = { index: i, numImages: length, imageProviders, imageSize, tagName, enableFooterButtons }; + html += getCardHtml(image, apiClient, options); + } + + elem.innerHTML = html; + imageLoader.lazyChildren(elem); +} + +function renderStandardImages(page, apiClient, item, imageInfos, imageProviders) { + const images = imageInfos.filter(function (i) { + return i.ImageType !== 'Backdrop' && i.ImageType !== 'Chapter'; + }); + + renderImages(page, item, apiClient, images, imageProviders, page.querySelector('#images')); +} + +function renderBackdrops(page, apiClient, item, imageInfos, imageProviders) { + const images = imageInfos.filter(function (i) { + return i.ImageType === 'Backdrop'; + }).sort(function (a, b) { + return a.ImageIndex - b.ImageIndex; + }); + + if (images.length) { + page.querySelector('#backdropsContainer', page).classList.remove('hide'); + renderImages(page, item, apiClient, images, imageProviders, page.querySelector('#backdrops')); + } else { + page.querySelector('#backdropsContainer', page).classList.add('hide'); + } +} + +function showImageDownloader(page, imageType) { + import('../imageDownloader/imageDownloader').then((ImageDownloader) => { + ImageDownloader.show( + currentItem.Id, + currentItem.ServerId, + currentItem.Type, + imageType, + currentItem.Type == 'Season' ? currentItem.ParentId : null + ).then(function () { + hasChanges = true; + reload(page); + }); + }); +} + +function showActionSheet(context, imageCard) { + const itemId = imageCard.getAttribute('data-id'); + const serverId = imageCard.getAttribute('data-serverid'); + const apiClient = ServerConnections.getApiClient(serverId); + + const type = imageCard.getAttribute('data-imagetype'); + const index = parseInt(imageCard.getAttribute('data-index'), 10); + const providerCount = parseInt(imageCard.getAttribute('data-providers'), 10); + const numImages = parseInt(imageCard.getAttribute('data-numimages'), 10); + + import('../actionSheet/actionSheet').then(({ default: actionSheet }) => { + const commands = []; + + commands.push({ + name: globalize.translate('Delete'), + id: 'delete' + }); if (type === 'Backdrop') { - options.tag = item.BackdropImageTags[index]; - } else if (type === 'Primary') { - options.tag = item.PrimaryImageTag || item.ImageTags[type]; - } else { - options.tag = item.ImageTags[type]; - } - - // For search hints - return apiClient.getScaledImageUrl(item.Id || item.ItemId, options); - } - - function getCardHtml(image, apiClient, options) { - // TODO move card creation code to Card component - - let html = ''; - - let cssClass = 'card scalableCard imageEditorCard'; - const cardBoxCssClass = 'cardBox visualCardBox'; - - cssClass += ' backdropCard backdropCard-scalable'; - - if (options.tagName === 'button') { - cssClass += ' btnImageCard'; - - if (layoutManager.tv) { - cssClass += ' show-focus'; - - if (enableFocusTransform) { - cssClass += ' show-animation'; - } + if (index > 0) { + commands.push({ + name: globalize.translate('MoveLeft'), + id: 'moveleft' + }); } - html += ''; - } else { - html += ''; - } - - if (options.index < options.numImages - 1) { - html += ''; - } else { - html += ''; - } - } else { - if (options.imageProviders.length) { - html += ''; - } + if (index < numImages - 1) { + commands.push({ + name: globalize.translate('MoveRight'), + id: 'moveright' + }); } - - html += ''; - html += '
'; } - html += '
'; - html += '
'; - html += ''; - - return html; - } - - function deleteImage(context, itemId, type, index, apiClient, enableConfirmation) { - const afterConfirm = function () { - apiClient.deleteItemImage(itemId, type, index).then(function () { - hasChanges = true; - reload(context); + if (providerCount) { + commands.push({ + name: globalize.translate('Search'), + id: 'search' }); + } + + actionSheet.show({ + + items: commands, + positionTo: imageCard + + }).then(function (id) { + switch (id) { + case 'delete': + deleteImage(context, itemId, type, index, apiClient, false); + break; + case 'search': + showImageDownloader(context, type); + break; + case 'moveleft': + moveImage(context, apiClient, itemId, type, index, index - 1, dom.parentWithClass(imageCard, 'itemsContainer')); + break; + case 'moveright': + moveImage(context, apiClient, itemId, type, index, index + 1, dom.parentWithClass(imageCard, 'itemsContainer')); + break; + default: + break; + } + }); + }); +} + +function initEditor(context, options) { + const uploadButtons = context.querySelectorAll('.btnOpenUploadMenu'); + const isFileInputSupported = appHost.supports('fileinput'); + for (let i = 0, length = uploadButtons.length; i < length; i++) { + if (isFileInputSupported) { + uploadButtons[i].classList.remove('hide'); + } else { + uploadButtons[i].classList.add('hide'); + } + } + + addListeners(context, 'btnOpenUploadMenu', 'click', function () { + const imageType = this.getAttribute('data-imagetype'); + + import('../imageUploader/imageUploader').then(({ default: imageUploader }) => { + imageUploader.show({ + + theme: options.theme, + imageType: imageType, + itemId: currentItem.Id, + serverId: currentItem.ServerId + + }).then(function (hasChanged) { + if (hasChanged) { + hasChanges = true; + reload(context); + } + }); + }); + }); + + addListeners(context, 'btnSearchImages', 'click', function () { + showImageDownloader(context, this.getAttribute('data-imagetype')); + }); + + addListeners(context, 'btnBrowseAllImages', 'click', function () { + showImageDownloader(context, this.getAttribute('data-imagetype') || 'Primary'); + }); + + addListeners(context, 'btnImageCard', 'click', function () { + showActionSheet(context, this); + }); + + addListeners(context, 'btnDeleteImage', 'click', function () { + const type = this.getAttribute('data-imagetype'); + let index = this.getAttribute('data-index'); + index = index === 'null' ? null : parseInt(index, 10); + const apiClient = ServerConnections.getApiClient(currentItem.ServerId); + deleteImage(context, currentItem.Id, type, index, apiClient, true); + }); + + addListeners(context, 'btnMoveImage', 'click', function () { + const type = this.getAttribute('data-imagetype'); + const index = this.getAttribute('data-index'); + const newIndex = this.getAttribute('data-newindex'); + const apiClient = ServerConnections.getApiClient(currentItem.ServerId); + moveImage(context, apiClient, currentItem.Id, type, index, newIndex, dom.parentWithClass(this, 'itemsContainer')); + }); +} + +function showEditor(options, resolve, reject) { + const itemId = options.itemId; + const serverId = options.serverId; + + loading.show(); + + const apiClient = ServerConnections.getApiClient(serverId); + apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(function (item) { + const dialogOptions = { + removeOnClose: true }; - if (!enableConfirmation) { - afterConfirm(); - return; - } - - confirm({ - text: globalize.translate('ConfirmDeleteImage'), - confirmText: globalize.translate('Delete'), - primary: 'delete' - }).then(afterConfirm); - } - - function moveImage(context, apiClient, itemId, type, index, newIndex, focusContext) { - apiClient.updateItemImageIndex(itemId, type, index, newIndex).then(function () { - hasChanges = true; - reload(context, null, focusContext); - }, function () { - alert(globalize.translate('ErrorDefault')); - }); - } - - function renderImages(page, item, apiClient, images, imageProviders, elem) { - let html = ''; - - let imageSize = 300; - const windowSize = dom.getWindowSize(); - if (windowSize.innerWidth >= 1280) { - imageSize = Math.round(windowSize.innerWidth / 4); - } - - const tagName = layoutManager.tv ? 'button' : 'div'; - const enableFooterButtons = !layoutManager.tv; - - for (let i = 0, length = images.length; i < length; i++) { - const image = images[i]; - const options = { index: i, numImages: length, imageProviders, imageSize, tagName, enableFooterButtons }; - html += getCardHtml(image, apiClient, options); - } - - elem.innerHTML = html; - imageLoader.lazyChildren(elem); - } - - function renderStandardImages(page, apiClient, item, imageInfos, imageProviders) { - const images = imageInfos.filter(function (i) { - return i.ImageType !== 'Backdrop' && i.ImageType !== 'Chapter'; - }); - - renderImages(page, item, apiClient, images, imageProviders, page.querySelector('#images')); - } - - function renderBackdrops(page, apiClient, item, imageInfos, imageProviders) { - const images = imageInfos.filter(function (i) { - return i.ImageType === 'Backdrop'; - }).sort(function (a, b) { - return a.ImageIndex - b.ImageIndex; - }); - - if (images.length) { - page.querySelector('#backdropsContainer', page).classList.remove('hide'); - renderImages(page, item, apiClient, images, imageProviders, page.querySelector('#backdrops')); + if (layoutManager.tv) { + dialogOptions.size = 'fullscreen'; } else { - page.querySelector('#backdropsContainer', page).classList.add('hide'); - } - } - - function showImageDownloader(page, imageType) { - import('../imageDownloader/imageDownloader').then((ImageDownloader) => { - ImageDownloader.show( - currentItem.Id, - currentItem.ServerId, - currentItem.Type, - imageType, - currentItem.Type == 'Season' ? currentItem.ParentId : null - ).then(function () { - hasChanges = true; - reload(page); - }); - }); - } - - function showActionSheet(context, imageCard) { - const itemId = imageCard.getAttribute('data-id'); - const serverId = imageCard.getAttribute('data-serverid'); - const apiClient = ServerConnections.getApiClient(serverId); - - const type = imageCard.getAttribute('data-imagetype'); - const index = parseInt(imageCard.getAttribute('data-index'), 10); - const providerCount = parseInt(imageCard.getAttribute('data-providers'), 10); - const numImages = parseInt(imageCard.getAttribute('data-numimages'), 10); - - import('../actionSheet/actionSheet').then(({ default: actionSheet }) => { - const commands = []; - - commands.push({ - name: globalize.translate('Delete'), - id: 'delete' - }); - - if (type === 'Backdrop') { - if (index > 0) { - commands.push({ - name: globalize.translate('MoveLeft'), - id: 'moveleft' - }); - } - - if (index < numImages - 1) { - commands.push({ - name: globalize.translate('MoveRight'), - id: 'moveright' - }); - } - } - - if (providerCount) { - commands.push({ - name: globalize.translate('Search'), - id: 'search' - }); - } - - actionSheet.show({ - - items: commands, - positionTo: imageCard - - }).then(function (id) { - switch (id) { - case 'delete': - deleteImage(context, itemId, type, index, apiClient, false); - break; - case 'search': - showImageDownloader(context, type); - break; - case 'moveleft': - moveImage(context, apiClient, itemId, type, index, index - 1, dom.parentWithClass(imageCard, 'itemsContainer')); - break; - case 'moveright': - moveImage(context, apiClient, itemId, type, index, index + 1, dom.parentWithClass(imageCard, 'itemsContainer')); - break; - default: - break; - } - }); - }); - } - - function initEditor(context, options) { - const uploadButtons = context.querySelectorAll('.btnOpenUploadMenu'); - const isFileInputSupported = appHost.supports('fileinput'); - for (let i = 0, length = uploadButtons.length; i < length; i++) { - if (isFileInputSupported) { - uploadButtons[i].classList.remove('hide'); - } else { - uploadButtons[i].classList.add('hide'); - } + dialogOptions.size = 'small'; } - addListeners(context, 'btnOpenUploadMenu', 'click', function () { - const imageType = this.getAttribute('data-imagetype'); + const dlg = dialogHelper.createDialog(dialogOptions); - import('../imageUploader/imageUploader').then(({ default: imageUploader }) => { - imageUploader.show({ + dlg.classList.add('formDialog'); - theme: options.theme, - imageType: imageType, - itemId: currentItem.Id, - serverId: currentItem.ServerId + dlg.innerHTML = globalize.translateHtml(template, 'core'); - }).then(function (hasChanged) { - if (hasChanged) { - hasChanges = true; - reload(context); - } - }); - }); - }); + if (layoutManager.tv) { + scrollHelper.centerFocus.on(dlg, false); + } - addListeners(context, 'btnSearchImages', 'click', function () { - showImageDownloader(context, this.getAttribute('data-imagetype')); - }); - - addListeners(context, 'btnBrowseAllImages', 'click', function () { - showImageDownloader(context, this.getAttribute('data-imagetype') || 'Primary'); - }); - - addListeners(context, 'btnImageCard', 'click', function () { - showActionSheet(context, this); - }); - - addListeners(context, 'btnDeleteImage', 'click', function () { - const type = this.getAttribute('data-imagetype'); - let index = this.getAttribute('data-index'); - index = index === 'null' ? null : parseInt(index, 10); - const apiClient = ServerConnections.getApiClient(currentItem.ServerId); - deleteImage(context, currentItem.Id, type, index, apiClient, true); - }); - - addListeners(context, 'btnMoveImage', 'click', function () { - const type = this.getAttribute('data-imagetype'); - const index = this.getAttribute('data-index'); - const newIndex = this.getAttribute('data-newindex'); - const apiClient = ServerConnections.getApiClient(currentItem.ServerId); - moveImage(context, apiClient, currentItem.Id, type, index, newIndex, dom.parentWithClass(this, 'itemsContainer')); - }); - } - - function showEditor(options, resolve, reject) { - const itemId = options.itemId; - const serverId = options.serverId; - - loading.show(); - - const apiClient = ServerConnections.getApiClient(serverId); - apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(function (item) { - const dialogOptions = { - removeOnClose: true - }; + initEditor(dlg, options); + // Has to be assigned a z-index after the call to .open() + dlg.addEventListener('close', function () { if (layoutManager.tv) { - dialogOptions.size = 'fullscreen'; + scrollHelper.centerFocus.off(dlg, false); + } + + loading.hide(); + + if (hasChanges) { + resolve(); } else { - dialogOptions.size = 'small'; + reject(); } - - const dlg = dialogHelper.createDialog(dialogOptions); - - dlg.classList.add('formDialog'); - - dlg.innerHTML = globalize.translateHtml(template, 'core'); - - if (layoutManager.tv) { - scrollHelper.centerFocus.on(dlg, false); - } - - initEditor(dlg, options); - - // Has to be assigned a z-index after the call to .open() - dlg.addEventListener('close', function () { - if (layoutManager.tv) { - scrollHelper.centerFocus.off(dlg, false); - } - - loading.hide(); - - if (hasChanges) { - resolve(); - } else { - reject(); - } - }); - - dialogHelper.open(dlg); - - reload(dlg, item); - - dlg.querySelector('.btnCancel').addEventListener('click', function () { - dialogHelper.close(dlg); - }); }); - } + + dialogHelper.open(dlg); + + reload(dlg, item); + + dlg.querySelector('.btnCancel').addEventListener('click', function () { + dialogHelper.close(dlg); + }); + }); +} export function show (options) { return new Promise(function (resolve, reject) { @@ -465,4 +463,3 @@ export default { show }; -/* eslint-enable indent */ diff --git a/src/components/images/imageLoader.js b/src/components/images/imageLoader.js index d1095a01dc..beb4bb31a5 100644 --- a/src/components/images/imageLoader.js +++ b/src/components/images/imageLoader.js @@ -17,237 +17,235 @@ worker.addEventListener( } } ); -/* eslint-disable indent */ - export function lazyImage(elem, source = elem.getAttribute('data-src')) { - if (!source) { - return; - } - - fillImageElement(elem, source); +export function lazyImage(elem, source = elem.getAttribute('data-src')) { + if (!source) { + return; } - function drawBlurhash(target, pixels, width, height) { - const canvas = document.createElement('canvas'); - canvas.setAttribute('aria-hidden', 'true'); - canvas.width = width; - canvas.height = height; - const ctx = canvas.getContext('2d'); - const imgData = ctx.createImageData(width, height); + fillImageElement(elem, source); +} - imgData.data.set(pixels); - ctx.putImageData(imgData, 0, 0); +function drawBlurhash(target, pixels, width, height) { + const canvas = document.createElement('canvas'); + canvas.setAttribute('aria-hidden', 'true'); + canvas.width = width; + canvas.height = height; + const ctx = canvas.getContext('2d'); + const imgData = ctx.createImageData(width, height); - requestAnimationFrame(() => { - // This class is just an utility class, so users can customize the canvas using their own CSS. - canvas.classList.add('blurhash-canvas'); + imgData.data.set(pixels); + ctx.putImageData(imgData, 0, 0); - target.parentNode.insertBefore(canvas, target); - target.classList.add('blurhashed'); - target.removeAttribute('data-blurhash'); + requestAnimationFrame(() => { + // This class is just an utility class, so users can customize the canvas using their own CSS. + canvas.classList.add('blurhash-canvas'); + + target.parentNode.insertBefore(canvas, target); + target.classList.add('blurhashed'); + target.removeAttribute('data-blurhash'); + }); +} + +function itemBlurhashing(target, hash) { + try { + // Although the default values recommended by Blurhash developers is 32x32, a size of 20x20 seems to be the sweet spot for us, + // improving the performance and reducing the memory usage, while retaining almost full blur quality. + // Lower values had more visible pixelation + const width = 20; + const height = 20; + targetDic[hash] = (targetDic[hash] || []).filter(item => item !== target); + targetDic[hash].push(target); + + worker.postMessage({ + hash, + width, + height }); + } catch (err) { + console.error(err); + target.classList.add('non-blurhashable'); + return; + } +} + +export function fillImage(entry) { + if (!entry) { + throw new Error('entry cannot be null'); + } + const target = entry.target; + let source = undefined; + + if (target) { + source = target.getAttribute('data-src'); + } else { + source = entry; } - function itemBlurhashing(target, hash) { - try { - // Although the default values recommended by Blurhash developers is 32x32, a size of 20x20 seems to be the sweet spot for us, - // improving the performance and reducing the memory usage, while retaining almost full blur quality. - // Lower values had more visible pixelation - const width = 20; - const height = 20; - targetDic[hash] = (targetDic[hash] || []).filter(item => item !== target); - targetDic[hash].push(target); - - worker.postMessage({ - hash, - width, - height - }); - } catch (err) { - console.error(err); - target.classList.add('non-blurhashable'); - return; + if (entry.isIntersecting) { + if (source) { + fillImageElement(target, source); } + } else if (!source) { + emptyImageElement(target); } +} - export function fillImage(entry) { - if (!entry) { - throw new Error('entry cannot be null'); - } - const target = entry.target; - let source = undefined; - - if (target) { - source = target.getAttribute('data-src'); - } else { - source = entry; - } - - if (entry.isIntersecting) { - if (source) { - fillImageElement(target, source); - } - } else if (!source) { - emptyImageElement(target); - } - } - - function onAnimationEnd(event) { - const elem = event.target; - requestAnimationFrame(() => { - const canvas = elem.previousSibling; - if (elem.classList.contains('blurhashed') && canvas?.tagName === 'CANVAS') { - canvas.classList.add('lazy-hidden'); - } - - // HACK: Hide the content of the card padder - elem.parentNode?.querySelector('.cardPadder')?.classList.add('lazy-hidden-children'); - }); - elem.removeEventListener('animationend', onAnimationEnd); - } - - function fillImageElement(elem, url) { - if (url === undefined) { - throw new TypeError('url cannot be undefined'); - } - - const preloaderImg = new Image(); - preloaderImg.src = url; - - elem.classList.add('lazy-hidden'); - elem.addEventListener('animationend', onAnimationEnd); - - preloaderImg.addEventListener('load', () => { - requestAnimationFrame(() => { - if (elem.tagName !== 'IMG') { - elem.style.backgroundImage = "url('" + url + "')"; - } else { - elem.setAttribute('src', url); - } - elem.removeAttribute('data-src'); - - if (userSettings.enableFastFadein()) { - elem.classList.add('lazy-image-fadein-fast'); - } else { - elem.classList.add('lazy-image-fadein'); - } - elem.classList.remove('lazy-hidden'); - }); - }); - } - - function emptyImageElement(elem) { - elem.removeEventListener('animationend', onAnimationEnd); +function onAnimationEnd(event) { + const elem = event.target; + requestAnimationFrame(() => { const canvas = elem.previousSibling; - if (canvas?.tagName === 'CANVAS') { - canvas.classList.remove('lazy-hidden'); + if (elem.classList.contains('blurhashed') && canvas?.tagName === 'CANVAS') { + canvas.classList.add('lazy-hidden'); } - // HACK: Unhide the content of the card padder - elem.parentNode?.querySelector('.cardPadder')?.classList.remove('lazy-hidden-children'); + // HACK: Hide the content of the card padder + elem.parentNode?.querySelector('.cardPadder')?.classList.add('lazy-hidden-children'); + }); + elem.removeEventListener('animationend', onAnimationEnd); +} - let url; - - if (elem.tagName !== 'IMG') { - url = elem.style.backgroundImage.slice(4, -1).replace(/"/g, ''); - elem.style.backgroundImage = 'none'; - } else { - url = elem.getAttribute('src'); - elem.setAttribute('src', ''); - } - elem.setAttribute('data-src', url); - - elem.classList.remove('lazy-image-fadein-fast', 'lazy-image-fadein'); - elem.classList.add('lazy-hidden'); +function fillImageElement(elem, url) { + if (url === undefined) { + throw new TypeError('url cannot be undefined'); } - export function lazyChildren(elem) { - if (userSettings.enableBlurhash()) { - for (const lazyElem of elem.querySelectorAll('.lazy')) { - const blurhashstr = lazyElem.getAttribute('data-blurhash'); - if (!lazyElem.classList.contains('blurhashed', 'non-blurhashable') && blurhashstr) { - itemBlurhashing(lazyElem, blurhashstr); - } else if (!blurhashstr && !lazyElem.classList.contains('blurhashed')) { - lazyElem.classList.add('non-blurhashable'); - } + const preloaderImg = new Image(); + preloaderImg.src = url; + + elem.classList.add('lazy-hidden'); + elem.addEventListener('animationend', onAnimationEnd); + + preloaderImg.addEventListener('load', () => { + requestAnimationFrame(() => { + if (elem.tagName !== 'IMG') { + elem.style.backgroundImage = "url('" + url + "')"; + } else { + elem.setAttribute('src', url); } - } + elem.removeAttribute('data-src'); - lazyLoader.lazyChildren(elem, fillImage); - } - - export function getPrimaryImageAspectRatio(items) { - const values = []; - - for (let i = 0, length = items.length; i < length; i++) { - const ratio = items[i].PrimaryImageAspectRatio || 0; - - if (!ratio) { - continue; + if (userSettings.enableFastFadein()) { + elem.classList.add('lazy-image-fadein-fast'); + } else { + elem.classList.add('lazy-image-fadein'); } - - values[values.length] = ratio; - } - - if (!values.length) { - return null; - } - - // Use the median - values.sort(function (a, b) { - return a - b; + elem.classList.remove('lazy-hidden'); }); + }); +} - const half = Math.floor(values.length / 2); - - let result; - - if (values.length % 2) { - result = values[half]; - } else { - result = (values[half - 1] + values[half]) / 2.0; - } - - // If really close to 2:3 (poster image), just return 2:3 - const aspect2x3 = 2 / 3; - if (Math.abs(aspect2x3 - result) <= 0.15) { - return aspect2x3; - } - - // If really close to 16:9 (episode image), just return 16:9 - const aspect16x9 = 16 / 9; - if (Math.abs(aspect16x9 - result) <= 0.2) { - return aspect16x9; - } - - // If really close to 1 (square image), just return 1 - if (Math.abs(1 - result) <= 0.15) { - return 1; - } - - // If really close to 4:3 (poster image), just return 2:3 - const aspect4x3 = 4 / 3; - if (Math.abs(aspect4x3 - result) <= 0.15) { - return aspect4x3; - } - - return result; +function emptyImageElement(elem) { + elem.removeEventListener('animationend', onAnimationEnd); + const canvas = elem.previousSibling; + if (canvas?.tagName === 'CANVAS') { + canvas.classList.remove('lazy-hidden'); } - export function fillImages(elems) { - for (let i = 0, length = elems.length; i < length; i++) { - const elem = elems[0]; - fillImage(elem); + // HACK: Unhide the content of the card padder + elem.parentNode?.querySelector('.cardPadder')?.classList.remove('lazy-hidden-children'); + + let url; + + if (elem.tagName !== 'IMG') { + url = elem.style.backgroundImage.slice(4, -1).replace(/"/g, ''); + elem.style.backgroundImage = 'none'; + } else { + url = elem.getAttribute('src'); + elem.setAttribute('src', ''); + } + elem.setAttribute('data-src', url); + + elem.classList.remove('lazy-image-fadein-fast', 'lazy-image-fadein'); + elem.classList.add('lazy-hidden'); +} + +export function lazyChildren(elem) { + if (userSettings.enableBlurhash()) { + for (const lazyElem of elem.querySelectorAll('.lazy')) { + const blurhashstr = lazyElem.getAttribute('data-blurhash'); + if (!lazyElem.classList.contains('blurhashed', 'non-blurhashable') && blurhashstr) { + itemBlurhashing(lazyElem, blurhashstr); + } else if (!blurhashstr && !lazyElem.classList.contains('blurhashed')) { + lazyElem.classList.add('non-blurhashable'); + } } } - export function setLazyImage(element, url) { - element.classList.add('lazy'); - element.setAttribute('data-src', url); - lazyImage(element); + lazyLoader.lazyChildren(elem, fillImage); +} + +export function getPrimaryImageAspectRatio(items) { + const values = []; + + for (let i = 0, length = items.length; i < length; i++) { + const ratio = items[i].PrimaryImageAspectRatio || 0; + + if (!ratio) { + continue; + } + + values[values.length] = ratio; } -/* eslint-enable indent */ + if (!values.length) { + return null; + } + + // Use the median + values.sort(function (a, b) { + return a - b; + }); + + const half = Math.floor(values.length / 2); + + let result; + + if (values.length % 2) { + result = values[half]; + } else { + result = (values[half - 1] + values[half]) / 2.0; + } + + // If really close to 2:3 (poster image), just return 2:3 + const aspect2x3 = 2 / 3; + if (Math.abs(aspect2x3 - result) <= 0.15) { + return aspect2x3; + } + + // If really close to 16:9 (episode image), just return 16:9 + const aspect16x9 = 16 / 9; + if (Math.abs(aspect16x9 - result) <= 0.2) { + return aspect16x9; + } + + // If really close to 1 (square image), just return 1 + if (Math.abs(1 - result) <= 0.15) { + return 1; + } + + // If really close to 4:3 (poster image), just return 2:3 + const aspect4x3 = 4 / 3; + if (Math.abs(aspect4x3 - result) <= 0.15) { + return aspect4x3; + } + + return result; +} + +export function fillImages(elems) { + for (let i = 0, length = elems.length; i < length; i++) { + const elem = elems[0]; + fillImage(elem); + } +} + +export function setLazyImage(element, url) { + element.classList.add('lazy'); + element.setAttribute('data-src', url); + lazyImage(element); +} + export default { setLazyImage: setLazyImage, fillImages: fillImages, diff --git a/src/components/itemContextMenu.js b/src/components/itemContextMenu.js index e132c706e3..a90effb336 100644 --- a/src/components/itemContextMenu.js +++ b/src/components/itemContextMenu.js @@ -9,636 +9,633 @@ import { playbackManager } from './playback/playbackmanager'; import ServerConnections from './ServerConnections'; import toast from './toast/toast'; -/* eslint-disable indent */ - export function getCommands(options) { - const item = options.item; - const user = options.user; +export function getCommands(options) { + const item = options.item; + const user = options.user; - const canPlay = playbackManager.canPlay(item); + const canPlay = playbackManager.canPlay(item); - const commands = []; + const commands = []; - if (canPlay && item.MediaType !== 'Photo') { - if (options.play !== false) { - commands.push({ - name: globalize.translate('Play'), - id: 'resume', - icon: 'play_arrow' - }); - } - - if (options.playAllFromHere && item.Type !== 'Program' && item.Type !== 'TvChannel') { - commands.push({ - name: globalize.translate('PlayAllFromHere'), - id: 'playallfromhere', - icon: 'play_arrow' - }); - } + if (canPlay && item.MediaType !== 'Photo') { + if (options.play !== false) { + commands.push({ + name: globalize.translate('Play'), + id: 'resume', + icon: 'play_arrow' + }); } - if (playbackManager.getCurrentPlayer() !== null) { - if (options.stopPlayback) { - commands.push({ - name: globalize.translate('StopPlayback'), - id: 'stopPlayback', - icon: 'stop' - }); - } - if (options.clearQueue) { - commands.push({ - name: globalize.translate('ClearQueue'), - id: 'clearQueue', - icon: 'clear_all' - }); - } + if (options.playAllFromHere && item.Type !== 'Program' && item.Type !== 'TvChannel') { + commands.push({ + name: globalize.translate('PlayAllFromHere'), + id: 'playallfromhere', + icon: 'play_arrow' + }); + } + } + + if (playbackManager.getCurrentPlayer() !== null) { + if (options.stopPlayback) { + commands.push({ + name: globalize.translate('StopPlayback'), + id: 'stopPlayback', + icon: 'stop' + }); + } + if (options.clearQueue) { + commands.push({ + name: globalize.translate('ClearQueue'), + id: 'clearQueue', + icon: 'clear_all' + }); + } + } + + if (playbackManager.canQueue(item)) { + if (options.queue !== false) { + commands.push({ + name: globalize.translate('AddToPlayQueue'), + id: 'queue', + icon: 'playlist_add' + }); } - if (playbackManager.canQueue(item)) { - if (options.queue !== false) { - commands.push({ - name: globalize.translate('AddToPlayQueue'), - id: 'queue', - icon: 'playlist_add' - }); - } - - if (options.queue !== false) { - commands.push({ - name: globalize.translate('PlayNext'), - id: 'queuenext', - icon: 'playlist_add' - }); - } + if (options.queue !== false) { + commands.push({ + name: globalize.translate('PlayNext'), + id: 'queuenext', + icon: 'playlist_add' + }); } + } - if ((item.IsFolder || item.Type === 'MusicArtist' || item.Type === 'MusicGenre') + if ((item.IsFolder || item.Type === 'MusicArtist' || item.Type === 'MusicGenre') && item.CollectionType !== 'livetv' && options.shuffle !== false - ) { - commands.push({ - name: globalize.translate('Shuffle'), - id: 'shuffle', - icon: 'shuffle' - }); - } + ) { + commands.push({ + name: globalize.translate('Shuffle'), + id: 'shuffle', + icon: 'shuffle' + }); + } - if ((item.MediaType === 'Audio' || item.Type === 'MusicAlbum' || item.Type === 'MusicArtist' || item.Type === 'MusicGenre') + if ((item.MediaType === 'Audio' || item.Type === 'MusicAlbum' || item.Type === 'MusicArtist' || item.Type === 'MusicGenre') && options.instantMix !== false && !itemHelper.isLocalItem(item) - ) { + ) { + commands.push({ + name: globalize.translate('InstantMix'), + id: 'instantmix', + icon: 'explore' + }); + } + + if (commands.length) { + commands.push({ + divider: true + }); + } + + if (!browser.tv) { + if (itemHelper.supportsAddingToCollection(item) && options.EnableCollectionManagement) { commands.push({ - name: globalize.translate('InstantMix'), - id: 'instantmix', - icon: 'explore' + name: globalize.translate('AddToCollection'), + id: 'addtocollection', + icon: 'playlist_add' }); } - if (commands.length) { + if (itemHelper.supportsAddingToPlaylist(item) && options.playlist !== false) { commands.push({ - divider: true + name: globalize.translate('AddToPlaylist'), + id: 'addtoplaylist', + icon: 'playlist_add' }); } + } - if (!browser.tv) { - if (itemHelper.supportsAddingToCollection(item) && options.EnableCollectionManagement) { - commands.push({ - name: globalize.translate('AddToCollection'), - id: 'addtocollection', - icon: 'playlist_add' - }); - } + if ((item.Type === 'Timer') && user.Policy.EnableLiveTvManagement && options.cancelTimer !== false) { + commands.push({ + name: globalize.translate('CancelRecording'), + id: 'canceltimer', + icon: 'cancel' + }); + } - if (itemHelper.supportsAddingToPlaylist(item) && options.playlist !== false) { - commands.push({ - name: globalize.translate('AddToPlaylist'), - id: 'addtoplaylist', - icon: 'playlist_add' - }); - } - } + if ((item.Type === 'Recording' && item.Status === 'InProgress') && user.Policy.EnableLiveTvManagement && options.cancelTimer !== false) { + commands.push({ + name: globalize.translate('CancelRecording'), + id: 'canceltimer', + icon: 'cancel' + }); + } - if ((item.Type === 'Timer') && user.Policy.EnableLiveTvManagement && options.cancelTimer !== false) { + if ((item.Type === 'SeriesTimer') && user.Policy.EnableLiveTvManagement && options.cancelTimer !== false) { + commands.push({ + name: globalize.translate('CancelSeries'), + id: 'cancelseriestimer', + icon: 'cancel' + }); + } + + if (item.Type === 'Season' || item.Type == 'Series') { + commands.push({ + name: globalize.translate('DownloadAll'), + id: 'downloadall', + icon: 'file_download' + }); + } + + if (item.CanDelete && options.deleteItem !== false) { + if (item.Type === 'Playlist' || item.Type === 'BoxSet') { commands.push({ - name: globalize.translate('CancelRecording'), - id: 'canceltimer', - icon: 'cancel' + name: globalize.translate('Delete'), + id: 'delete', + icon: 'delete' + }); + } else { + commands.push({ + name: globalize.translate('DeleteMedia'), + id: 'delete', + icon: 'delete' }); } + } - if ((item.Type === 'Recording' && item.Status === 'InProgress') && user.Policy.EnableLiveTvManagement && options.cancelTimer !== false) { - commands.push({ - name: globalize.translate('CancelRecording'), - id: 'canceltimer', - icon: 'cancel' - }); - } + // Books are promoted to major download Button and therefor excluded in the context menu + if ((item.CanDownload && appHost.supports('filedownload')) && item.Type !== 'Book') { + commands.push({ + name: globalize.translate('Download'), + id: 'download', + icon: 'file_download' + }); - if ((item.Type === 'SeriesTimer') && user.Policy.EnableLiveTvManagement && options.cancelTimer !== false) { - commands.push({ - name: globalize.translate('CancelSeries'), - id: 'cancelseriestimer', - icon: 'cancel' - }); - } + commands.push({ + name: globalize.translate('CopyStreamURL'), + id: 'copy-stream', + icon: 'content_copy' + }); + } - if (item.Type === 'Season' || item.Type == 'Series') { - commands.push({ - name: globalize.translate('DownloadAll'), - id: 'downloadall', - icon: 'file_download' - }); - } + if (commands.length) { + commands.push({ + divider: true + }); + } - if (item.CanDelete && options.deleteItem !== false) { - if (item.Type === 'Playlist' || item.Type === 'BoxSet') { - commands.push({ - name: globalize.translate('Delete'), - id: 'delete', - icon: 'delete' - }); - } else { - commands.push({ - name: globalize.translate('DeleteMedia'), - id: 'delete', - icon: 'delete' - }); - } - } + const canEdit = itemHelper.canEdit(user, item); + if (canEdit && options.edit !== false && item.Type !== 'SeriesTimer') { + const text = (item.Type === 'Timer' || item.Type === 'SeriesTimer') ? globalize.translate('Edit') : globalize.translate('EditMetadata'); + commands.push({ + name: text, + id: 'edit', + icon: 'edit' + }); + } - // Books are promoted to major download Button and therefor excluded in the context menu - if ((item.CanDownload && appHost.supports('filedownload')) && item.Type !== 'Book') { - commands.push({ - name: globalize.translate('Download'), - id: 'download', - icon: 'file_download' - }); + if (itemHelper.canEditImages(user, item) && options.editImages !== false) { + commands.push({ + name: globalize.translate('EditImages'), + id: 'editimages', + icon: 'image' + }); + } - commands.push({ - name: globalize.translate('CopyStreamURL'), - id: 'copy-stream', - icon: 'content_copy' - }); - } - - if (commands.length) { - commands.push({ - divider: true - }); - } - - const canEdit = itemHelper.canEdit(user, item); - if (canEdit && options.edit !== false && item.Type !== 'SeriesTimer') { - const text = (item.Type === 'Timer' || item.Type === 'SeriesTimer') ? globalize.translate('Edit') : globalize.translate('EditMetadata'); - commands.push({ - name: text, - id: 'edit', - icon: 'edit' - }); - } - - if (itemHelper.canEditImages(user, item) && options.editImages !== false) { - commands.push({ - name: globalize.translate('EditImages'), - id: 'editimages', - icon: 'image' - }); - } - - if (canEdit && item.MediaType === 'Video' && item.Type !== 'TvChannel' && item.Type !== 'Program' + if (canEdit && item.MediaType === 'Video' && item.Type !== 'TvChannel' && item.Type !== 'Program' && item.LocationType !== 'Virtual' && !(item.Type === 'Recording' && item.Status !== 'Completed') && options.editSubtitles !== false - ) { - commands.push({ - name: globalize.translate('EditSubtitles'), - id: 'editsubtitles', - icon: 'closed_caption' - }); - } - - if (options.identify !== false && itemHelper.canIdentify(user, item)) { - commands.push({ - name: globalize.translate('Identify'), - id: 'identify', - icon: 'edit' - }); - } - - if (item.MediaSources && options.moremediainfo !== false) { - commands.push({ - name: globalize.translate('MoreMediaInfo'), - id: 'moremediainfo', - icon: 'info' - }); - } - - if (item.Type === 'Program' && options.record !== false) { - if (item.TimerId) { - commands.push({ - name: globalize.translate('ManageRecording'), - id: 'record', - icon: 'fiber_manual_record' - }); - } else { - commands.push({ - name: globalize.translate('Record'), - id: 'record', - icon: 'fiber_manual_record' - }); - } - } - - if (itemHelper.canRefreshMetadata(item, user)) { - commands.push({ - name: globalize.translate('RefreshMetadata'), - id: 'refresh', - icon: 'refresh' - }); - } - - if (item.PlaylistItemId && options.playlistId) { - commands.push({ - name: globalize.translate('RemoveFromPlaylist'), - id: 'removefromplaylist', - icon: 'remove' - }); - } - - if (options.collectionId) { - commands.push({ - name: globalize.translate('RemoveFromCollection'), - id: 'removefromcollection', - icon: 'remove' - }); - } - - if (!browser.tv && options.share === true && itemHelper.canShare(item, user)) { - commands.push({ - name: globalize.translate('Share'), - id: 'share', - icon: 'share' - }); - } - - if (options.sync !== false && itemHelper.canSync(user, item)) { - commands.push({ - name: globalize.translate('Sync'), - id: 'sync', - icon: 'sync' - }); - } - - if (options.openAlbum !== false && item.AlbumId && item.MediaType !== 'Photo') { - commands.push({ - name: globalize.translate('ViewAlbum'), - id: 'album', - icon: 'album' - }); - } - // Show Album Artist by default, as a song can have multiple artists, which specific one would this option refer to? - // Although some albums can have multiple artists, it's not as common as songs. - if (options.openArtist !== false && item.AlbumArtists && item.AlbumArtists.length) { - commands.push({ - name: globalize.translate('ViewAlbumArtist'), - id: 'artist', - icon: 'person' - }); - } - - return commands; - } - - function getResolveFunction(resolve, id, changed, deleted) { - return function () { - resolve({ - command: id, - updated: changed, - deleted: deleted - }); - }; - } - - function executeCommand(item, id, options) { - const itemId = item.Id; - const serverId = item.ServerId; - const apiClient = ServerConnections.getApiClient(serverId); - - return new Promise(function (resolve, reject) { - // eslint-disable-next-line sonarjs/max-switch-cases - switch (id) { - case 'addtocollection': - import('./collectionEditor/collectionEditor').then(({ default: CollectionEditor }) => { - const collectionEditor = new CollectionEditor(); - collectionEditor.show({ - items: [itemId], - serverId: serverId - }).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id)); - }); - break; - case 'addtoplaylist': - import('./playlisteditor/playlisteditor').then(({ default: playlistEditor }) => { - new playlistEditor({ - items: [itemId], - serverId: serverId - }).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id)); - }); - break; - case 'download': - import('../scripts/fileDownloader').then((fileDownloader) => { - const downloadHref = apiClient.getItemDownloadUrl(itemId); - fileDownloader.download([{ - url: downloadHref, - itemId: itemId, - serverId: serverId, - title: item.Name, - filename: item.Path.replace(/^.*[\\/]/, '') - }]); - getResolveFunction(getResolveFunction(resolve, id), id)(); - }); - break; - case 'downloadall': { - const downloadEpisodes = episodes => { - import('../scripts/fileDownloader').then((fileDownloader) => { - const downloads = episodes.map(episode => { - const downloadHref = apiClient.getItemDownloadUrl(episode.Id); - return { - url: downloadHref, - itemId: episode.Id, - serverId: serverId, - title: episode.Name, - filename: episode.Path.replace(/^.*[\\/]/, '') - }; - }); - - fileDownloader.download(downloads); - }); - }; - const downloadSeasons = seasons => { - Promise.all(seasons.map(seasonItem => { - return apiClient.getEpisodes(seasonItem.SeriesId, { - seasonId: seasonItem.Id, - userId: options.user.Id, - Fields: 'CanDownload,Path' - }); - } - )).then(seasonData => { - downloadEpisodes(seasonData.map(season => season.Items).flat()); - }); - }; - - if (item.Type === 'Season') { - downloadSeasons([item]); - } else if (item.Type === 'Series') { - apiClient.getSeasons(item.Id, { - userId: options.user.Id, - Fields: 'ItemCounts' - }).then(seasons => downloadSeasons(seasons.Items)); - } - - getResolveFunction(getResolveFunction(resolve, id), id)(); - break; - } - case 'copy-stream': { - const downloadHref = apiClient.getItemDownloadUrl(itemId); - copy(downloadHref).then(() => { - toast(globalize.translate('CopyStreamURLSuccess')); - }).catch(() => { - prompt(globalize.translate('CopyStreamURL'), downloadHref); - }); - getResolveFunction(resolve, id)(); - break; - } - case 'editsubtitles': - import('./subtitleeditor/subtitleeditor').then(({ default: subtitleEditor }) => { - subtitleEditor.show(itemId, serverId).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id)); - }); - break; - case 'edit': - editItem(apiClient, item).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id)); - break; - case 'editimages': - import('./imageeditor/imageeditor').then((imageEditor) => { - imageEditor.show({ - itemId: itemId, - serverId: serverId - }).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id)); - }); - break; - case 'identify': - import('./itemidentifier/itemidentifier').then((itemIdentifier) => { - itemIdentifier.show(itemId, serverId).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id)); - }); - break; - case 'moremediainfo': - import('./itemMediaInfo/itemMediaInfo').then((itemMediaInfo) => { - itemMediaInfo.show(itemId, serverId).then(getResolveFunction(resolve, id), getResolveFunction(resolve, id)); - }); - break; - case 'refresh': - refresh(apiClient, item); - getResolveFunction(resolve, id)(); - break; - case 'open': - appRouter.showItem(item); - getResolveFunction(resolve, id)(); - break; - case 'play': - play(item, false); - getResolveFunction(resolve, id)(); - break; - case 'resume': - play(item, true); - getResolveFunction(resolve, id)(); - break; - case 'queue': - play(item, false, true); - getResolveFunction(resolve, id)(); - break; - case 'queuenext': - play(item, false, true, true); - getResolveFunction(resolve, id)(); - break; - case 'stopPlayback': - playbackManager.stop(); - break; - case 'clearQueue': - playbackManager.clearQueue(); - break; - case 'record': - import('./recordingcreator/recordingcreator').then(({ default: recordingCreator }) => { - recordingCreator.show(itemId, serverId).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id)); - }); - break; - case 'shuffle': - playbackManager.shuffle(item); - getResolveFunction(resolve, id)(); - break; - case 'instantmix': - playbackManager.instantMix(item); - getResolveFunction(resolve, id)(); - break; - case 'delete': - deleteItem(apiClient, item).then(getResolveFunction(resolve, id, true, true), getResolveFunction(resolve, id)); - break; - case 'share': - navigator.share({ - title: item.Name, - text: item.Overview, - url: `${apiClient.serverAddress()}/web/index.html${appRouter.getRouteUrl(item)}` - }); - break; - case 'album': - appRouter.showItem(item.AlbumId, item.ServerId); - getResolveFunction(resolve, id)(); - break; - case 'artist': - appRouter.showItem(item.AlbumArtists[0].Id, item.ServerId); - getResolveFunction(resolve, id)(); - break; - case 'playallfromhere': - getResolveFunction(resolve, id)(); - break; - case 'queueallfromhere': - getResolveFunction(resolve, id)(); - break; - case 'removefromplaylist': - apiClient.ajax({ - url: apiClient.getUrl('Playlists/' + options.playlistId + '/Items', { - EntryIds: [item.PlaylistItemId].join(',') - }), - type: 'DELETE' - }).then(function () { - getResolveFunction(resolve, id, true)(); - }); - break; - case 'removefromcollection': - apiClient.ajax({ - type: 'DELETE', - url: apiClient.getUrl('Collections/' + options.collectionId + '/Items', { - - Ids: [item.Id].join(',') - }) - }).then(function () { - getResolveFunction(resolve, id, true)(); - }); - break; - case 'canceltimer': - deleteTimer(apiClient, item, resolve, id); - break; - case 'cancelseriestimer': - deleteSeriesTimer(apiClient, item, resolve, id); - break; - default: - reject(); - break; - } + ) { + commands.push({ + name: globalize.translate('EditSubtitles'), + id: 'editsubtitles', + icon: 'closed_caption' }); } - function deleteTimer(apiClient, item, resolve, command) { - import('./recordingcreator/recordinghelper').then(({ default: recordingHelper }) => { - const timerId = item.TimerId || item.Id; - recordingHelper.cancelTimerWithConfirmation(timerId, item.ServerId).then(function () { - getResolveFunction(resolve, command, true)(); - }); + if (options.identify !== false && itemHelper.canIdentify(user, item)) { + commands.push({ + name: globalize.translate('Identify'), + id: 'identify', + icon: 'edit' }); } - function deleteSeriesTimer(apiClient, item, resolve, command) { - import('./recordingcreator/recordinghelper').then(({ default: recordingHelper }) => { - recordingHelper.cancelSeriesTimerWithConfirmation(item.Id, item.ServerId).then(function () { - getResolveFunction(resolve, command, true)(); - }); + if (item.MediaSources && options.moremediainfo !== false) { + commands.push({ + name: globalize.translate('MoreMediaInfo'), + id: 'moremediainfo', + icon: 'info' }); } - function play(item, resume, queue, queueNext) { - let method = 'play'; - if (queue) { - if (queueNext) { - method = 'queueNext'; - } else { - method = 'queue'; - } - } - - let startPosition = 0; - if (resume && item.UserData && item.UserData.PlaybackPositionTicks) { - startPosition = item.UserData.PlaybackPositionTicks; - } - - if (item.Type === 'Program') { - playbackManager[method]({ - ids: [item.ChannelId], - startPositionTicks: startPosition, - serverId: item.ServerId + if (item.Type === 'Program' && options.record !== false) { + if (item.TimerId) { + commands.push({ + name: globalize.translate('ManageRecording'), + id: 'record', + icon: 'fiber_manual_record' }); } else { - playbackManager[method]({ - items: [item], - startPositionTicks: startPosition + commands.push({ + name: globalize.translate('Record'), + id: 'record', + icon: 'fiber_manual_record' }); } } - function editItem(apiClient, item) { - return new Promise(function (resolve, reject) { - const serverId = apiClient.serverInfo().Id; + if (itemHelper.canRefreshMetadata(item, user)) { + commands.push({ + name: globalize.translate('RefreshMetadata'), + id: 'refresh', + icon: 'refresh' + }); + } - if (item.Type === 'Timer') { - import('./recordingcreator/recordingeditor').then(({ default: recordingEditor }) => { - recordingEditor.show(item.Id, serverId).then(resolve, reject); + if (item.PlaylistItemId && options.playlistId) { + commands.push({ + name: globalize.translate('RemoveFromPlaylist'), + id: 'removefromplaylist', + icon: 'remove' + }); + } + + if (options.collectionId) { + commands.push({ + name: globalize.translate('RemoveFromCollection'), + id: 'removefromcollection', + icon: 'remove' + }); + } + + if (!browser.tv && options.share === true && itemHelper.canShare(item, user)) { + commands.push({ + name: globalize.translate('Share'), + id: 'share', + icon: 'share' + }); + } + + if (options.sync !== false && itemHelper.canSync(user, item)) { + commands.push({ + name: globalize.translate('Sync'), + id: 'sync', + icon: 'sync' + }); + } + + if (options.openAlbum !== false && item.AlbumId && item.MediaType !== 'Photo') { + commands.push({ + name: globalize.translate('ViewAlbum'), + id: 'album', + icon: 'album' + }); + } + // Show Album Artist by default, as a song can have multiple artists, which specific one would this option refer to? + // Although some albums can have multiple artists, it's not as common as songs. + if (options.openArtist !== false && item.AlbumArtists && item.AlbumArtists.length) { + commands.push({ + name: globalize.translate('ViewAlbumArtist'), + id: 'artist', + icon: 'person' + }); + } + + return commands; +} + +function getResolveFunction(resolve, id, changed, deleted) { + return function () { + resolve({ + command: id, + updated: changed, + deleted: deleted + }); + }; +} + +function executeCommand(item, id, options) { + const itemId = item.Id; + const serverId = item.ServerId; + const apiClient = ServerConnections.getApiClient(serverId); + + return new Promise(function (resolve, reject) { + // eslint-disable-next-line sonarjs/max-switch-cases + switch (id) { + case 'addtocollection': + import('./collectionEditor/collectionEditor').then(({ default: CollectionEditor }) => { + const collectionEditor = new CollectionEditor(); + collectionEditor.show({ + items: [itemId], + serverId: serverId + }).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id)); }); - } else if (item.Type === 'SeriesTimer') { - import('./recordingcreator/seriesrecordingeditor').then(({ default: recordingEditor }) => { - recordingEditor.show(item.Id, serverId).then(resolve, reject); + break; + case 'addtoplaylist': + import('./playlisteditor/playlisteditor').then(({ default: playlistEditor }) => { + new playlistEditor({ + items: [itemId], + serverId: serverId + }).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id)); }); - } else { - import('./metadataEditor/metadataEditor').then(({ default: metadataEditor }) => { - metadataEditor.show(item.Id, serverId).then(resolve, reject); + break; + case 'download': + import('../scripts/fileDownloader').then((fileDownloader) => { + const downloadHref = apiClient.getItemDownloadUrl(itemId); + fileDownloader.download([{ + url: downloadHref, + itemId: itemId, + serverId: serverId, + title: item.Name, + filename: item.Path.replace(/^.*[\\/]/, '') + }]); + getResolveFunction(getResolveFunction(resolve, id), id)(); }); + break; + case 'downloadall': { + const downloadEpisodes = episodes => { + import('../scripts/fileDownloader').then((fileDownloader) => { + const downloads = episodes.map(episode => { + const downloadHref = apiClient.getItemDownloadUrl(episode.Id); + return { + url: downloadHref, + itemId: episode.Id, + serverId: serverId, + title: episode.Name, + filename: episode.Path.replace(/^.*[\\/]/, '') + }; + }); + + fileDownloader.download(downloads); + }); + }; + const downloadSeasons = seasons => { + Promise.all(seasons.map(seasonItem => { + return apiClient.getEpisodes(seasonItem.SeriesId, { + seasonId: seasonItem.Id, + userId: options.user.Id, + Fields: 'CanDownload,Path' + }); + } + )).then(seasonData => { + downloadEpisodes(seasonData.map(season => season.Items).flat()); + }); + }; + + if (item.Type === 'Season') { + downloadSeasons([item]); + } else if (item.Type === 'Series') { + apiClient.getSeasons(item.Id, { + userId: options.user.Id, + Fields: 'ItemCounts' + }).then(seasons => downloadSeasons(seasons.Items)); + } + + getResolveFunction(getResolveFunction(resolve, id), id)(); + break; } - }); - } - - function deleteItem(apiClient, item) { - return new Promise(function (resolve, reject) { - import('../scripts/deleteHelper').then((deleteHelper) => { - deleteHelper.deleteItem({ - item: item, - navigate: false + case 'copy-stream': { + const downloadHref = apiClient.getItemDownloadUrl(itemId); + copy(downloadHref).then(() => { + toast(globalize.translate('CopyStreamURLSuccess')); + }).catch(() => { + prompt(globalize.translate('CopyStreamURL'), downloadHref); + }); + getResolveFunction(resolve, id)(); + break; + } + case 'editsubtitles': + import('./subtitleeditor/subtitleeditor').then(({ default: subtitleEditor }) => { + subtitleEditor.show(itemId, serverId).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id)); + }); + break; + case 'edit': + editItem(apiClient, item).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id)); + break; + case 'editimages': + import('./imageeditor/imageeditor').then((imageEditor) => { + imageEditor.show({ + itemId: itemId, + serverId: serverId + }).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id)); + }); + break; + case 'identify': + import('./itemidentifier/itemidentifier').then((itemIdentifier) => { + itemIdentifier.show(itemId, serverId).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id)); + }); + break; + case 'moremediainfo': + import('./itemMediaInfo/itemMediaInfo').then((itemMediaInfo) => { + itemMediaInfo.show(itemId, serverId).then(getResolveFunction(resolve, id), getResolveFunction(resolve, id)); + }); + break; + case 'refresh': + refresh(apiClient, item); + getResolveFunction(resolve, id)(); + break; + case 'open': + appRouter.showItem(item); + getResolveFunction(resolve, id)(); + break; + case 'play': + play(item, false); + getResolveFunction(resolve, id)(); + break; + case 'resume': + play(item, true); + getResolveFunction(resolve, id)(); + break; + case 'queue': + play(item, false, true); + getResolveFunction(resolve, id)(); + break; + case 'queuenext': + play(item, false, true, true); + getResolveFunction(resolve, id)(); + break; + case 'stopPlayback': + playbackManager.stop(); + break; + case 'clearQueue': + playbackManager.clearQueue(); + break; + case 'record': + import('./recordingcreator/recordingcreator').then(({ default: recordingCreator }) => { + recordingCreator.show(itemId, serverId).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id)); + }); + break; + case 'shuffle': + playbackManager.shuffle(item); + getResolveFunction(resolve, id)(); + break; + case 'instantmix': + playbackManager.instantMix(item); + getResolveFunction(resolve, id)(); + break; + case 'delete': + deleteItem(apiClient, item).then(getResolveFunction(resolve, id, true, true), getResolveFunction(resolve, id)); + break; + case 'share': + navigator.share({ + title: item.Name, + text: item.Overview, + url: `${apiClient.serverAddress()}/web/index.html${appRouter.getRouteUrl(item)}` + }); + break; + case 'album': + appRouter.showItem(item.AlbumId, item.ServerId); + getResolveFunction(resolve, id)(); + break; + case 'artist': + appRouter.showItem(item.AlbumArtists[0].Id, item.ServerId); + getResolveFunction(resolve, id)(); + break; + case 'playallfromhere': + getResolveFunction(resolve, id)(); + break; + case 'queueallfromhere': + getResolveFunction(resolve, id)(); + break; + case 'removefromplaylist': + apiClient.ajax({ + url: apiClient.getUrl('Playlists/' + options.playlistId + '/Items', { + EntryIds: [item.PlaylistItemId].join(',') + }), + type: 'DELETE' }).then(function () { - resolve(true); - }, reject); - }); - }); - } + getResolveFunction(resolve, id, true)(); + }); + break; + case 'removefromcollection': + apiClient.ajax({ + type: 'DELETE', + url: apiClient.getUrl('Collections/' + options.collectionId + '/Items', { - function refresh(apiClient, item) { - import('./refreshdialog/refreshdialog').then(({ default: refreshDialog }) => { - new refreshDialog({ - itemIds: [item.Id], - serverId: apiClient.serverInfo().Id, - mode: item.Type === 'CollectionFolder' ? 'scan' : null - }).show(); - }); - } - - export function show(options) { - const commands = getCommands(options); - if (!commands.length) { - return Promise.reject(); + Ids: [item.Id].join(',') + }) + }).then(function () { + getResolveFunction(resolve, id, true)(); + }); + break; + case 'canceltimer': + deleteTimer(apiClient, item, resolve, id); + break; + case 'cancelseriestimer': + deleteSeriesTimer(apiClient, item, resolve, id); + break; + default: + reject(); + break; } + }); +} - return actionsheet.show({ - items: commands, - positionTo: options.positionTo, - resolveOnClick: ['share'] - }).then(function (id) { - return executeCommand(options.item, id, options); +function deleteTimer(apiClient, item, resolve, command) { + import('./recordingcreator/recordinghelper').then(({ default: recordingHelper }) => { + const timerId = item.TimerId || item.Id; + recordingHelper.cancelTimerWithConfirmation(timerId, item.ServerId).then(function () { + getResolveFunction(resolve, command, true)(); }); + }); +} + +function deleteSeriesTimer(apiClient, item, resolve, command) { + import('./recordingcreator/recordinghelper').then(({ default: recordingHelper }) => { + recordingHelper.cancelSeriesTimerWithConfirmation(item.Id, item.ServerId).then(function () { + getResolveFunction(resolve, command, true)(); + }); + }); +} + +function play(item, resume, queue, queueNext) { + let method = 'play'; + if (queue) { + if (queueNext) { + method = 'queueNext'; + } else { + method = 'queue'; + } } -/* eslint-enable indent */ + let startPosition = 0; + if (resume && item.UserData && item.UserData.PlaybackPositionTicks) { + startPosition = item.UserData.PlaybackPositionTicks; + } + + if (item.Type === 'Program') { + playbackManager[method]({ + ids: [item.ChannelId], + startPositionTicks: startPosition, + serverId: item.ServerId + }); + } else { + playbackManager[method]({ + items: [item], + startPositionTicks: startPosition + }); + } +} + +function editItem(apiClient, item) { + return new Promise(function (resolve, reject) { + const serverId = apiClient.serverInfo().Id; + + if (item.Type === 'Timer') { + import('./recordingcreator/recordingeditor').then(({ default: recordingEditor }) => { + recordingEditor.show(item.Id, serverId).then(resolve, reject); + }); + } else if (item.Type === 'SeriesTimer') { + import('./recordingcreator/seriesrecordingeditor').then(({ default: recordingEditor }) => { + recordingEditor.show(item.Id, serverId).then(resolve, reject); + }); + } else { + import('./metadataEditor/metadataEditor').then(({ default: metadataEditor }) => { + metadataEditor.show(item.Id, serverId).then(resolve, reject); + }); + } + }); +} + +function deleteItem(apiClient, item) { + return new Promise(function (resolve, reject) { + import('../scripts/deleteHelper').then((deleteHelper) => { + deleteHelper.deleteItem({ + item: item, + navigate: false + }).then(function () { + resolve(true); + }, reject); + }); + }); +} + +function refresh(apiClient, item) { + import('./refreshdialog/refreshdialog').then(({ default: refreshDialog }) => { + new refreshDialog({ + itemIds: [item.Id], + serverId: apiClient.serverInfo().Id, + mode: item.Type === 'CollectionFolder' ? 'scan' : null + }).show(); + }); +} + +export function show(options) { + const commands = getCommands(options); + if (!commands.length) { + return Promise.reject(); + } + + return actionsheet.show({ + items: commands, + positionTo: options.positionTo, + resolveOnClick: ['share'] + }).then(function (id) { + return executeCommand(options.item, id, options); + }); +} export default { getCommands: getCommands, diff --git a/src/components/itemMediaInfo/itemMediaInfo.js b/src/components/itemMediaInfo/itemMediaInfo.js index ac04df4815..0eb1348760 100644 --- a/src/components/itemMediaInfo/itemMediaInfo.js +++ b/src/components/itemMediaInfo/itemMediaInfo.js @@ -1,4 +1,3 @@ -/* eslint-disable indent */ /** * Module for display media info. @@ -30,229 +29,228 @@ const copyButtonHtml = layoutManager.tv ? '' : >`; const attributeDelimiterHtml = layoutManager.tv ? '' : ': '; - function setMediaInfo(user, page, item) { - let html = item.MediaSources.map(version => { - return getMediaSourceHtml(user, item, version); - }).join('
'); - if (item.MediaSources.length > 1) { - html = `
${html}`; - } - const mediaInfoContent = page.querySelector('#mediaInfoContent'); - mediaInfoContent.innerHTML = html; - - for (const btn of mediaInfoContent.querySelectorAll('.btnCopy')) { - btn.addEventListener('click', () => { - const infoBlock = dom.parentWithClass(btn, 'mediaInfoStream') || dom.parentWithClass(btn, 'mediaInfoSource') || mediaInfoContent; - - copy(infoBlock.textContent).then(() => { - toast(globalize.translate('Copied')); - }).catch(() => { - console.error('Could not copy text'); - toast(globalize.translate('CopyFailed')); - }); - }); - } +function setMediaInfo(user, page, item) { + let html = item.MediaSources.map(version => { + return getMediaSourceHtml(user, item, version); + }).join('
'); + if (item.MediaSources.length > 1) { + html = `
${html}`; } + const mediaInfoContent = page.querySelector('#mediaInfoContent'); + mediaInfoContent.innerHTML = html; - function getMediaSourceHtml(user, item, version) { - let html = '
'; - if (version.Name) { - html += `

${escapeHtml(version.Name)}${copyButtonHtml}

\n`; - } - if (version.Container) { - html += `${createAttribute(globalize.translate('MediaInfoContainer'), version.Container)}
`; - } - if (version.Formats && version.Formats.length) { - html += `${createAttribute(globalize.translate('MediaInfoFormat'), version.Formats.join(','))}
`; - } - if (version.Path && user && user.Policy.IsAdministrator) { - html += `${createAttribute(globalize.translate('MediaInfoPath'), version.Path, true)}
`; - } - if (version.Size) { - const size = `${(version.Size / (1024 * 1024)).toFixed(0)} MB`; - html += `${createAttribute(globalize.translate('MediaInfoSize'), size)}
`; - } - version.MediaStreams.sort(itemHelper.sortTracks); - for (const stream of version.MediaStreams) { - if (stream.Type === 'Data') { - continue; - } + for (const btn of mediaInfoContent.querySelectorAll('.btnCopy')) { + btn.addEventListener('click', () => { + const infoBlock = dom.parentWithClass(btn, 'mediaInfoStream') || dom.parentWithClass(btn, 'mediaInfoSource') || mediaInfoContent; - html += '
'; - let translateString; - switch (stream.Type) { - case 'Audio': - case 'Data': - case 'Subtitle': - case 'Video': - translateString = stream.Type; - break; - case 'EmbeddedImage': - translateString = 'Image'; - break; - } - - const displayType = globalize.translate(translateString); - html += `\n

${displayType}${copyButtonHtml}

\n`; - const attributes = []; - if (stream.DisplayTitle) { - attributes.push(createAttribute(globalize.translate('MediaInfoTitle'), stream.DisplayTitle)); - } - if (stream.Language && stream.Type !== 'Video') { - attributes.push(createAttribute(globalize.translate('MediaInfoLanguage'), stream.Language)); - } - if (stream.Codec) { - attributes.push(createAttribute(globalize.translate('MediaInfoCodec'), stream.Codec.toUpperCase())); - } - if (stream.CodecTag) { - attributes.push(createAttribute(globalize.translate('MediaInfoCodecTag'), stream.CodecTag)); - } - if (stream.IsAVC != null) { - attributes.push(createAttribute('AVC', (stream.IsAVC ? 'Yes' : 'No'))); - } - if (stream.Profile) { - attributes.push(createAttribute(globalize.translate('MediaInfoProfile'), stream.Profile)); - } - if (stream.Level > 0) { - attributes.push(createAttribute(globalize.translate('MediaInfoLevel'), stream.Level)); - } - if (stream.Width || stream.Height) { - attributes.push(createAttribute(globalize.translate('MediaInfoResolution'), `${stream.Width}x${stream.Height}`)); - } - if (stream.AspectRatio && stream.Codec !== 'mjpeg') { - attributes.push(createAttribute(globalize.translate('MediaInfoAspectRatio'), stream.AspectRatio)); - } - if (stream.Type === 'Video') { - if (stream.IsAnamorphic != null) { - attributes.push(createAttribute(globalize.translate('MediaInfoAnamorphic'), (stream.IsAnamorphic ? 'Yes' : 'No'))); - } - attributes.push(createAttribute(globalize.translate('MediaInfoInterlaced'), (stream.IsInterlaced ? 'Yes' : 'No'))); - } - if ((stream.AverageFrameRate || stream.RealFrameRate) && stream.Type === 'Video') { - attributes.push(createAttribute(globalize.translate('MediaInfoFramerate'), (stream.AverageFrameRate || stream.RealFrameRate))); - } - if (stream.ChannelLayout) { - attributes.push(createAttribute(globalize.translate('MediaInfoLayout'), stream.ChannelLayout)); - } - if (stream.Channels) { - attributes.push(createAttribute(globalize.translate('MediaInfoChannels'), `${stream.Channels} ch`)); - } - if (stream.BitRate) { - attributes.push(createAttribute(globalize.translate('MediaInfoBitrate'), `${parseInt(stream.BitRate / 1000, 10)} kbps`)); - } - if (stream.SampleRate) { - attributes.push(createAttribute(globalize.translate('MediaInfoSampleRate'), `${stream.SampleRate} Hz`)); - } - if (stream.BitDepth) { - attributes.push(createAttribute(globalize.translate('MediaInfoBitDepth'), `${stream.BitDepth} bit`)); - } - if (stream.VideoRange) { - attributes.push(createAttribute(globalize.translate('MediaInfoVideoRange'), stream.VideoRange)); - } - if (stream.VideoRangeType) { - attributes.push(createAttribute(globalize.translate('MediaInfoVideoRangeType'), stream.VideoRangeType)); - } - if (stream.VideoDoViTitle) { - attributes.push(createAttribute(globalize.translate('MediaInfoDoViTitle'), stream.VideoDoViTitle)); - if (stream.DvVersionMajor != null) { - attributes.push(createAttribute(globalize.translate('MediaInfoDvVersionMajor'), stream.DvVersionMajor)); - } - if (stream.DvVersionMinor != null) { - attributes.push(createAttribute(globalize.translate('MediaInfoDvVersionMinor'), stream.DvVersionMinor)); - } - if (stream.DvProfile != null) { - attributes.push(createAttribute(globalize.translate('MediaInfoDvProfile'), stream.DvProfile)); - } - if (stream.DvLevel != null) { - attributes.push(createAttribute(globalize.translate('MediaInfoDvLevel'), stream.DvLevel)); - } - if (stream.RpuPresentFlag != null) { - attributes.push(createAttribute(globalize.translate('MediaInfoRpuPresentFlag'), stream.RpuPresentFlag)); - } - if (stream.ElPresentFlag != null) { - attributes.push(createAttribute(globalize.translate('MediaInfoElPresentFlag'), stream.ElPresentFlag)); - } - if (stream.BlPresentFlag != null) { - attributes.push(createAttribute(globalize.translate('MediaInfoBlPresentFlag'), stream.BlPresentFlag)); - } - if (stream.DvBlSignalCompatibilityId != null) { - attributes.push(createAttribute(globalize.translate('MediaInfoDvBlSignalCompatibilityId'), stream.DvBlSignalCompatibilityId)); - } - } - if (stream.ColorSpace) { - attributes.push(createAttribute(globalize.translate('MediaInfoColorSpace'), stream.ColorSpace)); - } - if (stream.ColorTransfer) { - attributes.push(createAttribute(globalize.translate('MediaInfoColorTransfer'), stream.ColorTransfer)); - } - if (stream.ColorPrimaries) { - attributes.push(createAttribute(globalize.translate('MediaInfoColorPrimaries'), stream.ColorPrimaries)); - } - if (stream.PixelFormat) { - attributes.push(createAttribute(globalize.translate('MediaInfoPixelFormat'), stream.PixelFormat)); - } - if (stream.RefFrames) { - attributes.push(createAttribute(globalize.translate('MediaInfoRefFrames'), stream.RefFrames)); - } - if (stream.NalLengthSize) { - attributes.push(createAttribute('NAL', stream.NalLengthSize)); - } - if (stream.Type === 'Subtitle' || stream.Type === 'Audio') { - attributes.push(createAttribute(globalize.translate('MediaInfoDefault'), (stream.IsDefault ? 'Yes' : 'No'))); - attributes.push(createAttribute(globalize.translate('MediaInfoForced'), (stream.IsForced ? 'Yes' : 'No'))); - attributes.push(createAttribute(globalize.translate('MediaInfoExternal'), (stream.IsExternal ? 'Yes' : 'No'))); - } - if (stream.Type === 'Video' && version.Timestamp) { - attributes.push(createAttribute(globalize.translate('MediaInfoTimestamp'), version.Timestamp)); - } - html += attributes.join('
'); - html += '
'; - } - html += '
'; - return html; - } - - // File Paths should be always ltr. The isLtr parameter allows this. - function createAttribute(label, value, isLtr) { - return `${label}${attributeDelimiterHtml}${escapeHtml(value)}\n`; - } - - function loadMediaInfo(itemId, serverId) { - const apiClient = ServerConnections.getApiClient(serverId); - return apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(item => { - const dialogOptions = { - size: 'small', - removeOnClose: true, - scrollY: false - }; - if (layoutManager.tv) { - dialogOptions.size = 'fullscreen'; - } - const dlg = dialogHelper.createDialog(dialogOptions); - dlg.classList.add('formDialog'); - let html = ''; - html += globalize.translateHtml(template, 'core'); - dlg.innerHTML = html; - if (layoutManager.tv) { - dlg.querySelector('.formDialogContent'); - } - dialogHelper.open(dlg); - dlg.querySelector('.btnCancel').addEventListener('click', () => { - dialogHelper.close(dlg); + copy(infoBlock.textContent).then(() => { + toast(globalize.translate('Copied')); + }).catch(() => { + console.error('Could not copy text'); + toast(globalize.translate('CopyFailed')); }); - apiClient.getCurrentUser().then(user => { - setMediaInfo(user, dlg, item); - }); - loading.hide(); }); } +} - export function show(itemId, serverId) { - loading.show(); - return loadMediaInfo(itemId, serverId); +function getMediaSourceHtml(user, item, version) { + let html = '
'; + if (version.Name) { + html += `

${escapeHtml(version.Name)}${copyButtonHtml}

\n`; } + if (version.Container) { + html += `${createAttribute(globalize.translate('MediaInfoContainer'), version.Container)}
`; + } + if (version.Formats && version.Formats.length) { + html += `${createAttribute(globalize.translate('MediaInfoFormat'), version.Formats.join(','))}
`; + } + if (version.Path && user && user.Policy.IsAdministrator) { + html += `${createAttribute(globalize.translate('MediaInfoPath'), version.Path, true)}
`; + } + if (version.Size) { + const size = `${(version.Size / (1024 * 1024)).toFixed(0)} MB`; + html += `${createAttribute(globalize.translate('MediaInfoSize'), size)}
`; + } + version.MediaStreams.sort(itemHelper.sortTracks); + for (const stream of version.MediaStreams) { + if (stream.Type === 'Data') { + continue; + } + + html += '
'; + let translateString; + switch (stream.Type) { + case 'Audio': + case 'Data': + case 'Subtitle': + case 'Video': + translateString = stream.Type; + break; + case 'EmbeddedImage': + translateString = 'Image'; + break; + } + + const displayType = globalize.translate(translateString); + html += `\n

${displayType}${copyButtonHtml}

\n`; + const attributes = []; + if (stream.DisplayTitle) { + attributes.push(createAttribute(globalize.translate('MediaInfoTitle'), stream.DisplayTitle)); + } + if (stream.Language && stream.Type !== 'Video') { + attributes.push(createAttribute(globalize.translate('MediaInfoLanguage'), stream.Language)); + } + if (stream.Codec) { + attributes.push(createAttribute(globalize.translate('MediaInfoCodec'), stream.Codec.toUpperCase())); + } + if (stream.CodecTag) { + attributes.push(createAttribute(globalize.translate('MediaInfoCodecTag'), stream.CodecTag)); + } + if (stream.IsAVC != null) { + attributes.push(createAttribute('AVC', (stream.IsAVC ? 'Yes' : 'No'))); + } + if (stream.Profile) { + attributes.push(createAttribute(globalize.translate('MediaInfoProfile'), stream.Profile)); + } + if (stream.Level > 0) { + attributes.push(createAttribute(globalize.translate('MediaInfoLevel'), stream.Level)); + } + if (stream.Width || stream.Height) { + attributes.push(createAttribute(globalize.translate('MediaInfoResolution'), `${stream.Width}x${stream.Height}`)); + } + if (stream.AspectRatio && stream.Codec !== 'mjpeg') { + attributes.push(createAttribute(globalize.translate('MediaInfoAspectRatio'), stream.AspectRatio)); + } + if (stream.Type === 'Video') { + if (stream.IsAnamorphic != null) { + attributes.push(createAttribute(globalize.translate('MediaInfoAnamorphic'), (stream.IsAnamorphic ? 'Yes' : 'No'))); + } + attributes.push(createAttribute(globalize.translate('MediaInfoInterlaced'), (stream.IsInterlaced ? 'Yes' : 'No'))); + } + if ((stream.AverageFrameRate || stream.RealFrameRate) && stream.Type === 'Video') { + attributes.push(createAttribute(globalize.translate('MediaInfoFramerate'), (stream.AverageFrameRate || stream.RealFrameRate))); + } + if (stream.ChannelLayout) { + attributes.push(createAttribute(globalize.translate('MediaInfoLayout'), stream.ChannelLayout)); + } + if (stream.Channels) { + attributes.push(createAttribute(globalize.translate('MediaInfoChannels'), `${stream.Channels} ch`)); + } + if (stream.BitRate) { + attributes.push(createAttribute(globalize.translate('MediaInfoBitrate'), `${parseInt(stream.BitRate / 1000, 10)} kbps`)); + } + if (stream.SampleRate) { + attributes.push(createAttribute(globalize.translate('MediaInfoSampleRate'), `${stream.SampleRate} Hz`)); + } + if (stream.BitDepth) { + attributes.push(createAttribute(globalize.translate('MediaInfoBitDepth'), `${stream.BitDepth} bit`)); + } + if (stream.VideoRange) { + attributes.push(createAttribute(globalize.translate('MediaInfoVideoRange'), stream.VideoRange)); + } + if (stream.VideoRangeType) { + attributes.push(createAttribute(globalize.translate('MediaInfoVideoRangeType'), stream.VideoRangeType)); + } + if (stream.VideoDoViTitle) { + attributes.push(createAttribute(globalize.translate('MediaInfoDoViTitle'), stream.VideoDoViTitle)); + if (stream.DvVersionMajor != null) { + attributes.push(createAttribute(globalize.translate('MediaInfoDvVersionMajor'), stream.DvVersionMajor)); + } + if (stream.DvVersionMinor != null) { + attributes.push(createAttribute(globalize.translate('MediaInfoDvVersionMinor'), stream.DvVersionMinor)); + } + if (stream.DvProfile != null) { + attributes.push(createAttribute(globalize.translate('MediaInfoDvProfile'), stream.DvProfile)); + } + if (stream.DvLevel != null) { + attributes.push(createAttribute(globalize.translate('MediaInfoDvLevel'), stream.DvLevel)); + } + if (stream.RpuPresentFlag != null) { + attributes.push(createAttribute(globalize.translate('MediaInfoRpuPresentFlag'), stream.RpuPresentFlag)); + } + if (stream.ElPresentFlag != null) { + attributes.push(createAttribute(globalize.translate('MediaInfoElPresentFlag'), stream.ElPresentFlag)); + } + if (stream.BlPresentFlag != null) { + attributes.push(createAttribute(globalize.translate('MediaInfoBlPresentFlag'), stream.BlPresentFlag)); + } + if (stream.DvBlSignalCompatibilityId != null) { + attributes.push(createAttribute(globalize.translate('MediaInfoDvBlSignalCompatibilityId'), stream.DvBlSignalCompatibilityId)); + } + } + if (stream.ColorSpace) { + attributes.push(createAttribute(globalize.translate('MediaInfoColorSpace'), stream.ColorSpace)); + } + if (stream.ColorTransfer) { + attributes.push(createAttribute(globalize.translate('MediaInfoColorTransfer'), stream.ColorTransfer)); + } + if (stream.ColorPrimaries) { + attributes.push(createAttribute(globalize.translate('MediaInfoColorPrimaries'), stream.ColorPrimaries)); + } + if (stream.PixelFormat) { + attributes.push(createAttribute(globalize.translate('MediaInfoPixelFormat'), stream.PixelFormat)); + } + if (stream.RefFrames) { + attributes.push(createAttribute(globalize.translate('MediaInfoRefFrames'), stream.RefFrames)); + } + if (stream.NalLengthSize) { + attributes.push(createAttribute('NAL', stream.NalLengthSize)); + } + if (stream.Type === 'Subtitle' || stream.Type === 'Audio') { + attributes.push(createAttribute(globalize.translate('MediaInfoDefault'), (stream.IsDefault ? 'Yes' : 'No'))); + attributes.push(createAttribute(globalize.translate('MediaInfoForced'), (stream.IsForced ? 'Yes' : 'No'))); + attributes.push(createAttribute(globalize.translate('MediaInfoExternal'), (stream.IsExternal ? 'Yes' : 'No'))); + } + if (stream.Type === 'Video' && version.Timestamp) { + attributes.push(createAttribute(globalize.translate('MediaInfoTimestamp'), version.Timestamp)); + } + html += attributes.join('
'); + html += '
'; + } + html += '
'; + return html; +} + +// File Paths should be always ltr. The isLtr parameter allows this. +function createAttribute(label, value, isLtr) { + return `${label}${attributeDelimiterHtml}${escapeHtml(value)}\n`; +} + +function loadMediaInfo(itemId, serverId) { + const apiClient = ServerConnections.getApiClient(serverId); + return apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(item => { + const dialogOptions = { + size: 'small', + removeOnClose: true, + scrollY: false + }; + if (layoutManager.tv) { + dialogOptions.size = 'fullscreen'; + } + const dlg = dialogHelper.createDialog(dialogOptions); + dlg.classList.add('formDialog'); + let html = ''; + html += globalize.translateHtml(template, 'core'); + dlg.innerHTML = html; + if (layoutManager.tv) { + dlg.querySelector('.formDialogContent'); + } + dialogHelper.open(dlg); + dlg.querySelector('.btnCancel').addEventListener('click', () => { + dialogHelper.close(dlg); + }); + apiClient.getCurrentUser().then(user => { + setMediaInfo(user, dlg, item); + }); + loading.hide(); + }); +} + +export function show(itemId, serverId) { + loading.show(); + return loadMediaInfo(itemId, serverId); +} -/* eslint-enable indent */ export default { show: show }; diff --git a/src/components/itemidentifier/itemidentifier.js b/src/components/itemidentifier/itemidentifier.js index 9ada1d60c2..b1aa1a1b9b 100644 --- a/src/components/itemidentifier/itemidentifier.js +++ b/src/components/itemidentifier/itemidentifier.js @@ -1,4 +1,3 @@ -/* eslint-disable indent */ /** * Module for itemidentifier media item. @@ -24,388 +23,314 @@ import toast from '../toast/toast'; import template from './itemidentifier.template.html'; import datetime from '../../scripts/datetime'; - const enableFocusTransform = !browser.slow && !browser.edge; +const enableFocusTransform = !browser.slow && !browser.edge; - let currentItem; - let currentItemType; - let currentServerId; - let currentResolve; - let currentReject; - let hasChanges = false; - let currentSearchResult; +let currentItem; +let currentItemType; +let currentServerId; +let currentResolve; +let currentReject; +let hasChanges = false; +let currentSearchResult; - function getApiClient() { - return ServerConnections.getApiClient(currentServerId); +function getApiClient() { + return ServerConnections.getApiClient(currentServerId); +} + +function searchForIdentificationResults(page) { + let lookupInfo = { + ProviderIds: {} + }; + + let i; + let length; + const identifyField = page.querySelectorAll('.identifyField'); + let value; + for (i = 0, length = identifyField.length; i < length; i++) { + value = identifyField[i].value; + + if (value) { + if (identifyField[i].type === 'number') { + value = parseInt(value, 10); + } + + lookupInfo[identifyField[i].getAttribute('data-lookup')] = value; + } } - function searchForIdentificationResults(page) { - let lookupInfo = { - ProviderIds: {} - }; + let hasId = false; - let i; - let length; - const identifyField = page.querySelectorAll('.identifyField'); - let value; - for (i = 0, length = identifyField.length; i < length; i++) { - value = identifyField[i].value; + const txtLookupId = page.querySelectorAll('.txtLookupId'); + for (i = 0, length = txtLookupId.length; i < length; i++) { + value = txtLookupId[i].value; - if (value) { - if (identifyField[i].type === 'number') { - value = parseInt(value, 10); - } - - lookupInfo[identifyField[i].getAttribute('data-lookup')] = value; - } + if (value) { + hasId = true; } + lookupInfo.ProviderIds[txtLookupId[i].getAttribute('data-providerkey')] = value; + } - let hasId = false; + if (!hasId && !lookupInfo.Name) { + toast(globalize.translate('PleaseEnterNameOrId')); + return; + } - const txtLookupId = page.querySelectorAll('.txtLookupId'); - for (i = 0, length = txtLookupId.length; i < length; i++) { - value = txtLookupId[i].value; + lookupInfo = { + SearchInfo: lookupInfo + }; - if (value) { - hasId = true; - } - lookupInfo.ProviderIds[txtLookupId[i].getAttribute('data-providerkey')] = value; - } + if (currentItem && currentItem.Id) { + lookupInfo.ItemId = currentItem.Id; + } else { + lookupInfo.IncludeDisabledProviders = true; + } - if (!hasId && !lookupInfo.Name) { - toast(globalize.translate('PleaseEnterNameOrId')); - return; - } + loading.show(); - lookupInfo = { - SearchInfo: lookupInfo - }; + const apiClient = getApiClient(); - if (currentItem && currentItem.Id) { - lookupInfo.ItemId = currentItem.Id; + apiClient.ajax({ + type: 'POST', + url: apiClient.getUrl(`Items/RemoteSearch/${currentItemType}`), + data: JSON.stringify(lookupInfo), + contentType: 'application/json', + dataType: 'json' + + }).then(results => { + loading.hide(); + showIdentificationSearchResults(page, results); + }); +} + +function showIdentificationSearchResults(page, results) { + const identificationSearchResults = page.querySelector('.identificationSearchResults'); + + page.querySelector('.popupIdentifyForm').classList.add('hide'); + identificationSearchResults.classList.remove('hide'); + page.querySelector('.identifyOptionsForm').classList.add('hide'); + page.querySelector('.dialogContentInner').classList.remove('dialog-content-centered'); + + let html = ''; + let i; + let length; + for (i = 0, length = results.length; i < length; i++) { + const result = results[i]; + html += getSearchResultHtml(result, i); + } + + const elem = page.querySelector('.identificationSearchResultList'); + elem.innerHTML = html; + + function onSearchImageClick() { + const index = parseInt(this.getAttribute('data-index'), 10); + + const currentResult = results[index]; + + if (currentItem != null) { + showIdentifyOptions(page, currentResult); } else { - lookupInfo.IncludeDisabledProviders = true; - } - - loading.show(); - - const apiClient = getApiClient(); - - apiClient.ajax({ - type: 'POST', - url: apiClient.getUrl(`Items/RemoteSearch/${currentItemType}`), - data: JSON.stringify(lookupInfo), - contentType: 'application/json', - dataType: 'json' - - }).then(results => { - loading.hide(); - showIdentificationSearchResults(page, results); - }); - } - - function showIdentificationSearchResults(page, results) { - const identificationSearchResults = page.querySelector('.identificationSearchResults'); - - page.querySelector('.popupIdentifyForm').classList.add('hide'); - identificationSearchResults.classList.remove('hide'); - page.querySelector('.identifyOptionsForm').classList.add('hide'); - page.querySelector('.dialogContentInner').classList.remove('dialog-content-centered'); - - let html = ''; - let i; - let length; - for (i = 0, length = results.length; i < length; i++) { - const result = results[i]; - html += getSearchResultHtml(result, i); - } - - const elem = page.querySelector('.identificationSearchResultList'); - elem.innerHTML = html; - - function onSearchImageClick() { - const index = parseInt(this.getAttribute('data-index'), 10); - - const currentResult = results[index]; - - if (currentItem != null) { - showIdentifyOptions(page, currentResult); - } else { - finishFindNewDialog(page, currentResult); - } - } - - const searchImages = elem.querySelectorAll('.card'); - for (i = 0, length = searchImages.length; i < length; i++) { - searchImages[i].addEventListener('click', onSearchImageClick); - } - - if (layoutManager.tv) { - focusManager.autoFocus(identificationSearchResults); + finishFindNewDialog(page, currentResult); } } - function finishFindNewDialog(dlg, identifyResult) { - currentSearchResult = identifyResult; + const searchImages = elem.querySelectorAll('.card'); + for (i = 0, length = searchImages.length; i < length; i++) { + searchImages[i].addEventListener('click', onSearchImageClick); + } + + if (layoutManager.tv) { + focusManager.autoFocus(identificationSearchResults); + } +} + +function finishFindNewDialog(dlg, identifyResult) { + currentSearchResult = identifyResult; + hasChanges = true; + loading.hide(); + + dialogHelper.close(dlg); +} + +function showIdentifyOptions(page, identifyResult) { + const identifyOptionsForm = page.querySelector('.identifyOptionsForm'); + + page.querySelector('.popupIdentifyForm').classList.add('hide'); + page.querySelector('.identificationSearchResults').classList.add('hide'); + identifyOptionsForm.classList.remove('hide'); + page.querySelector('#chkIdentifyReplaceImages').checked = true; + page.querySelector('.dialogContentInner').classList.add('dialog-content-centered'); + + currentSearchResult = identifyResult; + + const lines = []; + lines.push(escapeHtml(identifyResult.Name)); + + if (identifyResult.ProductionYear) { + lines.push(datetime.toLocaleString(identifyResult.ProductionYear, { useGrouping: false })); + } + + let resultHtml = lines.join('
'); + + if (identifyResult.ImageUrl) { + resultHtml = `
${resultHtml}
`; + } + + page.querySelector('.selectedSearchResult').innerHTML = resultHtml; + + focusManager.focus(identifyOptionsForm.querySelector('.btnSubmit')); +} + +function getSearchResultHtml(result, index) { + // TODO move card creation code to Card component + + let html = ''; + let cssClass = 'card scalableCard'; + let cardBoxCssClass = 'cardBox'; + let padderClass; + + if (currentItemType === 'Episode') { + cssClass += ' backdropCard backdropCard-scalable'; + padderClass = 'cardPadder-backdrop'; + } else if (currentItemType === 'MusicAlbum' || currentItemType === 'MusicArtist') { + cssClass += ' squareCard squareCard-scalable'; + padderClass = 'cardPadder-square'; + } else { + cssClass += ' portraitCard portraitCard-scalable'; + padderClass = 'cardPadder-portrait'; + } + + if (layoutManager.tv) { + cssClass += ' show-focus'; + + if (enableFocusTransform) { + cssClass += ' show-animation'; + } + } + + cardBoxCssClass += ' cardBox-bottompadded'; + + html += `'; + return html; +} + +function submitIdentficationResult(page) { + loading.show(); + + const options = { + ReplaceAllImages: page.querySelector('#chkIdentifyReplaceImages').checked + }; + + const apiClient = getApiClient(); + + apiClient.ajax({ + type: 'POST', + url: apiClient.getUrl(`Items/RemoteSearch/Apply/${currentItem.Id}`, options), + data: JSON.stringify(currentSearchResult), + contentType: 'application/json' + + }).then(() => { hasChanges = true; loading.hide(); - dialogHelper.close(dlg); - } + dialogHelper.close(page); + }, () => { + loading.hide(); - function showIdentifyOptions(page, identifyResult) { - const identifyOptionsForm = page.querySelector('.identifyOptionsForm'); + dialogHelper.close(page); + }); +} - page.querySelector('.popupIdentifyForm').classList.add('hide'); - page.querySelector('.identificationSearchResults').classList.add('hide'); - identifyOptionsForm.classList.remove('hide'); - page.querySelector('#chkIdentifyReplaceImages').checked = true; - page.querySelector('.dialogContentInner').classList.add('dialog-content-centered'); - - currentSearchResult = identifyResult; - - const lines = []; - lines.push(escapeHtml(identifyResult.Name)); - - if (identifyResult.ProductionYear) { - lines.push(datetime.toLocaleString(identifyResult.ProductionYear, { useGrouping: false })); - } - - let resultHtml = lines.join('
'); - - if (identifyResult.ImageUrl) { - resultHtml = `
${resultHtml}
`; - } - - page.querySelector('.selectedSearchResult').innerHTML = resultHtml; - - focusManager.focus(identifyOptionsForm.querySelector('.btnSubmit')); - } - - function getSearchResultHtml(result, index) { - // TODO move card creation code to Card component +function showIdentificationForm(page, item) { + const apiClient = getApiClient(); + apiClient.getJSON(apiClient.getUrl(`Items/${item.Id}/ExternalIdInfos`)).then(idList => { let html = ''; - let cssClass = 'card scalableCard'; - let cardBoxCssClass = 'cardBox'; - let padderClass; - if (currentItemType === 'Episode') { - cssClass += ' backdropCard backdropCard-scalable'; - padderClass = 'cardPadder-backdrop'; - } else if (currentItemType === 'MusicAlbum' || currentItemType === 'MusicArtist') { - cssClass += ' squareCard squareCard-scalable'; - padderClass = 'cardPadder-square'; - } else { - cssClass += ' portraitCard portraitCard-scalable'; - padderClass = 'cardPadder-portrait'; - } + for (let i = 0, length = idList.length; i < length; i++) { + const idInfo = idList[i]; - if (layoutManager.tv) { - cssClass += ' show-focus'; + const id = `txtLookup${idInfo.Key}`; - if (enableFocusTransform) { - cssClass += ' show-animation'; + html += '
'; + + let fullName = idInfo.Name; + if (idInfo.Type) { + fullName = `${idInfo.Name} ${globalize.translate(idInfo.Type)}`; } - } - cardBoxCssClass += ' cardBox-bottompadded'; + const idLabel = globalize.translate('LabelDynamicExternalId', escapeHtml(fullName)); - html += `'; - return html; - } + page.querySelector('#txtLookupName').value = ''; - function submitIdentficationResult(page) { - loading.show(); - - const options = { - ReplaceAllImages: page.querySelector('#chkIdentifyReplaceImages').checked - }; - - const apiClient = getApiClient(); - - apiClient.ajax({ - type: 'POST', - url: apiClient.getUrl(`Items/RemoteSearch/Apply/${currentItem.Id}`, options), - data: JSON.stringify(currentSearchResult), - contentType: 'application/json' - - }).then(() => { - hasChanges = true; - loading.hide(); - - dialogHelper.close(page); - }, () => { - loading.hide(); - - dialogHelper.close(page); - }); - } - - function showIdentificationForm(page, item) { - const apiClient = getApiClient(); - - apiClient.getJSON(apiClient.getUrl(`Items/${item.Id}/ExternalIdInfos`)).then(idList => { - let html = ''; - - for (let i = 0, length = idList.length; i < length; i++) { - const idInfo = idList[i]; - - const id = `txtLookup${idInfo.Key}`; - - html += '
'; - - let fullName = idInfo.Name; - if (idInfo.Type) { - fullName = `${idInfo.Name} ${globalize.translate(idInfo.Type)}`; - } - - const idLabel = globalize.translate('LabelDynamicExternalId', escapeHtml(fullName)); - - html += ``; - - html += '
'; - } - - page.querySelector('#txtLookupName').value = ''; - - if (item.Type === 'Person' || item.Type === 'BoxSet') { - page.querySelector('.fldLookupYear').classList.add('hide'); - page.querySelector('#txtLookupYear').value = ''; - } else { - page.querySelector('.fldLookupYear').classList.remove('hide'); - page.querySelector('#txtLookupYear').value = ''; - } - - page.querySelector('.identifyProviderIds').innerHTML = html; - - page.querySelector('.formDialogHeaderTitle').innerHTML = globalize.translate('Identify'); - }); - } - - function showEditor(itemId) { - loading.show(); - - const apiClient = getApiClient(); - - apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(item => { - currentItem = item; - currentItemType = currentItem.Type; - - const dialogOptions = { - size: 'small', - removeOnClose: true, - scrollY: false - }; - - if (layoutManager.tv) { - dialogOptions.size = 'fullscreen'; - } - - const dlg = dialogHelper.createDialog(dialogOptions); - - dlg.classList.add('formDialog'); - dlg.classList.add('recordingDialog'); - - let html = ''; - html += globalize.translateHtml(template, 'core'); - - dlg.innerHTML = html; - - // Has to be assigned a z-index after the call to .open() - dlg.addEventListener('close', onDialogClosed); - - if (layoutManager.tv) { - scrollHelper.centerFocus.on(dlg.querySelector('.formDialogContent'), false); - } - - if (item.Path) { - dlg.querySelector('.fldPath').classList.remove('hide'); - } else { - dlg.querySelector('.fldPath').classList.add('hide'); - } - - dlg.querySelector('.txtPath').innerText = item.Path || ''; - - dialogHelper.open(dlg); - - dlg.querySelector('.popupIdentifyForm').addEventListener('submit', e => { - e.preventDefault(); - searchForIdentificationResults(dlg); - return false; - }); - - dlg.querySelector('.identifyOptionsForm').addEventListener('submit', e => { - e.preventDefault(); - submitIdentficationResult(dlg); - return false; - }); - - dlg.querySelector('.btnCancel').addEventListener('click', () => { - dialogHelper.close(dlg); - }); - - dlg.classList.add('identifyDialog'); - - showIdentificationForm(dlg, item); - loading.hide(); - }); - } - - function onDialogClosed() { - loading.hide(); - if (hasChanges) { - currentResolve(); + if (item.Type === 'Person' || item.Type === 'BoxSet') { + page.querySelector('.fldLookupYear').classList.add('hide'); + page.querySelector('#txtLookupYear').value = ''; } else { - currentReject(); + page.querySelector('.fldLookupYear').classList.remove('hide'); + page.querySelector('#txtLookupYear').value = ''; } - } - // TODO investigate where this was used - function showEditorFindNew(itemName, itemYear, itemType, resolveFunc) { - currentItem = null; - currentItemType = itemType; + page.querySelector('.identifyProviderIds').innerHTML = html; + + page.querySelector('.formDialogHeaderTitle').innerHTML = globalize.translate('Identify'); + }); +} + +function showEditor(itemId) { + loading.show(); + + const apiClient = getApiClient(); + + apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(item => { + currentItem = item; + currentItemType = currentItem.Type; const dialogOptions = { size: 'small', @@ -427,15 +352,22 @@ import datetime from '../../scripts/datetime'; dlg.innerHTML = html; + // Has to be assigned a z-index after the call to .open() + dlg.addEventListener('close', onDialogClosed); + if (layoutManager.tv) { scrollHelper.centerFocus.on(dlg.querySelector('.formDialogContent'), false); } - dialogHelper.open(dlg); + if (item.Path) { + dlg.querySelector('.fldPath').classList.remove('hide'); + } else { + dlg.querySelector('.fldPath').classList.add('hide'); + } - dlg.querySelector('.btnCancel').addEventListener('click', () => { - dialogHelper.close(dlg); - }); + dlg.querySelector('.txtPath').innerText = item.Path || ''; + + dialogHelper.open(dlg); dlg.querySelector('.popupIdentifyForm').addEventListener('submit', e => { e.preventDefault(); @@ -443,53 +375,119 @@ import datetime from '../../scripts/datetime'; return false; }); - dlg.addEventListener('close', () => { - loading.hide(); - const foundItem = hasChanges ? currentSearchResult : null; + dlg.querySelector('.identifyOptionsForm').addEventListener('submit', e => { + e.preventDefault(); + submitIdentficationResult(dlg); + return false; + }); - resolveFunc(foundItem); + dlg.querySelector('.btnCancel').addEventListener('click', () => { + dialogHelper.close(dlg); }); dlg.classList.add('identifyDialog'); - showIdentificationFormFindNew(dlg, itemName, itemYear, itemType); + showIdentificationForm(dlg, item); + loading.hide(); + }); +} + +function onDialogClosed() { + loading.hide(); + if (hasChanges) { + currentResolve(); + } else { + currentReject(); + } +} + +// TODO investigate where this was used +function showEditorFindNew(itemName, itemYear, itemType, resolveFunc) { + currentItem = null; + currentItemType = itemType; + + const dialogOptions = { + size: 'small', + removeOnClose: true, + scrollY: false + }; + + if (layoutManager.tv) { + dialogOptions.size = 'fullscreen'; } - function showIdentificationFormFindNew(dlg, itemName, itemYear, itemType) { - dlg.querySelector('#txtLookupName').value = itemName; + const dlg = dialogHelper.createDialog(dialogOptions); - if (itemType === 'Person' || itemType === 'BoxSet') { - dlg.querySelector('.fldLookupYear').classList.add('hide'); - dlg.querySelector('#txtLookupYear').value = ''; - } else { - dlg.querySelector('.fldLookupYear').classList.remove('hide'); - dlg.querySelector('#txtLookupYear').value = itemYear; - } + dlg.classList.add('formDialog'); + dlg.classList.add('recordingDialog'); - dlg.querySelector('.formDialogHeaderTitle').innerHTML = globalize.translate('Search'); + let html = ''; + html += globalize.translateHtml(template, 'core'); + + dlg.innerHTML = html; + + if (layoutManager.tv) { + scrollHelper.centerFocus.on(dlg.querySelector('.formDialogContent'), false); } - export function show(itemId, serverId) { - return new Promise((resolve, reject) => { - currentResolve = resolve; - currentReject = reject; - currentServerId = serverId; - hasChanges = false; + dialogHelper.open(dlg); - showEditor(itemId); - }); + dlg.querySelector('.btnCancel').addEventListener('click', () => { + dialogHelper.close(dlg); + }); + + dlg.querySelector('.popupIdentifyForm').addEventListener('submit', e => { + e.preventDefault(); + searchForIdentificationResults(dlg); + return false; + }); + + dlg.addEventListener('close', () => { + loading.hide(); + const foundItem = hasChanges ? currentSearchResult : null; + + resolveFunc(foundItem); + }); + + dlg.classList.add('identifyDialog'); + + showIdentificationFormFindNew(dlg, itemName, itemYear, itemType); +} + +function showIdentificationFormFindNew(dlg, itemName, itemYear, itemType) { + dlg.querySelector('#txtLookupName').value = itemName; + + if (itemType === 'Person' || itemType === 'BoxSet') { + dlg.querySelector('.fldLookupYear').classList.add('hide'); + dlg.querySelector('#txtLookupYear').value = ''; + } else { + dlg.querySelector('.fldLookupYear').classList.remove('hide'); + dlg.querySelector('#txtLookupYear').value = itemYear; } - export function showFindNew(itemName, itemYear, itemType, serverId) { - return new Promise((resolve) => { - currentServerId = serverId; + dlg.querySelector('.formDialogHeaderTitle').innerHTML = globalize.translate('Search'); +} - hasChanges = false; - showEditorFindNew(itemName, itemYear, itemType, resolve); - }); - } +export function show(itemId, serverId) { + return new Promise((resolve, reject) => { + currentResolve = resolve; + currentReject = reject; + currentServerId = serverId; + hasChanges = false; + + showEditor(itemId); + }); +} + +export function showFindNew(itemName, itemYear, itemType, serverId) { + return new Promise((resolve) => { + currentServerId = serverId; + + hasChanges = false; + showEditorFindNew(itemName, itemYear, itemType, resolve); + }); +} -/* eslint-enable indent */ export default { show: show, showFindNew: showFindNew diff --git a/src/components/lazyLoader/lazyLoaderIntersectionObserver.js b/src/components/lazyLoader/lazyLoaderIntersectionObserver.js index 3b78083cc2..81378710ff 100644 --- a/src/components/lazyLoader/lazyLoaderIntersectionObserver.js +++ b/src/components/lazyLoader/lazyLoaderIntersectionObserver.js @@ -1,69 +1,68 @@ -/* eslint-disable indent */ - export class LazyLoader { - constructor(options) { - this.options = options; - } - createObserver() { - const callback = this.options.callback; +export class LazyLoader { + constructor(options) { + this.options = options; + } - const observer = new IntersectionObserver( - (entries) => { - entries.forEach(entry => { - callback(entry); - }); - }, - { - rootMargin: '50%', - threshold: 0 + createObserver() { + const callback = this.options.callback; + + const observer = new IntersectionObserver( + (entries) => { + entries.forEach(entry => { + callback(entry); }); - - this.observer = observer; - } - - addElements(elements) { - let observer = this.observer; - - if (!observer) { - this.createObserver(); - observer = this.observer; - } - - Array.from(elements).forEach(element => { - observer.observe(element); + }, + { + rootMargin: '50%', + threshold: 0 }); - } - destroyObserver() { - const observer = this.observer; - - if (observer) { - observer.disconnect(); - this.observer = null; - } - } - - destroy() { - this.destroyObserver(); - this.options = null; - } + this.observer = observer; } - function unveilElements(elements, root, callback) { - if (!elements.length) { - return; + addElements(elements) { + let observer = this.observer; + + if (!observer) { + this.createObserver(); + observer = this.observer; } - const lazyLoader = new LazyLoader({ - callback: callback + + Array.from(elements).forEach(element => { + observer.observe(element); }); - lazyLoader.addElements(elements); } - export function lazyChildren(elem, callback) { - unveilElements(elem.getElementsByClassName('lazy'), elem, callback); + destroyObserver() { + const observer = this.observer; + + if (observer) { + observer.disconnect(); + this.observer = null; + } } -/* eslint-enable indent */ + destroy() { + this.destroyObserver(); + this.options = null; + } +} + +function unveilElements(elements, root, callback) { + if (!elements.length) { + return; + } + const lazyLoader = new LazyLoader({ + callback: callback + }); + lazyLoader.addElements(elements); +} + +export function lazyChildren(elem, callback) { + unveilElements(elem.getElementsByClassName('lazy'), elem, callback); +} + export default { LazyLoader: LazyLoader, lazyChildren: lazyChildren diff --git a/src/components/libraryoptionseditor/libraryoptionseditor.js b/src/components/libraryoptionseditor/libraryoptionseditor.js index dabaf1c8a9..0b0f66b61e 100644 --- a/src/components/libraryoptionseditor/libraryoptionseditor.js +++ b/src/components/libraryoptionseditor/libraryoptionseditor.js @@ -1,4 +1,3 @@ -/* eslint-disable indent */ /** * Module for library options editor. @@ -14,606 +13,605 @@ import '../../elements/emby-input/emby-input'; import './style.scss'; import template from './libraryoptionseditor.template.html'; - function populateLanguages(parent) { - return ApiClient.getCultures().then(languages => { - populateLanguagesIntoSelect(parent.querySelector('#selectLanguage'), languages); - populateLanguagesIntoList(parent.querySelector('.subtitleDownloadLanguages'), languages); - }); - } +function populateLanguages(parent) { + return ApiClient.getCultures().then(languages => { + populateLanguagesIntoSelect(parent.querySelector('#selectLanguage'), languages); + populateLanguagesIntoList(parent.querySelector('.subtitleDownloadLanguages'), languages); + }); +} - function populateLanguagesIntoSelect(select, languages) { +function populateLanguagesIntoSelect(select, languages) { + let html = ''; + html += ""; + for (let i = 0; i < languages.length; i++) { + const culture = languages[i]; + html += ``; + } + select.innerHTML = html; +} + +function populateLanguagesIntoList(element, languages) { + let html = ''; + for (let i = 0; i < languages.length; i++) { + const culture = languages[i]; + html += ``; + } + element.innerHTML = html; +} + +function populateCountries(select) { + return ApiClient.getCountries().then(allCountries => { let html = ''; html += ""; - for (let i = 0; i < languages.length; i++) { - const culture = languages[i]; - html += ``; + for (let i = 0; i < allCountries.length; i++) { + const culture = allCountries[i]; + html += ``; } select.innerHTML = html; + }); +} + +function populateRefreshInterval(select) { + let html = ''; + html += ``; + html += [30, 60, 90].map(val => { + return ``; + }).join(''); + select.innerHTML = html; +} + +function renderMetadataReaders(page, plugins) { + let html = ''; + const elem = page.querySelector('.metadataReaders'); + + if (plugins.length < 1) { + elem.innerHTML = ''; + elem.classList.add('hide'); + return false; } - function populateLanguagesIntoList(element, languages) { - let html = ''; - for (let i = 0; i < languages.length; i++) { - const culture = languages[i]; - html += ``; - } - element.innerHTML = html; - } - - function populateCountries(select) { - return ApiClient.getCountries().then(allCountries => { - let html = ''; - html += ""; - for (let i = 0; i < allCountries.length; i++) { - const culture = allCountries[i]; - html += ``; - } - select.innerHTML = html; - }); - } - - function populateRefreshInterval(select) { - let html = ''; - html += ``; - html += [30, 60, 90].map(val => { - return ``; - }).join(''); - select.innerHTML = html; - } - - function renderMetadataReaders(page, plugins) { - let html = ''; - const elem = page.querySelector('.metadataReaders'); - - if (plugins.length < 1) { - elem.innerHTML = ''; - elem.classList.add('hide'); - return false; - } - - html += `

${globalize.translate('LabelMetadataReaders')}

`; - html += '
'; - for (let i = 0; i < plugins.length; i++) { - const plugin = plugins[i]; - html += `
`; - html += ''; - html += '
'; - html += '

'; - html += escapeHtml(plugin.Name); - html += '

'; - html += '
'; - if (i > 0) { - html += ``; - } else if (plugins.length > 1) { - html += ``; - } - html += '
'; + html += `

${globalize.translate('LabelMetadataReaders')}

`; + html += '
'; + for (let i = 0; i < plugins.length; i++) { + const plugin = plugins[i]; + html += `
`; + html += ''; + html += '
'; + html += '

'; + html += escapeHtml(plugin.Name); + html += '

'; + html += '
'; + if (i > 0) { + html += ``; + } else if (plugins.length > 1) { + html += ``; } html += '
'; - html += `
${globalize.translate('LabelMetadataReadersHelp')}
`; - if (plugins.length < 2) { - elem.classList.add('hide'); - } else { - elem.classList.remove('hide'); - } - elem.innerHTML = html; - return true; } - - function renderMetadataSavers(page, metadataSavers) { - let html = ''; - const elem = page.querySelector('.metadataSavers'); - if (!metadataSavers.length) { - elem.innerHTML = ''; - elem.classList.add('hide'); - return false; - } - html += `

${globalize.translate('LabelMetadataSavers')}

`; - html += '
'; - for (let i = 0; i < metadataSavers.length; i++) { - const plugin = metadataSavers[i]; - html += ``; - } - html += '
'; - html += `
${globalize.translate('LabelMetadataSaversHelp')}
`; - elem.innerHTML = html; + html += '
'; + html += `
${globalize.translate('LabelMetadataReadersHelp')}
`; + if (plugins.length < 2) { + elem.classList.add('hide'); + } else { elem.classList.remove('hide'); - return true; } + elem.innerHTML = html; + return true; +} - function getMetadataFetchersForTypeHtml(availableTypeOptions, libraryOptionsForType) { - let html = ''; - let plugins = availableTypeOptions.MetadataFetchers; +function renderMetadataSavers(page, metadataSavers) { + let html = ''; + const elem = page.querySelector('.metadataSavers'); + if (!metadataSavers.length) { + elem.innerHTML = ''; + elem.classList.add('hide'); + return false; + } + html += `

${globalize.translate('LabelMetadataSavers')}

`; + html += '
'; + for (let i = 0; i < metadataSavers.length; i++) { + const plugin = metadataSavers[i]; + html += ``; + } + html += '
'; + html += `
${globalize.translate('LabelMetadataSaversHelp')}
`; + elem.innerHTML = html; + elem.classList.remove('hide'); + return true; +} - plugins = getOrderedPlugins(plugins, libraryOptionsForType.MetadataFetcherOrder || []); - if (!plugins.length) return html; +function getMetadataFetchersForTypeHtml(availableTypeOptions, libraryOptionsForType) { + let html = ''; + let plugins = availableTypeOptions.MetadataFetchers; - html += '
'; - html += '

' + globalize.translate('LabelTypeMetadataDownloaders', globalize.translate('TypeOptionPlural' + availableTypeOptions.Type)) + '

'; - html += '
'; + plugins = getOrderedPlugins(plugins, libraryOptionsForType.MetadataFetcherOrder || []); + if (!plugins.length) return html; - plugins.forEach((plugin, index) => { - html += '
'; - const isChecked = libraryOptionsForType.MetadataFetchers ? libraryOptionsForType.MetadataFetchers.includes(plugin.Name) : plugin.DefaultEnabled; - const checkedHtml = isChecked ? ' checked="checked"' : ''; - html += ''; - html += '
'; - html += '

'; - html += escapeHtml(plugin.Name); - html += '

'; - html += '
'; - if (index > 0) { - html += ''; - } else if (plugins.length > 1) { - html += ''; - } - html += '
'; - }); + html += '
'; + html += '

' + globalize.translate('LabelTypeMetadataDownloaders', globalize.translate('TypeOptionPlural' + availableTypeOptions.Type)) + '

'; + html += '
'; + plugins.forEach((plugin, index) => { + html += '
'; + const isChecked = libraryOptionsForType.MetadataFetchers ? libraryOptionsForType.MetadataFetchers.includes(plugin.Name) : plugin.DefaultEnabled; + const checkedHtml = isChecked ? ' checked="checked"' : ''; + html += ''; + html += '
'; + html += '

'; + html += escapeHtml(plugin.Name); + html += '

'; html += '
'; - html += '
' + globalize.translate('LabelMetadataDownloadersHelp') + '
'; - html += '
'; - return html; - } - - function getTypeOptions(allOptions, type) { - const allTypeOptions = allOptions.TypeOptions || []; - for (let i = 0; i < allTypeOptions.length; i++) { - const typeOptions = allTypeOptions[i]; - if (typeOptions.Type === type) return typeOptions; - } - return null; - } - - function renderMetadataFetchers(page, availableOptions, libraryOptions) { - let html = ''; - const elem = page.querySelector('.metadataFetchers'); - for (let i = 0; i < availableOptions.TypeOptions.length; i++) { - const availableTypeOptions = availableOptions.TypeOptions[i]; - html += getMetadataFetchersForTypeHtml(availableTypeOptions, getTypeOptions(libraryOptions, availableTypeOptions.Type) || {}); - } - elem.innerHTML = html; - if (html) { - elem.classList.remove('hide'); - page.querySelector('.fldAutoRefreshInterval').classList.remove('hide'); - page.querySelector('.fldMetadataLanguage').classList.remove('hide'); - page.querySelector('.fldMetadataCountry').classList.remove('hide'); - } else { - elem.classList.add('hide'); - page.querySelector('.fldAutoRefreshInterval').classList.add('hide'); - page.querySelector('.fldMetadataLanguage').classList.add('hide'); - page.querySelector('.fldMetadataCountry').classList.add('hide'); - } - return true; - } - - function renderSubtitleFetchers(page, availableOptions, libraryOptions) { - let html = ''; - const elem = page.querySelector('.subtitleFetchers'); - - let plugins = availableOptions.SubtitleFetchers; - plugins = getOrderedPlugins(plugins, libraryOptions.SubtitleFetcherOrder || []); - if (!plugins.length) return html; - - html += `

${globalize.translate('LabelSubtitleDownloaders')}

`; - html += '
'; - for (let i = 0; i < plugins.length; i++) { - const plugin = plugins[i]; - html += `
`; - const isChecked = libraryOptions.DisabledSubtitleFetchers ? !libraryOptions.DisabledSubtitleFetchers.includes(plugin.Name) : plugin.DefaultEnabled; - const checkedHtml = isChecked ? ' checked="checked"' : ''; - html += ``; - html += '
'; - html += '

'; - html += escapeHtml(plugin.Name); - html += '

'; - html += '
'; - if (i > 0) { - html += ``; - } else if (plugins.length > 1) { - html += ``; - } - html += '
'; + if (index > 0) { + html += ''; + } else if (plugins.length > 1) { + html += ''; } html += '
'; - html += `
${globalize.translate('SubtitleDownloadersHelp')}
`; - elem.innerHTML = html; + }); + + html += '
'; + html += '
' + globalize.translate('LabelMetadataDownloadersHelp') + '
'; + html += '
'; + return html; +} + +function getTypeOptions(allOptions, type) { + const allTypeOptions = allOptions.TypeOptions || []; + for (let i = 0; i < allTypeOptions.length; i++) { + const typeOptions = allTypeOptions[i]; + if (typeOptions.Type === type) return typeOptions; } + return null; +} - function getImageFetchersForTypeHtml(availableTypeOptions, libraryOptionsForType) { - let html = ''; - let plugins = availableTypeOptions.ImageFetchers; +function renderMetadataFetchers(page, availableOptions, libraryOptions) { + let html = ''; + const elem = page.querySelector('.metadataFetchers'); + for (let i = 0; i < availableOptions.TypeOptions.length; i++) { + const availableTypeOptions = availableOptions.TypeOptions[i]; + html += getMetadataFetchersForTypeHtml(availableTypeOptions, getTypeOptions(libraryOptions, availableTypeOptions.Type) || {}); + } + elem.innerHTML = html; + if (html) { + elem.classList.remove('hide'); + page.querySelector('.fldAutoRefreshInterval').classList.remove('hide'); + page.querySelector('.fldMetadataLanguage').classList.remove('hide'); + page.querySelector('.fldMetadataCountry').classList.remove('hide'); + } else { + elem.classList.add('hide'); + page.querySelector('.fldAutoRefreshInterval').classList.add('hide'); + page.querySelector('.fldMetadataLanguage').classList.add('hide'); + page.querySelector('.fldMetadataCountry').classList.add('hide'); + } + return true; +} - plugins = getOrderedPlugins(plugins, libraryOptionsForType.ImageFetcherOrder || []); - if (!plugins.length) return html; +function renderSubtitleFetchers(page, availableOptions, libraryOptions) { + let html = ''; + const elem = page.querySelector('.subtitleFetchers'); - html += '
'; - html += '
'; - html += '

' + globalize.translate('HeaderTypeImageFetchers', globalize.translate('TypeOptionPlural' + availableTypeOptions.Type)) + '

'; - const supportedImageTypes = availableTypeOptions.SupportedImageTypes || []; - if (supportedImageTypes.length > 1 || supportedImageTypes.length === 1 && supportedImageTypes[0] !== 'Primary') { - html += ''; + let plugins = availableOptions.SubtitleFetchers; + plugins = getOrderedPlugins(plugins, libraryOptions.SubtitleFetcherOrder || []); + if (!plugins.length) return html; + + html += `

${globalize.translate('LabelSubtitleDownloaders')}

`; + html += '
'; + for (let i = 0; i < plugins.length; i++) { + const plugin = plugins[i]; + html += `
`; + const isChecked = libraryOptions.DisabledSubtitleFetchers ? !libraryOptions.DisabledSubtitleFetchers.includes(plugin.Name) : plugin.DefaultEnabled; + const checkedHtml = isChecked ? ' checked="checked"' : ''; + html += ``; + html += '
'; + html += '

'; + html += escapeHtml(plugin.Name); + html += '

'; + html += '
'; + if (i > 0) { + html += ``; + } else if (plugins.length > 1) { + html += ``; } html += '
'; - html += '
'; - for (let i = 0; i < plugins.length; i++) { - const plugin = plugins[i]; - html += '
'; - const isChecked = libraryOptionsForType.ImageFetchers ? libraryOptionsForType.ImageFetchers.includes(plugin.Name) : plugin.DefaultEnabled; - const checkedHtml = isChecked ? ' checked="checked"' : ''; - html += ''; - html += '
'; - html += '

'; - html += escapeHtml(plugin.Name); - html += '

'; - html += '
'; - if (i > 0) { - html += ''; - } else if (plugins.length > 1) { - html += ''; - } - html += '
'; + } + html += '
'; + html += `
${globalize.translate('SubtitleDownloadersHelp')}
`; + elem.innerHTML = html; +} + +function getImageFetchersForTypeHtml(availableTypeOptions, libraryOptionsForType) { + let html = ''; + let plugins = availableTypeOptions.ImageFetchers; + + plugins = getOrderedPlugins(plugins, libraryOptionsForType.ImageFetcherOrder || []); + if (!plugins.length) return html; + + html += '
'; + html += '
'; + html += '

' + globalize.translate('HeaderTypeImageFetchers', globalize.translate('TypeOptionPlural' + availableTypeOptions.Type)) + '

'; + const supportedImageTypes = availableTypeOptions.SupportedImageTypes || []; + if (supportedImageTypes.length > 1 || supportedImageTypes.length === 1 && supportedImageTypes[0] !== 'Primary') { + html += ''; + } + html += '
'; + html += '
'; + for (let i = 0; i < plugins.length; i++) { + const plugin = plugins[i]; + html += '
'; + const isChecked = libraryOptionsForType.ImageFetchers ? libraryOptionsForType.ImageFetchers.includes(plugin.Name) : plugin.DefaultEnabled; + const checkedHtml = isChecked ? ' checked="checked"' : ''; + html += ''; + html += '
'; + html += '

'; + html += escapeHtml(plugin.Name); + html += '

'; + html += '
'; + if (i > 0) { + html += ''; + } else if (plugins.length > 1) { + html += ''; } html += '
'; - html += '
' + globalize.translate('LabelImageFetchersHelp') + '
'; - html += '
'; - return html; } + html += '
'; + html += '
' + globalize.translate('LabelImageFetchersHelp') + '
'; + html += '
'; + return html; +} - function renderImageFetchers(page, availableOptions, libraryOptions) { - let html = ''; - const elem = page.querySelector('.imageFetchers'); - for (let i = 0; i < availableOptions.TypeOptions.length; i++) { - const availableTypeOptions = availableOptions.TypeOptions[i]; - html += getImageFetchersForTypeHtml(availableTypeOptions, getTypeOptions(libraryOptions, availableTypeOptions.Type) || {}); +function renderImageFetchers(page, availableOptions, libraryOptions) { + let html = ''; + const elem = page.querySelector('.imageFetchers'); + for (let i = 0; i < availableOptions.TypeOptions.length; i++) { + const availableTypeOptions = availableOptions.TypeOptions[i]; + html += getImageFetchersForTypeHtml(availableTypeOptions, getTypeOptions(libraryOptions, availableTypeOptions.Type) || {}); + } + elem.innerHTML = html; + if (html) { + elem.classList.remove('hide'); + page.querySelector('.chkSaveLocalContainer').classList.remove('hide'); + } else { + elem.classList.add('hide'); + page.querySelector('.chkSaveLocalContainer').classList.add('hide'); + } + return true; +} + +function populateMetadataSettings(parent, contentType) { + const isNewLibrary = parent.classList.contains('newlibrary'); + return ApiClient.getJSON(ApiClient.getUrl('Libraries/AvailableOptions', { + LibraryContentType: contentType, + IsNewLibrary: isNewLibrary + })).then(availableOptions => { + currentAvailableOptions = availableOptions; + parent.availableOptions = availableOptions; + renderMetadataSavers(parent, availableOptions.MetadataSavers); + renderMetadataReaders(parent, availableOptions.MetadataReaders); + renderMetadataFetchers(parent, availableOptions, {}); + renderSubtitleFetchers(parent, availableOptions, {}); + renderImageFetchers(parent, availableOptions, {}); + availableOptions.SubtitleFetchers.length ? parent.querySelector('.subtitleDownloadSettings').classList.remove('hide') : parent.querySelector('.subtitleDownloadSettings').classList.add('hide'); + }).catch(() => { + return Promise.resolve(); + }); +} + +function adjustSortableListElement(elem) { + const btnSortable = elem.querySelector('.btnSortable'); + const inner = btnSortable.querySelector('.material-icons'); + if (elem.previousSibling) { + btnSortable.title = globalize.translate('Up'); + btnSortable.classList.add('btnSortableMoveUp'); + btnSortable.classList.remove('btnSortableMoveDown'); + inner.classList.remove('keyboard_arrow_down'); + inner.classList.add('keyboard_arrow_up'); + } else { + btnSortable.title = globalize.translate('Down'); + btnSortable.classList.remove('btnSortableMoveUp'); + btnSortable.classList.add('btnSortableMoveDown'); + inner.classList.remove('keyboard_arrow_up'); + inner.classList.add('keyboard_arrow_down'); + } +} + +function showImageOptionsForType(type) { + import('../imageOptionsEditor/imageOptionsEditor').then(({ default: ImageOptionsEditor }) => { + let typeOptions = getTypeOptions(currentLibraryOptions, type); + if (!typeOptions) { + typeOptions = { + Type: type + }; + currentLibraryOptions.TypeOptions.push(typeOptions); } - elem.innerHTML = html; - if (html) { - elem.classList.remove('hide'); - page.querySelector('.chkSaveLocalContainer').classList.remove('hide'); - } else { - elem.classList.add('hide'); - page.querySelector('.chkSaveLocalContainer').classList.add('hide'); - } - return true; - } + const availableOptions = getTypeOptions(currentAvailableOptions || {}, type); + const imageOptionsEditor = new ImageOptionsEditor(); + imageOptionsEditor.show(type, typeOptions, availableOptions); + }); +} - function populateMetadataSettings(parent, contentType) { - const isNewLibrary = parent.classList.contains('newlibrary'); - return ApiClient.getJSON(ApiClient.getUrl('Libraries/AvailableOptions', { - LibraryContentType: contentType, - IsNewLibrary: isNewLibrary - })).then(availableOptions => { - currentAvailableOptions = availableOptions; - parent.availableOptions = availableOptions; - renderMetadataSavers(parent, availableOptions.MetadataSavers); - renderMetadataReaders(parent, availableOptions.MetadataReaders); - renderMetadataFetchers(parent, availableOptions, {}); - renderSubtitleFetchers(parent, availableOptions, {}); - renderImageFetchers(parent, availableOptions, {}); - availableOptions.SubtitleFetchers.length ? parent.querySelector('.subtitleDownloadSettings').classList.remove('hide') : parent.querySelector('.subtitleDownloadSettings').classList.add('hide'); - }).catch(() => { - return Promise.resolve(); - }); +function onImageFetchersContainerClick(e) { + const btnImageOptionsForType = dom.parentWithClass(e.target, 'btnImageOptionsForType'); + if (btnImageOptionsForType) { + showImageOptionsForType(dom.parentWithClass(btnImageOptionsForType, 'imageFetcher').getAttribute('data-type')); + return; } + onSortableContainerClick.call(this, e); +} - function adjustSortableListElement(elem) { - const btnSortable = elem.querySelector('.btnSortable'); - const inner = btnSortable.querySelector('.material-icons'); - if (elem.previousSibling) { - btnSortable.title = globalize.translate('Up'); - btnSortable.classList.add('btnSortableMoveUp'); - btnSortable.classList.remove('btnSortableMoveDown'); - inner.classList.remove('keyboard_arrow_down'); - inner.classList.add('keyboard_arrow_up'); - } else { - btnSortable.title = globalize.translate('Down'); - btnSortable.classList.remove('btnSortableMoveUp'); - btnSortable.classList.add('btnSortableMoveDown'); - inner.classList.remove('keyboard_arrow_up'); - inner.classList.add('keyboard_arrow_down'); - } - } - - function showImageOptionsForType(type) { - import('../imageOptionsEditor/imageOptionsEditor').then(({ default: ImageOptionsEditor }) => { - let typeOptions = getTypeOptions(currentLibraryOptions, type); - if (!typeOptions) { - typeOptions = { - Type: type - }; - currentLibraryOptions.TypeOptions.push(typeOptions); +function onSortableContainerClick(e) { + const btnSortable = dom.parentWithClass(e.target, 'btnSortable'); + if (btnSortable) { + const li = dom.parentWithClass(btnSortable, 'sortableOption'); + const list = dom.parentWithClass(li, 'paperList'); + if (btnSortable.classList.contains('btnSortableMoveDown')) { + const next = li.nextSibling; + if (next) { + li.parentNode.removeChild(li); + next.parentNode.insertBefore(li, next.nextSibling); } - const availableOptions = getTypeOptions(currentAvailableOptions || {}, type); - const imageOptionsEditor = new ImageOptionsEditor(); - imageOptionsEditor.show(type, typeOptions, availableOptions); - }); - } - - function onImageFetchersContainerClick(e) { - const btnImageOptionsForType = dom.parentWithClass(e.target, 'btnImageOptionsForType'); - if (btnImageOptionsForType) { - showImageOptionsForType(dom.parentWithClass(btnImageOptionsForType, 'imageFetcher').getAttribute('data-type')); - return; - } - onSortableContainerClick.call(this, e); - } - - function onSortableContainerClick(e) { - const btnSortable = dom.parentWithClass(e.target, 'btnSortable'); - if (btnSortable) { - const li = dom.parentWithClass(btnSortable, 'sortableOption'); - const list = dom.parentWithClass(li, 'paperList'); - if (btnSortable.classList.contains('btnSortableMoveDown')) { - const next = li.nextSibling; - if (next) { - li.parentNode.removeChild(li); - next.parentNode.insertBefore(li, next.nextSibling); - } - } else { - const prev = li.previousSibling; - if (prev) { - li.parentNode.removeChild(li); - prev.parentNode.insertBefore(li, prev); - } - } - Array.prototype.forEach.call(list.querySelectorAll('.sortableOption'), adjustSortableListElement); - } - } - - function bindEvents(parent) { - parent.querySelector('.metadataReaders').addEventListener('click', onSortableContainerClick); - parent.querySelector('.subtitleFetchers').addEventListener('click', onSortableContainerClick); - parent.querySelector('.metadataFetchers').addEventListener('click', onSortableContainerClick); - parent.querySelector('.imageFetchers').addEventListener('click', onImageFetchersContainerClick); - - parent.querySelector('#chkEnableEmbeddedTitles').addEventListener('change', (e) => { - parent.querySelector('.chkEnableEmbeddedExtrasTitlesContainer').classList.toggle('hide', !e.currentTarget.checked); - }); - } - - export async function embed(parent, contentType, libraryOptions) { - currentLibraryOptions = { - TypeOptions: [] - }; - currentAvailableOptions = null; - const isNewLibrary = libraryOptions === null; - isNewLibrary && parent.classList.add('newlibrary'); - - parent.innerHTML = globalize.translateHtml(template); - populateRefreshInterval(parent.querySelector('#selectAutoRefreshInterval')); - const promises = [populateLanguages(parent), populateCountries(parent.querySelector('#selectCountry'))]; - Promise.all(promises).then(function() { - return setContentType(parent, contentType).then(function() { - libraryOptions && setLibraryOptions(parent, libraryOptions); - bindEvents(parent); - }); - }); - } - - export function setContentType(parent, contentType) { - if (contentType === 'homevideos' || contentType === 'photos') { - parent.querySelector('.chkEnablePhotosContainer').classList.remove('hide'); } else { - parent.querySelector('.chkEnablePhotosContainer').classList.add('hide'); - } - - if (contentType !== 'tvshows' && contentType !== 'movies' && contentType !== 'homevideos' && contentType !== 'musicvideos' && contentType !== 'mixed') { - parent.querySelector('.chapterSettingsSection').classList.add('hide'); - } else { - parent.querySelector('.chapterSettingsSection').classList.remove('hide'); - } - - if (contentType === 'tvshows') { - parent.querySelector('.chkAutomaticallyGroupSeriesContainer').classList.remove('hide'); - parent.querySelector('.fldSeasonZeroDisplayName').classList.remove('hide'); - parent.querySelector('#txtSeasonZeroName').setAttribute('required', 'required'); - } else { - parent.querySelector('.chkAutomaticallyGroupSeriesContainer').classList.add('hide'); - parent.querySelector('.fldSeasonZeroDisplayName').classList.add('hide'); - parent.querySelector('#txtSeasonZeroName').removeAttribute('required'); - } - - if (contentType === 'books' || contentType === 'boxsets' || contentType === 'playlists' || contentType === 'music') { - parent.querySelector('.chkEnableEmbeddedTitlesContainer').classList.add('hide'); - parent.querySelector('.chkEnableEmbeddedExtrasTitlesContainer').classList.add('hide'); - } else { - parent.querySelector('.chkEnableEmbeddedTitlesContainer').classList.remove('hide'); - if (parent.querySelector('#chkEnableEmbeddedTitles').checked) { - parent.querySelector('.chkEnableEmbeddedExtrasTitlesContainer').classList.remove('hide'); + const prev = li.previousSibling; + if (prev) { + li.parentNode.removeChild(li); + prev.parentNode.insertBefore(li, prev); } } - - if (contentType === 'tvshows') { - parent.querySelector('.chkEnableEmbeddedEpisodeInfosContainer').classList.remove('hide'); - } else { - parent.querySelector('.chkEnableEmbeddedEpisodeInfosContainer').classList.add('hide'); - } - - if (contentType === 'tvshows' || contentType === 'movies' || contentType === 'musicvideos' || contentType === 'mixed') { - parent.querySelector('.fldAllowEmbeddedSubtitlesContainer').classList.remove('hide'); - } else { - parent.querySelector('.fldAllowEmbeddedSubtitlesContainer').classList.add('hide'); - } - - parent.querySelector('.chkAutomaticallyAddToCollectionContainer').classList.toggle('hide', contentType !== 'movies' && contentType !== 'mixed'); - - return populateMetadataSettings(parent, contentType); + Array.prototype.forEach.call(list.querySelectorAll('.sortableOption'), adjustSortableListElement); } +} - function setSubtitleFetchersIntoOptions(parent, options) { - options.DisabledSubtitleFetchers = Array.prototype.map.call(Array.prototype.filter.call(parent.querySelectorAll('.chkSubtitleFetcher'), elem => { - return !elem.checked; - }), elem => { - return elem.getAttribute('data-pluginname'); +function bindEvents(parent) { + parent.querySelector('.metadataReaders').addEventListener('click', onSortableContainerClick); + parent.querySelector('.subtitleFetchers').addEventListener('click', onSortableContainerClick); + parent.querySelector('.metadataFetchers').addEventListener('click', onSortableContainerClick); + parent.querySelector('.imageFetchers').addEventListener('click', onImageFetchersContainerClick); + + parent.querySelector('#chkEnableEmbeddedTitles').addEventListener('change', (e) => { + parent.querySelector('.chkEnableEmbeddedExtrasTitlesContainer').classList.toggle('hide', !e.currentTarget.checked); + }); +} + +export async function embed(parent, contentType, libraryOptions) { + currentLibraryOptions = { + TypeOptions: [] + }; + currentAvailableOptions = null; + const isNewLibrary = libraryOptions === null; + isNewLibrary && parent.classList.add('newlibrary'); + + parent.innerHTML = globalize.translateHtml(template); + populateRefreshInterval(parent.querySelector('#selectAutoRefreshInterval')); + const promises = [populateLanguages(parent), populateCountries(parent.querySelector('#selectCountry'))]; + Promise.all(promises).then(function() { + return setContentType(parent, contentType).then(function() { + libraryOptions && setLibraryOptions(parent, libraryOptions); + bindEvents(parent); }); + }); +} - options.SubtitleFetcherOrder = Array.prototype.map.call(parent.querySelectorAll('.subtitleFetcherItem'), elem => { - return elem.getAttribute('data-pluginname'); - }); +export function setContentType(parent, contentType) { + if (contentType === 'homevideos' || contentType === 'photos') { + parent.querySelector('.chkEnablePhotosContainer').classList.remove('hide'); + } else { + parent.querySelector('.chkEnablePhotosContainer').classList.add('hide'); } - function setMetadataFetchersIntoOptions(parent, options) { - const sections = parent.querySelectorAll('.metadataFetcher'); - for (let i = 0; i < sections.length; i++) { - const section = sections[i]; - const type = section.getAttribute('data-type'); - let typeOptions = getTypeOptions(options, type); - if (!typeOptions) { - typeOptions = { - Type: type - }; - options.TypeOptions.push(typeOptions); - } - typeOptions.MetadataFetchers = Array.prototype.map.call(Array.prototype.filter.call(section.querySelectorAll('.chkMetadataFetcher'), elem => { - return elem.checked; - }), elem => { - return elem.getAttribute('data-pluginname'); - }); + if (contentType !== 'tvshows' && contentType !== 'movies' && contentType !== 'homevideos' && contentType !== 'musicvideos' && contentType !== 'mixed') { + parent.querySelector('.chapterSettingsSection').classList.add('hide'); + } else { + parent.querySelector('.chapterSettingsSection').classList.remove('hide'); + } - typeOptions.MetadataFetcherOrder = Array.prototype.map.call(section.querySelectorAll('.metadataFetcherItem'), elem => { - return elem.getAttribute('data-pluginname'); - }); + if (contentType === 'tvshows') { + parent.querySelector('.chkAutomaticallyGroupSeriesContainer').classList.remove('hide'); + parent.querySelector('.fldSeasonZeroDisplayName').classList.remove('hide'); + parent.querySelector('#txtSeasonZeroName').setAttribute('required', 'required'); + } else { + parent.querySelector('.chkAutomaticallyGroupSeriesContainer').classList.add('hide'); + parent.querySelector('.fldSeasonZeroDisplayName').classList.add('hide'); + parent.querySelector('#txtSeasonZeroName').removeAttribute('required'); + } + + if (contentType === 'books' || contentType === 'boxsets' || contentType === 'playlists' || contentType === 'music') { + parent.querySelector('.chkEnableEmbeddedTitlesContainer').classList.add('hide'); + parent.querySelector('.chkEnableEmbeddedExtrasTitlesContainer').classList.add('hide'); + } else { + parent.querySelector('.chkEnableEmbeddedTitlesContainer').classList.remove('hide'); + if (parent.querySelector('#chkEnableEmbeddedTitles').checked) { + parent.querySelector('.chkEnableEmbeddedExtrasTitlesContainer').classList.remove('hide'); } } - function setImageFetchersIntoOptions(parent, options) { - const sections = parent.querySelectorAll('.imageFetcher'); - for (let i = 0; i < sections.length; i++) { - const section = sections[i]; - const type = section.getAttribute('data-type'); - let typeOptions = getTypeOptions(options, type); - if (!typeOptions) { - typeOptions = { - Type: type - }; - options.TypeOptions.push(typeOptions); - } - - typeOptions.ImageFetchers = Array.prototype.map.call(Array.prototype.filter.call(section.querySelectorAll('.chkImageFetcher'), elem => { - return elem.checked; - }), elem => { - return elem.getAttribute('data-pluginname'); - }); - - typeOptions.ImageFetcherOrder = Array.prototype.map.call(section.querySelectorAll('.imageFetcherItem'), elem => { - return elem.getAttribute('data-pluginname'); - }); - } + if (contentType === 'tvshows') { + parent.querySelector('.chkEnableEmbeddedEpisodeInfosContainer').classList.remove('hide'); + } else { + parent.querySelector('.chkEnableEmbeddedEpisodeInfosContainer').classList.add('hide'); } - function setImageOptionsIntoOptions(options) { - const originalTypeOptions = (currentLibraryOptions || {}).TypeOptions || []; - for (let i = 0; i < originalTypeOptions.length; i++) { - const originalTypeOption = originalTypeOptions[i]; - let typeOptions = getTypeOptions(options, originalTypeOption.Type); - - if (!typeOptions) { - typeOptions = { - Type: originalTypeOption.Type - }; - options.TypeOptions.push(typeOptions); - } - originalTypeOption.ImageOptions && (typeOptions.ImageOptions = originalTypeOption.ImageOptions); - } + if (contentType === 'tvshows' || contentType === 'movies' || contentType === 'musicvideos' || contentType === 'mixed') { + parent.querySelector('.fldAllowEmbeddedSubtitlesContainer').classList.remove('hide'); + } else { + parent.querySelector('.fldAllowEmbeddedSubtitlesContainer').classList.add('hide'); } - export function getLibraryOptions(parent) { - const options = { - EnableArchiveMediaFiles: false, - EnablePhotos: parent.querySelector('.chkEnablePhotos').checked, - EnableRealtimeMonitor: parent.querySelector('.chkEnableRealtimeMonitor').checked, - ExtractChapterImagesDuringLibraryScan: parent.querySelector('.chkExtractChaptersDuringLibraryScan').checked, - EnableChapterImageExtraction: parent.querySelector('.chkExtractChapterImages').checked, - EnableInternetProviders: true, - SaveLocalMetadata: parent.querySelector('#chkSaveLocal').checked, - EnableAutomaticSeriesGrouping: parent.querySelector('.chkAutomaticallyGroupSeries').checked, - PreferredMetadataLanguage: parent.querySelector('#selectLanguage').value, - MetadataCountryCode: parent.querySelector('#selectCountry').value, - SeasonZeroDisplayName: parent.querySelector('#txtSeasonZeroName').value, - AutomaticRefreshIntervalDays: parseInt(parent.querySelector('#selectAutoRefreshInterval').value, 10), - EnableEmbeddedTitles: parent.querySelector('#chkEnableEmbeddedTitles').checked, - EnableEmbeddedExtrasTitles: parent.querySelector('#chkEnableEmbeddedExtrasTitles').checked, - EnableEmbeddedEpisodeInfos: parent.querySelector('#chkEnableEmbeddedEpisodeInfos').checked, - AllowEmbeddedSubtitles: parent.querySelector('#selectAllowEmbeddedSubtitles').value, - SkipSubtitlesIfEmbeddedSubtitlesPresent: parent.querySelector('#chkSkipIfGraphicalSubsPresent').checked, - SkipSubtitlesIfAudioTrackMatches: parent.querySelector('#chkSkipIfAudioTrackPresent').checked, - SaveSubtitlesWithMedia: parent.querySelector('#chkSaveSubtitlesLocally').checked, - RequirePerfectSubtitleMatch: parent.querySelector('#chkRequirePerfectMatch').checked, - AutomaticallyAddToCollection: parent.querySelector('#chkAutomaticallyAddToCollection').checked, - MetadataSavers: Array.prototype.map.call(Array.prototype.filter.call(parent.querySelectorAll('.chkMetadataSaver'), elem => { - return elem.checked; - }), elem => { - return elem.getAttribute('data-pluginname'); - }), - TypeOptions: [] - }; + parent.querySelector('.chkAutomaticallyAddToCollectionContainer').classList.toggle('hide', contentType !== 'movies' && contentType !== 'mixed'); - options.LocalMetadataReaderOrder = Array.prototype.map.call(parent.querySelectorAll('.localReaderOption'), elem => { - return elem.getAttribute('data-pluginname'); - }); - options.SubtitleDownloadLanguages = Array.prototype.map.call(Array.prototype.filter.call(parent.querySelectorAll('.chkSubtitleLanguage'), elem => { + return populateMetadataSettings(parent, contentType); +} + +function setSubtitleFetchersIntoOptions(parent, options) { + options.DisabledSubtitleFetchers = Array.prototype.map.call(Array.prototype.filter.call(parent.querySelectorAll('.chkSubtitleFetcher'), elem => { + return !elem.checked; + }), elem => { + return elem.getAttribute('data-pluginname'); + }); + + options.SubtitleFetcherOrder = Array.prototype.map.call(parent.querySelectorAll('.subtitleFetcherItem'), elem => { + return elem.getAttribute('data-pluginname'); + }); +} + +function setMetadataFetchersIntoOptions(parent, options) { + const sections = parent.querySelectorAll('.metadataFetcher'); + for (let i = 0; i < sections.length; i++) { + const section = sections[i]; + const type = section.getAttribute('data-type'); + let typeOptions = getTypeOptions(options, type); + if (!typeOptions) { + typeOptions = { + Type: type + }; + options.TypeOptions.push(typeOptions); + } + typeOptions.MetadataFetchers = Array.prototype.map.call(Array.prototype.filter.call(section.querySelectorAll('.chkMetadataFetcher'), elem => { return elem.checked; }), elem => { - return elem.getAttribute('data-lang'); + return elem.getAttribute('data-pluginname'); }); - setSubtitleFetchersIntoOptions(parent, options); - setMetadataFetchersIntoOptions(parent, options); - setImageFetchersIntoOptions(parent, options); - setImageOptionsIntoOptions(options); - return options; + typeOptions.MetadataFetcherOrder = Array.prototype.map.call(section.querySelectorAll('.metadataFetcherItem'), elem => { + return elem.getAttribute('data-pluginname'); + }); } +} - function getOrderedPlugins(plugins, configuredOrder) { - plugins = plugins.slice(0); - plugins.sort((a, b) => { - a = configuredOrder.indexOf(a.Name); - b = configuredOrder.indexOf(b.Name); - return a - b; +function setImageFetchersIntoOptions(parent, options) { + const sections = parent.querySelectorAll('.imageFetcher'); + for (let i = 0; i < sections.length; i++) { + const section = sections[i]; + const type = section.getAttribute('data-type'); + let typeOptions = getTypeOptions(options, type); + if (!typeOptions) { + typeOptions = { + Type: type + }; + options.TypeOptions.push(typeOptions); + } + + typeOptions.ImageFetchers = Array.prototype.map.call(Array.prototype.filter.call(section.querySelectorAll('.chkImageFetcher'), elem => { + return elem.checked; + }), elem => { + return elem.getAttribute('data-pluginname'); + }); + + typeOptions.ImageFetcherOrder = Array.prototype.map.call(section.querySelectorAll('.imageFetcherItem'), elem => { + return elem.getAttribute('data-pluginname'); }); - return plugins; } +} - export function setLibraryOptions(parent, options) { - currentLibraryOptions = options; - currentAvailableOptions = parent.availableOptions; - parent.querySelector('#selectLanguage').value = options.PreferredMetadataLanguage || ''; - parent.querySelector('#selectCountry').value = options.MetadataCountryCode || ''; - parent.querySelector('#selectAutoRefreshInterval').value = options.AutomaticRefreshIntervalDays || '0'; - parent.querySelector('#txtSeasonZeroName').value = options.SeasonZeroDisplayName || 'Specials'; - parent.querySelector('.chkEnablePhotos').checked = options.EnablePhotos; - parent.querySelector('.chkEnableRealtimeMonitor').checked = options.EnableRealtimeMonitor; - parent.querySelector('.chkExtractChaptersDuringLibraryScan').checked = options.ExtractChapterImagesDuringLibraryScan; - parent.querySelector('.chkExtractChapterImages').checked = options.EnableChapterImageExtraction; - parent.querySelector('#chkSaveLocal').checked = options.SaveLocalMetadata; - parent.querySelector('.chkAutomaticallyGroupSeries').checked = options.EnableAutomaticSeriesGrouping; - parent.querySelector('#chkEnableEmbeddedTitles').checked = options.EnableEmbeddedTitles; - parent.querySelector('.chkEnableEmbeddedExtrasTitlesContainer').classList.toggle('hide', !options.EnableEmbeddedTitles); - parent.querySelector('#chkEnableEmbeddedExtrasTitles').checked = options.EnableEmbeddedExtrasTitles; - parent.querySelector('#chkEnableEmbeddedEpisodeInfos').value = options.EnableEmbeddedEpisodeInfos; - parent.querySelector('#selectAllowEmbeddedSubtitles').value = options.AllowEmbeddedSubtitles; - parent.querySelector('#chkSkipIfGraphicalSubsPresent').checked = options.SkipSubtitlesIfEmbeddedSubtitlesPresent; - parent.querySelector('#chkSaveSubtitlesLocally').checked = options.SaveSubtitlesWithMedia; - parent.querySelector('#chkSkipIfAudioTrackPresent').checked = options.SkipSubtitlesIfAudioTrackMatches; - parent.querySelector('#chkRequirePerfectMatch').checked = options.RequirePerfectSubtitleMatch; - parent.querySelector('#chkAutomaticallyAddToCollection').checked = options.AutomaticallyAddToCollection; - Array.prototype.forEach.call(parent.querySelectorAll('.chkMetadataSaver'), elem => { - elem.checked = options.MetadataSavers ? options.MetadataSavers.includes(elem.getAttribute('data-pluginname')) : elem.getAttribute('data-defaultenabled') === 'true'; - }); - Array.prototype.forEach.call(parent.querySelectorAll('.chkSubtitleLanguage'), elem => { - elem.checked = !!options.SubtitleDownloadLanguages && options.SubtitleDownloadLanguages.includes(elem.getAttribute('data-lang')); - }); - renderMetadataReaders(parent, getOrderedPlugins(parent.availableOptions.MetadataReaders, options.LocalMetadataReaderOrder || [])); - renderMetadataFetchers(parent, parent.availableOptions, options); - renderImageFetchers(parent, parent.availableOptions, options); - renderSubtitleFetchers(parent, parent.availableOptions, options); +function setImageOptionsIntoOptions(options) { + const originalTypeOptions = (currentLibraryOptions || {}).TypeOptions || []; + for (let i = 0; i < originalTypeOptions.length; i++) { + const originalTypeOption = originalTypeOptions[i]; + let typeOptions = getTypeOptions(options, originalTypeOption.Type); + + if (!typeOptions) { + typeOptions = { + Type: originalTypeOption.Type + }; + options.TypeOptions.push(typeOptions); + } + originalTypeOption.ImageOptions && (typeOptions.ImageOptions = originalTypeOption.ImageOptions); } +} - let currentLibraryOptions; - let currentAvailableOptions; +export function getLibraryOptions(parent) { + const options = { + EnableArchiveMediaFiles: false, + EnablePhotos: parent.querySelector('.chkEnablePhotos').checked, + EnableRealtimeMonitor: parent.querySelector('.chkEnableRealtimeMonitor').checked, + ExtractChapterImagesDuringLibraryScan: parent.querySelector('.chkExtractChaptersDuringLibraryScan').checked, + EnableChapterImageExtraction: parent.querySelector('.chkExtractChapterImages').checked, + EnableInternetProviders: true, + SaveLocalMetadata: parent.querySelector('#chkSaveLocal').checked, + EnableAutomaticSeriesGrouping: parent.querySelector('.chkAutomaticallyGroupSeries').checked, + PreferredMetadataLanguage: parent.querySelector('#selectLanguage').value, + MetadataCountryCode: parent.querySelector('#selectCountry').value, + SeasonZeroDisplayName: parent.querySelector('#txtSeasonZeroName').value, + AutomaticRefreshIntervalDays: parseInt(parent.querySelector('#selectAutoRefreshInterval').value, 10), + EnableEmbeddedTitles: parent.querySelector('#chkEnableEmbeddedTitles').checked, + EnableEmbeddedExtrasTitles: parent.querySelector('#chkEnableEmbeddedExtrasTitles').checked, + EnableEmbeddedEpisodeInfos: parent.querySelector('#chkEnableEmbeddedEpisodeInfos').checked, + AllowEmbeddedSubtitles: parent.querySelector('#selectAllowEmbeddedSubtitles').value, + SkipSubtitlesIfEmbeddedSubtitlesPresent: parent.querySelector('#chkSkipIfGraphicalSubsPresent').checked, + SkipSubtitlesIfAudioTrackMatches: parent.querySelector('#chkSkipIfAudioTrackPresent').checked, + SaveSubtitlesWithMedia: parent.querySelector('#chkSaveSubtitlesLocally').checked, + RequirePerfectSubtitleMatch: parent.querySelector('#chkRequirePerfectMatch').checked, + AutomaticallyAddToCollection: parent.querySelector('#chkAutomaticallyAddToCollection').checked, + MetadataSavers: Array.prototype.map.call(Array.prototype.filter.call(parent.querySelectorAll('.chkMetadataSaver'), elem => { + return elem.checked; + }), elem => { + return elem.getAttribute('data-pluginname'); + }), + TypeOptions: [] + }; + + options.LocalMetadataReaderOrder = Array.prototype.map.call(parent.querySelectorAll('.localReaderOption'), elem => { + return elem.getAttribute('data-pluginname'); + }); + options.SubtitleDownloadLanguages = Array.prototype.map.call(Array.prototype.filter.call(parent.querySelectorAll('.chkSubtitleLanguage'), elem => { + return elem.checked; + }), elem => { + return elem.getAttribute('data-lang'); + }); + setSubtitleFetchersIntoOptions(parent, options); + setMetadataFetchersIntoOptions(parent, options); + setImageFetchersIntoOptions(parent, options); + setImageOptionsIntoOptions(options); + + return options; +} + +function getOrderedPlugins(plugins, configuredOrder) { + plugins = plugins.slice(0); + plugins.sort((a, b) => { + a = configuredOrder.indexOf(a.Name); + b = configuredOrder.indexOf(b.Name); + return a - b; + }); + return plugins; +} + +export function setLibraryOptions(parent, options) { + currentLibraryOptions = options; + currentAvailableOptions = parent.availableOptions; + parent.querySelector('#selectLanguage').value = options.PreferredMetadataLanguage || ''; + parent.querySelector('#selectCountry').value = options.MetadataCountryCode || ''; + parent.querySelector('#selectAutoRefreshInterval').value = options.AutomaticRefreshIntervalDays || '0'; + parent.querySelector('#txtSeasonZeroName').value = options.SeasonZeroDisplayName || 'Specials'; + parent.querySelector('.chkEnablePhotos').checked = options.EnablePhotos; + parent.querySelector('.chkEnableRealtimeMonitor').checked = options.EnableRealtimeMonitor; + parent.querySelector('.chkExtractChaptersDuringLibraryScan').checked = options.ExtractChapterImagesDuringLibraryScan; + parent.querySelector('.chkExtractChapterImages').checked = options.EnableChapterImageExtraction; + parent.querySelector('#chkSaveLocal').checked = options.SaveLocalMetadata; + parent.querySelector('.chkAutomaticallyGroupSeries').checked = options.EnableAutomaticSeriesGrouping; + parent.querySelector('#chkEnableEmbeddedTitles').checked = options.EnableEmbeddedTitles; + parent.querySelector('.chkEnableEmbeddedExtrasTitlesContainer').classList.toggle('hide', !options.EnableEmbeddedTitles); + parent.querySelector('#chkEnableEmbeddedExtrasTitles').checked = options.EnableEmbeddedExtrasTitles; + parent.querySelector('#chkEnableEmbeddedEpisodeInfos').value = options.EnableEmbeddedEpisodeInfos; + parent.querySelector('#selectAllowEmbeddedSubtitles').value = options.AllowEmbeddedSubtitles; + parent.querySelector('#chkSkipIfGraphicalSubsPresent').checked = options.SkipSubtitlesIfEmbeddedSubtitlesPresent; + parent.querySelector('#chkSaveSubtitlesLocally').checked = options.SaveSubtitlesWithMedia; + parent.querySelector('#chkSkipIfAudioTrackPresent').checked = options.SkipSubtitlesIfAudioTrackMatches; + parent.querySelector('#chkRequirePerfectMatch').checked = options.RequirePerfectSubtitleMatch; + parent.querySelector('#chkAutomaticallyAddToCollection').checked = options.AutomaticallyAddToCollection; + Array.prototype.forEach.call(parent.querySelectorAll('.chkMetadataSaver'), elem => { + elem.checked = options.MetadataSavers ? options.MetadataSavers.includes(elem.getAttribute('data-pluginname')) : elem.getAttribute('data-defaultenabled') === 'true'; + }); + Array.prototype.forEach.call(parent.querySelectorAll('.chkSubtitleLanguage'), elem => { + elem.checked = !!options.SubtitleDownloadLanguages && options.SubtitleDownloadLanguages.includes(elem.getAttribute('data-lang')); + }); + renderMetadataReaders(parent, getOrderedPlugins(parent.availableOptions.MetadataReaders, options.LocalMetadataReaderOrder || [])); + renderMetadataFetchers(parent, parent.availableOptions, options); + renderImageFetchers(parent, parent.availableOptions, options); + renderSubtitleFetchers(parent, parent.availableOptions, options); +} + +let currentLibraryOptions; +let currentAvailableOptions; -/* eslint-enable indent */ export default { embed: embed, setContentType: setContentType, diff --git a/src/components/listview/listview.js b/src/components/listview/listview.js index b9f156e9a2..a6a588dc66 100644 --- a/src/components/listview/listview.js +++ b/src/components/listview/listview.js @@ -1,4 +1,3 @@ -/* eslint-disable indent */ /** * Module for display list view. @@ -18,484 +17,483 @@ import '../../elements/emby-ratingbutton/emby-ratingbutton'; import '../../elements/emby-playstatebutton/emby-playstatebutton'; import ServerConnections from '../ServerConnections'; - function getIndex(item, options) { - if (options.index === 'disc') { - return item.ParentIndexNumber == null ? '' : globalize.translate('ValueDiscNumber', item.ParentIndexNumber); - } - - const sortBy = (options.sortBy || '').toLowerCase(); - let code; - let name; - - if (sortBy.indexOf('sortname') === 0) { - if (item.Type === 'Episode') { - return ''; - } - - // SortName - name = (item.SortName || item.Name || '?')[0].toUpperCase(); - - code = name.charCodeAt(0); - if (code < 65 || code > 90) { - return '#'; - } - - return name.toUpperCase(); - } - if (sortBy.indexOf('officialrating') === 0) { - return item.OfficialRating || globalize.translate('Unrated'); - } - if (sortBy.indexOf('communityrating') === 0) { - if (item.CommunityRating == null) { - return globalize.translate('Unrated'); - } - - return Math.floor(item.CommunityRating); - } - if (sortBy.indexOf('criticrating') === 0) { - if (item.CriticRating == null) { - return globalize.translate('Unrated'); - } - - return Math.floor(item.CriticRating); - } - if (sortBy.indexOf('albumartist') === 0) { - // SortName - if (!item.AlbumArtist) { - return ''; - } - - name = item.AlbumArtist[0].toUpperCase(); - - code = name.charCodeAt(0); - if (code < 65 || code > 90) { - return '#'; - } - - return name.toUpperCase(); - } - return ''; +function getIndex(item, options) { + if (options.index === 'disc') { + return item.ParentIndexNumber == null ? '' : globalize.translate('ValueDiscNumber', item.ParentIndexNumber); } - function getImageUrl(item, size) { - const apiClient = ServerConnections.getApiClient(item.ServerId); - let itemId; + const sortBy = (options.sortBy || '').toLowerCase(); + let code; + let name; - const options = { - fillWidth: size, - fillHeight: size, - type: 'Primary' - }; - - if (item.ImageTags && item.ImageTags.Primary) { - options.tag = item.ImageTags.Primary; - itemId = item.Id; - } else if (item.AlbumId && item.AlbumPrimaryImageTag) { - options.tag = item.AlbumPrimaryImageTag; - itemId = item.AlbumId; - } else if (item.SeriesId && item.SeriesPrimaryImageTag) { - options.tag = item.SeriesPrimaryImageTag; - itemId = item.SeriesId; - } else if (item.ParentPrimaryImageTag) { - options.tag = item.ParentPrimaryImageTag; - itemId = item.ParentPrimaryImageItemId; + if (sortBy.indexOf('sortname') === 0) { + if (item.Type === 'Episode') { + return ''; } - if (itemId) { - return apiClient.getScaledImageUrl(itemId, options); + // SortName + name = (item.SortName || item.Name || '?')[0].toUpperCase(); + + code = name.charCodeAt(0); + if (code < 65 || code > 90) { + return '#'; } - return null; + + return name.toUpperCase(); + } + if (sortBy.indexOf('officialrating') === 0) { + return item.OfficialRating || globalize.translate('Unrated'); + } + if (sortBy.indexOf('communityrating') === 0) { + if (item.CommunityRating == null) { + return globalize.translate('Unrated'); + } + + return Math.floor(item.CommunityRating); + } + if (sortBy.indexOf('criticrating') === 0) { + if (item.CriticRating == null) { + return globalize.translate('Unrated'); + } + + return Math.floor(item.CriticRating); + } + if (sortBy.indexOf('albumartist') === 0) { + // SortName + if (!item.AlbumArtist) { + return ''; + } + + name = item.AlbumArtist[0].toUpperCase(); + + code = name.charCodeAt(0); + if (code < 65 || code > 90) { + return '#'; + } + + return name.toUpperCase(); + } + return ''; +} + +function getImageUrl(item, size) { + const apiClient = ServerConnections.getApiClient(item.ServerId); + let itemId; + + const options = { + fillWidth: size, + fillHeight: size, + type: 'Primary' + }; + + if (item.ImageTags && item.ImageTags.Primary) { + options.tag = item.ImageTags.Primary; + itemId = item.Id; + } else if (item.AlbumId && item.AlbumPrimaryImageTag) { + options.tag = item.AlbumPrimaryImageTag; + itemId = item.AlbumId; + } else if (item.SeriesId && item.SeriesPrimaryImageTag) { + options.tag = item.SeriesPrimaryImageTag; + itemId = item.SeriesId; + } else if (item.ParentPrimaryImageTag) { + options.tag = item.ParentPrimaryImageTag; + itemId = item.ParentPrimaryImageItemId; } - function getChannelImageUrl(item, size) { - const apiClient = ServerConnections.getApiClient(item.ServerId); - const options = { - fillWidth: size, - fillHeight: size, - type: 'Primary' - }; + if (itemId) { + return apiClient.getScaledImageUrl(itemId, options); + } + return null; +} - if (item.ChannelId && item.ChannelPrimaryImageTag) { - options.tag = item.ChannelPrimaryImageTag; - } +function getChannelImageUrl(item, size) { + const apiClient = ServerConnections.getApiClient(item.ServerId); + const options = { + fillWidth: size, + fillHeight: size, + type: 'Primary' + }; - if (item.ChannelId) { - return apiClient.getScaledImageUrl(item.ChannelId, options); - } + if (item.ChannelId && item.ChannelPrimaryImageTag) { + options.tag = item.ChannelPrimaryImageTag; } - function getTextLinesHtml(textlines, isLargeStyle) { - let html = ''; + if (item.ChannelId) { + return apiClient.getScaledImageUrl(item.ChannelId, options); + } +} - const largeTitleTagName = layoutManager.tv ? 'h2' : 'div'; +function getTextLinesHtml(textlines, isLargeStyle) { + let html = ''; - for (const [i, text] of textlines.entries()) { - if (!text) { - continue; - } + const largeTitleTagName = layoutManager.tv ? 'h2' : 'div'; - let elem; + for (const [i, text] of textlines.entries()) { + if (!text) { + continue; + } - if (i === 0) { - if (isLargeStyle) { - elem = document.createElement(largeTitleTagName); - } else { - elem = document.createElement('div'); - } + let elem; + + if (i === 0) { + if (isLargeStyle) { + elem = document.createElement(largeTitleTagName); } else { elem = document.createElement('div'); - elem.classList.add('secondary'); } - - elem.classList.add('listItemBodyText'); - - elem.innerHTML = '' + escapeHtml(text) + ''; - - html += elem.outerHTML; + } else { + elem = document.createElement('div'); + elem.classList.add('secondary'); } - return html; + elem.classList.add('listItemBodyText'); + + elem.innerHTML = '' + escapeHtml(text) + ''; + + html += elem.outerHTML; } - function getRightButtonsHtml(options) { + return html; +} + +function getRightButtonsHtml(options) { + let html = ''; + + for (let i = 0, length = options.rightButtons.length; i < length; i++) { + const button = options.rightButtons[i]; + + html += ``; + } + + return html; +} + +export function getListViewHtml(options) { + const items = options.items; + + let groupTitle = ''; + const action = options.action || 'link'; + + const isLargeStyle = options.imageSize === 'large'; + const enableOverview = options.enableOverview; + + const clickEntireItem = layoutManager.tv ? true : false; + const outerTagName = clickEntireItem ? 'button' : 'div'; + const enableSideMediaInfo = options.enableSideMediaInfo != null ? options.enableSideMediaInfo : true; + + let outerHtml = ''; + + const enableContentWrapper = options.enableOverview && !layoutManager.tv; + + for (let i = 0, length = items.length; i < length; i++) { + const item = items[i]; + let html = ''; - for (let i = 0, length = options.rightButtons.length; i < length; i++) { - const button = options.rightButtons[i]; + if (options.showIndex) { + const itemGroupTitle = getIndex(item, options); - html += ``; + if (itemGroupTitle !== groupTitle) { + if (html) { + html += '
'; + } + + if (i === 0) { + html += '

'; + } else { + html += '

'; + } + html += escapeHtml(itemGroupTitle); + html += '

'; + + html += '
'; + + groupTitle = itemGroupTitle; + } } - return html; - } + let cssClass = 'listItem'; - export function getListViewHtml(options) { - const items = options.items; + if (options.border || (options.highlight !== false && !layoutManager.tv)) { + cssClass += ' listItem-border'; + } - let groupTitle = ''; - const action = options.action || 'link'; + if (clickEntireItem) { + cssClass += ' itemAction listItem-button'; + } - const isLargeStyle = options.imageSize === 'large'; - const enableOverview = options.enableOverview; + if (layoutManager.tv) { + cssClass += ' listItem-focusscale'; + } - const clickEntireItem = layoutManager.tv ? true : false; - const outerTagName = clickEntireItem ? 'button' : 'div'; - const enableSideMediaInfo = options.enableSideMediaInfo != null ? options.enableSideMediaInfo : true; + let downloadWidth = 80; - let outerHtml = ''; + if (isLargeStyle) { + cssClass += ' listItem-largeImage'; + downloadWidth = 500; + } - const enableContentWrapper = options.enableOverview && !layoutManager.tv; + const playlistItemId = item.PlaylistItemId ? (` data-playlistitemid="${item.PlaylistItemId}"`) : ''; - for (let i = 0, length = items.length; i < length; i++) { - const item = items[i]; + const positionTicksData = item.UserData && item.UserData.PlaybackPositionTicks ? (` data-positionticks="${item.UserData.PlaybackPositionTicks}"`) : ''; + const collectionIdData = options.collectionId ? (` data-collectionid="${options.collectionId}"`) : ''; + const playlistIdData = options.playlistId ? (` data-playlistid="${options.playlistId}"`) : ''; + const mediaTypeData = item.MediaType ? (` data-mediatype="${item.MediaType}"`) : ''; + const collectionTypeData = item.CollectionType ? (` data-collectiontype="${item.CollectionType}"`) : ''; + const channelIdData = item.ChannelId ? (` data-channelid="${item.ChannelId}"`) : ''; - let html = ''; + if (enableContentWrapper) { + cssClass += ' listItem-withContentWrapper'; + } - if (options.showIndex) { - const itemGroupTitle = getIndex(item, options); + html += `<${outerTagName} class="${cssClass}"${playlistItemId} data-action="${action}" data-isfolder="${item.IsFolder}" data-id="${item.Id}" data-serverid="${item.ServerId}" data-type="${item.Type}"${mediaTypeData}${collectionTypeData}${channelIdData}${positionTicksData}${collectionIdData}${playlistIdData}>`; - if (itemGroupTitle !== groupTitle) { - if (html) { - html += '
'; - } + if (enableContentWrapper) { + html += '
'; + } - if (i === 0) { - html += '

'; - } else { - html += '

'; - } - html += escapeHtml(itemGroupTitle); - html += '

'; + if (!clickEntireItem && options.dragHandle) { + html += ''; + } - html += '
'; + if (options.image !== false) { + const imgUrl = options.imageSource === 'channel' ? getChannelImageUrl(item, downloadWidth) : getImageUrl(item, downloadWidth); + let imageClass = isLargeStyle ? 'listItemImage listItemImage-large' : 'listItemImage'; - groupTitle = itemGroupTitle; - } + if (options.imageSource === 'channel') { + imageClass += ' listItemImage-channel'; } - let cssClass = 'listItem'; - - if (options.border || (options.highlight !== false && !layoutManager.tv)) { - cssClass += ' listItem-border'; + if (isLargeStyle && layoutManager.tv) { + imageClass += ' listItemImage-large-tv'; } - if (clickEntireItem) { - cssClass += ' itemAction listItem-button'; + const playOnImageClick = options.imagePlayButton && !layoutManager.tv; + + if (!clickEntireItem) { + imageClass += ' itemAction'; } - if (layoutManager.tv) { - cssClass += ' listItem-focusscale'; + const imageAction = playOnImageClick ? 'link' : action; + + if (imgUrl) { + html += '
'; + } else { + html += '
' + cardBuilder.getDefaultText(item, options); } - let downloadWidth = 80; - - if (isLargeStyle) { - cssClass += ' listItem-largeImage'; - downloadWidth = 500; + const mediaSourceCount = item.MediaSourceCount || 1; + if (mediaSourceCount > 1 && options.disableIndicators !== true) { + html += '
' + mediaSourceCount + '
'; } - const playlistItemId = item.PlaylistItemId ? (` data-playlistitemid="${item.PlaylistItemId}"`) : ''; + let indicatorsHtml = ''; + indicatorsHtml += indicators.getPlayedIndicatorHtml(item); - const positionTicksData = item.UserData && item.UserData.PlaybackPositionTicks ? (` data-positionticks="${item.UserData.PlaybackPositionTicks}"`) : ''; - const collectionIdData = options.collectionId ? (` data-collectionid="${options.collectionId}"`) : ''; - const playlistIdData = options.playlistId ? (` data-playlistid="${options.playlistId}"`) : ''; - const mediaTypeData = item.MediaType ? (` data-mediatype="${item.MediaType}"`) : ''; - const collectionTypeData = item.CollectionType ? (` data-collectiontype="${item.CollectionType}"`) : ''; - const channelIdData = item.ChannelId ? (` data-channelid="${item.ChannelId}"`) : ''; - - if (enableContentWrapper) { - cssClass += ' listItem-withContentWrapper'; + if (indicatorsHtml) { + html += `
${indicatorsHtml}
`; } - html += `<${outerTagName} class="${cssClass}"${playlistItemId} data-action="${action}" data-isfolder="${item.IsFolder}" data-id="${item.Id}" data-serverid="${item.ServerId}" data-type="${item.Type}"${mediaTypeData}${collectionTypeData}${channelIdData}${positionTicksData}${collectionIdData}${playlistIdData}>`; - - if (enableContentWrapper) { - html += '
'; + if (playOnImageClick) { + html += ''; } - if (!clickEntireItem && options.dragHandle) { - html += ''; - } - - if (options.image !== false) { - const imgUrl = options.imageSource === 'channel' ? getChannelImageUrl(item, downloadWidth) : getImageUrl(item, downloadWidth); - let imageClass = isLargeStyle ? 'listItemImage listItemImage-large' : 'listItemImage'; - - if (options.imageSource === 'channel') { - imageClass += ' listItemImage-channel'; - } - - if (isLargeStyle && layoutManager.tv) { - imageClass += ' listItemImage-large-tv'; - } - - const playOnImageClick = options.imagePlayButton && !layoutManager.tv; - - if (!clickEntireItem) { - imageClass += ' itemAction'; - } - - const imageAction = playOnImageClick ? 'link' : action; - - if (imgUrl) { - html += '
'; - } else { - html += '
' + cardBuilder.getDefaultText(item, options); - } - - const mediaSourceCount = item.MediaSourceCount || 1; - if (mediaSourceCount > 1 && options.disableIndicators !== true) { - html += '
' + mediaSourceCount + '
'; - } - - let indicatorsHtml = ''; - indicatorsHtml += indicators.getPlayedIndicatorHtml(item); - - if (indicatorsHtml) { - html += `
${indicatorsHtml}
`; - } - - if (playOnImageClick) { - html += ''; - } - - const progressHtml = indicators.getProgressBarHtml(item, { - containerClass: 'listItemProgressBar' - }); - - if (progressHtml) { - html += progressHtml; - } - html += '
'; - } - - if (options.showIndexNumberLeft) { - html += '
'; - html += (item.IndexNumber || ' '); - html += '
'; - } - - const textlines = []; - - if (options.showProgramDateTime) { - textlines.push(datetime.toLocaleString(datetime.parseISO8601Date(item.StartDate), { - - weekday: 'long', - month: 'short', - day: 'numeric', - hour: 'numeric', - minute: '2-digit' - })); - } - - if (options.showProgramTime) { - textlines.push(datetime.getDisplayTime(datetime.parseISO8601Date(item.StartDate))); - } - - if (options.showChannel && item.ChannelName) { - textlines.push(item.ChannelName); - } - - let parentTitle = null; - - if (options.showParentTitle) { - if (item.Type === 'Episode') { - parentTitle = item.SeriesName; - } else if (item.IsSeries || (item.EpisodeTitle && item.Name)) { - parentTitle = item.Name; - } - } - - let displayName = itemHelper.getDisplayName(item, { - includeParentInfo: options.includeParentInfoInTitle + const progressHtml = indicators.getProgressBarHtml(item, { + containerClass: 'listItemProgressBar' }); - if (options.showIndexNumber && item.IndexNumber != null) { - displayName = `${item.IndexNumber}. ${displayName}`; + if (progressHtml) { + html += progressHtml; + } + html += '
'; + } + + if (options.showIndexNumberLeft) { + html += '
'; + html += (item.IndexNumber || ' '); + html += '
'; + } + + const textlines = []; + + if (options.showProgramDateTime) { + textlines.push(datetime.toLocaleString(datetime.parseISO8601Date(item.StartDate), { + + weekday: 'long', + month: 'short', + day: 'numeric', + hour: 'numeric', + minute: '2-digit' + })); + } + + if (options.showProgramTime) { + textlines.push(datetime.getDisplayTime(datetime.parseISO8601Date(item.StartDate))); + } + + if (options.showChannel && item.ChannelName) { + textlines.push(item.ChannelName); + } + + let parentTitle = null; + + if (options.showParentTitle) { + if (item.Type === 'Episode') { + parentTitle = item.SeriesName; + } else if (item.IsSeries || (item.EpisodeTitle && item.Name)) { + parentTitle = item.Name; + } + } + + let displayName = itemHelper.getDisplayName(item, { + includeParentInfo: options.includeParentInfoInTitle + }); + + if (options.showIndexNumber && item.IndexNumber != null) { + displayName = `${item.IndexNumber}. ${displayName}`; + } + + if (options.showParentTitle && options.parentTitleWithTitle) { + if (displayName) { + if (parentTitle) { + parentTitle += ' - '; + } + parentTitle = (parentTitle || '') + displayName; } - if (options.showParentTitle && options.parentTitleWithTitle) { - if (displayName) { - if (parentTitle) { - parentTitle += ' - '; - } - parentTitle = (parentTitle || '') + displayName; + textlines.push(parentTitle || ''); + } else if (options.showParentTitle) { + textlines.push(parentTitle || ''); + } + + if (displayName && !options.parentTitleWithTitle) { + textlines.push(displayName); + } + + if (item.IsFolder) { + if (options.artist !== false && item.AlbumArtist && item.Type === 'MusicAlbum') { + textlines.push(item.AlbumArtist); + } + } else { + if (options.artist) { + const artistItems = item.ArtistItems; + if (artistItems && item.Type !== 'MusicAlbum') { + textlines.push(artistItems.map(a => { + return a.Name; + }).join(', ')); + } + } + } + + if (item.Type === 'TvChannel' && item.CurrentProgram) { + textlines.push(itemHelper.getDisplayName(item.CurrentProgram)); + } + + cssClass = 'listItemBody'; + if (!clickEntireItem) { + cssClass += ' itemAction'; + } + + if (options.image === false) { + cssClass += ' listItemBody-noleftpadding'; + } + + html += `
`; + + html += getTextLinesHtml(textlines, isLargeStyle); + + if (options.mediaInfo !== false && !enableSideMediaInfo) { + const mediaInfoClass = 'secondary listItemMediaInfo listItemBodyText'; + + html += `
`; + html += mediaInfo.getPrimaryMediaInfoHtml(item, { + episodeTitle: false, + originalAirDate: false, + subtitles: false + + }); + html += '
'; + } + + if (enableOverview && item.Overview) { + html += '
'; + html += '' + item.Overview + ''; + html += '
'; + } + + html += '
'; + + if (options.mediaInfo !== false && enableSideMediaInfo) { + html += '
'; + html += mediaInfo.getPrimaryMediaInfoHtml(item, { + + year: false, + container: false, + episodeTitle: false, + criticRating: false, + officialRating: false, + endsAt: false + + }); + html += '
'; + } + + if (!options.recordButton && (item.Type === 'Timer' || item.Type === 'Program')) { + html += indicators.getTimerIndicator(item).replace('indicatorIcon', 'indicatorIcon listItemAside'); + } + + html += '
'; + + if (!clickEntireItem) { + if (options.addToListButton) { + html += ''; + } + + if (options.infoButton) { + html += ''; + } + + if (options.rightButtons) { + html += getRightButtonsHtml(options); + } + + if (options.enableUserDataButtons !== false) { + const userData = item.UserData || {}; + const likes = userData.Likes == null ? '' : userData.Likes; + + if (itemHelper.canMarkPlayed(item) && options.enablePlayedButton !== false) { + html += ''; } - textlines.push(parentTitle || ''); - } else if (options.showParentTitle) { - textlines.push(parentTitle || ''); - } - - if (displayName && !options.parentTitleWithTitle) { - textlines.push(displayName); - } - - if (item.IsFolder) { - if (options.artist !== false && item.AlbumArtist && item.Type === 'MusicAlbum') { - textlines.push(item.AlbumArtist); - } - } else { - if (options.artist) { - const artistItems = item.ArtistItems; - if (artistItems && item.Type !== 'MusicAlbum') { - textlines.push(artistItems.map(a => { - return a.Name; - }).join(', ')); - } + if (itemHelper.canRate(item) && options.enableRatingButton !== false) { + html += ''; } } - if (item.Type === 'TvChannel' && item.CurrentProgram) { - textlines.push(itemHelper.getDisplayName(item.CurrentProgram)); + if (options.moreButton !== false) { + html += ''; } + } + html += '
'; - cssClass = 'listItemBody'; - if (!clickEntireItem) { - cssClass += ' itemAction'; - } - - if (options.image === false) { - cssClass += ' listItemBody-noleftpadding'; - } - - html += `
`; - - html += getTextLinesHtml(textlines, isLargeStyle); - - if (options.mediaInfo !== false && !enableSideMediaInfo) { - const mediaInfoClass = 'secondary listItemMediaInfo listItemBodyText'; - - html += `
`; - html += mediaInfo.getPrimaryMediaInfoHtml(item, { - episodeTitle: false, - originalAirDate: false, - subtitles: false - - }); - html += '
'; - } + if (enableContentWrapper) { + html += '
'; if (enableOverview && item.Overview) { - html += '
'; + html += '
'; html += '' + item.Overview + ''; html += '
'; } - - html += '
'; - - if (options.mediaInfo !== false && enableSideMediaInfo) { - html += '
'; - html += mediaInfo.getPrimaryMediaInfoHtml(item, { - - year: false, - container: false, - episodeTitle: false, - criticRating: false, - officialRating: false, - endsAt: false - - }); - html += '
'; - } - - if (!options.recordButton && (item.Type === 'Timer' || item.Type === 'Program')) { - html += indicators.getTimerIndicator(item).replace('indicatorIcon', 'indicatorIcon listItemAside'); - } - - html += '
'; - - if (!clickEntireItem) { - if (options.addToListButton) { - html += ''; - } - - if (options.infoButton) { - html += ''; - } - - if (options.rightButtons) { - html += getRightButtonsHtml(options); - } - - if (options.enableUserDataButtons !== false) { - const userData = item.UserData || {}; - const likes = userData.Likes == null ? '' : userData.Likes; - - if (itemHelper.canMarkPlayed(item) && options.enablePlayedButton !== false) { - html += ''; - } - - if (itemHelper.canRate(item) && options.enableRatingButton !== false) { - html += ''; - } - } - - if (options.moreButton !== false) { - html += ''; - } - } - html += '
'; - - if (enableContentWrapper) { - html += '
'; - - if (enableOverview && item.Overview) { - html += '
'; - html += '' + item.Overview + ''; - html += '
'; - } - } - - html += ``; - - outerHtml += html; } - return outerHtml; + html += ``; + + outerHtml += html; } -/* eslint-enable indent */ + return outerHtml; +} + export default { getListViewHtml: getListViewHtml }; diff --git a/src/components/maintabsmanager.js b/src/components/maintabsmanager.js index 76d0090ba8..57cb31f759 100644 --- a/src/components/maintabsmanager.js +++ b/src/components/maintabsmanager.js @@ -4,200 +4,196 @@ import Events from '../utils/events.ts'; import '../elements/emby-tabs/emby-tabs'; import '../elements/emby-button/emby-button'; -/* eslint-disable indent */ +let tabOwnerView; +const queryScope = document.querySelector('.skinHeader'); +let headerTabsContainer; +let tabsElem; - let tabOwnerView; - const queryScope = document.querySelector('.skinHeader'); - let headerTabsContainer; - let tabsElem; - - function ensureElements() { - if (!headerTabsContainer) { - headerTabsContainer = queryScope.querySelector('.headerTabs'); - } +function ensureElements() { + if (!headerTabsContainer) { + headerTabsContainer = queryScope.querySelector('.headerTabs'); } +} - function onViewTabsReady() { - this.selectedIndex(this.readySelectedIndex); - this.readySelectedIndex = null; - } +function onViewTabsReady() { + this.selectedIndex(this.readySelectedIndex); + this.readySelectedIndex = null; +} - function allowSwipe(target) { - function allowSwipeOn(elem) { - if (dom.parentWithTag(elem, 'input')) { - return false; - } - - const classList = elem.classList; - if (classList) { - return !classList.contains('scrollX') && !classList.contains('animatedScrollX'); - } - - return true; +function allowSwipe(target) { + function allowSwipeOn(elem) { + if (dom.parentWithTag(elem, 'input')) { + return false; } - let parent = target; - while (parent != null) { - if (!allowSwipeOn(parent)) { - return false; - } - parent = parent.parentNode; + const classList = elem.classList; + if (classList) { + return !classList.contains('scrollX') && !classList.contains('animatedScrollX'); } return true; } - function configureSwipeTabs(view, currentElement) { - if (!browser.touch) { - return; + let parent = target; + while (parent != null) { + if (!allowSwipeOn(parent)) { + return false; } - - // implement without hammer - const onSwipeLeft = function (e, target) { - if (allowSwipe(target) && view.contains(target)) { - currentElement.selectNext(); - } - }; - - const onSwipeRight = function (e, target) { - if (allowSwipe(target) && view.contains(target)) { - currentElement.selectPrevious(); - } - }; - - import('../scripts/touchHelper').then(({ default: TouchHelper }) => { - const touchHelper = new TouchHelper(view.parentNode.parentNode); - - Events.on(touchHelper, 'swipeleft', onSwipeLeft); - Events.on(touchHelper, 'swiperight', onSwipeRight); - - view.addEventListener('viewdestroy', function () { - touchHelper.destroy(); - }); - }); + parent = parent.parentNode; } - export function setTabs(view, selectedIndex, getTabsFn, getTabContainersFn, onBeforeTabChange, onTabChange, setSelectedIndex) { - ensureElements(); + return true; +} - if (!view) { - if (tabOwnerView) { - document.body.classList.remove('withSectionTabs'); +function configureSwipeTabs(view, currentElement) { + if (!browser.touch) { + return; + } - headerTabsContainer.innerHTML = ''; - headerTabsContainer.classList.add('hide'); - - tabOwnerView = null; - } - return { - tabsContainer: headerTabsContainer, - replaced: false - }; + // implement without hammer + const onSwipeLeft = function (e, target) { + if (allowSwipe(target) && view.contains(target)) { + currentElement.selectNext(); } + }; - const tabsContainerElem = headerTabsContainer; - - if (!tabOwnerView) { - tabsContainerElem.classList.remove('hide'); + const onSwipeRight = function (e, target) { + if (allowSwipe(target) && view.contains(target)) { + currentElement.selectPrevious(); } + }; - if (tabOwnerView !== view) { - let index = 0; + import('../scripts/touchHelper').then(({ default: TouchHelper }) => { + const touchHelper = new TouchHelper(view.parentNode.parentNode); - const indexAttribute = selectedIndex == null ? '' : (' data-index="' + selectedIndex + '"'); - const tabsHtml = '
' + getTabsFn().map(function (t) { - let tabClass = 'emby-tab-button'; + Events.on(touchHelper, 'swipeleft', onSwipeLeft); + Events.on(touchHelper, 'swiperight', onSwipeRight); - if (t.enabled === false) { - tabClass += ' hide'; - } + view.addEventListener('viewdestroy', function () { + touchHelper.destroy(); + }); + }); +} - let tabHtml; +export function setTabs(view, selectedIndex, getTabsFn, getTabContainersFn, onBeforeTabChange, onTabChange, setSelectedIndex) { + ensureElements(); - if (t.cssClass) { - tabClass += ' ' + t.cssClass; - } + if (!view) { + if (tabOwnerView) { + document.body.classList.remove('withSectionTabs'); - if (t.href) { - tabHtml = '
' + t.name + '
'; - } else { - tabHtml = ''; - } + headerTabsContainer.innerHTML = ''; + headerTabsContainer.classList.add('hide'); - index++; - return tabHtml; - }).join('') + '
'; - - tabsContainerElem.innerHTML = tabsHtml; - window.CustomElements.upgradeSubtree(tabsContainerElem); - - document.body.classList.add('withSectionTabs'); - tabOwnerView = view; - - tabsElem = tabsContainerElem.querySelector('[is="emby-tabs"]'); - - configureSwipeTabs(view, tabsElem); - - if (getTabContainersFn) { - tabsElem.addEventListener('beforetabchange', function (e) { - const tabContainers = getTabContainersFn(); - if (e.detail.previousIndex != null) { - const previousPanel = tabContainers[e.detail.previousIndex]; - if (previousPanel) { - previousPanel.classList.remove('is-active'); - } - } - - const newPanel = tabContainers[e.detail.selectedTabIndex]; - - if (newPanel) { - newPanel.classList.add('is-active'); - } - }); - } - - if (onBeforeTabChange) { - tabsElem.addEventListener('beforetabchange', onBeforeTabChange); - } - if (onTabChange) { - tabsElem.addEventListener('tabchange', onTabChange); - } - - if (setSelectedIndex !== false) { - if (tabsElem.selectedIndex) { - tabsElem.selectedIndex(selectedIndex); - } else { - tabsElem.readySelectedIndex = selectedIndex; - tabsElem.addEventListener('ready', onViewTabsReady); - } - } - - return { - tabsContainer: tabsContainerElem, - tabs: tabsElem, - replaced: true - }; + tabOwnerView = null; } - - tabsElem.selectedIndex(selectedIndex); - return { - tabsContainer: tabsContainerElem, - tabs: tabsElem, + tabsContainer: headerTabsContainer, replaced: false }; } - export function selectedTabIndex(index) { - if (index != null) { - tabsElem.selectedIndex(index); - } else { - tabsElem.triggerTabChange(); + const tabsContainerElem = headerTabsContainer; + + if (!tabOwnerView) { + tabsContainerElem.classList.remove('hide'); + } + + if (tabOwnerView !== view) { + let index = 0; + + const indexAttribute = selectedIndex == null ? '' : (' data-index="' + selectedIndex + '"'); + const tabsHtml = '
' + getTabsFn().map(function (t) { + let tabClass = 'emby-tab-button'; + + if (t.enabled === false) { + tabClass += ' hide'; + } + + let tabHtml; + + if (t.cssClass) { + tabClass += ' ' + t.cssClass; + } + + if (t.href) { + tabHtml = '
' + t.name + '
'; + } else { + tabHtml = ''; + } + + index++; + return tabHtml; + }).join('') + '
'; + + tabsContainerElem.innerHTML = tabsHtml; + window.CustomElements.upgradeSubtree(tabsContainerElem); + + document.body.classList.add('withSectionTabs'); + tabOwnerView = view; + + tabsElem = tabsContainerElem.querySelector('[is="emby-tabs"]'); + + configureSwipeTabs(view, tabsElem); + + if (getTabContainersFn) { + tabsElem.addEventListener('beforetabchange', function (e) { + const tabContainers = getTabContainersFn(); + if (e.detail.previousIndex != null) { + const previousPanel = tabContainers[e.detail.previousIndex]; + if (previousPanel) { + previousPanel.classList.remove('is-active'); + } + } + + const newPanel = tabContainers[e.detail.selectedTabIndex]; + + if (newPanel) { + newPanel.classList.add('is-active'); + } + }); } + + if (onBeforeTabChange) { + tabsElem.addEventListener('beforetabchange', onBeforeTabChange); + } + if (onTabChange) { + tabsElem.addEventListener('tabchange', onTabChange); + } + + if (setSelectedIndex !== false) { + if (tabsElem.selectedIndex) { + tabsElem.selectedIndex(selectedIndex); + } else { + tabsElem.readySelectedIndex = selectedIndex; + tabsElem.addEventListener('ready', onViewTabsReady); + } + } + + return { + tabsContainer: tabsContainerElem, + tabs: tabsElem, + replaced: true + }; } - export function getTabsElement() { - return document.querySelector('.tabs-viewmenubar'); - } + tabsElem.selectedIndex(selectedIndex); -/* eslint-enable indent */ + return { + tabsContainer: tabsContainerElem, + tabs: tabsElem, + replaced: false + }; +} + +export function selectedTabIndex(index) { + if (index != null) { + tabsElem.selectedIndex(index); + } else { + tabsElem.triggerTabChange(); + } +} + +export function getTabsElement() { + return document.querySelector('.tabs-viewmenubar'); +} diff --git a/src/components/mediaLibraryCreator/mediaLibraryCreator.js b/src/components/mediaLibraryCreator/mediaLibraryCreator.js index 1592b27c2e..d3adb15e5b 100644 --- a/src/components/mediaLibraryCreator/mediaLibraryCreator.js +++ b/src/components/mediaLibraryCreator/mediaLibraryCreator.js @@ -1,4 +1,3 @@ -/* eslint-disable indent */ /** * Module for media library creator. @@ -25,167 +24,167 @@ import toast from '../toast/toast'; import alert from '../alert'; import template from './mediaLibraryCreator.template.html'; - function onAddLibrary(e) { - if (isCreating) { - return false; - } +function onAddLibrary(e) { + if (isCreating) { + return false; + } - if (pathInfos.length == 0) { - alert({ - text: globalize.translate('PleaseAddAtLeastOneFolder'), - type: 'error' - }); - - return false; - } - - isCreating = true; - loading.show(); - const dlg = dom.parentWithClass(this, 'dlg-librarycreator'); - const name = $('#txtValue', dlg).val(); - let type = $('#selectCollectionType', dlg).val(); - - if (type == 'mixed') { - type = null; - } - - const libraryOptions = libraryoptionseditor.getLibraryOptions(dlg.querySelector('.libraryOptions')); - libraryOptions.PathInfos = pathInfos; - ApiClient.addVirtualFolder(name, type, currentOptions.refresh, libraryOptions).then(() => { - hasChanges = true; - isCreating = false; - loading.hide(); - dialogHelper.close(dlg); - }, () => { - toast(globalize.translate('ErrorAddingMediaPathToVirtualFolder')); - - isCreating = false; - loading.hide(); + if (pathInfos.length == 0) { + alert({ + text: globalize.translate('PleaseAddAtLeastOneFolder'), + type: 'error' }); - e.preventDefault(); + + return false; } - function getCollectionTypeOptionsHtml(collectionTypeOptions) { - return collectionTypeOptions.map(i => { - return ``; - }).join(''); + isCreating = true; + loading.show(); + const dlg = dom.parentWithClass(this, 'dlg-librarycreator'); + const name = $('#txtValue', dlg).val(); + let type = $('#selectCollectionType', dlg).val(); + + if (type == 'mixed') { + type = null; } - function initEditor(page, collectionTypeOptions) { - $('#selectCollectionType', page).html(getCollectionTypeOptionsHtml(collectionTypeOptions)).val('').on('change', function () { - const value = this.value; - const dlg = $(this).parents('.dialog')[0]; - libraryoptionseditor.setContentType(dlg.querySelector('.libraryOptions'), value); + const libraryOptions = libraryoptionseditor.getLibraryOptions(dlg.querySelector('.libraryOptions')); + libraryOptions.PathInfos = pathInfos; + ApiClient.addVirtualFolder(name, type, currentOptions.refresh, libraryOptions).then(() => { + hasChanges = true; + isCreating = false; + loading.hide(); + dialogHelper.close(dlg); + }, () => { + toast(globalize.translate('ErrorAddingMediaPathToVirtualFolder')); - if (value) { - dlg.querySelector('.libraryOptions').classList.remove('hide'); - } else { - dlg.querySelector('.libraryOptions').classList.add('hide'); - } + isCreating = false; + loading.hide(); + }); + e.preventDefault(); +} - if (value != 'mixed') { - const index = this.selectedIndex; +function getCollectionTypeOptionsHtml(collectionTypeOptions) { + return collectionTypeOptions.map(i => { + return ``; + }).join(''); +} - if (index != -1) { - const name = this.options[index].innerHTML.replace('*', '').replace('&', '&'); - $('#txtValue', dlg).val(name); - } - } +function initEditor(page, collectionTypeOptions) { + $('#selectCollectionType', page).html(getCollectionTypeOptionsHtml(collectionTypeOptions)).val('').on('change', function () { + const value = this.value; + const dlg = $(this).parents('.dialog')[0]; + libraryoptionseditor.setContentType(dlg.querySelector('.libraryOptions'), value); - const folderOption = collectionTypeOptions.find(i => i.value === value); - $('.collectionTypeFieldDescription', dlg).html(folderOption?.message || ''); - }); - page.querySelector('.btnAddFolder').addEventListener('click', onAddButtonClick); - page.querySelector('.addLibraryForm').addEventListener('submit', onAddLibrary); - page.querySelector('.folderList').addEventListener('click', onRemoveClick); - } - - function onAddButtonClick() { - const page = dom.parentWithClass(this, 'dlg-librarycreator'); - - import('../directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => { - const picker = new DirectoryBrowser(); - picker.show({ - enableNetworkSharePath: true, - callback: function (path, networkSharePath) { - if (path) { - addMediaLocation(page, path, networkSharePath); - } - - picker.close(); - } - }); - }); - } - - function getFolderHtml(pathInfo, index) { - let html = ''; - html += '
'; - html += `
`; - html += `
${escapeHtml(pathInfo.Path)}
`; - - if (pathInfo.NetworkPath) { - html += `
${escapeHtml(pathInfo.NetworkPath)}
`; - } - - html += '
'; - html += ``; - html += '
'; - return html; - } - - function renderPaths(page) { - const foldersHtml = pathInfos.map(getFolderHtml).join(''); - const folderList = page.querySelector('.folderList'); - folderList.innerHTML = foldersHtml; - - if (foldersHtml) { - folderList.classList.remove('hide'); + if (value) { + dlg.querySelector('.libraryOptions').classList.remove('hide'); } else { - folderList.classList.add('hide'); + dlg.querySelector('.libraryOptions').classList.add('hide'); } - } - function addMediaLocation(page, path, networkSharePath) { - const pathLower = path.toLowerCase(); - const pathFilter = pathInfos.filter(p => { - return p.Path.toLowerCase() == pathLower; - }); + if (value != 'mixed') { + const index = this.selectedIndex; - if (!pathFilter.length) { - const pathInfo = { - Path: path - }; - - if (networkSharePath) { - pathInfo.NetworkPath = networkSharePath; + if (index != -1) { + const name = this.options[index].innerHTML.replace('*', '').replace('&', '&'); + $('#txtValue', dlg).val(name); } - - pathInfos.push(pathInfo); - renderPaths(page); } - } - function onRemoveClick(e) { - const button = dom.parentWithClass(e.target, 'btnRemovePath'); - const index = parseInt(button.getAttribute('data-index'), 10); - const location = pathInfos[index].Path; - const locationLower = location.toLowerCase(); - pathInfos = pathInfos.filter(p => { - return p.Path.toLowerCase() != locationLower; + const folderOption = collectionTypeOptions.find(i => i.value === value); + $('.collectionTypeFieldDescription', dlg).html(folderOption?.message || ''); + }); + page.querySelector('.btnAddFolder').addEventListener('click', onAddButtonClick); + page.querySelector('.addLibraryForm').addEventListener('submit', onAddLibrary); + page.querySelector('.folderList').addEventListener('click', onRemoveClick); +} + +function onAddButtonClick() { + const page = dom.parentWithClass(this, 'dlg-librarycreator'); + + import('../directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => { + const picker = new DirectoryBrowser(); + picker.show({ + enableNetworkSharePath: true, + callback: function (path, networkSharePath) { + if (path) { + addMediaLocation(page, path, networkSharePath); + } + + picker.close(); + } }); - renderPaths(dom.parentWithClass(button, 'dlg-librarycreator')); + }); +} + +function getFolderHtml(pathInfo, index) { + let html = ''; + html += '
'; + html += `
`; + html += `
${escapeHtml(pathInfo.Path)}
`; + + if (pathInfo.NetworkPath) { + html += `
${escapeHtml(pathInfo.NetworkPath)}
`; } - function onDialogClosed() { - currentResolve(hasChanges); - } + html += '
'; + html += ``; + html += '
'; + return html; +} - function initLibraryOptions(dlg) { - libraryoptionseditor.embed(dlg.querySelector('.libraryOptions')).then(() => { - $('#selectCollectionType', dlg).trigger('change'); - }); +function renderPaths(page) { + const foldersHtml = pathInfos.map(getFolderHtml).join(''); + const folderList = page.querySelector('.folderList'); + folderList.innerHTML = foldersHtml; + + if (foldersHtml) { + folderList.classList.remove('hide'); + } else { + folderList.classList.add('hide'); } +} + +function addMediaLocation(page, path, networkSharePath) { + const pathLower = path.toLowerCase(); + const pathFilter = pathInfos.filter(p => { + return p.Path.toLowerCase() == pathLower; + }); + + if (!pathFilter.length) { + const pathInfo = { + Path: path + }; + + if (networkSharePath) { + pathInfo.NetworkPath = networkSharePath; + } + + pathInfos.push(pathInfo); + renderPaths(page); + } +} + +function onRemoveClick(e) { + const button = dom.parentWithClass(e.target, 'btnRemovePath'); + const index = parseInt(button.getAttribute('data-index'), 10); + const location = pathInfos[index].Path; + const locationLower = location.toLowerCase(); + pathInfos = pathInfos.filter(p => { + return p.Path.toLowerCase() != locationLower; + }); + renderPaths(dom.parentWithClass(button, 'dlg-librarycreator')); +} + +function onDialogClosed() { + currentResolve(hasChanges); +} + +function initLibraryOptions(dlg) { + libraryoptionseditor.embed(dlg.querySelector('.libraryOptions')).then(() => { + $('#selectCollectionType', dlg).trigger('change'); + }); +} export class showEditor { constructor(options) { @@ -217,11 +216,10 @@ export class showEditor { } } - let pathInfos = []; - let currentResolve; - let currentOptions; - let hasChanges = false; - let isCreating = false; +let pathInfos = []; +let currentResolve; +let currentOptions; +let hasChanges = false; +let isCreating = false; -/* eslint-enable indent */ export default showEditor; diff --git a/src/components/mediaLibraryEditor/mediaLibraryEditor.js b/src/components/mediaLibraryEditor/mediaLibraryEditor.js index c9f931297d..b1389552a8 100644 --- a/src/components/mediaLibraryEditor/mediaLibraryEditor.js +++ b/src/components/mediaLibraryEditor/mediaLibraryEditor.js @@ -1,4 +1,3 @@ -/* eslint-disable indent */ /** * Module for media library editor. @@ -23,180 +22,180 @@ import toast from '../toast/toast'; import confirm from '../confirm/confirm'; import template from './mediaLibraryEditor.template.html'; - function onEditLibrary() { - if (isCreating) { - return false; - } - - isCreating = true; - loading.show(); - const dlg = dom.parentWithClass(this, 'dlg-libraryeditor'); - let libraryOptions = libraryoptionseditor.getLibraryOptions(dlg.querySelector('.libraryOptions')); - libraryOptions = Object.assign(currentOptions.library.LibraryOptions || {}, libraryOptions); - ApiClient.updateVirtualFolderOptions(currentOptions.library.ItemId, libraryOptions).then(() => { - hasChanges = true; - isCreating = false; - loading.hide(); - dialogHelper.close(dlg); - }, () => { - isCreating = false; - loading.hide(); - }); +function onEditLibrary() { + if (isCreating) { return false; } - function addMediaLocation(page, path, networkSharePath) { - const virtualFolder = currentOptions.library; + isCreating = true; + loading.show(); + const dlg = dom.parentWithClass(this, 'dlg-libraryeditor'); + let libraryOptions = libraryoptionseditor.getLibraryOptions(dlg.querySelector('.libraryOptions')); + libraryOptions = Object.assign(currentOptions.library.LibraryOptions || {}, libraryOptions); + ApiClient.updateVirtualFolderOptions(currentOptions.library.ItemId, libraryOptions).then(() => { + hasChanges = true; + isCreating = false; + loading.hide(); + dialogHelper.close(dlg); + }, () => { + isCreating = false; + loading.hide(); + }); + return false; +} + +function addMediaLocation(page, path, networkSharePath) { + const virtualFolder = currentOptions.library; + const refreshAfterChange = currentOptions.refresh; + ApiClient.addMediaPath(virtualFolder.Name, path, networkSharePath, refreshAfterChange).then(() => { + hasChanges = true; + refreshLibraryFromServer(page); + }, () => { + toast(globalize.translate('ErrorAddingMediaPathToVirtualFolder')); + }); +} + +function updateMediaLocation(page, path, networkSharePath) { + const virtualFolder = currentOptions.library; + ApiClient.updateMediaPath(virtualFolder.Name, { + Path: path, + NetworkPath: networkSharePath + }).then(() => { + hasChanges = true; + refreshLibraryFromServer(page); + }, () => { + toast(globalize.translate('ErrorAddingMediaPathToVirtualFolder')); + }); +} + +function onRemoveClick(btnRemovePath, location) { + const button = btnRemovePath; + const virtualFolder = currentOptions.library; + + confirm({ + title: globalize.translate('HeaderRemoveMediaLocation'), + text: globalize.translate('MessageConfirmRemoveMediaLocation'), + confirmText: globalize.translate('Delete'), + primary: 'delete' + }).then(() => { const refreshAfterChange = currentOptions.refresh; - ApiClient.addMediaPath(virtualFolder.Name, path, networkSharePath, refreshAfterChange).then(() => { + ApiClient.removeMediaPath(virtualFolder.Name, location, refreshAfterChange).then(() => { hasChanges = true; - refreshLibraryFromServer(page); + refreshLibraryFromServer(dom.parentWithClass(button, 'dlg-libraryeditor')); }, () => { - toast(globalize.translate('ErrorAddingMediaPathToVirtualFolder')); + toast(globalize.translate('ErrorDefault')); + }); + }); +} + +function onListItemClick(e) { + const listItem = dom.parentWithClass(e.target, 'listItem'); + + if (listItem) { + const index = parseInt(listItem.getAttribute('data-index'), 10); + const pathInfos = (currentOptions.library.LibraryOptions || {}).PathInfos || []; + const pathInfo = index == null ? {} : pathInfos[index] || {}; + const originalPath = pathInfo.Path || (index == null ? null : currentOptions.library.Locations[index]); + const btnRemovePath = dom.parentWithClass(e.target, 'btnRemovePath'); + + if (btnRemovePath) { + onRemoveClick(btnRemovePath, originalPath); + return; + } + + showDirectoryBrowser(dom.parentWithClass(listItem, 'dlg-libraryeditor'), originalPath, pathInfo.NetworkPath); + } +} + +function getFolderHtml(pathInfo, index) { + let html = ''; + html += `
`; + html += `
`; + html += '

'; + html += escapeHtml(pathInfo.Path); + html += '

'; + + if (pathInfo.NetworkPath) { + html += `
${escapeHtml(pathInfo.NetworkPath)}
`; + } + + html += '
'; + html += ``; + html += '
'; + return html; +} + +function refreshLibraryFromServer(page) { + ApiClient.getVirtualFolders().then(result => { + const library = result.filter(f => { + return f.Name === currentOptions.library.Name; + })[0]; + + if (library) { + currentOptions.library = library; + renderLibrary(page, currentOptions); + } + }); +} + +function renderLibrary(page, options) { + let pathInfos = (options.library.LibraryOptions || {}).PathInfos || []; + + if (!pathInfos.length) { + pathInfos = options.library.Locations.map(p => { + return { + Path: p + }; }); } - function updateMediaLocation(page, path, networkSharePath) { - const virtualFolder = currentOptions.library; - ApiClient.updateMediaPath(virtualFolder.Name, { - Path: path, - NetworkPath: networkSharePath - }).then(() => { - hasChanges = true; - refreshLibraryFromServer(page); - }, () => { - toast(globalize.translate('ErrorAddingMediaPathToVirtualFolder')); - }); + if (options.library.CollectionType === 'boxsets') { + page.querySelector('.folders').classList.add('hide'); + } else { + page.querySelector('.folders').classList.remove('hide'); } - function onRemoveClick(btnRemovePath, location) { - const button = btnRemovePath; - const virtualFolder = currentOptions.library; + page.querySelector('.folderList').innerHTML = pathInfos.map(getFolderHtml).join(''); +} - confirm({ - title: globalize.translate('HeaderRemoveMediaLocation'), - text: globalize.translate('MessageConfirmRemoveMediaLocation'), - confirmText: globalize.translate('Delete'), - primary: 'delete' - }).then(() => { - const refreshAfterChange = currentOptions.refresh; - ApiClient.removeMediaPath(virtualFolder.Name, location, refreshAfterChange).then(() => { - hasChanges = true; - refreshLibraryFromServer(dom.parentWithClass(button, 'dlg-libraryeditor')); - }, () => { - toast(globalize.translate('ErrorDefault')); - }); - }); - } +function onAddButtonClick() { + showDirectoryBrowser(dom.parentWithClass(this, 'dlg-libraryeditor')); +} - function onListItemClick(e) { - const listItem = dom.parentWithClass(e.target, 'listItem'); - - if (listItem) { - const index = parseInt(listItem.getAttribute('data-index'), 10); - const pathInfos = (currentOptions.library.LibraryOptions || {}).PathInfos || []; - const pathInfo = index == null ? {} : pathInfos[index] || {}; - const originalPath = pathInfo.Path || (index == null ? null : currentOptions.library.Locations[index]); - const btnRemovePath = dom.parentWithClass(e.target, 'btnRemovePath'); - - if (btnRemovePath) { - onRemoveClick(btnRemovePath, originalPath); - return; - } - - showDirectoryBrowser(dom.parentWithClass(listItem, 'dlg-libraryeditor'), originalPath, pathInfo.NetworkPath); - } - } - - function getFolderHtml(pathInfo, index) { - let html = ''; - html += `
`; - html += `
`; - html += '

'; - html += escapeHtml(pathInfo.Path); - html += '

'; - - if (pathInfo.NetworkPath) { - html += `
${escapeHtml(pathInfo.NetworkPath)}
`; - } - - html += '
'; - html += ``; - html += '
'; - return html; - } - - function refreshLibraryFromServer(page) { - ApiClient.getVirtualFolders().then(result => { - const library = result.filter(f => { - return f.Name === currentOptions.library.Name; - })[0]; - - if (library) { - currentOptions.library = library; - renderLibrary(page, currentOptions); - } - }); - } - - function renderLibrary(page, options) { - let pathInfos = (options.library.LibraryOptions || {}).PathInfos || []; - - if (!pathInfos.length) { - pathInfos = options.library.Locations.map(p => { - return { - Path: p - }; - }); - } - - if (options.library.CollectionType === 'boxsets') { - page.querySelector('.folders').classList.add('hide'); - } else { - page.querySelector('.folders').classList.remove('hide'); - } - - page.querySelector('.folderList').innerHTML = pathInfos.map(getFolderHtml).join(''); - } - - function onAddButtonClick() { - showDirectoryBrowser(dom.parentWithClass(this, 'dlg-libraryeditor')); - } - - function showDirectoryBrowser(context, originalPath, networkPath) { - import('../directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => { - const picker = new DirectoryBrowser(); - picker.show({ - enableNetworkSharePath: true, - pathReadOnly: originalPath != null, - path: originalPath, - networkSharePath: networkPath, - callback: function (path, networkSharePath) { - if (path) { - if (originalPath) { - updateMediaLocation(context, originalPath, networkSharePath); - } else { - addMediaLocation(context, path, networkSharePath); - } +function showDirectoryBrowser(context, originalPath, networkPath) { + import('../directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => { + const picker = new DirectoryBrowser(); + picker.show({ + enableNetworkSharePath: true, + pathReadOnly: originalPath != null, + path: originalPath, + networkSharePath: networkPath, + callback: function (path, networkSharePath) { + if (path) { + if (originalPath) { + updateMediaLocation(context, originalPath, networkSharePath); + } else { + addMediaLocation(context, path, networkSharePath); } - - picker.close(); } - }); + + picker.close(); + } }); - } + }); +} - function initEditor(dlg, options) { - renderLibrary(dlg, options); - dlg.querySelector('.btnAddFolder').addEventListener('click', onAddButtonClick); - dlg.querySelector('.folderList').addEventListener('click', onListItemClick); - dlg.querySelector('.btnSubmit').addEventListener('click', onEditLibrary); - libraryoptionseditor.embed(dlg.querySelector('.libraryOptions'), options.library.CollectionType, options.library.LibraryOptions); - } +function initEditor(dlg, options) { + renderLibrary(dlg, options); + dlg.querySelector('.btnAddFolder').addEventListener('click', onAddButtonClick); + dlg.querySelector('.folderList').addEventListener('click', onListItemClick); + dlg.querySelector('.btnSubmit').addEventListener('click', onEditLibrary); + libraryoptionseditor.embed(dlg.querySelector('.libraryOptions'), options.library.CollectionType, options.library.LibraryOptions); +} - function onDialogClosed() { - currentDeferred.resolveWith(null, [hasChanges]); - } +function onDialogClosed() { + currentDeferred.resolveWith(null, [hasChanges]); +} export class showEditor { constructor(options) { @@ -227,10 +226,9 @@ export class showEditor { } } - let currentDeferred; - let currentOptions; - let hasChanges = false; - let isCreating = false; +let currentDeferred; +let currentOptions; +let hasChanges = false; +let isCreating = false; -/* eslint-enable indent */ export default showEditor; diff --git a/src/components/mediainfo/mediainfo.js b/src/components/mediainfo/mediainfo.js index 530e102f14..9f5c366083 100644 --- a/src/components/mediainfo/mediainfo.js +++ b/src/components/mediainfo/mediainfo.js @@ -10,574 +10,571 @@ import '../guide/programs.scss'; import '../../elements/emby-button/emby-button'; import * as userSettings from '../../scripts/settings/userSettings'; -/* eslint-disable indent */ - function getTimerIndicator(item) { - let status; +function getTimerIndicator(item) { + let status; - if (item.Type === 'SeriesTimer') { + if (item.Type === 'SeriesTimer') { + return ''; + } else if (item.TimerId || item.SeriesTimerId) { + status = item.Status || 'Cancelled'; + } else if (item.Type === 'Timer') { + status = item.Status; + } else { + return ''; + } + + if (item.SeriesTimerId) { + if (status !== 'Cancelled') { return ''; - } else if (item.TimerId || item.SeriesTimerId) { - status = item.Status || 'Cancelled'; - } else if (item.Type === 'Timer') { - status = item.Status; + } + + return ''; + } + + return ''; +} + +function getProgramInfoHtml(item, options) { + let html = ''; + + const miscInfo = []; + let text; + let date; + + if (item.StartDate && options.programTime !== false) { + try { + text = ''; + + date = datetime.parseISO8601Date(item.StartDate); + + if (options.startDate !== false) { + text += datetime.toLocaleDateString(date, { weekday: 'short', month: 'short', day: 'numeric' }); + } + + text += ` ${datetime.getDisplayTime(date)}`; + + if (item.EndDate) { + date = datetime.parseISO8601Date(item.EndDate); + text += ` - ${datetime.getDisplayTime(date)}`; + } + + miscInfo.push(text); + } catch (e) { + console.error('error parsing date:', item.StartDate); + } + } + + if (item.ChannelNumber) { + miscInfo.push(`CH ${item.ChannelNumber}`); + } + + if (item.ChannelName) { + if (options.interactive && item.ChannelId) { + miscInfo.push({ + html: `${escapeHtml(item.ChannelName)}` + }); } else { - return ''; + miscInfo.push(escapeHtml(item.ChannelName)); } - - if (item.SeriesTimerId) { - if (status !== 'Cancelled') { - return ''; - } - - return ''; - } - - return ''; } - function getProgramInfoHtml(item, options) { - let html = ''; - - const miscInfo = []; - let text; - let date; - - if (item.StartDate && options.programTime !== false) { - try { - text = ''; - - date = datetime.parseISO8601Date(item.StartDate); - - if (options.startDate !== false) { - text += datetime.toLocaleDateString(date, { weekday: 'short', month: 'short', day: 'numeric' }); - } - - text += ` ${datetime.getDisplayTime(date)}`; - - if (item.EndDate) { - date = datetime.parseISO8601Date(item.EndDate); - text += ` - ${datetime.getDisplayTime(date)}`; - } - - miscInfo.push(text); - } catch (e) { - console.error('error parsing date:', item.StartDate); - } + if (options.timerIndicator !== false) { + const timerHtml = getTimerIndicator(item); + if (timerHtml) { + miscInfo.push({ + html: timerHtml + }); } - - if (item.ChannelNumber) { - miscInfo.push(`CH ${item.ChannelNumber}`); - } - - if (item.ChannelName) { - if (options.interactive && item.ChannelId) { - miscInfo.push({ - html: `${escapeHtml(item.ChannelName)}` - }); - } else { - miscInfo.push(escapeHtml(item.ChannelName)); - } - } - - if (options.timerIndicator !== false) { - const timerHtml = getTimerIndicator(item); - if (timerHtml) { - miscInfo.push({ - html: timerHtml - }); - } - } - - html += miscInfo.map(m => { - return getMediaInfoItem(m); - }).join(''); - - return html; } - export function getMediaInfoHtml(item, options = {}) { - let html = ''; + html += miscInfo.map(m => { + return getMediaInfoItem(m); + }).join(''); - const miscInfo = []; - let text; - let date; - let count; + return html; +} - const showFolderRuntime = item.Type === 'MusicAlbum' || item.MediaType === 'MusicArtist' || item.Type === 'Playlist' || item.MediaType === 'Playlist' || item.MediaType === 'MusicGenre'; +export function getMediaInfoHtml(item, options = {}) { + let html = ''; - if (showFolderRuntime) { - count = item.SongCount || item.ChildCount; + const miscInfo = []; + let text; + let date; + let count; - if (count) { - miscInfo.push(globalize.translate('TrackCount', count)); - } + const showFolderRuntime = item.Type === 'MusicAlbum' || item.MediaType === 'MusicArtist' || item.Type === 'Playlist' || item.MediaType === 'Playlist' || item.MediaType === 'MusicGenre'; - if (item.RunTimeTicks) { - miscInfo.push(datetime.getDisplayDuration(item.RunTimeTicks)); - } - } else if (item.Type === 'PhotoAlbum' || item.Type === 'BoxSet') { - count = item.ChildCount; + if (showFolderRuntime) { + count = item.SongCount || item.ChildCount; - if (count) { - miscInfo.push(globalize.translate('ItemCount', count)); - } + if (count) { + miscInfo.push(globalize.translate('TrackCount', count)); } - if ((item.Type === 'Episode' || item.MediaType === 'Photo') + if (item.RunTimeTicks) { + miscInfo.push(datetime.getDisplayDuration(item.RunTimeTicks)); + } + } else if (item.Type === 'PhotoAlbum' || item.Type === 'BoxSet') { + count = item.ChildCount; + + if (count) { + miscInfo.push(globalize.translate('ItemCount', count)); + } + } + + if ((item.Type === 'Episode' || item.MediaType === 'Photo') && options.originalAirDate !== false && item.PremiereDate - ) { - try { - //don't modify date to locale if episode. Only Dates (not times) are stored, or editable in the edit metadata dialog - date = datetime.parseISO8601Date(item.PremiereDate, item.Type !== 'Episode'); + ) { + try { + //don't modify date to locale if episode. Only Dates (not times) are stored, or editable in the edit metadata dialog + date = datetime.parseISO8601Date(item.PremiereDate, item.Type !== 'Episode'); - text = datetime.toLocaleDateString(date); + text = datetime.toLocaleDateString(date); + miscInfo.push(text); + } catch (e) { + console.error('error parsing date:', item.PremiereDate); + } + } + + if (item.Type === 'SeriesTimer') { + if (item.RecordAnyTime) { + miscInfo.push(globalize.translate('Anytime')); + } else { + miscInfo.push(datetime.getDisplayTime(item.StartDate)); + } + + if (item.RecordAnyChannel) { + miscInfo.push(globalize.translate('AllChannels')); + } else { + miscInfo.push(item.ChannelName || globalize.translate('OneChannel')); + } + } + + if (item.StartDate && item.Type !== 'Program' && item.Type !== 'SeriesTimer' && item.Type !== 'Timer') { + try { + date = datetime.parseISO8601Date(item.StartDate); + + text = datetime.toLocaleDateString(date); + miscInfo.push(text); + + if (item.Type !== 'Recording') { + text = datetime.getDisplayTime(date); + miscInfo.push(text); + } + } catch (e) { + console.error('error parsing date:', item.StartDate); + } + } + + if (options.year !== false && item.ProductionYear && item.Type === 'Series') { + if (item.Status === 'Continuing') { + miscInfo.push(globalize.translate('SeriesYearToPresent', datetime.toLocaleString(item.ProductionYear, { useGrouping: false }))); + } else if (item.ProductionYear) { + text = datetime.toLocaleString(item.ProductionYear, { useGrouping: false }); + + if (item.EndDate) { + try { + const endYear = datetime.toLocaleString(datetime.parseISO8601Date(item.EndDate).getFullYear(), { useGrouping: false }); + + if (endYear !== item.ProductionYear) { + text += `-${endYear}`; + } + } catch (e) { + console.error('error parsing date:', item.EndDate); + } + } + + miscInfo.push(text); + } + } + + if (item.Type === 'Program' || item.Type === 'Timer') { + let program = item; + if (item.Type === 'Timer') { + program = item.ProgramInfo; + } + + if (options.programIndicator !== false) { + if (program.IsLive && userSettings.get('guide-indicator-live') === 'true') { + miscInfo.push({ + html: `
${globalize.translate('Live')}
` + }); + } else if (program.IsPremiere && userSettings.get('guide-indicator-premiere') === 'true') { + miscInfo.push({ + html: `
${globalize.translate('Premiere')}
` + }); + } else if (program.IsSeries && !program.IsRepeat && userSettings.get('guide-indicator-new') === 'true') { + miscInfo.push({ + html: `
${globalize.translate('New')}
` + }); + } else if (program.IsSeries && program.IsRepeat && userSettings.get('guide-indicator-repeat') === 'true') { + miscInfo.push({ + html: `
${globalize.translate('Repeat')}
` + }); + } + } + + if ((program.IsSeries || program.EpisodeTitle) && options.episodeTitle !== false) { + text = itemHelper.getDisplayName(program, { + includeIndexNumber: options.episodeTitleIndexNumber + }); + + if (text) { + miscInfo.push(escapeHtml(text)); + } + } else if (program.IsMovie && program.ProductionYear && options.originalAirDate !== false) { + miscInfo.push(program.ProductionYear); + } else if (program.PremiereDate && options.originalAirDate !== false) { + try { + date = datetime.parseISO8601Date(program.PremiereDate); + text = globalize.translate('OriginalAirDateValue', datetime.toLocaleDateString(date)); + miscInfo.push(text); + } catch (e) { + console.error('error parsing date:', program.PremiereDate); + } + } else if (program.ProductionYear && options.year !== false ) { + miscInfo.push(program.ProductionYear); + } + } + + if (options.year !== false && item.Type !== 'Series' && item.Type !== 'Episode' && item.Type !== 'Person' + && item.MediaType !== 'Photo' && item.Type !== 'Program' && item.Type !== 'Season' + ) { + if (item.ProductionYear) { + miscInfo.push(item.ProductionYear); + } else if (item.PremiereDate) { + try { + text = datetime.toLocaleString(datetime.parseISO8601Date(item.PremiereDate).getFullYear(), { useGrouping: false }); miscInfo.push(text); } catch (e) { console.error('error parsing date:', item.PremiereDate); } } - - if (item.Type === 'SeriesTimer') { - if (item.RecordAnyTime) { - miscInfo.push(globalize.translate('Anytime')); - } else { - miscInfo.push(datetime.getDisplayTime(item.StartDate)); - } - - if (item.RecordAnyChannel) { - miscInfo.push(globalize.translate('AllChannels')); - } else { - miscInfo.push(item.ChannelName || globalize.translate('OneChannel')); - } - } - - if (item.StartDate && item.Type !== 'Program' && item.Type !== 'SeriesTimer' && item.Type !== 'Timer') { - try { - date = datetime.parseISO8601Date(item.StartDate); - - text = datetime.toLocaleDateString(date); - miscInfo.push(text); - - if (item.Type !== 'Recording') { - text = datetime.getDisplayTime(date); - miscInfo.push(text); - } - } catch (e) { - console.error('error parsing date:', item.StartDate); - } - } - - if (options.year !== false && item.ProductionYear && item.Type === 'Series') { - if (item.Status === 'Continuing') { - miscInfo.push(globalize.translate('SeriesYearToPresent', datetime.toLocaleString(item.ProductionYear, { useGrouping: false }))); - } else if (item.ProductionYear) { - text = datetime.toLocaleString(item.ProductionYear, { useGrouping: false }); - - if (item.EndDate) { - try { - const endYear = datetime.toLocaleString(datetime.parseISO8601Date(item.EndDate).getFullYear(), { useGrouping: false }); - - if (endYear !== item.ProductionYear) { - text += `-${endYear}`; - } - } catch (e) { - console.error('error parsing date:', item.EndDate); - } - } - - miscInfo.push(text); - } - } - - if (item.Type === 'Program' || item.Type === 'Timer') { - let program = item; - if (item.Type === 'Timer') { - program = item.ProgramInfo; - } - - if (options.programIndicator !== false) { - if (program.IsLive && userSettings.get('guide-indicator-live') === 'true') { - miscInfo.push({ - html: `
${globalize.translate('Live')}
` - }); - } else if (program.IsPremiere && userSettings.get('guide-indicator-premiere') === 'true') { - miscInfo.push({ - html: `
${globalize.translate('Premiere')}
` - }); - } else if (program.IsSeries && !program.IsRepeat && userSettings.get('guide-indicator-new') === 'true') { - miscInfo.push({ - html: `
${globalize.translate('New')}
` - }); - } else if (program.IsSeries && program.IsRepeat && userSettings.get('guide-indicator-repeat') === 'true') { - miscInfo.push({ - html: `
${globalize.translate('Repeat')}
` - }); - } - } - - if ((program.IsSeries || program.EpisodeTitle) && options.episodeTitle !== false) { - text = itemHelper.getDisplayName(program, { - includeIndexNumber: options.episodeTitleIndexNumber - }); - - if (text) { - miscInfo.push(escapeHtml(text)); - } - } else if (program.IsMovie && program.ProductionYear && options.originalAirDate !== false) { - miscInfo.push(program.ProductionYear); - } else if (program.PremiereDate && options.originalAirDate !== false) { - try { - date = datetime.parseISO8601Date(program.PremiereDate); - text = globalize.translate('OriginalAirDateValue', datetime.toLocaleDateString(date)); - miscInfo.push(text); - } catch (e) { - console.error('error parsing date:', program.PremiereDate); - } - } else if (program.ProductionYear && options.year !== false ) { - miscInfo.push(program.ProductionYear); - } - } - - if (options.year !== false && item.Type !== 'Series' && item.Type !== 'Episode' && item.Type !== 'Person' - && item.MediaType !== 'Photo' && item.Type !== 'Program' && item.Type !== 'Season' - ) { - if (item.ProductionYear) { - miscInfo.push(item.ProductionYear); - } else if (item.PremiereDate) { - try { - text = datetime.toLocaleString(datetime.parseISO8601Date(item.PremiereDate).getFullYear(), { useGrouping: false }); - miscInfo.push(text); - } catch (e) { - console.error('error parsing date:', item.PremiereDate); - } - } - } - - if (item.RunTimeTicks && item.Type !== 'Series' && item.Type !== 'Program' && item.Type !== 'Timer' && item.Type !== 'Book' && !showFolderRuntime && options.runtime !== false) { - if (item.Type === 'Audio') { - miscInfo.push(datetime.getDisplayRunningTime(item.RunTimeTicks)); - } else { - miscInfo.push(datetime.getDisplayDuration(item.RunTimeTicks)); - } - } - - if (options.officialRating !== false && item.OfficialRating && item.Type !== 'Season' && item.Type !== 'Episode') { - miscInfo.push({ - text: item.OfficialRating, - cssClass: 'mediaInfoOfficialRating' - }); - } - - if (item.Video3DFormat) { - miscInfo.push('3D'); - } - - if (item.MediaType === 'Photo' && item.Width && item.Height) { - miscInfo.push(`${item.Width}x${item.Height}`); - } - - if (options.container !== false && item.Type === 'Audio' && item.Container) { - miscInfo.push(item.Container); - } - - html += miscInfo.map(m => { - return getMediaInfoItem(m); - }).join(''); - - if (options.starRating !== false) { - html += getStarIconsHtml(item); - } - - if (item.HasSubtitles && options.subtitles !== false) { - html += '
CC
'; - } - - if (item.CriticRating && options.criticRating !== false) { - if (item.CriticRating >= 60) { - html += `
${item.CriticRating}
`; - } else { - html += `
${item.CriticRating}
`; - } - } - - if (options.endsAt !== false) { - const endsAt = getEndsAt(item); - if (endsAt) { - html += getMediaInfoItem(endsAt, 'endsAt'); - } - } - - html += indicators.getMissingIndicator(item); - - return html; } - export function getEndsAt(item) { - if (item.MediaType === 'Video' && item.RunTimeTicks && !item.StartDate) { - let endDate = new Date().getTime() + (item.RunTimeTicks / 10000); - endDate = new Date(endDate); - - const displayTime = datetime.getDisplayTime(endDate); - return globalize.translate('EndsAtValue', displayTime); + if (item.RunTimeTicks && item.Type !== 'Series' && item.Type !== 'Program' && item.Type !== 'Timer' && item.Type !== 'Book' && !showFolderRuntime && options.runtime !== false) { + if (item.Type === 'Audio') { + miscInfo.push(datetime.getDisplayRunningTime(item.RunTimeTicks)); + } else { + miscInfo.push(datetime.getDisplayDuration(item.RunTimeTicks)); } - - return null; } - export function getEndsAtFromPosition(runtimeTicks, positionTicks, playbackRate, includeText) { - let endDate = new Date().getTime() + (1 / playbackRate) * ((runtimeTicks - (positionTicks || 0)) / 10000); + if (options.officialRating !== false && item.OfficialRating && item.Type !== 'Season' && item.Type !== 'Episode') { + miscInfo.push({ + text: item.OfficialRating, + cssClass: 'mediaInfoOfficialRating' + }); + } + + if (item.Video3DFormat) { + miscInfo.push('3D'); + } + + if (item.MediaType === 'Photo' && item.Width && item.Height) { + miscInfo.push(`${item.Width}x${item.Height}`); + } + + if (options.container !== false && item.Type === 'Audio' && item.Container) { + miscInfo.push(item.Container); + } + + html += miscInfo.map(m => { + return getMediaInfoItem(m); + }).join(''); + + if (options.starRating !== false) { + html += getStarIconsHtml(item); + } + + if (item.HasSubtitles && options.subtitles !== false) { + html += '
CC
'; + } + + if (item.CriticRating && options.criticRating !== false) { + if (item.CriticRating >= 60) { + html += `
${item.CriticRating}
`; + } else { + html += `
${item.CriticRating}
`; + } + } + + if (options.endsAt !== false) { + const endsAt = getEndsAt(item); + if (endsAt) { + html += getMediaInfoItem(endsAt, 'endsAt'); + } + } + + html += indicators.getMissingIndicator(item); + + return html; +} + +export function getEndsAt(item) { + if (item.MediaType === 'Video' && item.RunTimeTicks && !item.StartDate) { + let endDate = new Date().getTime() + (item.RunTimeTicks / 10000); endDate = new Date(endDate); const displayTime = datetime.getDisplayTime(endDate); - - if (includeText === false) { - return displayTime; - } return globalize.translate('EndsAtValue', displayTime); } - function getMediaInfoItem(m, cssClass) { - cssClass = cssClass ? (`${cssClass} mediaInfoItem`) : 'mediaInfoItem'; - let mediaInfoText = m; + return null; +} - if (typeof (m) !== 'string' && typeof (m) !== 'number') { - if (m.html) { - return m.html; - } - mediaInfoText = m.text; - cssClass += ` ${m.cssClass}`; +export function getEndsAtFromPosition(runtimeTicks, positionTicks, playbackRate, includeText) { + let endDate = new Date().getTime() + (1 / playbackRate) * ((runtimeTicks - (positionTicks || 0)) / 10000); + endDate = new Date(endDate); + + const displayTime = datetime.getDisplayTime(endDate); + + if (includeText === false) { + return displayTime; + } + return globalize.translate('EndsAtValue', displayTime); +} + +function getMediaInfoItem(m, cssClass) { + cssClass = cssClass ? (`${cssClass} mediaInfoItem`) : 'mediaInfoItem'; + let mediaInfoText = m; + + if (typeof (m) !== 'string' && typeof (m) !== 'number') { + if (m.html) { + return m.html; } - return `
${mediaInfoText}
`; + mediaInfoText = m.text; + cssClass += ` ${m.cssClass}`; + } + return `
${mediaInfoText}
`; +} + +function getStarIconsHtml(item) { + let html = ''; + + if (item.CommunityRating) { + html += '
'; + + html += ''; + html += item.CommunityRating.toFixed(1); + html += '
'; } - function getStarIconsHtml(item) { - let html = ''; + return html; +} - if (item.CommunityRating) { - html += '
'; - - html += ''; - html += item.CommunityRating.toFixed(1); - html += '
'; +function dynamicEndTime(elem, item) { + const interval = setInterval(() => { + if (!document.body.contains(elem)) { + clearInterval(interval); + return; } - return html; - } + elem.innerHTML = getEndsAt(item); + }, 60000); +} - function dynamicEndTime(elem, item) { - const interval = setInterval(() => { - if (!document.body.contains(elem)) { - clearInterval(interval); - return; - } +export function fillPrimaryMediaInfo(elem, item, options) { + const html = getPrimaryMediaInfoHtml(item, options); - elem.innerHTML = getEndsAt(item); - }, 60000); - } + elem.innerHTML = html; + afterFill(elem, item, options); +} - export function fillPrimaryMediaInfo(elem, item, options) { - const html = getPrimaryMediaInfoHtml(item, options); +export function fillSecondaryMediaInfo(elem, item, options) { + const html = getSecondaryMediaInfoHtml(item, options); - elem.innerHTML = html; - afterFill(elem, item, options); - } + elem.innerHTML = html; + afterFill(elem, item, options); +} - export function fillSecondaryMediaInfo(elem, item, options) { - const html = getSecondaryMediaInfoHtml(item, options); - - elem.innerHTML = html; - afterFill(elem, item, options); - } - - function afterFill(elem, item, options) { - if (options.endsAt !== false) { - const endsAtElem = elem.querySelector('.endsAt'); - if (endsAtElem) { - dynamicEndTime(endsAtElem, item); - } - } - - const lnkChannel = elem.querySelector('.lnkChannel'); - if (lnkChannel) { - lnkChannel.addEventListener('click', onChannelLinkClick); +function afterFill(elem, item, options) { + if (options.endsAt !== false) { + const endsAtElem = elem.querySelector('.endsAt'); + if (endsAtElem) { + dynamicEndTime(endsAtElem, item); } } - function onChannelLinkClick(e) { - const channelId = this.getAttribute('data-id'); - const serverId = this.getAttribute('data-serverid'); + const lnkChannel = elem.querySelector('.lnkChannel'); + if (lnkChannel) { + lnkChannel.addEventListener('click', onChannelLinkClick); + } +} - appRouter.showItem(channelId, serverId); +function onChannelLinkClick(e) { + const channelId = this.getAttribute('data-id'); + const serverId = this.getAttribute('data-serverid'); - e.preventDefault(); - return false; + appRouter.showItem(channelId, serverId); + + e.preventDefault(); + return false; +} + +export function getPrimaryMediaInfoHtml(item, options = {}) { + if (options.interactive === undefined) { + options.interactive = false; } - export function getPrimaryMediaInfoHtml(item, options = {}) { - if (options.interactive === undefined) { - options.interactive = false; - } + return getMediaInfoHtml(item, options); +} - return getMediaInfoHtml(item, options); +export function getSecondaryMediaInfoHtml(item, options) { + options = options || {}; + if (options.interactive == null) { + options.interactive = false; + } + if (item.Type === 'Program') { + return getProgramInfoHtml(item, options); } - export function getSecondaryMediaInfoHtml(item, options) { - options = options || {}; - if (options.interactive == null) { - options.interactive = false; - } - if (item.Type === 'Program') { - return getProgramInfoHtml(item, options); - } + return ''; +} - return ''; +export function getResolutionText(i) { + const width = i.Width; + const height = i.Height; + + if (width && height) { + if (width >= 3800 || height >= 2000) { + return '4K'; + } + if (width >= 2500 || height >= 1400) { + if (i.IsInterlaced) { + return '1440i'; + } + return '1440p'; + } + if (width >= 1800 || height >= 1000) { + if (i.IsInterlaced) { + return '1080i'; + } + return '1080p'; + } + if (width >= 1200 || height >= 700) { + if (i.IsInterlaced) { + return '720i'; + } + return '720p'; + } + if (width >= 700 || height >= 400) { + if (i.IsInterlaced) { + return '480i'; + } + return '480p'; + } } + return null; +} - export function getResolutionText(i) { - const width = i.Width; - const height = i.Height; - - if (width && height) { - if (width >= 3800 || height >= 2000) { - return '4K'; - } - if (width >= 2500 || height >= 1400) { - if (i.IsInterlaced) { - return '1440i'; - } - return '1440p'; - } - if (width >= 1800 || height >= 1000) { - if (i.IsInterlaced) { - return '1080i'; - } - return '1080p'; - } - if (width >= 1200 || height >= 700) { - if (i.IsInterlaced) { - return '720i'; - } - return '720p'; - } - if (width >= 700 || height >= 400) { - if (i.IsInterlaced) { - return '480i'; - } - return '480p'; - } - } +function getAudioStreamForDisplay(item) { + if (!item.MediaSources) { return null; } - function getAudioStreamForDisplay(item) { - if (!item.MediaSources) { - return null; - } - - const mediaSource = item.MediaSources[0]; - if (!mediaSource) { - return null; - } - - return (mediaSource.MediaStreams || []).filter(i => { - return i.Type === 'Audio' && (i.Index === mediaSource.DefaultAudioStreamIndex || mediaSource.DefaultAudioStreamIndex == null); - })[0]; + const mediaSource = item.MediaSources[0]; + if (!mediaSource) { + return null; } - export function getMediaInfoStats(item) { - const list = []; + return (mediaSource.MediaStreams || []).filter(i => { + return i.Type === 'Audio' && (i.Index === mediaSource.DefaultAudioStreamIndex || mediaSource.DefaultAudioStreamIndex == null); + })[0]; +} - const mediaSource = (item.MediaSources || [])[0] || {}; +export function getMediaInfoStats(item) { + const list = []; - const videoStream = (mediaSource.MediaStreams || []).filter(i => { - return i.Type === 'Video'; - })[0] || {}; - const audioStream = getAudioStreamForDisplay(item) || {}; + const mediaSource = (item.MediaSources || [])[0] || {}; - if (item.VideoType === 'Dvd') { - list.push({ - type: 'mediainfo', - text: 'Dvd' - }); - } + const videoStream = (mediaSource.MediaStreams || []).filter(i => { + return i.Type === 'Video'; + })[0] || {}; + const audioStream = getAudioStreamForDisplay(item) || {}; - if (item.VideoType === 'BluRay') { - list.push({ - type: 'mediainfo', - text: 'BluRay' - }); - } - - const resolutionText = getResolutionText(videoStream); - if (resolutionText) { - list.push({ - type: 'mediainfo', - text: resolutionText - }); - } - - if (videoStream.Codec) { - list.push({ - type: 'mediainfo', - text: videoStream.Codec - }); - } - - const channels = audioStream.Channels; - let channelText; - - if (channels === 8) { - channelText = '7.1'; - } else if (channels === 7) { - channelText = '6.1'; - } else if (channels === 6) { - channelText = '5.1'; - } else if (channels === 2) { - channelText = '2.0'; - } - - if (channelText) { - list.push({ - type: 'mediainfo', - text: channelText - }); - } - - const audioCodec = (audioStream.Codec || '').toLowerCase(); - - if ((audioCodec === 'dca' || audioCodec === 'dts') && audioStream.Profile) { - list.push({ - type: 'mediainfo', - text: audioStream.Profile - }); - } else if (audioStream.Codec) { - list.push({ - type: 'mediainfo', - text: audioStream.Codec - }); - } - - if (item.DateCreated && itemHelper.enableDateAddedDisplay(item)) { - const dateCreated = datetime.parseISO8601Date(item.DateCreated); - - list.push({ - type: 'added', - text: globalize.translate('AddedOnValue', `${datetime.toLocaleDateString(dateCreated)} ${datetime.getDisplayTime(dateCreated)}`) - }); - } - - return list; + if (item.VideoType === 'Dvd') { + list.push({ + type: 'mediainfo', + text: 'Dvd' + }); } -/* eslint-enable indent */ + if (item.VideoType === 'BluRay') { + list.push({ + type: 'mediainfo', + text: 'BluRay' + }); + } + + const resolutionText = getResolutionText(videoStream); + if (resolutionText) { + list.push({ + type: 'mediainfo', + text: resolutionText + }); + } + + if (videoStream.Codec) { + list.push({ + type: 'mediainfo', + text: videoStream.Codec + }); + } + + const channels = audioStream.Channels; + let channelText; + + if (channels === 8) { + channelText = '7.1'; + } else if (channels === 7) { + channelText = '6.1'; + } else if (channels === 6) { + channelText = '5.1'; + } else if (channels === 2) { + channelText = '2.0'; + } + + if (channelText) { + list.push({ + type: 'mediainfo', + text: channelText + }); + } + + const audioCodec = (audioStream.Codec || '').toLowerCase(); + + if ((audioCodec === 'dca' || audioCodec === 'dts') && audioStream.Profile) { + list.push({ + type: 'mediainfo', + text: audioStream.Profile + }); + } else if (audioStream.Codec) { + list.push({ + type: 'mediainfo', + text: audioStream.Codec + }); + } + + if (item.DateCreated && itemHelper.enableDateAddedDisplay(item)) { + const dateCreated = datetime.parseISO8601Date(item.DateCreated); + + list.push({ + type: 'added', + text: globalize.translate('AddedOnValue', `${datetime.toLocaleDateString(dateCreated)} ${datetime.getDisplayTime(dateCreated)}`) + }); + } + + return list; +} export default { getMediaInfoHtml: getPrimaryMediaInfoHtml, diff --git a/src/components/metadataEditor/metadataEditor.js b/src/components/metadataEditor/metadataEditor.js index a9596c3786..3aeaee63a3 100644 --- a/src/components/metadataEditor/metadataEditor.js +++ b/src/components/metadataEditor/metadataEditor.js @@ -23,1094 +23,1091 @@ import toast from '../toast/toast'; import { appRouter } from '../appRouter'; import template from './metadataEditor.template.html'; -/* eslint-disable indent */ +let currentContext; +let metadataEditorInfo; +let currentItem; - let currentContext; - let metadataEditorInfo; - let currentItem; +function isDialog() { + return currentContext.classList.contains('dialog'); +} - function isDialog() { - return currentContext.classList.contains('dialog'); +function closeDialog() { + if (isDialog()) { + dialogHelper.close(currentContext); + } +} + +function submitUpdatedItem(form, item) { + function afterContentTypeUpdated() { + toast(globalize.translate('MessageItemSaved')); + + loading.hide(); + closeDialog(); } - function closeDialog() { - if (isDialog()) { - dialogHelper.close(currentContext); - } - } + const apiClient = getApiClient(); - function submitUpdatedItem(form, item) { - function afterContentTypeUpdated() { - toast(globalize.translate('MessageItemSaved')); + apiClient.updateItem(item).then(function () { + const newContentType = form.querySelector('#selectContentType').value || ''; - loading.hide(); - closeDialog(); - } + if ((metadataEditorInfo.ContentType || '') !== newContentType) { + apiClient.ajax({ - const apiClient = getApiClient(); + url: apiClient.getUrl('Items/' + item.Id + '/ContentType', { + ContentType: newContentType + }), - apiClient.updateItem(item).then(function () { - const newContentType = form.querySelector('#selectContentType').value || ''; + type: 'POST' - if ((metadataEditorInfo.ContentType || '') !== newContentType) { - apiClient.ajax({ - - url: apiClient.getUrl('Items/' + item.Id + '/ContentType', { - ContentType: newContentType - }), - - type: 'POST' - - }).then(function () { - afterContentTypeUpdated(); - }); - } else { + }).then(function () { afterContentTypeUpdated(); - } - }); - } - - function getSelectedAirDays(form) { - const checkedItems = form.querySelectorAll('.chkAirDay:checked') || []; - return Array.prototype.map.call(checkedItems, function (c) { - return c.getAttribute('data-day'); - }); - } - - function getAlbumArtists(form) { - return form.querySelector('#txtAlbumArtist').value.trim().split(';').filter(function (s) { - return s.length > 0; - }).map(function (a) { - return { - Name: a - }; - }); - } - - function getArtists(form) { - return form.querySelector('#txtArtist').value.trim().split(';').filter(function (s) { - return s.length > 0; - }).map(function (a) { - return { - Name: a - }; - }); - } - - function getDateValue(form, element, property) { - let val = form.querySelector(element).value; - - if (!val) { - return null; + }); + } else { + afterContentTypeUpdated(); } + }); +} - if (currentItem[property]) { - const date = datetime.parseISO8601Date(currentItem[property], true); +function getSelectedAirDays(form) { + const checkedItems = form.querySelectorAll('.chkAirDay:checked') || []; + return Array.prototype.map.call(checkedItems, function (c) { + return c.getAttribute('data-day'); + }); +} - const parts = date.toISOString().split('T'); - - // If the date is the same, preserve the time - if (parts[0].indexOf(val) === 0) { - const iso = parts[1]; - - val += 'T' + iso; - } - } - - return val; - } - - function onSubmit(e) { - loading.show(); - - const form = this; - - const item = { - Id: currentItem.Id, - Name: form.querySelector('#txtName').value, - OriginalTitle: form.querySelector('#txtOriginalName').value, - ForcedSortName: form.querySelector('#txtSortName').value, - CommunityRating: form.querySelector('#txtCommunityRating').value, - CriticRating: form.querySelector('#txtCriticRating').value, - IndexNumber: form.querySelector('#txtIndexNumber').value || null, - AirsBeforeSeasonNumber: form.querySelector('#txtAirsBeforeSeason').value, - AirsAfterSeasonNumber: form.querySelector('#txtAirsAfterSeason').value, - AirsBeforeEpisodeNumber: form.querySelector('#txtAirsBeforeEpisode').value, - ParentIndexNumber: form.querySelector('#txtParentIndexNumber').value || null, - DisplayOrder: form.querySelector('#selectDisplayOrder').value, - Album: form.querySelector('#txtAlbum').value, - AlbumArtists: getAlbumArtists(form), - ArtistItems: getArtists(form), - Overview: form.querySelector('#txtOverview').value, - Status: form.querySelector('#selectStatus').value, - AirDays: getSelectedAirDays(form), - AirTime: form.querySelector('#txtAirTime').value, - Genres: getListValues(form.querySelector('#listGenres')), - Tags: getListValues(form.querySelector('#listTags')), - Studios: getListValues(form.querySelector('#listStudios')).map(function (element) { - return { Name: element }; - }), - - PremiereDate: getDateValue(form, '#txtPremiereDate', 'PremiereDate'), - DateCreated: getDateValue(form, '#txtDateAdded', 'DateCreated'), - EndDate: getDateValue(form, '#txtEndDate', 'EndDate'), - ProductionYear: form.querySelector('#txtProductionYear').value, - AspectRatio: form.querySelector('#txtOriginalAspectRatio').value, - Video3DFormat: form.querySelector('#select3dFormat').value, - - OfficialRating: form.querySelector('#selectOfficialRating').value, - CustomRating: form.querySelector('#selectCustomRating').value, - People: currentItem.People, - LockData: form.querySelector('#chkLockData').checked, - LockedFields: Array.prototype.filter.call(form.querySelectorAll('.selectLockedField'), function (c) { - return !c.checked; - }).map(function (c) { - return c.getAttribute('data-value'); - }) +function getAlbumArtists(form) { + return form.querySelector('#txtAlbumArtist').value.trim().split(';').filter(function (s) { + return s.length > 0; + }).map(function (a) { + return { + Name: a }; + }); +} - item.ProviderIds = Object.assign({}, currentItem.ProviderIds); +function getArtists(form) { + return form.querySelector('#txtArtist').value.trim().split(';').filter(function (s) { + return s.length > 0; + }).map(function (a) { + return { + Name: a + }; + }); +} - const idElements = form.querySelectorAll('.txtExternalId'); - Array.prototype.map.call(idElements, function (idElem) { - const providerKey = idElem.getAttribute('data-providerkey'); - item.ProviderIds[providerKey] = idElem.value; - }); +function getDateValue(form, element, property) { + let val = form.querySelector(element).value; - item.PreferredMetadataLanguage = form.querySelector('#selectLanguage').value; - item.PreferredMetadataCountryCode = form.querySelector('#selectCountry').value; - - if (currentItem.Type === 'Person') { - const placeOfBirth = form.querySelector('#txtPlaceOfBirth').value; - - item.ProductionLocations = placeOfBirth ? [placeOfBirth] : []; - } - - if (currentItem.Type === 'Series') { - // 600000000 - const seriesRuntime = form.querySelector('#txtSeriesRuntime').value; - item.RunTimeTicks = seriesRuntime ? (seriesRuntime * 600000000) : null; - } - - const tagline = form.querySelector('#txtTagline').value; - item.Taglines = tagline ? [tagline] : []; - - submitUpdatedItem(form, item); - - e.preventDefault(); - e.stopPropagation(); - - // Disable default form submission - return false; + if (!val) { + return null; } - function getListValues(list) { - return Array.prototype.map.call(list.querySelectorAll('.textValue'), function (el) { - return el.textContent; - }); - } + if (currentItem[property]) { + const date = datetime.parseISO8601Date(currentItem[property], true); - function addElementToList(source, sortCallback) { - import('../prompt/prompt').then(({ default: prompt }) => { - prompt({ - label: 'Value:' - }).then(function (text) { - const list = dom.parentWithClass(source, 'editableListviewContainer').querySelector('.paperList'); - const items = getListValues(list); - items.push(text); - populateListView(list, items, sortCallback); - }); - }); - } + const parts = date.toISOString().split('T'); - function removeElementFromList(source) { - const el = dom.parentWithClass(source, 'listItem'); - el.parentNode.removeChild(el); - } + // If the date is the same, preserve the time + if (parts[0].indexOf(val) === 0) { + const iso = parts[1]; - function editPerson(context, person, index) { - import('./personEditor').then(({ default: personEditor }) => { - personEditor.show(person).then(function (updatedPerson) { - const isNew = index === -1; - - if (isNew) { - currentItem.People.push(updatedPerson); - } - - populatePeople(context, currentItem.People); - }); - }); - } - - function afterDeleted(context, item) { - const parentId = item.ParentId || item.SeasonId || item.SeriesId; - - if (parentId) { - reload(context, parentId, item.ServerId); - } else { - appRouter.goHome(); + val += 'T' + iso; } } - function showMoreMenu(context, button, user) { - import('../itemContextMenu').then(({ default: itemContextMenu }) => { - const item = currentItem; + return val; +} - itemContextMenu.show({ - item: item, - positionTo: button, - edit: false, - editImages: true, - editSubtitles: true, - sync: false, - share: false, - play: false, - queue: false, - user: user - }).then(function (result) { - if (result.deleted) { - afterDeleted(context, item); - } else if (result.updated) { - reload(context, item.Id, item.ServerId); - } - }); - }); +function onSubmit(e) { + loading.show(); + + const form = this; + + const item = { + Id: currentItem.Id, + Name: form.querySelector('#txtName').value, + OriginalTitle: form.querySelector('#txtOriginalName').value, + ForcedSortName: form.querySelector('#txtSortName').value, + CommunityRating: form.querySelector('#txtCommunityRating').value, + CriticRating: form.querySelector('#txtCriticRating').value, + IndexNumber: form.querySelector('#txtIndexNumber').value || null, + AirsBeforeSeasonNumber: form.querySelector('#txtAirsBeforeSeason').value, + AirsAfterSeasonNumber: form.querySelector('#txtAirsAfterSeason').value, + AirsBeforeEpisodeNumber: form.querySelector('#txtAirsBeforeEpisode').value, + ParentIndexNumber: form.querySelector('#txtParentIndexNumber').value || null, + DisplayOrder: form.querySelector('#selectDisplayOrder').value, + Album: form.querySelector('#txtAlbum').value, + AlbumArtists: getAlbumArtists(form), + ArtistItems: getArtists(form), + Overview: form.querySelector('#txtOverview').value, + Status: form.querySelector('#selectStatus').value, + AirDays: getSelectedAirDays(form), + AirTime: form.querySelector('#txtAirTime').value, + Genres: getListValues(form.querySelector('#listGenres')), + Tags: getListValues(form.querySelector('#listTags')), + Studios: getListValues(form.querySelector('#listStudios')).map(function (element) { + return { Name: element }; + }), + + PremiereDate: getDateValue(form, '#txtPremiereDate', 'PremiereDate'), + DateCreated: getDateValue(form, '#txtDateAdded', 'DateCreated'), + EndDate: getDateValue(form, '#txtEndDate', 'EndDate'), + ProductionYear: form.querySelector('#txtProductionYear').value, + AspectRatio: form.querySelector('#txtOriginalAspectRatio').value, + Video3DFormat: form.querySelector('#select3dFormat').value, + + OfficialRating: form.querySelector('#selectOfficialRating').value, + CustomRating: form.querySelector('#selectCustomRating').value, + People: currentItem.People, + LockData: form.querySelector('#chkLockData').checked, + LockedFields: Array.prototype.filter.call(form.querySelectorAll('.selectLockedField'), function (c) { + return !c.checked; + }).map(function (c) { + return c.getAttribute('data-value'); + }) + }; + + item.ProviderIds = Object.assign({}, currentItem.ProviderIds); + + const idElements = form.querySelectorAll('.txtExternalId'); + Array.prototype.map.call(idElements, function (idElem) { + const providerKey = idElem.getAttribute('data-providerkey'); + item.ProviderIds[providerKey] = idElem.value; + }); + + item.PreferredMetadataLanguage = form.querySelector('#selectLanguage').value; + item.PreferredMetadataCountryCode = form.querySelector('#selectCountry').value; + + if (currentItem.Type === 'Person') { + const placeOfBirth = form.querySelector('#txtPlaceOfBirth').value; + + item.ProductionLocations = placeOfBirth ? [placeOfBirth] : []; } - function onEditorClick(e) { - const btnRemoveFromEditorList = dom.parentWithClass(e.target, 'btnRemoveFromEditorList'); - if (btnRemoveFromEditorList) { - removeElementFromList(btnRemoveFromEditorList); - return; - } - - const btnAddTextItem = dom.parentWithClass(e.target, 'btnAddTextItem'); - if (btnAddTextItem) { - addElementToList(btnAddTextItem); - } + if (currentItem.Type === 'Series') { + // 600000000 + const seriesRuntime = form.querySelector('#txtSeriesRuntime').value; + item.RunTimeTicks = seriesRuntime ? (seriesRuntime * 600000000) : null; } - function getApiClient() { - return ServerConnections.getApiClient(currentItem.ServerId); - } + const tagline = form.querySelector('#txtTagline').value; + item.Taglines = tagline ? [tagline] : []; - function bindAll(elems, eventName, fn) { - for (let i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener(eventName, fn); - } - } + submitUpdatedItem(form, item); - function init(context) { - context.querySelector('.externalIds').addEventListener('click', function (e) { - const btnOpenExternalId = dom.parentWithClass(e.target, 'btnOpenExternalId'); - if (btnOpenExternalId) { - const field = context.querySelector('#' + btnOpenExternalId.getAttribute('data-fieldid')); + e.preventDefault(); + e.stopPropagation(); - const formatString = field.getAttribute('data-formatstring'); + // Disable default form submission + return false; +} - if (field.value) { - shell.openUrl(formatString.replace('{0}', field.value)); - } - } +function getListValues(list) { + return Array.prototype.map.call(list.querySelectorAll('.textValue'), function (el) { + return el.textContent; + }); +} + +function addElementToList(source, sortCallback) { + import('../prompt/prompt').then(({ default: prompt }) => { + prompt({ + label: 'Value:' + }).then(function (text) { + const list = dom.parentWithClass(source, 'editableListviewContainer').querySelector('.paperList'); + const items = getListValues(list); + items.push(text); + populateListView(list, items, sortCallback); }); + }); +} - if (!layoutManager.desktop) { - context.querySelector('.btnBack').classList.remove('hide'); - context.querySelector('.btnClose').classList.add('hide'); - } +function removeElementFromList(source) { + const el = dom.parentWithClass(source, 'listItem'); + el.parentNode.removeChild(el); +} - bindAll(context.querySelectorAll('.btnCancel'), 'click', function (event) { - event.preventDefault(); - closeDialog(); - }); +function editPerson(context, person, index) { + import('./personEditor').then(({ default: personEditor }) => { + personEditor.show(person).then(function (updatedPerson) { + const isNew = index === -1; - context.querySelector('.btnMore').addEventListener('click', function (e) { - getApiClient().getCurrentUser().then(function (user) { - showMoreMenu(context, e.target, user); - }); - }); - - context.querySelector('.btnHeaderSave').addEventListener('click', function () { - context.querySelector('.btnSave').click(); - }); - - context.querySelector('#chkLockData').addEventListener('click', function (e) { - if (!e.target.checked) { - showElement('.providerSettingsContainer'); - } else { - hideElement('.providerSettingsContainer'); - } - }); - - context.removeEventListener('click', onEditorClick); - context.addEventListener('click', onEditorClick); - - const form = context.querySelector('form'); - form.removeEventListener('submit', onSubmit); - form.addEventListener('submit', onSubmit); - - context.querySelector('#btnAddPerson').addEventListener('click', function () { - editPerson(context, {}, -1); - }); - - context.querySelector('#peopleList').addEventListener('click', function (e) { - let index; - const btnDeletePerson = dom.parentWithClass(e.target, 'btnDeletePerson'); - if (btnDeletePerson) { - index = parseInt(btnDeletePerson.getAttribute('data-index'), 10); - currentItem.People.splice(index, 1); - populatePeople(context, currentItem.People); + if (isNew) { + currentItem.People.push(updatedPerson); } - const btnEditPerson = dom.parentWithClass(e.target, 'btnEditPerson'); - if (btnEditPerson) { - index = parseInt(btnEditPerson.getAttribute('data-index'), 10); - editPerson(context, currentItem.People[index], index); + populatePeople(context, currentItem.People); + }); + }); +} + +function afterDeleted(context, item) { + const parentId = item.ParentId || item.SeasonId || item.SeriesId; + + if (parentId) { + reload(context, parentId, item.ServerId); + } else { + appRouter.goHome(); + } +} + +function showMoreMenu(context, button, user) { + import('../itemContextMenu').then(({ default: itemContextMenu }) => { + const item = currentItem; + + itemContextMenu.show({ + item: item, + positionTo: button, + edit: false, + editImages: true, + editSubtitles: true, + sync: false, + share: false, + play: false, + queue: false, + user: user + }).then(function (result) { + if (result.deleted) { + afterDeleted(context, item); + } else if (result.updated) { + reload(context, item.Id, item.ServerId); } }); + }); +} + +function onEditorClick(e) { + const btnRemoveFromEditorList = dom.parentWithClass(e.target, 'btnRemoveFromEditorList'); + if (btnRemoveFromEditorList) { + removeElementFromList(btnRemoveFromEditorList); + return; } - function getItem(itemId, serverId) { - const apiClient = ServerConnections.getApiClient(serverId); - - if (itemId) { - return apiClient.getItem(apiClient.getCurrentUserId(), itemId); - } - - return apiClient.getRootFolder(apiClient.getCurrentUserId()); + const btnAddTextItem = dom.parentWithClass(e.target, 'btnAddTextItem'); + if (btnAddTextItem) { + addElementToList(btnAddTextItem); } +} - function getEditorConfig(itemId, serverId) { - const apiClient = ServerConnections.getApiClient(serverId); +function getApiClient() { + return ServerConnections.getApiClient(currentItem.ServerId); +} - if (itemId) { - return apiClient.getJSON(apiClient.getUrl('Items/' + itemId + '/MetadataEditor')); - } - - return Promise.resolve({}); +function bindAll(elems, eventName, fn) { + for (let i = 0, length = elems.length; i < length; i++) { + elems[i].addEventListener(eventName, fn); } +} - function populateCountries(select, allCountries) { - let html = ''; +function init(context) { + context.querySelector('.externalIds').addEventListener('click', function (e) { + const btnOpenExternalId = dom.parentWithClass(e.target, 'btnOpenExternalId'); + if (btnOpenExternalId) { + const field = context.querySelector('#' + btnOpenExternalId.getAttribute('data-fieldid')); - html += ""; + const formatString = field.getAttribute('data-formatstring'); - for (let i = 0, length = allCountries.length; i < length; i++) { - const culture = allCountries[i]; - - html += "'; - } - - select.innerHTML = html; - } - - function populateLanguages(select, languages) { - let html = ''; - - html += ""; - - for (let i = 0, length = languages.length; i < length; i++) { - const culture = languages[i]; - - html += "'; - } - - select.innerHTML = html; - } - - function renderContentTypeOptions(context, metadataInfo) { - if (!metadataInfo.ContentTypeOptions.length) { - hideElement('#fldContentType', context); - } else { - showElement('#fldContentType', context); - } - - const html = metadataInfo.ContentTypeOptions.map(function (i) { - return ''; - }).join(''); - - const selectEl = context.querySelector('#selectContentType'); - selectEl.innerHTML = html; - selectEl.value = metadataInfo.ContentType || ''; - } - - function loadExternalIds(context, item, externalIds) { - let html = ''; - - const providerIds = item.ProviderIds || {}; - - for (let i = 0, length = externalIds.length; i < length; i++) { - const idInfo = externalIds[i]; - - const id = 'txt1' + idInfo.Key; - const formatString = idInfo.UrlFormatString || ''; - - let fullName = idInfo.Name; - if (idInfo.Type) { - fullName = idInfo.Name + ' ' + globalize.translate(idInfo.Type); + if (field.value) { + shell.openUrl(formatString.replace('{0}', field.value)); } + } + }); - const labelText = globalize.translate('LabelDynamicExternalId', escapeHtml(fullName)); + if (!layoutManager.desktop) { + context.querySelector('.btnBack').classList.remove('hide'); + context.querySelector('.btnClose').classList.add('hide'); + } - html += '
'; - html += '
'; + bindAll(context.querySelectorAll('.btnCancel'), 'click', function (event) { + event.preventDefault(); + closeDialog(); + }); - const value = escapeHtml(providerIds[idInfo.Key] || ''); + context.querySelector('.btnMore').addEventListener('click', function (e) { + getApiClient().getCurrentUser().then(function (user) { + showMoreMenu(context, e.target, user); + }); + }); - html += '
'; - html += ''; - html += '
'; + context.querySelector('.btnHeaderSave').addEventListener('click', function () { + context.querySelector('.btnSave').click(); + }); - if (formatString) { - html += ''; + context.querySelector('#chkLockData').addEventListener('click', function (e) { + if (!e.target.checked) { + showElement('.providerSettingsContainer'); + } else { + hideElement('.providerSettingsContainer'); + } + }); + + context.removeEventListener('click', onEditorClick); + context.addEventListener('click', onEditorClick); + + const form = context.querySelector('form'); + form.removeEventListener('submit', onSubmit); + form.addEventListener('submit', onSubmit); + + context.querySelector('#btnAddPerson').addEventListener('click', function () { + editPerson(context, {}, -1); + }); + + context.querySelector('#peopleList').addEventListener('click', function (e) { + let index; + const btnDeletePerson = dom.parentWithClass(e.target, 'btnDeletePerson'); + if (btnDeletePerson) { + index = parseInt(btnDeletePerson.getAttribute('data-index'), 10); + currentItem.People.splice(index, 1); + populatePeople(context, currentItem.People); + } + + const btnEditPerson = dom.parentWithClass(e.target, 'btnEditPerson'); + if (btnEditPerson) { + index = parseInt(btnEditPerson.getAttribute('data-index'), 10); + editPerson(context, currentItem.People[index], index); + } + }); +} + +function getItem(itemId, serverId) { + const apiClient = ServerConnections.getApiClient(serverId); + + if (itemId) { + return apiClient.getItem(apiClient.getCurrentUserId(), itemId); + } + + return apiClient.getRootFolder(apiClient.getCurrentUserId()); +} + +function getEditorConfig(itemId, serverId) { + const apiClient = ServerConnections.getApiClient(serverId); + + if (itemId) { + return apiClient.getJSON(apiClient.getUrl('Items/' + itemId + '/MetadataEditor')); + } + + return Promise.resolve({}); +} + +function populateCountries(select, allCountries) { + let html = ''; + + html += ""; + + for (let i = 0, length = allCountries.length; i < length; i++) { + const culture = allCountries[i]; + + html += "'; + } + + select.innerHTML = html; +} + +function populateLanguages(select, languages) { + let html = ''; + + html += ""; + + for (let i = 0, length = languages.length; i < length; i++) { + const culture = languages[i]; + + html += "'; + } + + select.innerHTML = html; +} + +function renderContentTypeOptions(context, metadataInfo) { + if (!metadataInfo.ContentTypeOptions.length) { + hideElement('#fldContentType', context); + } else { + showElement('#fldContentType', context); + } + + const html = metadataInfo.ContentTypeOptions.map(function (i) { + return ''; + }).join(''); + + const selectEl = context.querySelector('#selectContentType'); + selectEl.innerHTML = html; + selectEl.value = metadataInfo.ContentType || ''; +} + +function loadExternalIds(context, item, externalIds) { + let html = ''; + + const providerIds = item.ProviderIds || {}; + + for (let i = 0, length = externalIds.length; i < length; i++) { + const idInfo = externalIds[i]; + + const id = 'txt1' + idInfo.Key; + const formatString = idInfo.UrlFormatString || ''; + + let fullName = idInfo.Name; + if (idInfo.Type) { + fullName = idInfo.Name + ' ' + globalize.translate(idInfo.Type); + } + + const labelText = globalize.translate('LabelDynamicExternalId', escapeHtml(fullName)); + + html += '
'; + html += '
'; + + const value = escapeHtml(providerIds[idInfo.Key] || ''); + + html += '
'; + html += ''; + html += '
'; + + if (formatString) { + html += ''; + } + html += '
'; + + html += '
'; + } + + const elem = context.querySelector('.externalIds', context); + elem.innerHTML = html; + + if (externalIds.length) { + context.querySelector('.externalIdsSection').classList.remove('hide'); + } else { + context.querySelector('.externalIdsSection').classList.add('hide'); + } +} + +// Function to hide the element by selector or raw element +// Selector can be an element or a selector string +// Context is optional and restricts the querySelector to the context +function hideElement(selector, context, multiple) { + context = context || document; + if (typeof selector === 'string') { + const elements = multiple ? context.querySelectorAll(selector) : [context.querySelector(selector)]; + + Array.prototype.forEach.call(elements, function (el) { + if (el) { + el.classList.add('hide'); } - html += '
'; + }); + } else { + selector.classList.add('hide'); + } +} - html += '
'; - } +// Function to show the element by selector or raw element +// Selector can be an element or a selector string +// Context is optional and restricts the querySelector to the context +function showElement(selector, context, multiple) { + context = context || document; + if (typeof selector === 'string') { + const elements = multiple ? context.querySelectorAll(selector) : [context.querySelector(selector)]; - const elem = context.querySelector('.externalIds', context); - elem.innerHTML = html; + Array.prototype.forEach.call(elements, function (el) { + if (el) { + el.classList.remove('hide'); + } + }); + } else { + selector.classList.remove('hide'); + } +} - if (externalIds.length) { - context.querySelector('.externalIdsSection').classList.remove('hide'); - } else { - context.querySelector('.externalIdsSection').classList.add('hide'); - } +function setFieldVisibilities(context, item) { + if (item.Path && item.EnableMediaSourceDisplay !== false) { + showElement('#fldPath', context); + } else { + hideElement('#fldPath', context); } - // Function to hide the element by selector or raw element - // Selector can be an element or a selector string - // Context is optional and restricts the querySelector to the context - function hideElement(selector, context, multiple) { - context = context || document; - if (typeof selector === 'string') { - const elements = multiple ? context.querySelectorAll(selector) : [context.querySelector(selector)]; - - Array.prototype.forEach.call(elements, function (el) { - if (el) { - el.classList.add('hide'); - } - }); - } else { - selector.classList.add('hide'); - } + if (item.Type === 'Series' || item.Type === 'Movie' || item.Type === 'Trailer' || item.Type === 'Person') { + showElement('#fldOriginalName', context); + } else { + hideElement('#fldOriginalName', context); } - // Function to show the element by selector or raw element - // Selector can be an element or a selector string - // Context is optional and restricts the querySelector to the context - function showElement(selector, context, multiple) { - context = context || document; - if (typeof selector === 'string') { - const elements = multiple ? context.querySelectorAll(selector) : [context.querySelector(selector)]; - - Array.prototype.forEach.call(elements, function (el) { - if (el) { - el.classList.remove('hide'); - } - }); - } else { - selector.classList.remove('hide'); - } + if (item.Type === 'Series') { + showElement('#fldSeriesRuntime', context); + } else { + hideElement('#fldSeriesRuntime', context); } - function setFieldVisibilities(context, item) { - if (item.Path && item.EnableMediaSourceDisplay !== false) { - showElement('#fldPath', context); - } else { - hideElement('#fldPath', context); - } + if (item.Type === 'Series' || item.Type === 'Person') { + showElement('#fldEndDate', context); + } else { + hideElement('#fldEndDate', context); + } - if (item.Type === 'Series' || item.Type === 'Movie' || item.Type === 'Trailer' || item.Type === 'Person') { - showElement('#fldOriginalName', context); - } else { - hideElement('#fldOriginalName', context); - } + if (item.Type === 'MusicAlbum') { + showElement('#albumAssociationMessage', context); + } else { + hideElement('#albumAssociationMessage', context); + } - if (item.Type === 'Series') { - showElement('#fldSeriesRuntime', context); - } else { - hideElement('#fldSeriesRuntime', context); - } + if (item.Type === 'Movie' || item.Type === 'Trailer') { + showElement('#fldCriticRating', context); + } else { + hideElement('#fldCriticRating', context); + } - if (item.Type === 'Series' || item.Type === 'Person') { - showElement('#fldEndDate', context); - } else { - hideElement('#fldEndDate', context); - } + if (item.Type === 'Series') { + showElement('#fldStatus', context); + showElement('#fldAirDays', context); + showElement('#fldAirTime', context); + } else { + hideElement('#fldStatus', context); + hideElement('#fldAirDays', context); + hideElement('#fldAirTime', context); + } - if (item.Type === 'MusicAlbum') { - showElement('#albumAssociationMessage', context); - } else { - hideElement('#albumAssociationMessage', context); - } + if (item.MediaType === 'Video' && item.Type !== 'TvChannel') { + showElement('#fld3dFormat', context); + } else { + hideElement('#fld3dFormat', context); + } - if (item.Type === 'Movie' || item.Type === 'Trailer') { - showElement('#fldCriticRating', context); - } else { - hideElement('#fldCriticRating', context); - } + if (item.Type === 'Audio') { + showElement('#fldAlbumArtist', context); + } else { + hideElement('#fldAlbumArtist', context); + } - if (item.Type === 'Series') { - showElement('#fldStatus', context); - showElement('#fldAirDays', context); - showElement('#fldAirTime', context); - } else { - hideElement('#fldStatus', context); - hideElement('#fldAirDays', context); - hideElement('#fldAirTime', context); - } + if (item.Type === 'Audio' || item.Type === 'MusicVideo') { + showElement('#fldArtist', context); + showElement('#fldAlbum', context); + } else { + hideElement('#fldArtist', context); + hideElement('#fldAlbum', context); + } - if (item.MediaType === 'Video' && item.Type !== 'TvChannel') { - showElement('#fld3dFormat', context); - } else { - hideElement('#fld3dFormat', context); - } + if (item.Type === 'Episode' && item.ParentIndexNumber === 0) { + showElement('#collapsibleSpecialEpisodeInfo', context); + } else { + hideElement('#collapsibleSpecialEpisodeInfo', context); + } - if (item.Type === 'Audio') { - showElement('#fldAlbumArtist', context); - } else { - hideElement('#fldAlbumArtist', context); - } - - if (item.Type === 'Audio' || item.Type === 'MusicVideo') { - showElement('#fldArtist', context); - showElement('#fldAlbum', context); - } else { - hideElement('#fldArtist', context); - hideElement('#fldAlbum', context); - } - - if (item.Type === 'Episode' && item.ParentIndexNumber === 0) { - showElement('#collapsibleSpecialEpisodeInfo', context); - } else { - hideElement('#collapsibleSpecialEpisodeInfo', context); - } - - if (item.Type === 'Person' + if (item.Type === 'Person' || item.Type === 'Genre' || item.Type === 'Studio' || item.Type === 'MusicGenre' || item.Type === 'TvChannel' || item.Type === 'Book') { - hideElement('#peopleCollapsible', context); - } else { - showElement('#peopleCollapsible', context); - } - - if (item.Type === 'Person' || item.Type === 'Genre' || item.Type === 'Studio' || item.Type === 'MusicGenre' || item.Type === 'TvChannel') { - hideElement('#fldCommunityRating', context); - hideElement('#genresCollapsible', context); - hideElement('#studiosCollapsible', context); - - if (item.Type === 'TvChannel') { - showElement('#fldOfficialRating', context); - } else { - hideElement('#fldOfficialRating', context); - } - hideElement('#fldCustomRating', context); - } else { - showElement('#fldCommunityRating', context); - showElement('#genresCollapsible', context); - showElement('#studiosCollapsible', context); - showElement('#fldOfficialRating', context); - showElement('#fldCustomRating', context); - } - - showElement('#tagsCollapsible', context); - - if (item.Type === 'TvChannel') { - hideElement('#metadataSettingsCollapsible', context); - hideElement('#fldPremiereDate', context); - hideElement('#fldDateAdded', context); - hideElement('#fldYear', context); - } else { - showElement('#metadataSettingsCollapsible', context); - showElement('#fldPremiereDate', context); - showElement('#fldDateAdded', context); - showElement('#fldYear', context); - } - - if (item.Type === 'TvChannel') { - hideElement('.overviewContainer', context); - } else { - showElement('.overviewContainer', context); - } - - if (item.Type === 'Person') { - context.querySelector('#txtName').label(globalize.translate('LabelName')); - context.querySelector('#txtSortName').label(globalize.translate('LabelSortName')); - context.querySelector('#txtOriginalName').label(globalize.translate('LabelOriginalName')); - context.querySelector('#txtProductionYear').label(globalize.translate('LabelBirthYear')); - context.querySelector('#txtPremiereDate').label(globalize.translate('LabelBirthDate')); - context.querySelector('#txtEndDate').label(globalize.translate('LabelDeathDate')); - showElement('#fldPlaceOfBirth'); - } else { - context.querySelector('#txtProductionYear').label(globalize.translate('LabelYear')); - context.querySelector('#txtPremiereDate').label(globalize.translate('LabelReleaseDate')); - context.querySelector('#txtEndDate').label(globalize.translate('LabelEndDate')); - hideElement('#fldPlaceOfBirth'); - } - - if (item.MediaType === 'Video' && item.Type !== 'TvChannel') { - showElement('#fldOriginalAspectRatio'); - } else { - hideElement('#fldOriginalAspectRatio'); - } - - if (item.Type === 'Audio' || item.Type === 'Episode' || item.Type === 'Season') { - showElement('#fldIndexNumber'); - - if (item.Type === 'Episode') { - context.querySelector('#txtIndexNumber').label(globalize.translate('LabelEpisodeNumber')); - } else if (item.Type === 'Season') { - context.querySelector('#txtIndexNumber').label(globalize.translate('LabelSeasonNumber')); - } else if (item.Type === 'Audio') { - context.querySelector('#txtIndexNumber').label(globalize.translate('LabelTrackNumber')); - } else { - context.querySelector('#txtIndexNumber').label(globalize.translate('LabelNumber')); - } - } else { - hideElement('#fldIndexNumber'); - } - - if (item.Type === 'Audio' || item.Type === 'Episode') { - showElement('#fldParentIndexNumber'); - - if (item.Type === 'Episode') { - context.querySelector('#txtParentIndexNumber').label(globalize.translate('LabelSeasonNumber')); - } else if (item.Type === 'Audio') { - context.querySelector('#txtParentIndexNumber').label(globalize.translate('LabelDiscNumber')); - } else { - context.querySelector('#txtParentIndexNumber').label(globalize.translate('LabelParentNumber')); - } - } else { - hideElement('#fldParentIndexNumber', context); - } - - if (item.Type === 'BoxSet') { - showElement('#fldDisplayOrder', context); - hideElement('.seriesDisplayOrderDescription', context); - - context.querySelector('#selectDisplayOrder').innerHTML = ''; - } else if (item.Type === 'Series') { - showElement('#fldDisplayOrder', context); - showElement('.seriesDisplayOrderDescription', context); - - let html = ''; - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - - context.querySelector('#selectDisplayOrder').innerHTML = html; - } else { - context.querySelector('#selectDisplayOrder').innerHTML = ''; - hideElement('#fldDisplayOrder', context); - } + hideElement('#peopleCollapsible', context); + } else { + showElement('#peopleCollapsible', context); } - function fillItemInfo(context, item, parentalRatingOptions) { - let select = context.querySelector('#selectOfficialRating'); + if (item.Type === 'Person' || item.Type === 'Genre' || item.Type === 'Studio' || item.Type === 'MusicGenre' || item.Type === 'TvChannel') { + hideElement('#fldCommunityRating', context); + hideElement('#genresCollapsible', context); + hideElement('#studiosCollapsible', context); - populateRatings(parentalRatingOptions, select, item.OfficialRating); - - select.value = item.OfficialRating || ''; - - select = context.querySelector('#selectCustomRating'); - - populateRatings(parentalRatingOptions, select, item.CustomRating); - - select.value = item.CustomRating || ''; - - const selectStatus = context.querySelector('#selectStatus'); - populateStatus(selectStatus); - selectStatus.value = item.Status || ''; - - context.querySelector('#select3dFormat', context).value = item.Video3DFormat || ''; - - Array.prototype.forEach.call(context.querySelectorAll('.chkAirDay', context), function (el) { - el.checked = (item.AirDays || []).indexOf(el.getAttribute('data-day')) !== -1; - }); - - populateListView(context.querySelector('#listGenres'), item.Genres); - populatePeople(context, item.People || []); - - populateListView(context.querySelector('#listStudios'), (item.Studios || []).map(function (element) { - return element.Name || ''; - })); - - populateListView(context.querySelector('#listTags'), item.Tags); - - const lockData = (item.LockData || false); - const chkLockData = context.querySelector('#chkLockData'); - chkLockData.checked = lockData; - if (chkLockData.checked) { - hideElement('.providerSettingsContainer', context); + if (item.Type === 'TvChannel') { + showElement('#fldOfficialRating', context); } else { - showElement('.providerSettingsContainer', context); + hideElement('#fldOfficialRating', context); } - fillMetadataSettings(context, item, item.LockedFields); + hideElement('#fldCustomRating', context); + } else { + showElement('#fldCommunityRating', context); + showElement('#genresCollapsible', context); + showElement('#studiosCollapsible', context); + showElement('#fldOfficialRating', context); + showElement('#fldCustomRating', context); + } - context.querySelector('#txtPath').value = item.Path || ''; - context.querySelector('#txtName').value = item.Name || ''; - context.querySelector('#txtOriginalName').value = item.OriginalTitle || ''; - context.querySelector('#txtOverview').value = item.Overview || ''; - context.querySelector('#txtTagline').value = (item.Taglines && item.Taglines.length ? item.Taglines[0] : ''); - context.querySelector('#txtSortName').value = item.ForcedSortName || ''; - context.querySelector('#txtCommunityRating').value = item.CommunityRating || ''; + showElement('#tagsCollapsible', context); - context.querySelector('#txtCriticRating').value = item.CriticRating || ''; + if (item.Type === 'TvChannel') { + hideElement('#metadataSettingsCollapsible', context); + hideElement('#fldPremiereDate', context); + hideElement('#fldDateAdded', context); + hideElement('#fldYear', context); + } else { + showElement('#metadataSettingsCollapsible', context); + showElement('#fldPremiereDate', context); + showElement('#fldDateAdded', context); + showElement('#fldYear', context); + } - context.querySelector('#txtIndexNumber').value = item.IndexNumber == null ? '' : item.IndexNumber; - context.querySelector('#txtParentIndexNumber').value = item.ParentIndexNumber == null ? '' : item.ParentIndexNumber; + if (item.Type === 'TvChannel') { + hideElement('.overviewContainer', context); + } else { + showElement('.overviewContainer', context); + } - context.querySelector('#txtAirsBeforeSeason').value = ('AirsBeforeSeasonNumber' in item) ? item.AirsBeforeSeasonNumber : ''; - context.querySelector('#txtAirsAfterSeason').value = ('AirsAfterSeasonNumber' in item) ? item.AirsAfterSeasonNumber : ''; - context.querySelector('#txtAirsBeforeEpisode').value = ('AirsBeforeEpisodeNumber' in item) ? item.AirsBeforeEpisodeNumber : ''; + if (item.Type === 'Person') { + context.querySelector('#txtName').label(globalize.translate('LabelName')); + context.querySelector('#txtSortName').label(globalize.translate('LabelSortName')); + context.querySelector('#txtOriginalName').label(globalize.translate('LabelOriginalName')); + context.querySelector('#txtProductionYear').label(globalize.translate('LabelBirthYear')); + context.querySelector('#txtPremiereDate').label(globalize.translate('LabelBirthDate')); + context.querySelector('#txtEndDate').label(globalize.translate('LabelDeathDate')); + showElement('#fldPlaceOfBirth'); + } else { + context.querySelector('#txtProductionYear').label(globalize.translate('LabelYear')); + context.querySelector('#txtPremiereDate').label(globalize.translate('LabelReleaseDate')); + context.querySelector('#txtEndDate').label(globalize.translate('LabelEndDate')); + hideElement('#fldPlaceOfBirth'); + } - context.querySelector('#txtAlbum').value = item.Album || ''; + if (item.MediaType === 'Video' && item.Type !== 'TvChannel') { + showElement('#fldOriginalAspectRatio'); + } else { + hideElement('#fldOriginalAspectRatio'); + } - context.querySelector('#txtAlbumArtist').value = (item.AlbumArtists || []).map(function (a) { - return a.Name; - }).join(';'); + if (item.Type === 'Audio' || item.Type === 'Episode' || item.Type === 'Season') { + showElement('#fldIndexNumber'); - context.querySelector('#selectDisplayOrder').value = item.DisplayOrder || ''; - - context.querySelector('#txtArtist').value = (item.ArtistItems || []).map(function (a) { - return a.Name; - }).join(';'); - - let date; - - if (item.DateCreated) { - try { - date = datetime.parseISO8601Date(item.DateCreated, true); - - context.querySelector('#txtDateAdded').value = date.toISOString().slice(0, 10); - } catch (e) { - context.querySelector('#txtDateAdded').value = ''; - } + if (item.Type === 'Episode') { + context.querySelector('#txtIndexNumber').label(globalize.translate('LabelEpisodeNumber')); + } else if (item.Type === 'Season') { + context.querySelector('#txtIndexNumber').label(globalize.translate('LabelSeasonNumber')); + } else if (item.Type === 'Audio') { + context.querySelector('#txtIndexNumber').label(globalize.translate('LabelTrackNumber')); } else { + context.querySelector('#txtIndexNumber').label(globalize.translate('LabelNumber')); + } + } else { + hideElement('#fldIndexNumber'); + } + + if (item.Type === 'Audio' || item.Type === 'Episode') { + showElement('#fldParentIndexNumber'); + + if (item.Type === 'Episode') { + context.querySelector('#txtParentIndexNumber').label(globalize.translate('LabelSeasonNumber')); + } else if (item.Type === 'Audio') { + context.querySelector('#txtParentIndexNumber').label(globalize.translate('LabelDiscNumber')); + } else { + context.querySelector('#txtParentIndexNumber').label(globalize.translate('LabelParentNumber')); + } + } else { + hideElement('#fldParentIndexNumber', context); + } + + if (item.Type === 'BoxSet') { + showElement('#fldDisplayOrder', context); + hideElement('.seriesDisplayOrderDescription', context); + + context.querySelector('#selectDisplayOrder').innerHTML = ''; + } else if (item.Type === 'Series') { + showElement('#fldDisplayOrder', context); + showElement('.seriesDisplayOrderDescription', context); + + let html = ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + + context.querySelector('#selectDisplayOrder').innerHTML = html; + } else { + context.querySelector('#selectDisplayOrder').innerHTML = ''; + hideElement('#fldDisplayOrder', context); + } +} + +function fillItemInfo(context, item, parentalRatingOptions) { + let select = context.querySelector('#selectOfficialRating'); + + populateRatings(parentalRatingOptions, select, item.OfficialRating); + + select.value = item.OfficialRating || ''; + + select = context.querySelector('#selectCustomRating'); + + populateRatings(parentalRatingOptions, select, item.CustomRating); + + select.value = item.CustomRating || ''; + + const selectStatus = context.querySelector('#selectStatus'); + populateStatus(selectStatus); + selectStatus.value = item.Status || ''; + + context.querySelector('#select3dFormat', context).value = item.Video3DFormat || ''; + + Array.prototype.forEach.call(context.querySelectorAll('.chkAirDay', context), function (el) { + el.checked = (item.AirDays || []).indexOf(el.getAttribute('data-day')) !== -1; + }); + + populateListView(context.querySelector('#listGenres'), item.Genres); + populatePeople(context, item.People || []); + + populateListView(context.querySelector('#listStudios'), (item.Studios || []).map(function (element) { + return element.Name || ''; + })); + + populateListView(context.querySelector('#listTags'), item.Tags); + + const lockData = (item.LockData || false); + const chkLockData = context.querySelector('#chkLockData'); + chkLockData.checked = lockData; + if (chkLockData.checked) { + hideElement('.providerSettingsContainer', context); + } else { + showElement('.providerSettingsContainer', context); + } + fillMetadataSettings(context, item, item.LockedFields); + + context.querySelector('#txtPath').value = item.Path || ''; + context.querySelector('#txtName').value = item.Name || ''; + context.querySelector('#txtOriginalName').value = item.OriginalTitle || ''; + context.querySelector('#txtOverview').value = item.Overview || ''; + context.querySelector('#txtTagline').value = (item.Taglines && item.Taglines.length ? item.Taglines[0] : ''); + context.querySelector('#txtSortName').value = item.ForcedSortName || ''; + context.querySelector('#txtCommunityRating').value = item.CommunityRating || ''; + + context.querySelector('#txtCriticRating').value = item.CriticRating || ''; + + context.querySelector('#txtIndexNumber').value = item.IndexNumber == null ? '' : item.IndexNumber; + context.querySelector('#txtParentIndexNumber').value = item.ParentIndexNumber == null ? '' : item.ParentIndexNumber; + + context.querySelector('#txtAirsBeforeSeason').value = ('AirsBeforeSeasonNumber' in item) ? item.AirsBeforeSeasonNumber : ''; + context.querySelector('#txtAirsAfterSeason').value = ('AirsAfterSeasonNumber' in item) ? item.AirsAfterSeasonNumber : ''; + context.querySelector('#txtAirsBeforeEpisode').value = ('AirsBeforeEpisodeNumber' in item) ? item.AirsBeforeEpisodeNumber : ''; + + context.querySelector('#txtAlbum').value = item.Album || ''; + + context.querySelector('#txtAlbumArtist').value = (item.AlbumArtists || []).map(function (a) { + return a.Name; + }).join(';'); + + context.querySelector('#selectDisplayOrder').value = item.DisplayOrder || ''; + + context.querySelector('#txtArtist').value = (item.ArtistItems || []).map(function (a) { + return a.Name; + }).join(';'); + + let date; + + if (item.DateCreated) { + try { + date = datetime.parseISO8601Date(item.DateCreated, true); + + context.querySelector('#txtDateAdded').value = date.toISOString().slice(0, 10); + } catch (e) { context.querySelector('#txtDateAdded').value = ''; } + } else { + context.querySelector('#txtDateAdded').value = ''; + } - if (item.PremiereDate) { - try { - date = datetime.parseISO8601Date(item.PremiereDate, true); + if (item.PremiereDate) { + try { + date = datetime.parseISO8601Date(item.PremiereDate, true); - context.querySelector('#txtPremiereDate').value = date.toISOString().slice(0, 10); - } catch (e) { - context.querySelector('#txtPremiereDate').value = ''; - } - } else { + context.querySelector('#txtPremiereDate').value = date.toISOString().slice(0, 10); + } catch (e) { context.querySelector('#txtPremiereDate').value = ''; } + } else { + context.querySelector('#txtPremiereDate').value = ''; + } - if (item.EndDate) { - try { - date = datetime.parseISO8601Date(item.EndDate, true); + if (item.EndDate) { + try { + date = datetime.parseISO8601Date(item.EndDate, true); - context.querySelector('#txtEndDate').value = date.toISOString().slice(0, 10); - } catch (e) { - context.querySelector('#txtEndDate').value = ''; - } - } else { + context.querySelector('#txtEndDate').value = date.toISOString().slice(0, 10); + } catch (e) { context.querySelector('#txtEndDate').value = ''; } + } else { + context.querySelector('#txtEndDate').value = ''; + } - context.querySelector('#txtProductionYear').value = item.ProductionYear || ''; + context.querySelector('#txtProductionYear').value = item.ProductionYear || ''; - context.querySelector('#txtAirTime').value = item.AirTime || ''; + context.querySelector('#txtAirTime').value = item.AirTime || ''; - const placeofBirth = item.ProductionLocations && item.ProductionLocations.length ? item.ProductionLocations[0] : ''; - context.querySelector('#txtPlaceOfBirth').value = placeofBirth; + const placeofBirth = item.ProductionLocations && item.ProductionLocations.length ? item.ProductionLocations[0] : ''; + context.querySelector('#txtPlaceOfBirth').value = placeofBirth; - context.querySelector('#txtOriginalAspectRatio').value = item.AspectRatio || ''; + context.querySelector('#txtOriginalAspectRatio').value = item.AspectRatio || ''; - context.querySelector('#selectLanguage').value = item.PreferredMetadataLanguage || ''; - context.querySelector('#selectCountry').value = item.PreferredMetadataCountryCode || ''; + context.querySelector('#selectLanguage').value = item.PreferredMetadataLanguage || ''; + context.querySelector('#selectCountry').value = item.PreferredMetadataCountryCode || ''; - if (item.RunTimeTicks) { - const minutes = item.RunTimeTicks / 600000000; + if (item.RunTimeTicks) { + const minutes = item.RunTimeTicks / 600000000; - context.querySelector('#txtSeriesRuntime').value = Math.round(minutes); - } else { - context.querySelector('#txtSeriesRuntime', context).value = ''; + context.querySelector('#txtSeriesRuntime').value = Math.round(minutes); + } else { + context.querySelector('#txtSeriesRuntime', context).value = ''; + } +} + +function populateRatings(allParentalRatings, select, currentValue) { + let html = ''; + + html += ""; + + const ratings = []; + let rating; + + let currentValueFound = false; + + for (let i = 0, length = allParentalRatings.length; i < length; i++) { + rating = allParentalRatings[i]; + + ratings.push({ Name: rating.Name, Value: rating.Name }); + + if (rating.Name === currentValue) { + currentValueFound = true; } } - function populateRatings(allParentalRatings, select, currentValue) { - let html = ''; - - html += ""; - - const ratings = []; - let rating; - - let currentValueFound = false; - - for (let i = 0, length = allParentalRatings.length; i < length; i++) { - rating = allParentalRatings[i]; - - ratings.push({ Name: rating.Name, Value: rating.Name }); - - if (rating.Name === currentValue) { - currentValueFound = true; - } - } - - if (currentValue && !currentValueFound) { - ratings.push({ Name: currentValue, Value: currentValue }); - } - - for (let i = 0, length = ratings.length; i < length; i++) { - rating = ratings[i]; - - html += "'; - } - - select.innerHTML = html; + if (currentValue && !currentValueFound) { + ratings.push({ Name: currentValue, Value: currentValue }); } - function populateStatus(select) { - let html = ''; + for (let i = 0, length = ratings.length; i < length; i++) { + rating = ratings[i]; - html += ""; - html += "'; - html += "'; - select.innerHTML = html; + html += "'; } - function populateListView(list, items, sortCallback) { - items = items || []; - if (typeof (sortCallback) === 'undefined') { - items.sort(function (a, b) { - return a.toLowerCase().localeCompare(b.toLowerCase()); - }); - } else { - items = sortCallback(items); - } - let html = ''; - for (let i = 0; i < items.length; i++) { - html += '
'; + select.innerHTML = html; +} - html += ''; +function populateStatus(select) { + let html = ''; - html += '
'; + html += ""; + html += "'; + html += "'; + select.innerHTML = html; +} - html += '
'; - html += escapeHtml(items[i]); - html += '
'; - - html += '
'; - - html += ''; - - html += '
'; - } - - list.innerHTML = html; - } - - function populatePeople(context, people) { - const lastType = ''; - let html = ''; - - const elem = context.querySelector('#peopleList'); - - for (let i = 0, length = people.length; i < length; i++) { - const person = people[i]; - - html += '
'; - - html += ''; - - html += '
'; - html += ''; - html += '
'; - - html += ''; - - html += '
'; - } - - elem.innerHTML = html; - } - - function getLockedFieldsHtml(fields, currentFields) { - let html = ''; - for (let i = 0; i < fields.length; i++) { - const field = fields[i]; - const name = field.name; - const value = field.value || field.name; - const checkedHtml = currentFields.indexOf(value) === -1 ? ' checked' : ''; - html += ''; - } - return html; - } - - function fillMetadataSettings(context, item, lockedFields) { - const container = context.querySelector('.providerSettingsContainer'); - lockedFields = lockedFields || []; - - const lockedFieldsList = [ - { name: globalize.translate('Name'), value: 'Name' }, - { name: globalize.translate('Overview'), value: 'Overview' }, - { name: globalize.translate('Genres'), value: 'Genres' }, - { name: globalize.translate('ParentalRating'), value: 'OfficialRating' }, - { name: globalize.translate('People'), value: 'Cast' } - ]; - - if (item.Type === 'Person') { - lockedFieldsList.push({ name: globalize.translate('BirthLocation'), value: 'ProductionLocations' }); - } else { - lockedFieldsList.push({ name: globalize.translate('ProductionLocations'), value: 'ProductionLocations' }); - } - - if (item.Type === 'Series') { - lockedFieldsList.push({ name: globalize.translate('Runtime'), value: 'Runtime' }); - } - - lockedFieldsList.push({ name: globalize.translate('Studios'), value: 'Studios' }); - lockedFieldsList.push({ name: globalize.translate('Tags'), value: 'Tags' }); - - let html = ''; - - html += '

' + globalize.translate('HeaderEnabledFields') + '

'; - html += '

' + globalize.translate('HeaderEnabledFieldsHelp') + '

'; - html += getLockedFieldsHtml(lockedFieldsList, lockedFields); - container.innerHTML = html; - } - - function reload(context, itemId, serverId) { - loading.show(); - - Promise.all([getItem(itemId, serverId), getEditorConfig(itemId, serverId)]).then(function (responses) { - const item = responses[0]; - metadataEditorInfo = responses[1]; - - currentItem = item; - - const languages = metadataEditorInfo.Cultures; - const countries = metadataEditorInfo.Countries; - - renderContentTypeOptions(context, metadataEditorInfo); - - loadExternalIds(context, item, metadataEditorInfo.ExternalIdInfos); - - populateLanguages(context.querySelector('#selectLanguage'), languages); - populateCountries(context.querySelector('#selectCountry'), countries); - - setFieldVisibilities(context, item); - fillItemInfo(context, item, metadataEditorInfo.ParentalRatingOptions); - - if (item.MediaType === 'Video' && item.Type !== 'Episode' && item.Type !== 'TvChannel') { - showElement('#fldTagline', context); - } else { - hideElement('#fldTagline', context); - } - - loading.hide(); +function populateListView(list, items, sortCallback) { + items = items || []; + if (typeof (sortCallback) === 'undefined') { + items.sort(function (a, b) { + return a.toLowerCase().localeCompare(b.toLowerCase()); }); + } else { + items = sortCallback(items); + } + let html = ''; + for (let i = 0; i < items.length; i++) { + html += '
'; + + html += ''; + + html += '
'; + + html += '
'; + html += escapeHtml(items[i]); + html += '
'; + + html += '
'; + + html += ''; + + html += '
'; } - function centerFocus(elem, horiz, on) { - import('../../scripts/scrollHelper').then((scrollHelper) => { - const fn = on ? 'on' : 'off'; - scrollHelper.centerFocus[fn](elem, horiz); - }); - } + list.innerHTML = html; +} - function show(itemId, serverId, resolve) { - loading.show(); +function populatePeople(context, people) { + const lastType = ''; + let html = ''; - const dialogOptions = { - removeOnClose: true, - scrollY: false - }; + const elem = context.querySelector('#peopleList'); - if (layoutManager.tv) { - dialogOptions.size = 'fullscreen'; + for (let i = 0, length = people.length; i < length; i++) { + const person = people[i]; + + html += '
'; + + html += ''; + + html += '
'; + html += ''; + html += '
'; - dlg.classList.add('formDialog'); + html += ''; - let html = ''; - - html += globalize.translateHtml(template, 'core'); - - dlg.innerHTML = html; - - if (layoutManager.tv) { - centerFocus(dlg.querySelector('.formDialogContent'), false, true); - } - - dialogHelper.open(dlg); - - dlg.addEventListener('close', function () { - if (layoutManager.tv) { - centerFocus(dlg.querySelector('.formDialogContent'), false, false); - } - - resolve(); - }); - - currentContext = dlg; - - init(dlg); - - reload(dlg, itemId, serverId); + html += '
'; } - export default { - show: function (itemId, serverId) { - return new Promise(resolve => show(itemId, serverId, resolve)); - }, + elem.innerHTML = html; +} - embed: function (elem, itemId, serverId) { - return new Promise(function () { - loading.show(); +function getLockedFieldsHtml(fields, currentFields) { + let html = ''; + for (let i = 0; i < fields.length; i++) { + const field = fields[i]; + const name = field.name; + const value = field.value || field.name; + const checkedHtml = currentFields.indexOf(value) === -1 ? ' checked' : ''; + html += ''; + } + return html; +} - elem.innerHTML = globalize.translateHtml(template, 'core'); +function fillMetadataSettings(context, item, lockedFields) { + const container = context.querySelector('.providerSettingsContainer'); + lockedFields = lockedFields || []; - elem.querySelector('.formDialogFooter').classList.remove('formDialogFooter'); - elem.querySelector('.btnClose').classList.add('hide'); - elem.querySelector('.btnHeaderSave').classList.remove('hide'); - elem.querySelector('.btnCancel').classList.add('hide'); + const lockedFieldsList = [ + { name: globalize.translate('Name'), value: 'Name' }, + { name: globalize.translate('Overview'), value: 'Overview' }, + { name: globalize.translate('Genres'), value: 'Genres' }, + { name: globalize.translate('ParentalRating'), value: 'OfficialRating' }, + { name: globalize.translate('People'), value: 'Cast' } + ]; - currentContext = elem; + if (item.Type === 'Person') { + lockedFieldsList.push({ name: globalize.translate('BirthLocation'), value: 'ProductionLocations' }); + } else { + lockedFieldsList.push({ name: globalize.translate('ProductionLocations'), value: 'ProductionLocations' }); + } - init(elem); - reload(elem, itemId, serverId); + if (item.Type === 'Series') { + lockedFieldsList.push({ name: globalize.translate('Runtime'), value: 'Runtime' }); + } - focusManager.autoFocus(elem); - }); + lockedFieldsList.push({ name: globalize.translate('Studios'), value: 'Studios' }); + lockedFieldsList.push({ name: globalize.translate('Tags'), value: 'Tags' }); + + let html = ''; + + html += '

' + globalize.translate('HeaderEnabledFields') + '

'; + html += '

' + globalize.translate('HeaderEnabledFieldsHelp') + '

'; + html += getLockedFieldsHtml(lockedFieldsList, lockedFields); + container.innerHTML = html; +} + +function reload(context, itemId, serverId) { + loading.show(); + + Promise.all([getItem(itemId, serverId), getEditorConfig(itemId, serverId)]).then(function (responses) { + const item = responses[0]; + metadataEditorInfo = responses[1]; + + currentItem = item; + + const languages = metadataEditorInfo.Cultures; + const countries = metadataEditorInfo.Countries; + + renderContentTypeOptions(context, metadataEditorInfo); + + loadExternalIds(context, item, metadataEditorInfo.ExternalIdInfos); + + populateLanguages(context.querySelector('#selectLanguage'), languages); + populateCountries(context.querySelector('#selectCountry'), countries); + + setFieldVisibilities(context, item); + fillItemInfo(context, item, metadataEditorInfo.ParentalRatingOptions); + + if (item.MediaType === 'Video' && item.Type !== 'Episode' && item.Type !== 'TvChannel') { + showElement('#fldTagline', context); + } else { + hideElement('#fldTagline', context); } + + loading.hide(); + }); +} + +function centerFocus(elem, horiz, on) { + import('../../scripts/scrollHelper').then((scrollHelper) => { + const fn = on ? 'on' : 'off'; + scrollHelper.centerFocus[fn](elem, horiz); + }); +} + +function show(itemId, serverId, resolve) { + loading.show(); + + const dialogOptions = { + removeOnClose: true, + scrollY: false }; -/* eslint-enable indent */ + if (layoutManager.tv) { + dialogOptions.size = 'fullscreen'; + } else { + dialogOptions.size = 'small'; + } + + const dlg = dialogHelper.createDialog(dialogOptions); + + dlg.classList.add('formDialog'); + + let html = ''; + + html += globalize.translateHtml(template, 'core'); + + dlg.innerHTML = html; + + if (layoutManager.tv) { + centerFocus(dlg.querySelector('.formDialogContent'), false, true); + } + + dialogHelper.open(dlg); + + dlg.addEventListener('close', function () { + if (layoutManager.tv) { + centerFocus(dlg.querySelector('.formDialogContent'), false, false); + } + + resolve(); + }); + + currentContext = dlg; + + init(dlg); + + reload(dlg, itemId, serverId); +} + +export default { + show: function (itemId, serverId) { + return new Promise(resolve => show(itemId, serverId, resolve)); + }, + + embed: function (elem, itemId, serverId) { + return new Promise(function () { + loading.show(); + + elem.innerHTML = globalize.translateHtml(template, 'core'); + + elem.querySelector('.formDialogFooter').classList.remove('formDialogFooter'); + elem.querySelector('.btnClose').classList.add('hide'); + elem.querySelector('.btnHeaderSave').classList.remove('hide'); + elem.querySelector('.btnCancel').classList.add('hide'); + + currentContext = elem; + + init(elem); + reload(elem, itemId, serverId); + + focusManager.autoFocus(elem); + }); + } +}; + diff --git a/src/components/metadataEditor/personEditor.js b/src/components/metadataEditor/personEditor.js index 51f784db4f..4236137197 100644 --- a/src/components/metadataEditor/personEditor.js +++ b/src/components/metadataEditor/personEditor.js @@ -8,94 +8,91 @@ import '../../elements/emby-select/emby-select'; import '../formdialog.scss'; import template from './personEditor.template.html'; -/* eslint-disable indent */ +function centerFocus(elem, horiz, on) { + import('../../scripts/scrollHelper').then((scrollHelper) => { + const fn = on ? 'on' : 'off'; + scrollHelper.centerFocus[fn](elem, horiz); + }); +} - function centerFocus(elem, horiz, on) { - import('../../scripts/scrollHelper').then((scrollHelper) => { - const fn = on ? 'on' : 'off'; - scrollHelper.centerFocus[fn](elem, horiz); - }); - } +function show(person) { + return new Promise(function (resolve, reject) { + const dialogOptions = { + removeOnClose: true, + scrollY: false + }; - function show(person) { - return new Promise(function (resolve, reject) { - const dialogOptions = { - removeOnClose: true, - scrollY: false - }; + if (layoutManager.tv) { + dialogOptions.size = 'fullscreen'; + } else { + dialogOptions.size = 'small'; + } + const dlg = dialogHelper.createDialog(dialogOptions); + + dlg.classList.add('formDialog'); + + let html = ''; + let submitted = false; + + html += globalize.translateHtml(template, 'core'); + + dlg.innerHTML = html; + + dlg.querySelector('.txtPersonName', dlg).value = person.Name || ''; + dlg.querySelector('.selectPersonType', dlg).value = person.Type || ''; + dlg.querySelector('.txtPersonRole', dlg).value = person.Role || ''; + + if (layoutManager.tv) { + centerFocus(dlg.querySelector('.formDialogContent'), false, true); + } + + dialogHelper.open(dlg); + + dlg.addEventListener('close', function () { if (layoutManager.tv) { - dialogOptions.size = 'fullscreen'; + centerFocus(dlg.querySelector('.formDialogContent'), false, false); + } + + if (submitted) { + resolve(person); } else { - dialogOptions.size = 'small'; + reject(); } - - const dlg = dialogHelper.createDialog(dialogOptions); - - dlg.classList.add('formDialog'); - - let html = ''; - let submitted = false; - - html += globalize.translateHtml(template, 'core'); - - dlg.innerHTML = html; - - dlg.querySelector('.txtPersonName', dlg).value = person.Name || ''; - dlg.querySelector('.selectPersonType', dlg).value = person.Type || ''; - dlg.querySelector('.txtPersonRole', dlg).value = person.Role || ''; - - if (layoutManager.tv) { - centerFocus(dlg.querySelector('.formDialogContent'), false, true); - } - - dialogHelper.open(dlg); - - dlg.addEventListener('close', function () { - if (layoutManager.tv) { - centerFocus(dlg.querySelector('.formDialogContent'), false, false); - } - - if (submitted) { - resolve(person); - } else { - reject(); - } - }); - - dlg.querySelector('.selectPersonType').addEventListener('change', function () { - if (this.value === 'Actor') { - dlg.querySelector('.fldRole').classList.remove('hide'); - } else { - dlg.querySelector('.fldRole').classList.add('hide'); - } - }); - - dlg.querySelector('.btnCancel').addEventListener('click', function () { - dialogHelper.close(dlg); - }); - - dlg.querySelector('form').addEventListener('submit', function (e) { - submitted = true; - - person.Name = dlg.querySelector('.txtPersonName', dlg).value; - person.Type = dlg.querySelector('.selectPersonType', dlg).value; - person.Role = dlg.querySelector('.txtPersonRole', dlg).value || null; - - dialogHelper.close(dlg); - - e.preventDefault(); - return false; - }); - - dlg.querySelector('.selectPersonType').dispatchEvent(new CustomEvent('change', { - bubbles: true - })); }); - } + + dlg.querySelector('.selectPersonType').addEventListener('change', function () { + if (this.value === 'Actor') { + dlg.querySelector('.fldRole').classList.remove('hide'); + } else { + dlg.querySelector('.fldRole').classList.add('hide'); + } + }); + + dlg.querySelector('.btnCancel').addEventListener('click', function () { + dialogHelper.close(dlg); + }); + + dlg.querySelector('form').addEventListener('submit', function (e) { + submitted = true; + + person.Name = dlg.querySelector('.txtPersonName', dlg).value; + person.Type = dlg.querySelector('.selectPersonType', dlg).value; + person.Role = dlg.querySelector('.txtPersonRole', dlg).value || null; + + dialogHelper.close(dlg); + + e.preventDefault(); + return false; + }); + + dlg.querySelector('.selectPersonType').dispatchEvent(new CustomEvent('change', { + bubbles: true + })); + }); +} export default { show: show }; -/* eslint-enable indent */ diff --git a/src/components/multiSelect/multiSelect.js b/src/components/multiSelect/multiSelect.js index c361c182fb..142a31a5d7 100644 --- a/src/components/multiSelect/multiSelect.js +++ b/src/components/multiSelect/multiSelect.js @@ -11,42 +11,373 @@ import confirm from '../confirm/confirm'; import itemHelper from '../itemHelper'; import datetime from '../../scripts/datetime'; -/* eslint-disable indent */ +let selectedItems = []; +let selectedElements = []; +let currentSelectionCommandsPanel; - let selectedItems = []; - let selectedElements = []; - let currentSelectionCommandsPanel; +function hideSelections() { + const selectionCommandsPanel = currentSelectionCommandsPanel; + if (selectionCommandsPanel) { + selectionCommandsPanel.parentNode.removeChild(selectionCommandsPanel); + currentSelectionCommandsPanel = null; - function hideSelections() { - const selectionCommandsPanel = currentSelectionCommandsPanel; - if (selectionCommandsPanel) { - selectionCommandsPanel.parentNode.removeChild(selectionCommandsPanel); - currentSelectionCommandsPanel = null; + selectedItems = []; + selectedElements = []; + const elems = document.querySelectorAll('.itemSelectionPanel'); + for (let i = 0, length = elems.length; i < length; i++) { + const parent = elems[i].parentNode; + parent.removeChild(elems[i]); + parent.classList.remove('withMultiSelect'); + } + } +} - selectedItems = []; - selectedElements = []; - const elems = document.querySelectorAll('.itemSelectionPanel'); - for (let i = 0, length = elems.length; i < length; i++) { - const parent = elems[i].parentNode; - parent.removeChild(elems[i]); - parent.classList.remove('withMultiSelect'); +function onItemSelectionPanelClick(e, itemSelectionPanel) { + // toggle the checkbox, if it wasn't clicked on + if (!dom.parentWithClass(e.target, 'chkItemSelect')) { + const chkItemSelect = itemSelectionPanel.querySelector('.chkItemSelect'); + + if (chkItemSelect) { + if (chkItemSelect.classList.contains('checkedInitial')) { + chkItemSelect.classList.remove('checkedInitial'); + } else { + const newValue = !chkItemSelect.checked; + chkItemSelect.checked = newValue; + updateItemSelection(chkItemSelect, newValue); } } } - function onItemSelectionPanelClick(e, itemSelectionPanel) { - // toggle the checkbox, if it wasn't clicked on - if (!dom.parentWithClass(e.target, 'chkItemSelect')) { - const chkItemSelect = itemSelectionPanel.querySelector('.chkItemSelect'); + e.preventDefault(); + e.stopPropagation(); + return false; +} - if (chkItemSelect) { - if (chkItemSelect.classList.contains('checkedInitial')) { - chkItemSelect.classList.remove('checkedInitial'); - } else { - const newValue = !chkItemSelect.checked; - chkItemSelect.checked = newValue; - updateItemSelection(chkItemSelect, newValue); - } +function updateItemSelection(chkItemSelect, selected) { + const id = dom.parentWithAttribute(chkItemSelect, 'data-id').getAttribute('data-id'); + + if (selected) { + const current = selectedItems.filter(i => { + return i === id; + }); + + if (!current.length) { + selectedItems.push(id); + selectedElements.push(chkItemSelect); + } + } else { + selectedItems = selectedItems.filter(i => { + return i !== id; + }); + selectedElements = selectedElements.filter(i => { + return i !== chkItemSelect; + }); + } + + if (selectedItems.length) { + const itemSelectionCount = document.querySelector('.itemSelectionCount'); + if (itemSelectionCount) { + itemSelectionCount.innerHTML = datetime.toLocaleString(selectedItems.length); + } + } else { + hideSelections(); + } +} + +function onSelectionChange() { + updateItemSelection(this, this.checked); +} + +function showSelection(item, isChecked) { + let itemSelectionPanel = item.querySelector('.itemSelectionPanel'); + + if (!itemSelectionPanel) { + itemSelectionPanel = document.createElement('div'); + itemSelectionPanel.classList.add('itemSelectionPanel'); + + const parent = item.querySelector('.cardBox') || item.querySelector('.cardContent'); + parent.classList.add('withMultiSelect'); + parent.appendChild(itemSelectionPanel); + + let cssClass = 'chkItemSelect'; + if (isChecked && !browser.firefox) { + // In firefox, the initial tap hold doesnt' get treated as a click + // In other browsers it does, so we need to make sure that initial click is ignored + cssClass += ' checkedInitial'; + } + const checkedAttribute = isChecked ? ' checked' : ''; + itemSelectionPanel.innerHTML = ``; + const chkItemSelect = itemSelectionPanel.querySelector('.chkItemSelect'); + chkItemSelect.addEventListener('change', onSelectionChange); + } +} + +function showSelectionCommands() { + let selectionCommandsPanel = currentSelectionCommandsPanel; + + if (!selectionCommandsPanel) { + selectionCommandsPanel = document.createElement('div'); + selectionCommandsPanel.classList.add('selectionCommandsPanel'); + + document.body.appendChild(selectionCommandsPanel); + currentSelectionCommandsPanel = selectionCommandsPanel; + + let html = ''; + + html += ''; + html += '

'; + + const moreIcon = 'more_vert'; + html += ``; + + selectionCommandsPanel.innerHTML = html; + + selectionCommandsPanel.querySelector('.btnCloseSelectionPanel').addEventListener('click', hideSelections); + + const btnSelectionPanelOptions = selectionCommandsPanel.querySelector('.btnSelectionPanelOptions'); + + dom.addEventListener(btnSelectionPanelOptions, 'click', showMenuForSelectedItems, { passive: true }); + } +} + +function alertText(options) { + return new Promise((resolve) => { + alert(options).then(resolve, resolve); + }); +} + +function deleteItems(apiClient, itemIds) { + return new Promise((resolve, reject) => { + let msg = globalize.translate('ConfirmDeleteItem'); + let title = globalize.translate('HeaderDeleteItem'); + + if (itemIds.length > 1) { + msg = globalize.translate('ConfirmDeleteItems'); + title = globalize.translate('HeaderDeleteItems'); + } + + confirm(msg, title).then(() => { + const promises = itemIds.map(itemId => apiClient.deleteItem(itemId)); + + Promise.all(promises).then(resolve, () => { + alertText(globalize.translate('ErrorDeletingItem')).then(reject, reject); + }); + }, reject); + }); +} + +function showMenuForSelectedItems(e) { + const apiClient = ServerConnections.currentApiClient(); + + apiClient.getCurrentUser().then(user => { + // get first selected item to perform metadata refresh permission check + apiClient.getItem(apiClient.getCurrentUserId(), selectedItems[0]).then(firstItem => { + const menuItems = []; + + menuItems.push({ + name: globalize.translate('SelectAll'), + id: 'selectall', + icon: 'select_all' + }); + + menuItems.push({ + name: globalize.translate('AddToCollection'), + id: 'addtocollection', + icon: 'add' + }); + + menuItems.push({ + name: globalize.translate('AddToPlaylist'), + id: 'playlist', + icon: 'playlist_add' + }); + + // TODO: Be more dynamic based on what is selected + if (user.Policy.EnableContentDeletion) { + menuItems.push({ + name: globalize.translate('Delete'), + id: 'delete', + icon: 'delete' + }); + } + + if (user.Policy.EnableContentDownloading && appHost.supports('filedownload')) { + // Disabled because there is no callback for this item + } + + if (user.Policy.IsAdministrator) { + menuItems.push({ + name: globalize.translate('GroupVersions'), + id: 'groupvideos', + icon: 'call_merge' + }); + } + + menuItems.push({ + name: globalize.translate('MarkPlayed'), + id: 'markplayed', + icon: 'check_box' + }); + + menuItems.push({ + name: globalize.translate('MarkUnplayed'), + id: 'markunplayed', + icon: 'check_box_outline_blank' + }); + + // this assues that if the user can refresh metadata for the first item + // they can refresh metadata for all items + if (itemHelper.canRefreshMetadata(firstItem, user)) { + menuItems.push({ + name: globalize.translate('RefreshMetadata'), + id: 'refresh', + icon: 'refresh' + }); + } + + import('../actionSheet/actionSheet').then((actionsheet) => { + actionsheet.show({ + items: menuItems, + positionTo: e.target, + callback: function (id) { + const items = selectedItems.slice(0); + const serverId = apiClient.serverInfo().Id; + + switch (id) { + case 'selectall': + { + const elems = document.querySelectorAll('.itemSelectionPanel'); + for (let i = 0, length = elems.length; i < length; i++) { + const chkItemSelect = elems[i].querySelector('.chkItemSelect'); + + if (chkItemSelect && !chkItemSelect.classList.contains('checkedInitial') && !chkItemSelect.checked && chkItemSelect.getBoundingClientRect().width != 0) { + chkItemSelect.checked = true; + updateItemSelection(chkItemSelect, true); + } + } + } + break; + case 'addtocollection': + import('../collectionEditor/collectionEditor').then(({ default: CollectionEditor }) => { + const collectionEditor = new CollectionEditor(); + collectionEditor.show({ + items: items, + serverId: serverId + }); + }); + hideSelections(); + dispatchNeedsRefresh(); + break; + case 'playlist': + new playlistEditor({ + items: items, + serverId: serverId + }); + hideSelections(); + dispatchNeedsRefresh(); + break; + case 'delete': + deleteItems(apiClient, items).then(dispatchNeedsRefresh); + hideSelections(); + dispatchNeedsRefresh(); + break; + case 'groupvideos': + combineVersions(apiClient, items); + break; + case 'markplayed': + items.forEach(itemId => { + apiClient.markPlayed(apiClient.getCurrentUserId(), itemId); + }); + hideSelections(); + dispatchNeedsRefresh(); + break; + case 'markunplayed': + items.forEach(itemId => { + apiClient.markUnplayed(apiClient.getCurrentUserId(), itemId); + }); + hideSelections(); + dispatchNeedsRefresh(); + break; + case 'refresh': + import('../refreshdialog/refreshdialog').then(({ default: refreshDialog }) => { + new refreshDialog({ + itemIds: items, + serverId: serverId + }).show(); + }); + hideSelections(); + dispatchNeedsRefresh(); + break; + default: + break; + } + } + }); + }); + }); + }); +} + +function dispatchNeedsRefresh() { + const elems = []; + + [].forEach.call(selectedElements, i => { + const container = dom.parentWithAttribute(i, 'is', 'emby-itemscontainer'); + + if (container && !elems.includes(container)) { + elems.push(container); + } + }); + + for (let i = 0, length = elems.length; i < length; i++) { + elems[i].notifyRefreshNeeded(true); + } +} + +function combineVersions(apiClient, selection) { + if (selection.length < 2) { + alert({ + text: globalize.translate('PleaseSelectTwoItems') + }); + + return; + } + + loading.show(); + + apiClient.ajax({ + + type: 'POST', + url: apiClient.getUrl('Videos/MergeVersions', { Ids: selection.join(',') }) + + }).then(() => { + loading.hide(); + hideSelections(); + dispatchNeedsRefresh(); + }); +} + +function showSelections(initialCard) { + import('../../elements/emby-checkbox/emby-checkbox').then(() => { + const cards = document.querySelectorAll('.card'); + for (let i = 0, length = cards.length; i < length; i++) { + showSelection(cards[i], initialCard === cards[i]); + } + + showSelectionCommands(); + updateItemSelection(initialCard, true); + }); +} + +function onContainerClick(e) { + const target = e.target; + + if (selectedItems.length) { + const card = dom.parentWithClass(target, 'card'); + if (card) { + const itemSelectionPanel = card.querySelector('.itemSelectionPanel'); + if (itemSelectionPanel) { + return onItemSelectionPanelClick(e, itemSelectionPanel); } } @@ -54,516 +385,182 @@ import datetime from '../../scripts/datetime'; e.stopPropagation(); return false; } +} - function updateItemSelection(chkItemSelect, selected) { - const id = dom.parentWithAttribute(chkItemSelect, 'data-id').getAttribute('data-id'); +document.addEventListener('viewbeforehide', hideSelections); - if (selected) { - const current = selectedItems.filter(i => { - return i === id; - }); +export default function (options) { + const self = this; - if (!current.length) { - selectedItems.push(id); - selectedElements.push(chkItemSelect); - } - } else { - selectedItems = selectedItems.filter(i => { - return i !== id; - }); - selectedElements = selectedElements.filter(i => { - return i !== chkItemSelect; - }); + const container = options.container; + + function onTapHold(e) { + const card = dom.parentWithClass(e.target, 'card'); + + if (card) { + showSelections(card); } - if (selectedItems.length) { - const itemSelectionCount = document.querySelector('.itemSelectionCount'); - if (itemSelectionCount) { - itemSelectionCount.innerHTML = datetime.toLocaleString(selectedItems.length); - } - } else { - hideSelections(); + e.preventDefault(); + // It won't have this if it's a hammer event + if (e.stopPropagation) { + e.stopPropagation(); } + return false; } - function onSelectionChange() { - updateItemSelection(this, this.checked); + function getTouches(e) { + return e.changedTouches || e.targetTouches || e.touches; } - function showSelection(item, isChecked) { - let itemSelectionPanel = item.querySelector('.itemSelectionPanel'); + let touchTarget; + let touchStartTimeout; + let touchStartX; + let touchStartY; + function onTouchStart(e) { + const touch = getTouches(e)[0]; + touchTarget = null; + touchStartX = 0; + touchStartY = 0; - if (!itemSelectionPanel) { - itemSelectionPanel = document.createElement('div'); - itemSelectionPanel.classList.add('itemSelectionPanel'); + if (touch) { + touchStartX = touch.clientX; + touchStartY = touch.clientY; + const element = touch.target; - const parent = item.querySelector('.cardBox') || item.querySelector('.cardContent'); - parent.classList.add('withMultiSelect'); - parent.appendChild(itemSelectionPanel); + if (element) { + const card = dom.parentWithClass(element, 'card'); - let cssClass = 'chkItemSelect'; - if (isChecked && !browser.firefox) { - // In firefox, the initial tap hold doesnt' get treated as a click - // In other browsers it does, so we need to make sure that initial click is ignored - cssClass += ' checkedInitial'; - } - const checkedAttribute = isChecked ? ' checked' : ''; - itemSelectionPanel.innerHTML = ``; - const chkItemSelect = itemSelectionPanel.querySelector('.chkItemSelect'); - chkItemSelect.addEventListener('change', onSelectionChange); - } - } + if (card) { + if (touchStartTimeout) { + clearTimeout(touchStartTimeout); + touchStartTimeout = null; + } - function showSelectionCommands() { - let selectionCommandsPanel = currentSelectionCommandsPanel; - - if (!selectionCommandsPanel) { - selectionCommandsPanel = document.createElement('div'); - selectionCommandsPanel.classList.add('selectionCommandsPanel'); - - document.body.appendChild(selectionCommandsPanel); - currentSelectionCommandsPanel = selectionCommandsPanel; - - let html = ''; - - html += ''; - html += '

'; - - const moreIcon = 'more_vert'; - html += ``; - - selectionCommandsPanel.innerHTML = html; - - selectionCommandsPanel.querySelector('.btnCloseSelectionPanel').addEventListener('click', hideSelections); - - const btnSelectionPanelOptions = selectionCommandsPanel.querySelector('.btnSelectionPanelOptions'); - - dom.addEventListener(btnSelectionPanelOptions, 'click', showMenuForSelectedItems, { passive: true }); - } - } - - function alertText(options) { - return new Promise((resolve) => { - alert(options).then(resolve, resolve); - }); - } - - function deleteItems(apiClient, itemIds) { - return new Promise((resolve, reject) => { - let msg = globalize.translate('ConfirmDeleteItem'); - let title = globalize.translate('HeaderDeleteItem'); - - if (itemIds.length > 1) { - msg = globalize.translate('ConfirmDeleteItems'); - title = globalize.translate('HeaderDeleteItems'); - } - - confirm(msg, title).then(() => { - const promises = itemIds.map(itemId => apiClient.deleteItem(itemId)); - - Promise.all(promises).then(resolve, () => { - alertText(globalize.translate('ErrorDeletingItem')).then(reject, reject); - }); - }, reject); - }); - } - - function showMenuForSelectedItems(e) { - const apiClient = ServerConnections.currentApiClient(); - - apiClient.getCurrentUser().then(user => { - // get first selected item to perform metadata refresh permission check - apiClient.getItem(apiClient.getCurrentUserId(), selectedItems[0]).then(firstItem => { - const menuItems = []; - - menuItems.push({ - name: globalize.translate('SelectAll'), - id: 'selectall', - icon: 'select_all' - }); - - menuItems.push({ - name: globalize.translate('AddToCollection'), - id: 'addtocollection', - icon: 'add' - }); - - menuItems.push({ - name: globalize.translate('AddToPlaylist'), - id: 'playlist', - icon: 'playlist_add' - }); - - // TODO: Be more dynamic based on what is selected - if (user.Policy.EnableContentDeletion) { - menuItems.push({ - name: globalize.translate('Delete'), - id: 'delete', - icon: 'delete' - }); + touchTarget = card; + touchStartTimeout = setTimeout(onTouchStartTimerFired, 550); } - - if (user.Policy.EnableContentDownloading && appHost.supports('filedownload')) { - // Disabled because there is no callback for this item - } - - if (user.Policy.IsAdministrator) { - menuItems.push({ - name: globalize.translate('GroupVersions'), - id: 'groupvideos', - icon: 'call_merge' - }); - } - - menuItems.push({ - name: globalize.translate('MarkPlayed'), - id: 'markplayed', - icon: 'check_box' - }); - - menuItems.push({ - name: globalize.translate('MarkUnplayed'), - id: 'markunplayed', - icon: 'check_box_outline_blank' - }); - - // this assues that if the user can refresh metadata for the first item - // they can refresh metadata for all items - if (itemHelper.canRefreshMetadata(firstItem, user)) { - menuItems.push({ - name: globalize.translate('RefreshMetadata'), - id: 'refresh', - icon: 'refresh' - }); - } - - import('../actionSheet/actionSheet').then((actionsheet) => { - actionsheet.show({ - items: menuItems, - positionTo: e.target, - callback: function (id) { - const items = selectedItems.slice(0); - const serverId = apiClient.serverInfo().Id; - - switch (id) { - case 'selectall': - { - const elems = document.querySelectorAll('.itemSelectionPanel'); - for (let i = 0, length = elems.length; i < length; i++) { - const chkItemSelect = elems[i].querySelector('.chkItemSelect'); - - if (chkItemSelect && !chkItemSelect.classList.contains('checkedInitial') && !chkItemSelect.checked && chkItemSelect.getBoundingClientRect().width != 0) { - chkItemSelect.checked = true; - updateItemSelection(chkItemSelect, true); - } - } - } - break; - case 'addtocollection': - import('../collectionEditor/collectionEditor').then(({ default: CollectionEditor }) => { - const collectionEditor = new CollectionEditor(); - collectionEditor.show({ - items: items, - serverId: serverId - }); - }); - hideSelections(); - dispatchNeedsRefresh(); - break; - case 'playlist': - new playlistEditor({ - items: items, - serverId: serverId - }); - hideSelections(); - dispatchNeedsRefresh(); - break; - case 'delete': - deleteItems(apiClient, items).then(dispatchNeedsRefresh); - hideSelections(); - dispatchNeedsRefresh(); - break; - case 'groupvideos': - combineVersions(apiClient, items); - break; - case 'markplayed': - items.forEach(itemId => { - apiClient.markPlayed(apiClient.getCurrentUserId(), itemId); - }); - hideSelections(); - dispatchNeedsRefresh(); - break; - case 'markunplayed': - items.forEach(itemId => { - apiClient.markUnplayed(apiClient.getCurrentUserId(), itemId); - }); - hideSelections(); - dispatchNeedsRefresh(); - break; - case 'refresh': - import('../refreshdialog/refreshdialog').then(({ default: refreshDialog }) => { - new refreshDialog({ - itemIds: items, - serverId: serverId - }).show(); - }); - hideSelections(); - dispatchNeedsRefresh(); - break; - default: - break; - } - } - }); - }); - }); - }); - } - - function dispatchNeedsRefresh() { - const elems = []; - - [].forEach.call(selectedElements, i => { - const container = dom.parentWithAttribute(i, 'is', 'emby-itemscontainer'); - - if (container && !elems.includes(container)) { - elems.push(container); } - }); - - for (let i = 0, length = elems.length; i < length; i++) { - elems[i].notifyRefreshNeeded(true); } } - function combineVersions(apiClient, selection) { - if (selection.length < 2) { - alert({ - text: globalize.translate('PleaseSelectTwoItems') - }); + function onTouchMove(e) { + if (touchTarget) { + const touch = getTouches(e)[0]; + let deltaX; + let deltaY; + if (touch) { + const touchEndX = touch.clientX || 0; + const touchEndY = touch.clientY || 0; + deltaX = Math.abs(touchEndX - (touchStartX || 0)); + deltaY = Math.abs(touchEndY - (touchStartY || 0)); + } else { + deltaX = 100; + deltaY = 100; + } + if (deltaX >= 5 || deltaY >= 5) { + onMouseOut(); + } + } + } + + function onTouchEnd() { + onMouseOut(); + } + + function onMouseDown(e) { + if (touchStartTimeout) { + clearTimeout(touchStartTimeout); + touchStartTimeout = null; + } + + touchTarget = e.target; + touchStartTimeout = setTimeout(onTouchStartTimerFired, 550); + } + + function onMouseOut() { + if (touchStartTimeout) { + clearTimeout(touchStartTimeout); + touchStartTimeout = null; + } + touchTarget = null; + } + + function onTouchStartTimerFired() { + if (!touchTarget) { return; } - loading.show(); + const card = dom.parentWithClass(touchTarget, 'card'); + touchTarget = null; - apiClient.ajax({ + if (card) { + showSelections(card); + } + } - type: 'POST', - url: apiClient.getUrl('Videos/MergeVersions', { Ids: selection.join(',') }) + function initTapHold(element) { + // mobile safari doesn't allow contextmenu override + if (browser.touch && !browser.safari) { + element.addEventListener('contextmenu', onTapHold); + } else { + dom.addEventListener(element, 'touchstart', onTouchStart, { + passive: true + }); + dom.addEventListener(element, 'touchmove', onTouchMove, { + passive: true + }); + dom.addEventListener(element, 'touchend', onTouchEnd, { + passive: true + }); + dom.addEventListener(element, 'touchcancel', onTouchEnd, { + passive: true + }); + dom.addEventListener(element, 'mousedown', onMouseDown, { + passive: true + }); + dom.addEventListener(element, 'mouseleave', onMouseOut, { + passive: true + }); + dom.addEventListener(element, 'mouseup', onMouseOut, { + passive: true + }); + } + } - }).then(() => { - loading.hide(); - hideSelections(); - dispatchNeedsRefresh(); + initTapHold(container); + + if (options.bindOnClick !== false) { + container.addEventListener('click', onContainerClick); + } + + self.onContainerClick = onContainerClick; + + self.destroy = () => { + container.removeEventListener('click', onContainerClick); + container.removeEventListener('contextmenu', onTapHold); + + const element = container; + + dom.removeEventListener(element, 'touchstart', onTouchStart, { + passive: true }); - } - - function showSelections(initialCard) { - import('../../elements/emby-checkbox/emby-checkbox').then(() => { - const cards = document.querySelectorAll('.card'); - for (let i = 0, length = cards.length; i < length; i++) { - showSelection(cards[i], initialCard === cards[i]); - } - - showSelectionCommands(); - updateItemSelection(initialCard, true); + dom.removeEventListener(element, 'touchmove', onTouchMove, { + passive: true }); - } + dom.removeEventListener(element, 'touchend', onTouchEnd, { + passive: true + }); + dom.removeEventListener(element, 'mousedown', onMouseDown, { + passive: true + }); + dom.removeEventListener(element, 'mouseleave', onMouseOut, { + passive: true + }); + dom.removeEventListener(element, 'mouseup', onMouseOut, { + passive: true + }); + }; +} - function onContainerClick(e) { - const target = e.target; - - if (selectedItems.length) { - const card = dom.parentWithClass(target, 'card'); - if (card) { - const itemSelectionPanel = card.querySelector('.itemSelectionPanel'); - if (itemSelectionPanel) { - return onItemSelectionPanelClick(e, itemSelectionPanel); - } - } - - e.preventDefault(); - e.stopPropagation(); - return false; - } - } - - document.addEventListener('viewbeforehide', hideSelections); - - export default function (options) { - const self = this; - - const container = options.container; - - function onTapHold(e) { - const card = dom.parentWithClass(e.target, 'card'); - - if (card) { - showSelections(card); - } - - e.preventDefault(); - // It won't have this if it's a hammer event - if (e.stopPropagation) { - e.stopPropagation(); - } - return false; - } - - function getTouches(e) { - return e.changedTouches || e.targetTouches || e.touches; - } - - let touchTarget; - let touchStartTimeout; - let touchStartX; - let touchStartY; - function onTouchStart(e) { - const touch = getTouches(e)[0]; - touchTarget = null; - touchStartX = 0; - touchStartY = 0; - - if (touch) { - touchStartX = touch.clientX; - touchStartY = touch.clientY; - const element = touch.target; - - if (element) { - const card = dom.parentWithClass(element, 'card'); - - if (card) { - if (touchStartTimeout) { - clearTimeout(touchStartTimeout); - touchStartTimeout = null; - } - - touchTarget = card; - touchStartTimeout = setTimeout(onTouchStartTimerFired, 550); - } - } - } - } - - function onTouchMove(e) { - if (touchTarget) { - const touch = getTouches(e)[0]; - let deltaX; - let deltaY; - - if (touch) { - const touchEndX = touch.clientX || 0; - const touchEndY = touch.clientY || 0; - deltaX = Math.abs(touchEndX - (touchStartX || 0)); - deltaY = Math.abs(touchEndY - (touchStartY || 0)); - } else { - deltaX = 100; - deltaY = 100; - } - if (deltaX >= 5 || deltaY >= 5) { - onMouseOut(); - } - } - } - - function onTouchEnd() { - onMouseOut(); - } - - function onMouseDown(e) { - if (touchStartTimeout) { - clearTimeout(touchStartTimeout); - touchStartTimeout = null; - } - - touchTarget = e.target; - touchStartTimeout = setTimeout(onTouchStartTimerFired, 550); - } - - function onMouseOut() { - if (touchStartTimeout) { - clearTimeout(touchStartTimeout); - touchStartTimeout = null; - } - touchTarget = null; - } - - function onTouchStartTimerFired() { - if (!touchTarget) { - return; - } - - const card = dom.parentWithClass(touchTarget, 'card'); - touchTarget = null; - - if (card) { - showSelections(card); - } - } - - function initTapHold(element) { - // mobile safari doesn't allow contextmenu override - if (browser.touch && !browser.safari) { - element.addEventListener('contextmenu', onTapHold); - } else { - dom.addEventListener(element, 'touchstart', onTouchStart, { - passive: true - }); - dom.addEventListener(element, 'touchmove', onTouchMove, { - passive: true - }); - dom.addEventListener(element, 'touchend', onTouchEnd, { - passive: true - }); - dom.addEventListener(element, 'touchcancel', onTouchEnd, { - passive: true - }); - dom.addEventListener(element, 'mousedown', onMouseDown, { - passive: true - }); - dom.addEventListener(element, 'mouseleave', onMouseOut, { - passive: true - }); - dom.addEventListener(element, 'mouseup', onMouseOut, { - passive: true - }); - } - } - - initTapHold(container); - - if (options.bindOnClick !== false) { - container.addEventListener('click', onContainerClick); - } - - self.onContainerClick = onContainerClick; - - self.destroy = () => { - container.removeEventListener('click', onContainerClick); - container.removeEventListener('contextmenu', onTapHold); - - const element = container; - - dom.removeEventListener(element, 'touchstart', onTouchStart, { - passive: true - }); - dom.removeEventListener(element, 'touchmove', onTouchMove, { - passive: true - }); - dom.removeEventListener(element, 'touchend', onTouchEnd, { - passive: true - }); - dom.removeEventListener(element, 'mousedown', onMouseDown, { - passive: true - }); - dom.removeEventListener(element, 'mouseleave', onMouseOut, { - passive: true - }); - dom.removeEventListener(element, 'mouseup', onMouseOut, { - passive: true - }); - }; - } - -/* eslint-enable indent */ diff --git a/src/components/nowPlayingBar/nowPlayingBar.js b/src/components/nowPlayingBar/nowPlayingBar.js index 5fee5f2db1..e977f458f9 100644 --- a/src/components/nowPlayingBar/nowPlayingBar.js +++ b/src/components/nowPlayingBar/nowPlayingBar.js @@ -17,771 +17,768 @@ import './nowPlayingBar.scss'; import '../../elements/emby-slider/emby-slider'; import { appRouter } from '../appRouter'; -/* eslint-disable indent */ +let currentPlayer; +let currentPlayerSupportedCommands = []; - let currentPlayer; - let currentPlayerSupportedCommands = []; +let currentTimeElement; +let nowPlayingImageElement; +let nowPlayingImageUrl; +let nowPlayingTextElement; +let nowPlayingUserData; +let muteButton; +let volumeSlider; +let volumeSliderContainer; +let playPauseButtons; +let positionSlider; +let toggleRepeatButton; +let toggleRepeatButtonIcon; - let currentTimeElement; - let nowPlayingImageElement; - let nowPlayingImageUrl; - let nowPlayingTextElement; - let nowPlayingUserData; - let muteButton; - let volumeSlider; - let volumeSliderContainer; - let playPauseButtons; - let positionSlider; - let toggleRepeatButton; - let toggleRepeatButtonIcon; +let lastUpdateTime = 0; +let lastPlayerState = {}; +let isEnabled; +let currentRuntimeTicks = 0; - let lastUpdateTime = 0; - let lastPlayerState = {}; - let isEnabled; - let currentRuntimeTicks = 0; +let isVisibilityAllowed = true; - let isVisibilityAllowed = true; +function getNowPlayingBarHtml() { + let html = ''; - function getNowPlayingBarHtml() { - let html = ''; + html += '
'; - html += '
'; + html += '
'; + html += '
'; + html += ''; + html += '
'; - html += '
'; - html += '
'; - html += ''; - html += '
'; + html += '
'; + html += '
'; + html += '
'; + html += '
'; - html += '
'; - html += '
'; - html += '
'; - html += '
'; + // The onclicks are needed due to the return false above + html += '
'; - // The onclicks are needed due to the return false above - html += '
'; + html += ''; - html += ''; + html += ''; - html += ''; + html += ''; + if (!layoutManager.mobile) { + html += ''; + } - html += ''; - if (!layoutManager.mobile) { - html += ''; + html += '
'; + html += '
'; + + html += '
'; + + html += ''; + + html += '
'; + html += ''; + html += '
'; + + html += ''; + html += ''; + + html += '
'; + html += '
'; + + html += ''; + if (layoutManager.mobile) { + html += ''; + } else { + html += ''; + } + + html += '
'; + html += '
'; + + html += '
'; + + return html; +} + +function onSlideDownComplete() { + this.classList.add('hide'); +} + +function slideDown(elem) { + // trigger reflow + void elem.offsetWidth; + + elem.classList.add('nowPlayingBar-hidden'); + + dom.addEventListener(elem, dom.whichTransitionEvent(), onSlideDownComplete, { + once: true + }); +} + +function slideUp(elem) { + dom.removeEventListener(elem, dom.whichTransitionEvent(), onSlideDownComplete, { + once: true + }); + + elem.classList.remove('hide'); + + // trigger reflow + void elem.offsetWidth; + + elem.classList.remove('nowPlayingBar-hidden'); +} + +function onPlayPauseClick() { + playbackManager.playPause(currentPlayer); +} + +function bindEvents(elem) { + currentTimeElement = elem.querySelector('.nowPlayingBarCurrentTime'); + nowPlayingImageElement = elem.querySelector('.nowPlayingImage'); + nowPlayingTextElement = elem.querySelector('.nowPlayingBarText'); + nowPlayingUserData = elem.querySelector('.nowPlayingBarUserDataButtons'); + positionSlider = elem.querySelector('.nowPlayingBarPositionSlider'); + muteButton = elem.querySelector('.muteButton'); + playPauseButtons = elem.querySelectorAll('.playPauseButton'); + toggleRepeatButton = elem.querySelector('.toggleRepeatButton'); + volumeSlider = elem.querySelector('.nowPlayingBarVolumeSlider'); + volumeSliderContainer = elem.querySelector('.nowPlayingBarVolumeSliderContainer'); + + muteButton.addEventListener('click', function () { + if (currentPlayer) { + playbackManager.toggleMute(currentPlayer); } + }); - html += '
'; - html += '
'; - - html += '
'; - - html += ''; - - html += '
'; - html += ''; - html += '
'; - - html += ''; - html += ''; - - html += '
'; - html += '
'; - - html += ''; - if (layoutManager.mobile) { - html += ''; - } else { - html += ''; + elem.querySelector('.stopButton').addEventListener('click', function () { + if (currentPlayer) { + playbackManager.stop(currentPlayer); } + }); - html += '
'; - html += '
'; + playPauseButtons.forEach((button) => { + button.addEventListener('click', onPlayPauseClick); + }); - html += '
'; + elem.querySelector('.nextTrackButton').addEventListener('click', function () { + if (currentPlayer) { + playbackManager.nextTrack(currentPlayer); + } + }); - return html; - } - - function onSlideDownComplete() { - this.classList.add('hide'); - } - - function slideDown(elem) { - // trigger reflow - void elem.offsetWidth; - - elem.classList.add('nowPlayingBar-hidden'); - - dom.addEventListener(elem, dom.whichTransitionEvent(), onSlideDownComplete, { - once: true - }); - } - - function slideUp(elem) { - dom.removeEventListener(elem, dom.whichTransitionEvent(), onSlideDownComplete, { - once: true - }); - - elem.classList.remove('hide'); - - // trigger reflow - void elem.offsetWidth; - - elem.classList.remove('nowPlayingBar-hidden'); - } - - function onPlayPauseClick() { - playbackManager.playPause(currentPlayer); - } - - function bindEvents(elem) { - currentTimeElement = elem.querySelector('.nowPlayingBarCurrentTime'); - nowPlayingImageElement = elem.querySelector('.nowPlayingImage'); - nowPlayingTextElement = elem.querySelector('.nowPlayingBarText'); - nowPlayingUserData = elem.querySelector('.nowPlayingBarUserDataButtons'); - positionSlider = elem.querySelector('.nowPlayingBarPositionSlider'); - muteButton = elem.querySelector('.muteButton'); - playPauseButtons = elem.querySelectorAll('.playPauseButton'); - toggleRepeatButton = elem.querySelector('.toggleRepeatButton'); - volumeSlider = elem.querySelector('.nowPlayingBarVolumeSlider'); - volumeSliderContainer = elem.querySelector('.nowPlayingBarVolumeSliderContainer'); - - muteButton.addEventListener('click', function () { - if (currentPlayer) { - playbackManager.toggleMute(currentPlayer); - } - }); - - elem.querySelector('.stopButton').addEventListener('click', function () { - if (currentPlayer) { - playbackManager.stop(currentPlayer); - } - }); - - playPauseButtons.forEach((button) => { - button.addEventListener('click', onPlayPauseClick); - }); - - elem.querySelector('.nextTrackButton').addEventListener('click', function () { - if (currentPlayer) { - playbackManager.nextTrack(currentPlayer); - } - }); - - elem.querySelector('.previousTrackButton').addEventListener('click', function (e) { - if (currentPlayer) { - if (lastPlayerState.NowPlayingItem.MediaType === 'Audio') { - // Cancel this event if doubleclick is fired. The actual previousTrack will be processed by the 'dblclick' event - if (e.detail > 1 ) { - return; - } - // Return to start of track, unless we are already (almost) at the beginning. In the latter case, continue and move - // to the previous track, unless we are at the first track so no previous track exists. - if (currentPlayer._currentTime >= 5 || playbackManager.getCurrentPlaylistIndex(currentPlayer) <= 1) { - playbackManager.seekPercent(0, currentPlayer); - // This is done automatically by playbackManager, however, setting this here gives instant visual feedback. - // TODO: Check why seekPercentage doesn't reflect the changes inmmediately, so we can remove this workaround. - positionSlider.value = 0; - return; - } + elem.querySelector('.previousTrackButton').addEventListener('click', function (e) { + if (currentPlayer) { + if (lastPlayerState.NowPlayingItem.MediaType === 'Audio') { + // Cancel this event if doubleclick is fired. The actual previousTrack will be processed by the 'dblclick' event + if (e.detail > 1 ) { + return; + } + // Return to start of track, unless we are already (almost) at the beginning. In the latter case, continue and move + // to the previous track, unless we are at the first track so no previous track exists. + if (currentPlayer._currentTime >= 5 || playbackManager.getCurrentPlaylistIndex(currentPlayer) <= 1) { + playbackManager.seekPercent(0, currentPlayer); + // This is done automatically by playbackManager, however, setting this here gives instant visual feedback. + // TODO: Check why seekPercentage doesn't reflect the changes inmmediately, so we can remove this workaround. + positionSlider.value = 0; + return; } - playbackManager.previousTrack(currentPlayer); } - }); + playbackManager.previousTrack(currentPlayer); + } + }); - elem.querySelector('.previousTrackButton').addEventListener('dblclick', function () { - if (currentPlayer) { - playbackManager.previousTrack(currentPlayer); - } - }); + elem.querySelector('.previousTrackButton').addEventListener('dblclick', function () { + if (currentPlayer) { + playbackManager.previousTrack(currentPlayer); + } + }); - elem.querySelector('.btnShuffleQueue').addEventListener('click', function () { - if (currentPlayer) { - playbackManager.toggleQueueShuffleMode(); - } - }); + elem.querySelector('.btnShuffleQueue').addEventListener('click', function () { + if (currentPlayer) { + playbackManager.toggleQueueShuffleMode(); + } + }); - toggleRepeatButton = elem.querySelector('.toggleRepeatButton'); - toggleRepeatButton.addEventListener('click', function () { - switch (playbackManager.getRepeatMode()) { - case 'RepeatAll': - playbackManager.setRepeatMode('RepeatOne'); - break; - case 'RepeatOne': - playbackManager.setRepeatMode('RepeatNone'); - break; - case 'RepeatNone': - playbackManager.setRepeatMode('RepeatAll'); - } - }); + toggleRepeatButton = elem.querySelector('.toggleRepeatButton'); + toggleRepeatButton.addEventListener('click', function () { + switch (playbackManager.getRepeatMode()) { + case 'RepeatAll': + playbackManager.setRepeatMode('RepeatOne'); + break; + case 'RepeatOne': + playbackManager.setRepeatMode('RepeatNone'); + break; + case 'RepeatNone': + playbackManager.setRepeatMode('RepeatAll'); + } + }); - toggleRepeatButtonIcon = toggleRepeatButton.querySelector('.material-icons'); + toggleRepeatButtonIcon = toggleRepeatButton.querySelector('.material-icons'); - volumeSliderContainer.classList.toggle('hide', appHost.supports('physicalvolumecontrol')); + volumeSliderContainer.classList.toggle('hide', appHost.supports('physicalvolumecontrol')); - volumeSlider.addEventListener('input', (e) => { - if (currentPlayer) { - currentPlayer.setVolume(e.target.value); - } - }); + volumeSlider.addEventListener('input', (e) => { + if (currentPlayer) { + currentPlayer.setVolume(e.target.value); + } + }); - positionSlider.addEventListener('change', function () { - if (currentPlayer) { - const newPercent = parseFloat(this.value); + positionSlider.addEventListener('change', function () { + if (currentPlayer) { + const newPercent = parseFloat(this.value); - playbackManager.seekPercent(newPercent, currentPlayer); - } - }); + playbackManager.seekPercent(newPercent, currentPlayer); + } + }); - positionSlider.getBubbleText = function (value) { - const state = lastPlayerState; + positionSlider.getBubbleText = function (value) { + const state = lastPlayerState; - if (!state || !state.NowPlayingItem || !currentRuntimeTicks) { - return '--:--'; - } - - let ticks = currentRuntimeTicks; - ticks /= 100; - ticks *= value; - - return datetime.getDisplayRunningTime(ticks); - }; - - elem.addEventListener('click', function (e) { - if (!dom.parentWithTag(e.target, ['BUTTON', 'INPUT'])) { - showRemoteControl(); - } - }); - } - - function showRemoteControl() { - appRouter.showNowPlaying(); - } - - let nowPlayingBarElement; - function getNowPlayingBar() { - if (nowPlayingBarElement) { - return nowPlayingBarElement; + if (!state || !state.NowPlayingItem || !currentRuntimeTicks) { + return '--:--'; } - const parentContainer = appFooter.element; - nowPlayingBarElement = parentContainer.querySelector('.nowPlayingBar'); + let ticks = currentRuntimeTicks; + ticks /= 100; + ticks *= value; - if (nowPlayingBarElement) { - return nowPlayingBarElement; + return datetime.getDisplayRunningTime(ticks); + }; + + elem.addEventListener('click', function (e) { + if (!dom.parentWithTag(e.target, ['BUTTON', 'INPUT'])) { + showRemoteControl(); } + }); +} - parentContainer.insertAdjacentHTML('afterbegin', getNowPlayingBarHtml()); - window.CustomElements.upgradeSubtree(parentContainer); - - nowPlayingBarElement = parentContainer.querySelector('.nowPlayingBar'); - - if (layoutManager.mobile) { - hideButton(nowPlayingBarElement.querySelector('.btnShuffleQueue')); - hideButton(nowPlayingBarElement.querySelector('.nowPlayingBarCenter')); - } - - if (browser.safari && browser.slow) { - // Not handled well here. The wrong elements receive events, bar doesn't update quickly enough, etc. - nowPlayingBarElement.classList.add('noMediaProgress'); - } - - itemShortcuts.on(nowPlayingBarElement); - - bindEvents(nowPlayingBarElement); +function showRemoteControl() { + appRouter.showNowPlaying(); +} +let nowPlayingBarElement; +function getNowPlayingBar() { + if (nowPlayingBarElement) { return nowPlayingBarElement; } - function showButton(button) { - button.classList.remove('hide'); + const parentContainer = appFooter.element; + nowPlayingBarElement = parentContainer.querySelector('.nowPlayingBar'); + + if (nowPlayingBarElement) { + return nowPlayingBarElement; } - function hideButton(button) { - button.classList.add('hide'); + parentContainer.insertAdjacentHTML('afterbegin', getNowPlayingBarHtml()); + window.CustomElements.upgradeSubtree(parentContainer); + + nowPlayingBarElement = parentContainer.querySelector('.nowPlayingBar'); + + if (layoutManager.mobile) { + hideButton(nowPlayingBarElement.querySelector('.btnShuffleQueue')); + hideButton(nowPlayingBarElement.querySelector('.nowPlayingBarCenter')); } - function updatePlayPauseState(isPaused) { - if (playPauseButtons) { - playPauseButtons.forEach((button) => { - const icon = button.querySelector('.material-icons'); - icon.classList.remove('play_arrow', 'pause'); - icon.classList.add(isPaused ? 'play_arrow' : 'pause'); - }); - } + if (browser.safari && browser.slow) { + // Not handled well here. The wrong elements receive events, bar doesn't update quickly enough, etc. + nowPlayingBarElement.classList.add('noMediaProgress'); } - function updatePlayerStateInternal(event, state, player) { - showNowPlayingBar(); + itemShortcuts.on(nowPlayingBarElement); - lastPlayerState = state; + bindEvents(nowPlayingBarElement); - const playerInfo = playbackManager.getPlayerInfo(); + return nowPlayingBarElement; +} - const playState = state.PlayState || {}; +function showButton(button) { + button.classList.remove('hide'); +} - updatePlayPauseState(playState.IsPaused); +function hideButton(button) { + button.classList.add('hide'); +} - const supportedCommands = playerInfo.supportedCommands; - currentPlayerSupportedCommands = supportedCommands; +function updatePlayPauseState(isPaused) { + if (playPauseButtons) { + playPauseButtons.forEach((button) => { + const icon = button.querySelector('.material-icons'); + icon.classList.remove('play_arrow', 'pause'); + icon.classList.add(isPaused ? 'play_arrow' : 'pause'); + }); + } +} - if (supportedCommands.indexOf('SetRepeatMode') === -1) { - toggleRepeatButton.classList.add('hide'); +function updatePlayerStateInternal(event, state, player) { + showNowPlayingBar(); + + lastPlayerState = state; + + const playerInfo = playbackManager.getPlayerInfo(); + + const playState = state.PlayState || {}; + + updatePlayPauseState(playState.IsPaused); + + const supportedCommands = playerInfo.supportedCommands; + currentPlayerSupportedCommands = supportedCommands; + + if (supportedCommands.indexOf('SetRepeatMode') === -1) { + toggleRepeatButton.classList.add('hide'); + } else { + toggleRepeatButton.classList.remove('hide'); + } + + updateRepeatModeDisplay(playbackManager.getRepeatMode()); + onQueueShuffleModeChange(); + + updatePlayerVolumeState(playState.IsMuted, playState.VolumeLevel); + + if (positionSlider && !positionSlider.dragging) { + positionSlider.disabled = !playState.CanSeek; + + // determines if both forward and backward buffer progress will be visible + const isProgressClear = state.MediaSource && state.MediaSource.RunTimeTicks == null; + positionSlider.setIsClear(isProgressClear); + } + + const nowPlayingItem = state.NowPlayingItem || {}; + updateTimeDisplay(playState.PositionTicks, nowPlayingItem.RunTimeTicks, playbackManager.getBufferedRanges(player)); + + updateNowPlayingInfo(state); +} + +function updateRepeatModeDisplay(repeatMode) { + toggleRepeatButtonIcon.classList.remove('repeat', 'repeat_one'); + const cssClass = 'buttonActive'; + + switch (repeatMode) { + case 'RepeatAll': + toggleRepeatButtonIcon.classList.add('repeat'); + toggleRepeatButton.classList.add(cssClass); + break; + case 'RepeatOne': + toggleRepeatButtonIcon.classList.add('repeat_one'); + toggleRepeatButton.classList.add(cssClass); + break; + case 'RepeatNone': + default: + toggleRepeatButtonIcon.classList.add('repeat'); + toggleRepeatButton.classList.remove(cssClass); + break; + } +} + +function updateTimeDisplay(positionTicks, runtimeTicks, bufferedRanges) { + // See bindEvents for why this is necessary + if (positionSlider && !positionSlider.dragging) { + if (runtimeTicks) { + let pct = positionTicks / runtimeTicks; + pct *= 100; + + positionSlider.value = pct; } else { - toggleRepeatButton.classList.remove('hide'); - } - - updateRepeatModeDisplay(playbackManager.getRepeatMode()); - onQueueShuffleModeChange(); - - updatePlayerVolumeState(playState.IsMuted, playState.VolumeLevel); - - if (positionSlider && !positionSlider.dragging) { - positionSlider.disabled = !playState.CanSeek; - - // determines if both forward and backward buffer progress will be visible - const isProgressClear = state.MediaSource && state.MediaSource.RunTimeTicks == null; - positionSlider.setIsClear(isProgressClear); - } - - const nowPlayingItem = state.NowPlayingItem || {}; - updateTimeDisplay(playState.PositionTicks, nowPlayingItem.RunTimeTicks, playbackManager.getBufferedRanges(player)); - - updateNowPlayingInfo(state); - } - - function updateRepeatModeDisplay(repeatMode) { - toggleRepeatButtonIcon.classList.remove('repeat', 'repeat_one'); - const cssClass = 'buttonActive'; - - switch (repeatMode) { - case 'RepeatAll': - toggleRepeatButtonIcon.classList.add('repeat'); - toggleRepeatButton.classList.add(cssClass); - break; - case 'RepeatOne': - toggleRepeatButtonIcon.classList.add('repeat_one'); - toggleRepeatButton.classList.add(cssClass); - break; - case 'RepeatNone': - default: - toggleRepeatButtonIcon.classList.add('repeat'); - toggleRepeatButton.classList.remove(cssClass); - break; + positionSlider.value = 0; } } - function updateTimeDisplay(positionTicks, runtimeTicks, bufferedRanges) { - // See bindEvents for why this is necessary - if (positionSlider && !positionSlider.dragging) { - if (runtimeTicks) { - let pct = positionTicks / runtimeTicks; - pct *= 100; - - positionSlider.value = pct; - } else { - positionSlider.value = 0; - } - } - - if (positionSlider) { - positionSlider.setBufferedRanges(bufferedRanges, runtimeTicks, positionTicks); - } - - if (currentTimeElement) { - let timeText = positionTicks == null ? '--:--' : datetime.getDisplayRunningTime(positionTicks); - if (runtimeTicks) { - timeText += ' / ' + datetime.getDisplayRunningTime(runtimeTicks); - } - - currentTimeElement.innerHTML = timeText; - } + if (positionSlider) { + positionSlider.setBufferedRanges(bufferedRanges, runtimeTicks, positionTicks); } - function updatePlayerVolumeState(isMuted, volumeLevel) { - const supportedCommands = currentPlayerSupportedCommands; - - let showMuteButton = true; - let showVolumeSlider = true; - - if (supportedCommands.indexOf('ToggleMute') === -1) { - showMuteButton = false; + if (currentTimeElement) { + let timeText = positionTicks == null ? '--:--' : datetime.getDisplayRunningTime(positionTicks); + if (runtimeTicks) { + timeText += ' / ' + datetime.getDisplayRunningTime(runtimeTicks); } - const muteButtonIcon = muteButton.querySelector('.material-icons'); - muteButtonIcon.classList.remove('volume_off', 'volume_up'); - muteButtonIcon.classList.add(isMuted ? 'volume_off' : 'volume_up'); + currentTimeElement.innerHTML = timeText; + } +} - if (supportedCommands.indexOf('SetVolume') === -1) { - showVolumeSlider = false; - } +function updatePlayerVolumeState(isMuted, volumeLevel) { + const supportedCommands = currentPlayerSupportedCommands; - if (currentPlayer.isLocalPlayer && appHost.supports('physicalvolumecontrol')) { - showMuteButton = false; - showVolumeSlider = false; - } + let showMuteButton = true; + let showVolumeSlider = true; - if (showMuteButton) { - showButton(muteButton); - } else { - hideButton(muteButton); - } - - // See bindEvents for why this is necessary - if (volumeSlider) { - volumeSliderContainer.classList.toggle('hide', !showVolumeSlider); - - if (!volumeSlider.dragging) { - volumeSlider.value = volumeLevel || 0; - } - } + if (supportedCommands.indexOf('ToggleMute') === -1) { + showMuteButton = false; } - function seriesImageUrl(item, options) { - if (!item) { - throw new Error('item cannot be null!'); + const muteButtonIcon = muteButton.querySelector('.material-icons'); + muteButtonIcon.classList.remove('volume_off', 'volume_up'); + muteButtonIcon.classList.add(isMuted ? 'volume_off' : 'volume_up'); + + if (supportedCommands.indexOf('SetVolume') === -1) { + showVolumeSlider = false; + } + + if (currentPlayer.isLocalPlayer && appHost.supports('physicalvolumecontrol')) { + showMuteButton = false; + showVolumeSlider = false; + } + + if (showMuteButton) { + showButton(muteButton); + } else { + hideButton(muteButton); + } + + // See bindEvents for why this is necessary + if (volumeSlider) { + volumeSliderContainer.classList.toggle('hide', !showVolumeSlider); + + if (!volumeSlider.dragging) { + volumeSlider.value = volumeLevel || 0; } + } +} - if (item.Type !== 'Episode') { - return null; - } +function seriesImageUrl(item, options) { + if (!item) { + throw new Error('item cannot be null!'); + } - options = options || {}; - options.type = options.type || 'Primary'; + if (item.Type !== 'Episode') { + return null; + } - if (options.type === 'Primary' && item.SeriesPrimaryImageTag) { - options.tag = item.SeriesPrimaryImageTag; + options = options || {}; + options.type = options.type || 'Primary'; + + if (options.type === 'Primary' && item.SeriesPrimaryImageTag) { + options.tag = item.SeriesPrimaryImageTag; + + return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); + } + + if (options.type === 'Thumb') { + if (item.SeriesThumbImageTag) { + options.tag = item.SeriesThumbImageTag; return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); } + if (item.ParentThumbImageTag) { + options.tag = item.ParentThumbImageTag; - if (options.type === 'Thumb') { - if (item.SeriesThumbImageTag) { - options.tag = item.SeriesThumbImageTag; - - return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); - } - if (item.ParentThumbImageTag) { - options.tag = item.ParentThumbImageTag; - - return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.ParentThumbItemId, options); - } + return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.ParentThumbItemId, options); } - - return null; } - function imageUrl(item, options) { - if (!item) { - throw new Error('item cannot be null!'); - } + return null; +} - options = options || {}; - options.type = options.type || 'Primary'; - - if (item.ImageTags && item.ImageTags[options.type]) { - options.tag = item.ImageTags[options.type]; - return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.PrimaryImageItemId || item.Id, options); - } - - if (item.AlbumId && item.AlbumPrimaryImageTag) { - options.tag = item.AlbumPrimaryImageTag; - return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.AlbumId, options); - } - - return null; +function imageUrl(item, options) { + if (!item) { + throw new Error('item cannot be null!'); } - function updateNowPlayingInfo(state) { - const nowPlayingItem = state.NowPlayingItem; + options = options || {}; + options.type = options.type || 'Primary'; - const textLines = nowPlayingItem ? nowPlayingHelper.getNowPlayingNames(nowPlayingItem) : []; - nowPlayingTextElement.innerHTML = ''; - if (textLines) { - const itemText = document.createElement('div'); - const secondaryText = document.createElement('div'); - secondaryText.classList.add('nowPlayingBarSecondaryText'); - if (textLines.length > 1) { - textLines[1].secondary = true; - if (textLines[1].text) { - const text = document.createElement('a'); - text.innerText = textLines[1].text; - secondaryText.appendChild(text); - } - } + if (item.ImageTags && item.ImageTags[options.type]) { + options.tag = item.ImageTags[options.type]; + return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.PrimaryImageItemId || item.Id, options); + } - if (textLines[0].text) { + if (item.AlbumId && item.AlbumPrimaryImageTag) { + options.tag = item.AlbumPrimaryImageTag; + return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.AlbumId, options); + } + + return null; +} + +function updateNowPlayingInfo(state) { + const nowPlayingItem = state.NowPlayingItem; + + const textLines = nowPlayingItem ? nowPlayingHelper.getNowPlayingNames(nowPlayingItem) : []; + nowPlayingTextElement.innerHTML = ''; + if (textLines) { + const itemText = document.createElement('div'); + const secondaryText = document.createElement('div'); + secondaryText.classList.add('nowPlayingBarSecondaryText'); + if (textLines.length > 1) { + textLines[1].secondary = true; + if (textLines[1].text) { const text = document.createElement('a'); - text.innerText = textLines[0].text; - itemText.appendChild(text); - } - nowPlayingTextElement.appendChild(itemText); - nowPlayingTextElement.appendChild(secondaryText); - } - - const imgHeight = 70; - - const url = nowPlayingItem ? (seriesImageUrl(nowPlayingItem, { - height: imgHeight - }) || imageUrl(nowPlayingItem, { - height: imgHeight - })) : null; - - if (url !== nowPlayingImageUrl) { - if (url) { - nowPlayingImageUrl = url; - imageLoader.lazyImage(nowPlayingImageElement, nowPlayingImageUrl); - nowPlayingImageElement.style.display = null; - nowPlayingTextElement.style.marginLeft = null; - } else { - nowPlayingImageUrl = null; - nowPlayingImageElement.style.backgroundImage = ''; - nowPlayingImageElement.style.display = 'none'; - nowPlayingTextElement.style.marginLeft = '1em'; + text.innerText = textLines[1].text; + secondaryText.appendChild(text); } } - if (nowPlayingItem.Id) { - const apiClient = ServerConnections.getApiClient(nowPlayingItem.ServerId); - apiClient.getItem(apiClient.getCurrentUserId(), nowPlayingItem.Id).then(function (item) { - const userData = item.UserData || {}; - const likes = userData.Likes == null ? '' : userData.Likes; - if (!layoutManager.mobile) { - let contextButton = nowPlayingBarElement.querySelector('.btnToggleContextMenu'); - // We remove the previous event listener by replacing the item in each update event - const contextButtonClone = contextButton.cloneNode(true); - contextButton.parentNode.replaceChild(contextButtonClone, contextButton); - contextButton = nowPlayingBarElement.querySelector('.btnToggleContextMenu'); - const options = { - play: false, - queue: false, - stopPlayback: true, - clearQueue: true, - positionTo: contextButton - }; - apiClient.getCurrentUser().then(function (user) { - contextButton.addEventListener('click', function () { - itemContextMenu.show(Object.assign({ - item: item, - user: user - }, options)); - }); + if (textLines[0].text) { + const text = document.createElement('a'); + text.innerText = textLines[0].text; + itemText.appendChild(text); + } + nowPlayingTextElement.appendChild(itemText); + nowPlayingTextElement.appendChild(secondaryText); + } + + const imgHeight = 70; + + const url = nowPlayingItem ? (seriesImageUrl(nowPlayingItem, { + height: imgHeight + }) || imageUrl(nowPlayingItem, { + height: imgHeight + })) : null; + + if (url !== nowPlayingImageUrl) { + if (url) { + nowPlayingImageUrl = url; + imageLoader.lazyImage(nowPlayingImageElement, nowPlayingImageUrl); + nowPlayingImageElement.style.display = null; + nowPlayingTextElement.style.marginLeft = null; + } else { + nowPlayingImageUrl = null; + nowPlayingImageElement.style.backgroundImage = ''; + nowPlayingImageElement.style.display = 'none'; + nowPlayingTextElement.style.marginLeft = '1em'; + } + } + + if (nowPlayingItem.Id) { + const apiClient = ServerConnections.getApiClient(nowPlayingItem.ServerId); + apiClient.getItem(apiClient.getCurrentUserId(), nowPlayingItem.Id).then(function (item) { + const userData = item.UserData || {}; + const likes = userData.Likes == null ? '' : userData.Likes; + if (!layoutManager.mobile) { + let contextButton = nowPlayingBarElement.querySelector('.btnToggleContextMenu'); + // We remove the previous event listener by replacing the item in each update event + const contextButtonClone = contextButton.cloneNode(true); + contextButton.parentNode.replaceChild(contextButtonClone, contextButton); + contextButton = nowPlayingBarElement.querySelector('.btnToggleContextMenu'); + const options = { + play: false, + queue: false, + stopPlayback: true, + clearQueue: true, + positionTo: contextButton + }; + apiClient.getCurrentUser().then(function (user) { + contextButton.addEventListener('click', function () { + itemContextMenu.show(Object.assign({ + item: item, + user: user + }, options)); }); - } - nowPlayingUserData.innerHTML = ''; - }); - } else { - nowPlayingUserData.innerHTML = ''; - } - } - - function onPlaybackStart(e, state) { - console.debug('nowplaying event: ' + e.type); - const player = this; - onStateChanged.call(player, e, state); - } - - function onRepeatModeChange() { - if (!isEnabled) { - return; - } - - updateRepeatModeDisplay(playbackManager.getRepeatMode()); - } - - function onQueueShuffleModeChange() { - if (!isEnabled) { - return; - } - - const shuffleMode = playbackManager.getQueueShuffleMode(); - const context = nowPlayingBarElement; - const cssClass = 'buttonActive'; - const toggleShuffleButton = context.querySelector('.btnShuffleQueue'); - switch (shuffleMode) { - case 'Shuffle': - toggleShuffleButton.classList.add(cssClass); - break; - case 'Sorted': - default: - toggleShuffleButton.classList.remove(cssClass); - break; - } - } - - function showNowPlayingBar() { - if (!isVisibilityAllowed) { - hideNowPlayingBar(); - return; - } - - slideUp(getNowPlayingBar()); - } - - function hideNowPlayingBar() { - isEnabled = false; - - // Use a timeout to prevent the bar from hiding and showing quickly - // in the event of a stop->play command - - // Don't call getNowPlayingBar here because we don't want to end up creating it just to hide it - const elem = document.getElementsByClassName('nowPlayingBar')[0]; - if (elem) { - slideDown(elem); - } - } - - function onPlaybackStopped(e, state) { - console.debug('nowplaying event: ' + e.type); - const player = this; - - if (player.isLocalPlayer) { - if (state.NextMediaType !== 'Audio') { - hideNowPlayingBar(); + }); } - } else { - if (!state.NextMediaType) { - hideNowPlayingBar(); - } - } + nowPlayingUserData.innerHTML = ''; + }); + } else { + nowPlayingUserData.innerHTML = ''; + } +} + +function onPlaybackStart(e, state) { + console.debug('nowplaying event: ' + e.type); + const player = this; + onStateChanged.call(player, e, state); +} + +function onRepeatModeChange() { + if (!isEnabled) { + return; } - function onPlayPauseStateChanged() { - if (!isEnabled) { - return; - } + updateRepeatModeDisplay(playbackManager.getRepeatMode()); +} - const player = this; - updatePlayPauseState(player.paused()); +function onQueueShuffleModeChange() { + if (!isEnabled) { + return; } - function onStateChanged(event, state) { - if (event.type === 'init') { - // skip non-ready state - return; - } + const shuffleMode = playbackManager.getQueueShuffleMode(); + const context = nowPlayingBarElement; + const cssClass = 'buttonActive'; + const toggleShuffleButton = context.querySelector('.btnShuffleQueue'); + switch (shuffleMode) { + case 'Shuffle': + toggleShuffleButton.classList.add(cssClass); + break; + case 'Sorted': + default: + toggleShuffleButton.classList.remove(cssClass); + break; + } +} - console.debug('nowplaying event: ' + event.type); - const player = this; +function showNowPlayingBar() { + if (!isVisibilityAllowed) { + hideNowPlayingBar(); + return; + } - if (!state.NowPlayingItem || layoutManager.tv || state.IsFullscreen === false) { + slideUp(getNowPlayingBar()); +} + +function hideNowPlayingBar() { + isEnabled = false; + + // Use a timeout to prevent the bar from hiding and showing quickly + // in the event of a stop->play command + + // Don't call getNowPlayingBar here because we don't want to end up creating it just to hide it + const elem = document.getElementsByClassName('nowPlayingBar')[0]; + if (elem) { + slideDown(elem); + } +} + +function onPlaybackStopped(e, state) { + console.debug('nowplaying event: ' + e.type); + const player = this; + + if (player.isLocalPlayer) { + if (state.NextMediaType !== 'Audio') { hideNowPlayingBar(); - return; } - - if (player.isLocalPlayer && state.NowPlayingItem && state.NowPlayingItem.MediaType === 'Video') { + } else { + if (!state.NextMediaType) { hideNowPlayingBar(); - return; } + } +} - isEnabled = true; +function onPlayPauseStateChanged() { + if (!isEnabled) { + return; + } - if (nowPlayingBarElement) { - updatePlayerStateInternal(event, state, player); - return; - } + const player = this; + updatePlayPauseState(player.paused()); +} - getNowPlayingBar(); +function onStateChanged(event, state) { + if (event.type === 'init') { + // skip non-ready state + return; + } + + console.debug('nowplaying event: ' + event.type); + const player = this; + + if (!state.NowPlayingItem || layoutManager.tv || state.IsFullscreen === false) { + hideNowPlayingBar(); + return; + } + + if (player.isLocalPlayer && state.NowPlayingItem && state.NowPlayingItem.MediaType === 'Video') { + hideNowPlayingBar(); + return; + } + + isEnabled = true; + + if (nowPlayingBarElement) { updatePlayerStateInternal(event, state, player); + return; } - function onTimeUpdate() { - if (!isEnabled) { - return; - } + getNowPlayingBar(); + updatePlayerStateInternal(event, state, player); +} - // Try to avoid hammering the document with changes - const now = new Date().getTime(); - if ((now - lastUpdateTime) < 700) { - return; - } - lastUpdateTime = now; - - const player = this; - currentRuntimeTicks = playbackManager.duration(player); - updateTimeDisplay(playbackManager.currentTime(player) * 10000, currentRuntimeTicks, playbackManager.getBufferedRanges(player)); +function onTimeUpdate() { + if (!isEnabled) { + return; } - function releaseCurrentPlayer() { - const player = currentPlayer; + // Try to avoid hammering the document with changes + const now = new Date().getTime(); + if ((now - lastUpdateTime) < 700) { + return; + } + lastUpdateTime = now; - if (player) { - Events.off(player, 'playbackstart', onPlaybackStart); - Events.off(player, 'statechange', onPlaybackStart); - Events.off(player, 'repeatmodechange', onRepeatModeChange); - Events.off(player, 'shufflequeuemodechange', onQueueShuffleModeChange); - Events.off(player, 'playbackstop', onPlaybackStopped); - Events.off(player, 'volumechange', onVolumeChanged); - Events.off(player, 'pause', onPlayPauseStateChanged); - Events.off(player, 'unpause', onPlayPauseStateChanged); - Events.off(player, 'timeupdate', onTimeUpdate); + const player = this; + currentRuntimeTicks = playbackManager.duration(player); + updateTimeDisplay(playbackManager.currentTime(player) * 10000, currentRuntimeTicks, playbackManager.getBufferedRanges(player)); +} - currentPlayer = null; +function releaseCurrentPlayer() { + const player = currentPlayer; + + if (player) { + Events.off(player, 'playbackstart', onPlaybackStart); + Events.off(player, 'statechange', onPlaybackStart); + Events.off(player, 'repeatmodechange', onRepeatModeChange); + Events.off(player, 'shufflequeuemodechange', onQueueShuffleModeChange); + Events.off(player, 'playbackstop', onPlaybackStopped); + Events.off(player, 'volumechange', onVolumeChanged); + Events.off(player, 'pause', onPlayPauseStateChanged); + Events.off(player, 'unpause', onPlayPauseStateChanged); + Events.off(player, 'timeupdate', onTimeUpdate); + + currentPlayer = null; + hideNowPlayingBar(); + } +} + +function onVolumeChanged() { + if (!isEnabled) { + return; + } + + const player = this; + + updatePlayerVolumeState(player.isMuted(), player.getVolume()); +} + +function refreshFromPlayer(player, type) { + const state = playbackManager.getPlayerState(player); + + onStateChanged.call(player, { type }, state); +} + +function bindToPlayer(player) { + if (player === currentPlayer) { + return; + } + + releaseCurrentPlayer(); + + currentPlayer = player; + + if (!player) { + return; + } + + refreshFromPlayer(player, 'init'); + + Events.on(player, 'playbackstart', onPlaybackStart); + Events.on(player, 'statechange', onPlaybackStart); + Events.on(player, 'repeatmodechange', onRepeatModeChange); + Events.on(player, 'shufflequeuemodechange', onQueueShuffleModeChange); + Events.on(player, 'playbackstop', onPlaybackStopped); + Events.on(player, 'volumechange', onVolumeChanged); + Events.on(player, 'pause', onPlayPauseStateChanged); + Events.on(player, 'unpause', onPlayPauseStateChanged); + Events.on(player, 'timeupdate', onTimeUpdate); +} + +Events.on(playbackManager, 'playerchange', function () { + bindToPlayer(playbackManager.getCurrentPlayer()); +}); + +bindToPlayer(playbackManager.getCurrentPlayer()); + +document.addEventListener('viewbeforeshow', function (e) { + if (!e.detail.options.enableMediaControl) { + if (isVisibilityAllowed) { + isVisibilityAllowed = false; + hideNowPlayingBar(); + } + } else if (!isVisibilityAllowed) { + isVisibilityAllowed = true; + if (currentPlayer) { + refreshFromPlayer(currentPlayer, 'refresh'); + } else { hideNowPlayingBar(); } } +}); - function onVolumeChanged() { - if (!isEnabled) { - return; - } - - const player = this; - - updatePlayerVolumeState(player.isMuted(), player.getVolume()); - } - - function refreshFromPlayer(player, type) { - const state = playbackManager.getPlayerState(player); - - onStateChanged.call(player, { type }, state); - } - - function bindToPlayer(player) { - if (player === currentPlayer) { - return; - } - - releaseCurrentPlayer(); - - currentPlayer = player; - - if (!player) { - return; - } - - refreshFromPlayer(player, 'init'); - - Events.on(player, 'playbackstart', onPlaybackStart); - Events.on(player, 'statechange', onPlaybackStart); - Events.on(player, 'repeatmodechange', onRepeatModeChange); - Events.on(player, 'shufflequeuemodechange', onQueueShuffleModeChange); - Events.on(player, 'playbackstop', onPlaybackStopped); - Events.on(player, 'volumechange', onVolumeChanged); - Events.on(player, 'pause', onPlayPauseStateChanged); - Events.on(player, 'unpause', onPlayPauseStateChanged); - Events.on(player, 'timeupdate', onTimeUpdate); - } - - Events.on(playbackManager, 'playerchange', function () { - bindToPlayer(playbackManager.getCurrentPlayer()); - }); - - bindToPlayer(playbackManager.getCurrentPlayer()); - - document.addEventListener('viewbeforeshow', function (e) { - if (!e.detail.options.enableMediaControl) { - if (isVisibilityAllowed) { - isVisibilityAllowed = false; - hideNowPlayingBar(); - } - } else if (!isVisibilityAllowed) { - isVisibilityAllowed = true; - if (currentPlayer) { - refreshFromPlayer(currentPlayer, 'refresh'); - } else { - hideNowPlayingBar(); - } - } - }); - -/* eslint-enable indent */ diff --git a/src/components/packageManager.js b/src/components/packageManager.js index 3890577b24..b4b7edcf72 100644 --- a/src/components/packageManager.js +++ b/src/components/packageManager.js @@ -1,18 +1,17 @@ import appSettings from '../scripts/settings/appSettings'; import { pluginManager } from './pluginManager'; -/* eslint-disable indent */ - class PackageManager { - #packagesList = []; - #settingsKey = 'installedpackages1'; +class PackageManager { + #packagesList = []; + #settingsKey = 'installedpackages1'; - init() { - console.groupCollapsed('loading packages'); - const manifestUrls = JSON.parse(appSettings.get(this.#settingsKey) || '[]'); + init() { + console.groupCollapsed('loading packages'); + const manifestUrls = JSON.parse(appSettings.get(this.#settingsKey) || '[]'); - return Promise.all(manifestUrls.map((url) => { - return this.loadPackage(url); - })) + return Promise.all(manifestUrls.map((url) => { + return this.loadPackage(url); + })) .then(() => { console.debug('finished loading packages'); return Promise.resolve(); @@ -22,119 +21,117 @@ import { pluginManager } from './pluginManager'; }).finally(() => { console.groupEnd('loading packages'); }); - } - - get packages() { - return this.#packagesList.slice(0); - } - - install(url) { - return this.loadPackage(url, true).then((pkg) => { - const manifestUrls = JSON.parse(appSettings.get(this.#settingsKey) || '[]'); - - if (!manifestUrls.includes(url)) { - manifestUrls.push(url); - appSettings.set(this.#settingsKey, JSON.stringify(manifestUrls)); - } - - return pkg; - }); - } - - uninstall(name) { - const pkg = this.#packagesList.filter((p) => { - return p.name === name; - })[0]; - - if (pkg) { - this.#packagesList = this.#packagesList.filter((p) => { - return p.name !== name; - }); - - this.removeUrl(pkg.url); - } - - return Promise.resolve(); - } - - mapPath(pkg, pluginUrl) { - const urlLower = pluginUrl.toLowerCase(); - if (urlLower.startsWith('http:') || urlLower.startsWith('https:') || urlLower.startsWith('file:')) { - return pluginUrl; - } - - let packageUrl = pkg.url; - packageUrl = packageUrl.substring(0, packageUrl.lastIndexOf('/')); - - packageUrl += '/'; - packageUrl += pluginUrl; - - return packageUrl; - } - - addPackage(pkg) { - this.#packagesList = this.#packagesList.filter((p) => { - return p.name !== pkg.name; - }); - - this.#packagesList.push(pkg); - } - - removeUrl(url) { - let manifestUrls = JSON.parse(appSettings.get(this.#settingsKey) || '[]'); - - manifestUrls = manifestUrls.filter((i) => { - return i !== url; - }); - - appSettings.set(this.#settingsKey, JSON.stringify(manifestUrls)); - } - - loadPackage(url, throwError = false) { - return new Promise((resolve, reject) => { - const xhr = new XMLHttpRequest(); - const originalUrl = url; - url += url.indexOf('?') === -1 ? '?' : '&'; - url += 't=' + new Date().getTime(); - - xhr.open('GET', url, true); - - const onError = () => { - if (throwError === true) { - reject(); - } else { - this.removeUrl(originalUrl); - resolve(); - } - }; - - xhr.onload = () => { - if (this.status < 400) { - const pkg = JSON.parse(this.response); - pkg.url = originalUrl; - - this.addPackage(pkg); - - const plugins = pkg.plugins || []; - if (pkg.plugin) { - plugins.push(pkg.plugin); - } - const promises = plugins.map((pluginUrl) => { - return pluginManager.loadPlugin(this.mapPath(pkg, pluginUrl)); - }); - Promise.all(promises).then(resolve, resolve); - } else { - onError(); - } - }; - - xhr.onerror = onError; - - xhr.send(); - }); - } } -/* eslint-enable indent */ + get packages() { + return this.#packagesList.slice(0); + } + + install(url) { + return this.loadPackage(url, true).then((pkg) => { + const manifestUrls = JSON.parse(appSettings.get(this.#settingsKey) || '[]'); + + if (!manifestUrls.includes(url)) { + manifestUrls.push(url); + appSettings.set(this.#settingsKey, JSON.stringify(manifestUrls)); + } + + return pkg; + }); + } + + uninstall(name) { + const pkg = this.#packagesList.filter((p) => { + return p.name === name; + })[0]; + + if (pkg) { + this.#packagesList = this.#packagesList.filter((p) => { + return p.name !== name; + }); + + this.removeUrl(pkg.url); + } + + return Promise.resolve(); + } + + mapPath(pkg, pluginUrl) { + const urlLower = pluginUrl.toLowerCase(); + if (urlLower.startsWith('http:') || urlLower.startsWith('https:') || urlLower.startsWith('file:')) { + return pluginUrl; + } + + let packageUrl = pkg.url; + packageUrl = packageUrl.substring(0, packageUrl.lastIndexOf('/')); + + packageUrl += '/'; + packageUrl += pluginUrl; + + return packageUrl; + } + + addPackage(pkg) { + this.#packagesList = this.#packagesList.filter((p) => { + return p.name !== pkg.name; + }); + + this.#packagesList.push(pkg); + } + + removeUrl(url) { + let manifestUrls = JSON.parse(appSettings.get(this.#settingsKey) || '[]'); + + manifestUrls = manifestUrls.filter((i) => { + return i !== url; + }); + + appSettings.set(this.#settingsKey, JSON.stringify(manifestUrls)); + } + + loadPackage(url, throwError = false) { + return new Promise((resolve, reject) => { + const xhr = new XMLHttpRequest(); + const originalUrl = url; + url += url.indexOf('?') === -1 ? '?' : '&'; + url += 't=' + new Date().getTime(); + + xhr.open('GET', url, true); + + const onError = () => { + if (throwError === true) { + reject(); + } else { + this.removeUrl(originalUrl); + resolve(); + } + }; + + xhr.onload = () => { + if (this.status < 400) { + const pkg = JSON.parse(this.response); + pkg.url = originalUrl; + + this.addPackage(pkg); + + const plugins = pkg.plugins || []; + if (pkg.plugin) { + plugins.push(pkg.plugin); + } + const promises = plugins.map((pluginUrl) => { + return pluginManager.loadPlugin(this.mapPath(pkg, pluginUrl)); + }); + Promise.all(promises).then(resolve, resolve); + } else { + onError(); + } + }; + + xhr.onerror = onError; + + xhr.send(); + }); + } +} export default new PackageManager(); diff --git a/src/components/playback/mediasession.js b/src/components/playback/mediasession.js index 6c560c222d..3bfe44e37d 100644 --- a/src/components/playback/mediasession.js +++ b/src/components/playback/mediasession.js @@ -4,259 +4,256 @@ import Events from '../../utils/events.ts'; import ServerConnections from '../ServerConnections'; import shell from '../../scripts/shell'; -/* eslint-disable indent */ +// Reports media playback to the device for lock screen control - // Reports media playback to the device for lock screen control +let currentPlayer; - let currentPlayer; +function seriesImageUrl(item, options = {}) { + options.type = options.type || 'Primary'; - function seriesImageUrl(item, options = {}) { - options.type = options.type || 'Primary'; + if (item.Type !== 'Episode') { + return null; + } else if (options.type === 'Primary' && item.SeriesPrimaryImageTag) { + options.tag = item.SeriesPrimaryImageTag; - if (item.Type !== 'Episode') { - return null; - } else if (options.type === 'Primary' && item.SeriesPrimaryImageTag) { - options.tag = item.SeriesPrimaryImageTag; + return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); + } else if (options.type === 'Thumb') { + if (item.SeriesThumbImageTag) { + options.tag = item.SeriesThumbImageTag; return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); - } else if (options.type === 'Thumb') { - if (item.SeriesThumbImageTag) { - options.tag = item.SeriesThumbImageTag; + } else if (item.ParentThumbImageTag) { + options.tag = item.ParentThumbImageTag; - return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); - } else if (item.ParentThumbImageTag) { - options.tag = item.ParentThumbImageTag; - - return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.ParentThumbItemId, options); - } + return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.ParentThumbItemId, options); } + } + return null; +} + +function imageUrl(item, options = {}) { + options.type = options.type || 'Primary'; + + if (item.ImageTags && item.ImageTags[options.type]) { + options.tag = item.ImageTags[options.type]; + + return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.Id, options); + } else if (item.AlbumId && item.AlbumPrimaryImageTag) { + options.tag = item.AlbumPrimaryImageTag; + + return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.AlbumId, options); + } + + return null; +} + +function getImageUrl(item, imageOptions = {}) { + const url = seriesImageUrl(item, imageOptions) || imageUrl(item, imageOptions); + + if (url) { + const height = imageOptions.height || imageOptions.maxHeight; + + return { + src: url, + sizes: height + 'x' + height + }; + } else { return null; } +} - function imageUrl(item, options = {}) { - options.type = options.type || 'Primary'; +function getImageUrls(item, imageSizes = [96, 128, 192, 256, 384, 512]) { + const list = []; - if (item.ImageTags && item.ImageTags[options.type]) { - options.tag = item.ImageTags[options.type]; - - return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.Id, options); - } else if (item.AlbumId && item.AlbumPrimaryImageTag) { - options.tag = item.AlbumPrimaryImageTag; - - return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.AlbumId, options); + imageSizes.forEach((size) => { + const url = getImageUrl(item, { height: size }); + if (url !== null) { + list.push(url); } + }); - return null; + return list; +} + +function updatePlayerState(player, state, eventName) { + // Don't go crazy reporting position changes + if (eventName === 'timeupdate') { + // Only report if this item hasn't been reported yet, or if there's an actual playback change. + // Don't report on simple time updates + return; } - function getImageUrl(item, imageOptions = {}) { - const url = seriesImageUrl(item, imageOptions) || imageUrl(item, imageOptions); + const item = state.NowPlayingItem; - if (url) { - const height = imageOptions.height || imageOptions.maxHeight; - - return { - src: url, - sizes: height + 'x' + height - }; - } else { - return null; - } - } - - function getImageUrls(item, imageSizes = [96, 128, 192, 256, 384, 512]) { - const list = []; - - imageSizes.forEach((size) => { - const url = getImageUrl(item, { height: size }); - if (url !== null) { - list.push(url); - } - }); - - return list; - } - - function updatePlayerState(player, state, eventName) { - // Don't go crazy reporting position changes - if (eventName === 'timeupdate') { - // Only report if this item hasn't been reported yet, or if there's an actual playback change. - // Don't report on simple time updates - return; - } - - const item = state.NowPlayingItem; - - if (!item) { - hideMediaControls(); - return; - } - - if (eventName === 'init') { // transform "init" event into "timeupdate" to restraint update rate - eventName = 'timeupdate'; - } - - const isVideo = item.MediaType === 'Video'; - const isLocalPlayer = player.isLocalPlayer || false; - - // Local players do their own notifications - if (isLocalPlayer && isVideo) { - return; - } - - const playState = state.PlayState || {}; - const parts = nowPlayingHelper.getNowPlayingNames(item); - const artist = parts[parts.length - 1].text; - const title = parts.length === 1 ? '' : parts[0].text; - - const album = item.Album || ''; - const itemId = item.Id; - - // Convert to ms - const duration = parseInt(item.RunTimeTicks ? (item.RunTimeTicks / 10000) : 0, 10); - const currentTime = parseInt(playState.PositionTicks ? (playState.PositionTicks / 10000) : 0, 10); - - const isPaused = playState.IsPaused || false; - const canSeek = playState.CanSeek || false; - - if ('mediaSession' in navigator) { - /* eslint-disable-next-line compat/compat */ - navigator.mediaSession.metadata = new MediaMetadata({ - title: title, - artist: artist, - album: album, - artwork: getImageUrls(item) - }); - } else { - const itemImageUrl = seriesImageUrl(item, { maxHeight: 3000 }) || imageUrl(item, { maxHeight: 3000 }); - shell.updateMediaSession({ - action: eventName, - isLocalPlayer: isLocalPlayer, - itemId: itemId, - title: title, - artist: artist, - album: album, - duration: duration, - position: currentTime, - imageUrl: itemImageUrl, - canSeek: canSeek, - isPaused: isPaused - }); - } - } - - function onGeneralEvent(e) { - const state = playbackManager.getPlayerState(this); - - updatePlayerState(this, state, e.type); - } - - function onStateChanged(e, state) { - updatePlayerState(this, state, 'statechange'); - } - - function onPlaybackStart(e, state) { - updatePlayerState(this, state, e.type); - } - - function onPlaybackStopped() { + if (!item) { hideMediaControls(); + return; } - function releaseCurrentPlayer() { - if (currentPlayer) { - Events.off(currentPlayer, 'playbackstart', onPlaybackStart); - Events.off(currentPlayer, 'playbackstop', onPlaybackStopped); - Events.off(currentPlayer, 'unpause', onGeneralEvent); - Events.off(currentPlayer, 'pause', onGeneralEvent); - Events.off(currentPlayer, 'statechange', onStateChanged); - Events.off(currentPlayer, 'timeupdate', onGeneralEvent); - - currentPlayer = null; - - hideMediaControls(); - } + if (eventName === 'init') { // transform "init" event into "timeupdate" to restraint update rate + eventName = 'timeupdate'; } - function hideMediaControls() { - if ('mediaSession' in navigator) { - /* eslint-disable-next-line compat/compat */ - navigator.mediaSession.metadata = null; - } else { - shell.hideMediaSession(); - } + const isVideo = item.MediaType === 'Video'; + const isLocalPlayer = player.isLocalPlayer || false; + + // Local players do their own notifications + if (isLocalPlayer && isVideo) { + return; } - function bindToPlayer(player) { - releaseCurrentPlayer(); + const playState = state.PlayState || {}; + const parts = nowPlayingHelper.getNowPlayingNames(item); + const artist = parts[parts.length - 1].text; + const title = parts.length === 1 ? '' : parts[0].text; - if (!player) { - return; - } + const album = item.Album || ''; + const itemId = item.Id; - currentPlayer = player; + // Convert to ms + const duration = parseInt(item.RunTimeTicks ? (item.RunTimeTicks / 10000) : 0, 10); + const currentTime = parseInt(playState.PositionTicks ? (playState.PositionTicks / 10000) : 0, 10); - const state = playbackManager.getPlayerState(player); - updatePlayerState(player, state, 'init'); - - Events.on(currentPlayer, 'playbackstart', onPlaybackStart); - Events.on(currentPlayer, 'playbackstop', onPlaybackStopped); - Events.on(currentPlayer, 'unpause', onGeneralEvent); - Events.on(currentPlayer, 'pause', onGeneralEvent); - Events.on(currentPlayer, 'statechange', onStateChanged); - Events.on(currentPlayer, 'timeupdate', onGeneralEvent); - } - - function execute(name) { - playbackManager[name](currentPlayer); - } + const isPaused = playState.IsPaused || false; + const canSeek = playState.CanSeek || false; if ('mediaSession' in navigator) { /* eslint-disable-next-line compat/compat */ - navigator.mediaSession.setActionHandler('previoustrack', function () { - execute('previousTrack'); + navigator.mediaSession.metadata = new MediaMetadata({ + title: title, + artist: artist, + album: album, + artwork: getImageUrls(item) }); - - /* eslint-disable-next-line compat/compat */ - navigator.mediaSession.setActionHandler('nexttrack', function () { - execute('nextTrack'); - }); - - /* eslint-disable-next-line compat/compat */ - navigator.mediaSession.setActionHandler('play', function () { - execute('unpause'); - }); - - /* eslint-disable-next-line compat/compat */ - navigator.mediaSession.setActionHandler('pause', function () { - execute('pause'); - }); - - /* eslint-disable-next-line compat/compat */ - navigator.mediaSession.setActionHandler('seekbackward', function () { - execute('rewind'); - }); - - /* eslint-disable-next-line compat/compat */ - navigator.mediaSession.setActionHandler('seekforward', function () { - execute('fastForward'); - }); - - /* eslint-disable-next-line compat/compat */ - navigator.mediaSession.setActionHandler('seekto', function (object) { - const item = playbackManager.getPlayerState(currentPlayer).NowPlayingItem; - // Convert to ms - const duration = parseInt(item.RunTimeTicks ? (item.RunTimeTicks / 10000) : 0, 10); - const wantedTime = object.seekTime * 1000; - playbackManager.seekPercent(wantedTime / duration * 100, currentPlayer); + } else { + const itemImageUrl = seriesImageUrl(item, { maxHeight: 3000 }) || imageUrl(item, { maxHeight: 3000 }); + shell.updateMediaSession({ + action: eventName, + isLocalPlayer: isLocalPlayer, + itemId: itemId, + title: title, + artist: artist, + album: album, + duration: duration, + position: currentTime, + imageUrl: itemImageUrl, + canSeek: canSeek, + isPaused: isPaused }); } +} - Events.on(playbackManager, 'playerchange', function () { - bindToPlayer(playbackManager.getCurrentPlayer()); +function onGeneralEvent(e) { + const state = playbackManager.getPlayerState(this); + + updatePlayerState(this, state, e.type); +} + +function onStateChanged(e, state) { + updatePlayerState(this, state, 'statechange'); +} + +function onPlaybackStart(e, state) { + updatePlayerState(this, state, e.type); +} + +function onPlaybackStopped() { + hideMediaControls(); +} + +function releaseCurrentPlayer() { + if (currentPlayer) { + Events.off(currentPlayer, 'playbackstart', onPlaybackStart); + Events.off(currentPlayer, 'playbackstop', onPlaybackStopped); + Events.off(currentPlayer, 'unpause', onGeneralEvent); + Events.off(currentPlayer, 'pause', onGeneralEvent); + Events.off(currentPlayer, 'statechange', onStateChanged); + Events.off(currentPlayer, 'timeupdate', onGeneralEvent); + + currentPlayer = null; + + hideMediaControls(); + } +} + +function hideMediaControls() { + if ('mediaSession' in navigator) { + /* eslint-disable-next-line compat/compat */ + navigator.mediaSession.metadata = null; + } else { + shell.hideMediaSession(); + } +} + +function bindToPlayer(player) { + releaseCurrentPlayer(); + + if (!player) { + return; + } + + currentPlayer = player; + + const state = playbackManager.getPlayerState(player); + updatePlayerState(player, state, 'init'); + + Events.on(currentPlayer, 'playbackstart', onPlaybackStart); + Events.on(currentPlayer, 'playbackstop', onPlaybackStopped); + Events.on(currentPlayer, 'unpause', onGeneralEvent); + Events.on(currentPlayer, 'pause', onGeneralEvent); + Events.on(currentPlayer, 'statechange', onStateChanged); + Events.on(currentPlayer, 'timeupdate', onGeneralEvent); +} + +function execute(name) { + playbackManager[name](currentPlayer); +} + +if ('mediaSession' in navigator) { + /* eslint-disable-next-line compat/compat */ + navigator.mediaSession.setActionHandler('previoustrack', function () { + execute('previousTrack'); }); - bindToPlayer(playbackManager.getCurrentPlayer()); + /* eslint-disable-next-line compat/compat */ + navigator.mediaSession.setActionHandler('nexttrack', function () { + execute('nextTrack'); + }); + + /* eslint-disable-next-line compat/compat */ + navigator.mediaSession.setActionHandler('play', function () { + execute('unpause'); + }); + + /* eslint-disable-next-line compat/compat */ + navigator.mediaSession.setActionHandler('pause', function () { + execute('pause'); + }); + + /* eslint-disable-next-line compat/compat */ + navigator.mediaSession.setActionHandler('seekbackward', function () { + execute('rewind'); + }); + + /* eslint-disable-next-line compat/compat */ + navigator.mediaSession.setActionHandler('seekforward', function () { + execute('fastForward'); + }); + + /* eslint-disable-next-line compat/compat */ + navigator.mediaSession.setActionHandler('seekto', function (object) { + const item = playbackManager.getPlayerState(currentPlayer).NowPlayingItem; + // Convert to ms + const duration = parseInt(item.RunTimeTicks ? (item.RunTimeTicks / 10000) : 0, 10); + const wantedTime = object.seekTime * 1000; + playbackManager.seekPercent(wantedTime / duration * 100, currentPlayer); + }); +} + +Events.on(playbackManager, 'playerchange', function () { + bindToPlayer(playbackManager.getCurrentPlayer()); +}); + +bindToPlayer(playbackManager.getCurrentPlayer()); -/* eslint-enable indent */ diff --git a/src/components/playbackSettings/playbackSettings.js b/src/components/playbackSettings/playbackSettings.js index 1969d478c1..2d5b660629 100644 --- a/src/components/playbackSettings/playbackSettings.js +++ b/src/components/playbackSettings/playbackSettings.js @@ -12,102 +12,120 @@ import ServerConnections from '../ServerConnections'; import toast from '../toast/toast'; import template from './playbackSettings.template.html'; -/* eslint-disable indent */ +function fillSkipLengths(select) { + const options = [5, 10, 15, 20, 25, 30]; - function fillSkipLengths(select) { - const options = [5, 10, 15, 20, 25, 30]; + select.innerHTML = options.map(option => { + return { + name: globalize.translate('ValueSeconds', option), + value: option * 1000 + }; + }).map(o => { + return ``; + }).join(''); +} - select.innerHTML = options.map(option => { - return { - name: globalize.translate('ValueSeconds', option), - value: option * 1000 - }; - }).map(o => { - return ``; - }).join(''); +function populateLanguages(select, languages) { + let html = ''; + + html += ``; + + for (let i = 0, length = languages.length; i < length; i++) { + const culture = languages[i]; + + html += ``; } - function populateLanguages(select, languages) { - let html = ''; + select.innerHTML = html; +} - html += ``; +function fillQuality(select, isInNetwork, mediatype, maxVideoWidth) { + const options = mediatype === 'Audio' ? qualityoptions.getAudioQualityOptions({ - for (let i = 0, length = languages.length; i < length; i++) { - const culture = languages[i]; + currentMaxBitrate: appSettings.maxStreamingBitrate(isInNetwork, mediatype), + isAutomaticBitrateEnabled: appSettings.enableAutomaticBitrateDetection(isInNetwork, mediatype), + enableAuto: true - html += ``; - } + }) : qualityoptions.getVideoQualityOptions({ - select.innerHTML = html; + currentMaxBitrate: appSettings.maxStreamingBitrate(isInNetwork, mediatype), + isAutomaticBitrateEnabled: appSettings.enableAutomaticBitrateDetection(isInNetwork, mediatype), + enableAuto: true, + maxVideoWidth + + }); + + select.innerHTML = options.map(i => { + // render empty string instead of 0 for the auto option + return ``; + }).join(''); +} + +function setMaxBitrateIntoField(select, isInNetwork, mediatype) { + fillQuality(select, isInNetwork, mediatype); + + if (appSettings.enableAutomaticBitrateDetection(isInNetwork, mediatype)) { + select.value = ''; + } else { + select.value = appSettings.maxStreamingBitrate(isInNetwork, mediatype); + } +} + +function fillChromecastQuality(select, maxVideoWidth) { + const options = qualityoptions.getVideoQualityOptions({ + + currentMaxBitrate: appSettings.maxChromecastBitrate(), + isAutomaticBitrateEnabled: !appSettings.maxChromecastBitrate(), + enableAuto: true, + maxVideoWidth + }); + + select.innerHTML = options.map(i => { + // render empty string instead of 0 for the auto option + return ``; + }).join(''); + + select.value = appSettings.maxChromecastBitrate() || ''; +} + +function setMaxBitrateFromField(select, isInNetwork, mediatype) { + if (select.value) { + appSettings.maxStreamingBitrate(isInNetwork, mediatype, select.value); + appSettings.enableAutomaticBitrateDetection(isInNetwork, mediatype, false); + } else { + appSettings.enableAutomaticBitrateDetection(isInNetwork, mediatype, true); + } +} + +function showHideQualityFields(context, user, apiClient) { + if (user.Policy.EnableVideoPlaybackTranscoding) { + context.querySelector('.videoQualitySection').classList.remove('hide'); + } else { + context.querySelector('.videoQualitySection').classList.add('hide'); } - function fillQuality(select, isInNetwork, mediatype, maxVideoWidth) { - const options = mediatype === 'Audio' ? qualityoptions.getAudioQualityOptions({ + if (appHost.supports('multiserver')) { + context.querySelector('.fldVideoInNetworkQuality').classList.remove('hide'); + context.querySelector('.fldVideoInternetQuality').classList.remove('hide'); - currentMaxBitrate: appSettings.maxStreamingBitrate(isInNetwork, mediatype), - isAutomaticBitrateEnabled: appSettings.enableAutomaticBitrateDetection(isInNetwork, mediatype), - enableAuto: true - - }) : qualityoptions.getVideoQualityOptions({ - - currentMaxBitrate: appSettings.maxStreamingBitrate(isInNetwork, mediatype), - isAutomaticBitrateEnabled: appSettings.enableAutomaticBitrateDetection(isInNetwork, mediatype), - enableAuto: true, - maxVideoWidth - - }); - - select.innerHTML = options.map(i => { - // render empty string instead of 0 for the auto option - return ``; - }).join(''); - } - - function setMaxBitrateIntoField(select, isInNetwork, mediatype) { - fillQuality(select, isInNetwork, mediatype); - - if (appSettings.enableAutomaticBitrateDetection(isInNetwork, mediatype)) { - select.value = ''; + if (user.Policy.EnableAudioPlaybackTranscoding) { + context.querySelector('.musicQualitySection').classList.remove('hide'); } else { - select.value = appSettings.maxStreamingBitrate(isInNetwork, mediatype); - } - } - - function fillChromecastQuality(select, maxVideoWidth) { - const options = qualityoptions.getVideoQualityOptions({ - - currentMaxBitrate: appSettings.maxChromecastBitrate(), - isAutomaticBitrateEnabled: !appSettings.maxChromecastBitrate(), - enableAuto: true, - maxVideoWidth - }); - - select.innerHTML = options.map(i => { - // render empty string instead of 0 for the auto option - return ``; - }).join(''); - - select.value = appSettings.maxChromecastBitrate() || ''; - } - - function setMaxBitrateFromField(select, isInNetwork, mediatype) { - if (select.value) { - appSettings.maxStreamingBitrate(isInNetwork, mediatype, select.value); - appSettings.enableAutomaticBitrateDetection(isInNetwork, mediatype, false); - } else { - appSettings.enableAutomaticBitrateDetection(isInNetwork, mediatype, true); - } - } - - function showHideQualityFields(context, user, apiClient) { - if (user.Policy.EnableVideoPlaybackTranscoding) { - context.querySelector('.videoQualitySection').classList.remove('hide'); - } else { - context.querySelector('.videoQualitySection').classList.add('hide'); + context.querySelector('.musicQualitySection').classList.add('hide'); } - if (appHost.supports('multiserver')) { + return; + } + + apiClient.getEndpointInfo().then(endpointInfo => { + if (endpointInfo.IsInNetwork) { context.querySelector('.fldVideoInNetworkQuality').classList.remove('hide'); + + context.querySelector('.fldVideoInternetQuality').classList.add('hide'); + context.querySelector('.musicQualitySection').classList.add('hide'); + } else { + context.querySelector('.fldVideoInNetworkQuality').classList.add('hide'); + context.querySelector('.fldVideoInternetQuality').classList.remove('hide'); if (user.Policy.EnableAudioPlaybackTranscoding) { @@ -115,249 +133,228 @@ import template from './playbackSettings.template.html'; } else { context.querySelector('.musicQualitySection').classList.add('hide'); } - - return; } + }); +} - apiClient.getEndpointInfo().then(endpointInfo => { - if (endpointInfo.IsInNetwork) { - context.querySelector('.fldVideoInNetworkQuality').classList.remove('hide'); - - context.querySelector('.fldVideoInternetQuality').classList.add('hide'); - context.querySelector('.musicQualitySection').classList.add('hide'); - } else { - context.querySelector('.fldVideoInNetworkQuality').classList.add('hide'); - - context.querySelector('.fldVideoInternetQuality').classList.remove('hide'); - - if (user.Policy.EnableAudioPlaybackTranscoding) { - context.querySelector('.musicQualitySection').classList.remove('hide'); - } else { - context.querySelector('.musicQualitySection').classList.add('hide'); - } - } - }); +function showOrHideEpisodesField(context) { + if (browser.tizen || browser.web0s) { + context.querySelector('.fldEpisodeAutoPlay').classList.add('hide'); + return; } - function showOrHideEpisodesField(context) { - if (browser.tizen || browser.web0s) { - context.querySelector('.fldEpisodeAutoPlay').classList.add('hide'); - return; - } + context.querySelector('.fldEpisodeAutoPlay').classList.remove('hide'); +} - context.querySelector('.fldEpisodeAutoPlay').classList.remove('hide'); +function loadForm(context, user, userSettings, apiClient) { + const loggedInUserId = apiClient.getCurrentUserId(); + const userId = user.Id; + + showHideQualityFields(context, user, apiClient); + + context.querySelector('#selectAllowedAudioChannels').value = userSettings.allowedAudioChannels(); + + apiClient.getCultures().then(allCultures => { + populateLanguages(context.querySelector('#selectAudioLanguage'), allCultures); + + context.querySelector('#selectAudioLanguage', context).value = user.Configuration.AudioLanguagePreference || ''; + context.querySelector('.chkEpisodeAutoPlay').checked = user.Configuration.EnableNextEpisodeAutoPlay || false; + }); + + if (appHost.supports('externalplayerintent') && userId === loggedInUserId) { + context.querySelector('.fldExternalPlayer').classList.remove('hide'); + } else { + context.querySelector('.fldExternalPlayer').classList.add('hide'); } - function loadForm(context, user, userSettings, apiClient) { - const loggedInUserId = apiClient.getCurrentUserId(); - const userId = user.Id; + if (userId === loggedInUserId && (user.Policy.EnableVideoPlaybackTranscoding || user.Policy.EnableAudioPlaybackTranscoding)) { + context.querySelector('.qualitySections').classList.remove('hide'); - showHideQualityFields(context, user, apiClient); - - context.querySelector('#selectAllowedAudioChannels').value = userSettings.allowedAudioChannels(); - - apiClient.getCultures().then(allCultures => { - populateLanguages(context.querySelector('#selectAudioLanguage'), allCultures); - - context.querySelector('#selectAudioLanguage', context).value = user.Configuration.AudioLanguagePreference || ''; - context.querySelector('.chkEpisodeAutoPlay').checked = user.Configuration.EnableNextEpisodeAutoPlay || false; - }); - - if (appHost.supports('externalplayerintent') && userId === loggedInUserId) { - context.querySelector('.fldExternalPlayer').classList.remove('hide'); + if (appHost.supports('chromecast') && user.Policy.EnableVideoPlaybackTranscoding) { + context.querySelector('.fldChromecastQuality').classList.remove('hide'); } else { - context.querySelector('.fldExternalPlayer').classList.add('hide'); - } - - if (userId === loggedInUserId && (user.Policy.EnableVideoPlaybackTranscoding || user.Policy.EnableAudioPlaybackTranscoding)) { - context.querySelector('.qualitySections').classList.remove('hide'); - - if (appHost.supports('chromecast') && user.Policy.EnableVideoPlaybackTranscoding) { - context.querySelector('.fldChromecastQuality').classList.remove('hide'); - } else { - context.querySelector('.fldChromecastQuality').classList.add('hide'); - } - } else { - context.querySelector('.qualitySections').classList.add('hide'); context.querySelector('.fldChromecastQuality').classList.add('hide'); } - - context.querySelector('.chkPlayDefaultAudioTrack').checked = user.Configuration.PlayDefaultAudioTrack || false; - context.querySelector('.chkPreferFmp4HlsContainer').checked = userSettings.preferFmp4HlsContainer(); - context.querySelector('.chkEnableCinemaMode').checked = userSettings.enableCinemaMode(); - context.querySelector('.chkEnableNextVideoOverlay').checked = userSettings.enableNextVideoInfoOverlay(); - context.querySelector('.chkRememberAudioSelections').checked = user.Configuration.RememberAudioSelections || false; - context.querySelector('.chkRememberSubtitleSelections').checked = user.Configuration.RememberSubtitleSelections || false; - context.querySelector('.chkExternalVideoPlayer').checked = appSettings.enableSystemExternalPlayers(); - - setMaxBitrateIntoField(context.querySelector('.selectVideoInNetworkQuality'), true, 'Video'); - setMaxBitrateIntoField(context.querySelector('.selectVideoInternetQuality'), false, 'Video'); - setMaxBitrateIntoField(context.querySelector('.selectMusicInternetQuality'), false, 'Audio'); - - fillChromecastQuality(context.querySelector('.selectChromecastVideoQuality')); - - const selectChromecastVersion = context.querySelector('.selectChromecastVersion'); - selectChromecastVersion.value = userSettings.chromecastVersion(); - - const selectLabelMaxVideoWidth = context.querySelector('.selectLabelMaxVideoWidth'); - selectLabelMaxVideoWidth.value = appSettings.maxVideoWidth(); - - const selectSkipForwardLength = context.querySelector('.selectSkipForwardLength'); - fillSkipLengths(selectSkipForwardLength); - selectSkipForwardLength.value = userSettings.skipForwardLength(); - - const selectSkipBackLength = context.querySelector('.selectSkipBackLength'); - fillSkipLengths(selectSkipBackLength); - selectSkipBackLength.value = userSettings.skipBackLength(); - - showOrHideEpisodesField(context); - - loading.hide(); + } else { + context.querySelector('.qualitySections').classList.add('hide'); + context.querySelector('.fldChromecastQuality').classList.add('hide'); } - function saveUser(context, user, userSettingsInstance, apiClient) { - appSettings.enableSystemExternalPlayers(context.querySelector('.chkExternalVideoPlayer').checked); + context.querySelector('.chkPlayDefaultAudioTrack').checked = user.Configuration.PlayDefaultAudioTrack || false; + context.querySelector('.chkPreferFmp4HlsContainer').checked = userSettings.preferFmp4HlsContainer(); + context.querySelector('.chkEnableCinemaMode').checked = userSettings.enableCinemaMode(); + context.querySelector('.chkEnableNextVideoOverlay').checked = userSettings.enableNextVideoInfoOverlay(); + context.querySelector('.chkRememberAudioSelections').checked = user.Configuration.RememberAudioSelections || false; + context.querySelector('.chkRememberSubtitleSelections').checked = user.Configuration.RememberSubtitleSelections || false; + context.querySelector('.chkExternalVideoPlayer').checked = appSettings.enableSystemExternalPlayers(); - appSettings.maxChromecastBitrate(context.querySelector('.selectChromecastVideoQuality').value); - appSettings.maxVideoWidth(context.querySelector('.selectLabelMaxVideoWidth').value); + setMaxBitrateIntoField(context.querySelector('.selectVideoInNetworkQuality'), true, 'Video'); + setMaxBitrateIntoField(context.querySelector('.selectVideoInternetQuality'), false, 'Video'); + setMaxBitrateIntoField(context.querySelector('.selectMusicInternetQuality'), false, 'Audio'); - setMaxBitrateFromField(context.querySelector('.selectVideoInNetworkQuality'), true, 'Video'); - setMaxBitrateFromField(context.querySelector('.selectVideoInternetQuality'), false, 'Video'); - setMaxBitrateFromField(context.querySelector('.selectMusicInternetQuality'), false, 'Audio'); + fillChromecastQuality(context.querySelector('.selectChromecastVideoQuality')); - userSettingsInstance.allowedAudioChannels(context.querySelector('#selectAllowedAudioChannels').value); - user.Configuration.AudioLanguagePreference = context.querySelector('#selectAudioLanguage').value; - user.Configuration.PlayDefaultAudioTrack = context.querySelector('.chkPlayDefaultAudioTrack').checked; - user.Configuration.EnableNextEpisodeAutoPlay = context.querySelector('.chkEpisodeAutoPlay').checked; - userSettingsInstance.preferFmp4HlsContainer(context.querySelector('.chkPreferFmp4HlsContainer').checked); - userSettingsInstance.enableCinemaMode(context.querySelector('.chkEnableCinemaMode').checked); + const selectChromecastVersion = context.querySelector('.selectChromecastVersion'); + selectChromecastVersion.value = userSettings.chromecastVersion(); - userSettingsInstance.enableNextVideoInfoOverlay(context.querySelector('.chkEnableNextVideoOverlay').checked); - user.Configuration.RememberAudioSelections = context.querySelector('.chkRememberAudioSelections').checked; - user.Configuration.RememberSubtitleSelections = context.querySelector('.chkRememberSubtitleSelections').checked; - userSettingsInstance.chromecastVersion(context.querySelector('.selectChromecastVersion').value); - userSettingsInstance.skipForwardLength(context.querySelector('.selectSkipForwardLength').value); - userSettingsInstance.skipBackLength(context.querySelector('.selectSkipBackLength').value); + const selectLabelMaxVideoWidth = context.querySelector('.selectLabelMaxVideoWidth'); + selectLabelMaxVideoWidth.value = appSettings.maxVideoWidth(); - return apiClient.updateUserConfiguration(user.Id, user.Configuration); + const selectSkipForwardLength = context.querySelector('.selectSkipForwardLength'); + fillSkipLengths(selectSkipForwardLength); + selectSkipForwardLength.value = userSettings.skipForwardLength(); + + const selectSkipBackLength = context.querySelector('.selectSkipBackLength'); + fillSkipLengths(selectSkipBackLength); + selectSkipBackLength.value = userSettings.skipBackLength(); + + showOrHideEpisodesField(context); + + loading.hide(); +} + +function saveUser(context, user, userSettingsInstance, apiClient) { + appSettings.enableSystemExternalPlayers(context.querySelector('.chkExternalVideoPlayer').checked); + + appSettings.maxChromecastBitrate(context.querySelector('.selectChromecastVideoQuality').value); + appSettings.maxVideoWidth(context.querySelector('.selectLabelMaxVideoWidth').value); + + setMaxBitrateFromField(context.querySelector('.selectVideoInNetworkQuality'), true, 'Video'); + setMaxBitrateFromField(context.querySelector('.selectVideoInternetQuality'), false, 'Video'); + setMaxBitrateFromField(context.querySelector('.selectMusicInternetQuality'), false, 'Audio'); + + userSettingsInstance.allowedAudioChannels(context.querySelector('#selectAllowedAudioChannels').value); + user.Configuration.AudioLanguagePreference = context.querySelector('#selectAudioLanguage').value; + user.Configuration.PlayDefaultAudioTrack = context.querySelector('.chkPlayDefaultAudioTrack').checked; + user.Configuration.EnableNextEpisodeAutoPlay = context.querySelector('.chkEpisodeAutoPlay').checked; + userSettingsInstance.preferFmp4HlsContainer(context.querySelector('.chkPreferFmp4HlsContainer').checked); + userSettingsInstance.enableCinemaMode(context.querySelector('.chkEnableCinemaMode').checked); + + userSettingsInstance.enableNextVideoInfoOverlay(context.querySelector('.chkEnableNextVideoOverlay').checked); + user.Configuration.RememberAudioSelections = context.querySelector('.chkRememberAudioSelections').checked; + user.Configuration.RememberSubtitleSelections = context.querySelector('.chkRememberSubtitleSelections').checked; + userSettingsInstance.chromecastVersion(context.querySelector('.selectChromecastVersion').value); + userSettingsInstance.skipForwardLength(context.querySelector('.selectSkipForwardLength').value); + userSettingsInstance.skipBackLength(context.querySelector('.selectSkipBackLength').value); + + return apiClient.updateUserConfiguration(user.Id, user.Configuration); +} + +function save(instance, context, userId, userSettings, apiClient, enableSaveConfirmation) { + loading.show(); + + apiClient.getUser(userId).then(user => { + saveUser(context, user, userSettings, apiClient).then(() => { + loading.hide(); + if (enableSaveConfirmation) { + toast(globalize.translate('SettingsSaved')); + } + + Events.trigger(instance, 'saved'); + }, () => { + loading.hide(); + }); + }); +} + +function setSelectValue(select, value, defaultValue) { + select.value = value; + + if (select.selectedIndex < 0) { + select.value = defaultValue; + } +} + +function onMaxVideoWidthChange(e) { + const context = this.options.element; + + const selectVideoInNetworkQuality = context.querySelector('.selectVideoInNetworkQuality'); + const selectVideoInternetQuality = context.querySelector('.selectVideoInternetQuality'); + const selectChromecastVideoQuality = context.querySelector('.selectChromecastVideoQuality'); + + const selectVideoInNetworkQualityValue = selectVideoInNetworkQuality.value; + const selectVideoInternetQualityValue = selectVideoInternetQuality.value; + const selectChromecastVideoQualityValue = selectChromecastVideoQuality.value; + + const maxVideoWidth = parseInt(e.target.value || '0', 10) || 0; + + fillQuality(selectVideoInNetworkQuality, true, 'Video', maxVideoWidth); + fillQuality(selectVideoInternetQuality, false, 'Video', maxVideoWidth); + fillChromecastQuality(selectChromecastVideoQuality, maxVideoWidth); + + setSelectValue(selectVideoInNetworkQuality, selectVideoInNetworkQualityValue, ''); + setSelectValue(selectVideoInternetQuality, selectVideoInternetQualityValue, ''); + setSelectValue(selectChromecastVideoQuality, selectChromecastVideoQualityValue, ''); +} + +function onSubmit(e) { + const self = this; + const apiClient = ServerConnections.getApiClient(self.options.serverId); + const userId = self.options.userId; + const userSettings = self.options.userSettings; + + userSettings.setUserInfo(userId, apiClient).then(() => { + const 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) { + options.element.innerHTML = globalize.translateHtml(template, 'core'); + + options.element.querySelector('form').addEventListener('submit', onSubmit.bind(self)); + + if (options.enableSaveButton) { + options.element.querySelector('.btnSave').classList.remove('hide'); } - function save(instance, context, userId, userSettings, apiClient, enableSaveConfirmation) { + options.element.querySelector('.selectLabelMaxVideoWidth').addEventListener('change', onMaxVideoWidthChange.bind(self)); + + self.loadData(); + + if (options.autoFocus) { + focusManager.autoFocus(options.element); + } +} + +class PlaybackSettings { + constructor(options) { + this.options = options; + embed(options, this); + } + + loadData() { + const self = this; + const context = self.options.element; + loading.show(); - apiClient.getUser(userId).then(user => { - saveUser(context, user, userSettings, apiClient).then(() => { - loading.hide(); - if (enableSaveConfirmation) { - toast(globalize.translate('SettingsSaved')); - } - - Events.trigger(instance, 'saved'); - }, () => { - loading.hide(); - }); - }); - } - - function setSelectValue(select, value, defaultValue) { - select.value = value; - - if (select.selectedIndex < 0) { - select.value = defaultValue; - } - } - - function onMaxVideoWidthChange(e) { - const context = this.options.element; - - const selectVideoInNetworkQuality = context.querySelector('.selectVideoInNetworkQuality'); - const selectVideoInternetQuality = context.querySelector('.selectVideoInternetQuality'); - const selectChromecastVideoQuality = context.querySelector('.selectChromecastVideoQuality'); - - const selectVideoInNetworkQualityValue = selectVideoInNetworkQuality.value; - const selectVideoInternetQualityValue = selectVideoInternetQuality.value; - const selectChromecastVideoQualityValue = selectChromecastVideoQuality.value; - - const maxVideoWidth = parseInt(e.target.value || '0', 10) || 0; - - fillQuality(selectVideoInNetworkQuality, true, 'Video', maxVideoWidth); - fillQuality(selectVideoInternetQuality, false, 'Video', maxVideoWidth); - fillChromecastQuality(selectChromecastVideoQuality, maxVideoWidth); - - setSelectValue(selectVideoInNetworkQuality, selectVideoInNetworkQualityValue, ''); - setSelectValue(selectVideoInternetQuality, selectVideoInternetQualityValue, ''); - setSelectValue(selectChromecastVideoQuality, selectChromecastVideoQualityValue, ''); - } - - function onSubmit(e) { - const self = this; - const apiClient = ServerConnections.getApiClient(self.options.serverId); const userId = self.options.userId; + const apiClient = ServerConnections.getApiClient(self.options.serverId); const userSettings = self.options.userSettings; - userSettings.setUserInfo(userId, apiClient).then(() => { - const enableSaveConfirmation = self.options.enableSaveConfirmation; - save(self, self.options.element, userId, userSettings, apiClient, enableSaveConfirmation); - }); + apiClient.getUser(userId).then(user => { + userSettings.setUserInfo(userId, apiClient).then(() => { + self.dataLoaded = true; - // Disable default form submission - if (e) { - e.preventDefault(); - } - return false; - } - - function embed(options, self) { - options.element.innerHTML = globalize.translateHtml(template, 'core'); - - options.element.querySelector('form').addEventListener('submit', onSubmit.bind(self)); - - if (options.enableSaveButton) { - options.element.querySelector('.btnSave').classList.remove('hide'); - } - - options.element.querySelector('.selectLabelMaxVideoWidth').addEventListener('change', onMaxVideoWidthChange.bind(self)); - - self.loadData(); - - if (options.autoFocus) { - focusManager.autoFocus(options.element); - } - } - - class PlaybackSettings { - constructor(options) { - this.options = options; - embed(options, this); - } - - loadData() { - const self = this; - const context = self.options.element; - - loading.show(); - - const userId = self.options.userId; - const apiClient = ServerConnections.getApiClient(self.options.serverId); - const userSettings = self.options.userSettings; - - apiClient.getUser(userId).then(user => { - userSettings.setUserInfo(userId, apiClient).then(() => { - self.dataLoaded = true; - - loadForm(context, user, userSettings, apiClient); - }); + loadForm(context, user, userSettings, apiClient); }); - } - - submit() { - onSubmit.call(this); - } - - destroy() { - this.options = null; - } + }); } -/* eslint-enable indent */ + submit() { + onSubmit.call(this); + } + + destroy() { + this.options = null; + } +} + export default PlaybackSettings; diff --git a/src/components/playerstats/playerstats.js b/src/components/playerstats/playerstats.js index 7ce9448306..d03db1ef71 100644 --- a/src/components/playerstats/playerstats.js +++ b/src/components/playerstats/playerstats.js @@ -9,473 +9,471 @@ import { PluginType } from '../../types/plugin.ts'; import './playerstats.scss'; import ServerConnections from '../ServerConnections'; -/* eslint-disable indent */ +function init(instance) { + const parent = document.createElement('div'); - function init(instance) { - const parent = document.createElement('div'); + parent.classList.add('playerStats'); - parent.classList.add('playerStats'); - - if (layoutManager.tv) { - parent.classList.add('playerStats-tv'); - } - - parent.classList.add('hide'); - - let button; - - if (layoutManager.tv) { - button = ''; - } else { - button = ''; - } - - const contentClass = layoutManager.tv ? 'playerStats-content playerStats-content-tv' : 'playerStats-content'; - - parent.innerHTML = '
' + button + '
'; - - button = parent.querySelector('.playerStats-closeButton'); - - if (button) { - button.addEventListener('click', onCloseButtonClick.bind(instance)); - } - - document.body.appendChild(parent); - - instance.element = parent; + if (layoutManager.tv) { + parent.classList.add('playerStats-tv'); } - function onCloseButtonClick() { - this.enabled(false); + parent.classList.add('hide'); + + let button; + + if (layoutManager.tv) { + button = ''; + } else { + button = ''; } - function renderStats(elem, categories) { - elem.querySelector('.playerStats-stats').innerHTML = categories.map(function (category) { - let categoryHtml = ''; + const contentClass = layoutManager.tv ? 'playerStats-content playerStats-content-tv' : 'playerStats-content'; - const stats = category.stats; + parent.innerHTML = '
' + button + '
'; - if (stats.length && category.name) { - categoryHtml += '
'; + button = parent.querySelector('.playerStats-closeButton'); - categoryHtml += '
'; - categoryHtml += category.name; - categoryHtml += '
'; - - categoryHtml += '
'; - categoryHtml += category.subText || ''; - categoryHtml += '
'; - - categoryHtml += '
'; - } - - for (let i = 0, length = stats.length; i < length; i++) { - categoryHtml += '
'; - - const stat = stats[i]; - - categoryHtml += '
'; - categoryHtml += stat.label; - categoryHtml += '
'; - - categoryHtml += '
'; - categoryHtml += stat.value; - categoryHtml += '
'; - - categoryHtml += '
'; - } - - return categoryHtml; - }).join(''); + if (button) { + button.addEventListener('click', onCloseButtonClick.bind(instance)); } - function getSession(instance, player) { - const now = new Date().getTime(); + document.body.appendChild(parent); - if ((now - (instance.lastSessionTime || 0)) < 10000) { - return Promise.resolve(instance.lastSession); + instance.element = parent; +} + +function onCloseButtonClick() { + this.enabled(false); +} + +function renderStats(elem, categories) { + elem.querySelector('.playerStats-stats').innerHTML = categories.map(function (category) { + let categoryHtml = ''; + + const stats = category.stats; + + if (stats.length && category.name) { + categoryHtml += '
'; + + categoryHtml += '
'; + categoryHtml += category.name; + categoryHtml += '
'; + + categoryHtml += '
'; + categoryHtml += category.subText || ''; + categoryHtml += '
'; + + categoryHtml += '
'; } - const apiClient = ServerConnections.getApiClient(playbackManager.currentItem(player).ServerId); + for (let i = 0, length = stats.length; i < length; i++) { + categoryHtml += '
'; - return apiClient.getSessions({ - deviceId: apiClient.deviceId() - }).then(function (sessions) { - instance.lastSession = sessions[0] || {}; - instance.lastSessionTime = new Date().getTime(); + const stat = stats[i]; - return Promise.resolve(instance.lastSession); - }, function () { - return Promise.resolve({}); + categoryHtml += '
'; + categoryHtml += stat.label; + categoryHtml += '
'; + + categoryHtml += '
'; + categoryHtml += stat.value; + categoryHtml += '
'; + + categoryHtml += '
'; + } + + return categoryHtml; + }).join(''); +} + +function getSession(instance, player) { + const now = new Date().getTime(); + + if ((now - (instance.lastSessionTime || 0)) < 10000) { + return Promise.resolve(instance.lastSession); + } + + const apiClient = ServerConnections.getApiClient(playbackManager.currentItem(player).ServerId); + + return apiClient.getSessions({ + deviceId: apiClient.deviceId() + }).then(function (sessions) { + instance.lastSession = sessions[0] || {}; + instance.lastSessionTime = new Date().getTime(); + + return Promise.resolve(instance.lastSession); + }, function () { + return Promise.resolve({}); + }); +} + +function translateReason(reason) { + return globalize.translate('' + reason); +} + +function getTranscodingStats(session, player, displayPlayMethod) { + const sessionStats = []; + + let videoCodec; + let audioCodec; + let totalBitrate; + let audioChannels; + + if (session.TranscodingInfo) { + videoCodec = session.TranscodingInfo.VideoCodec; + audioCodec = session.TranscodingInfo.AudioCodec; + totalBitrate = session.TranscodingInfo.Bitrate; + audioChannels = session.TranscodingInfo.AudioChannels; + } + + if (videoCodec) { + sessionStats.push({ + label: globalize.translate('LabelVideoCodec'), + value: session.TranscodingInfo.IsVideoDirect ? (videoCodec.toUpperCase() + ' (direct)') : videoCodec.toUpperCase() }); } - function translateReason(reason) { - return globalize.translate('' + reason); + if (audioCodec) { + sessionStats.push({ + label: globalize.translate('LabelAudioCodec'), + value: session.TranscodingInfo.IsAudioDirect ? (audioCodec.toUpperCase() + ' (direct)') : audioCodec.toUpperCase() + }); } - function getTranscodingStats(session, player, displayPlayMethod) { - const sessionStats = []; - - let videoCodec; - let audioCodec; - let totalBitrate; - let audioChannels; - - if (session.TranscodingInfo) { - videoCodec = session.TranscodingInfo.VideoCodec; - audioCodec = session.TranscodingInfo.AudioCodec; - totalBitrate = session.TranscodingInfo.Bitrate; - audioChannels = session.TranscodingInfo.AudioChannels; - } - - if (videoCodec) { - sessionStats.push({ - label: globalize.translate('LabelVideoCodec'), - value: session.TranscodingInfo.IsVideoDirect ? (videoCodec.toUpperCase() + ' (direct)') : videoCodec.toUpperCase() - }); - } - - if (audioCodec) { - sessionStats.push({ - label: globalize.translate('LabelAudioCodec'), - value: session.TranscodingInfo.IsAudioDirect ? (audioCodec.toUpperCase() + ' (direct)') : audioCodec.toUpperCase() - }); - } - - if (displayPlayMethod === 'Transcode') { - if (audioChannels) { - sessionStats.push({ - label: globalize.translate('LabelAudioChannels'), - value: audioChannels - }); - } - if (totalBitrate) { - sessionStats.push({ - label: globalize.translate('LabelBitrate'), - value: getDisplayBitrate(totalBitrate) - }); - } - if (session.TranscodingInfo.CompletionPercentage) { - sessionStats.push({ - label: globalize.translate('LabelTranscodingProgress'), - value: session.TranscodingInfo.CompletionPercentage.toFixed(1) + '%' - }); - } - if (session.TranscodingInfo.Framerate) { - sessionStats.push({ - label: globalize.translate('LabelTranscodingFramerate'), - value: session.TranscodingInfo.Framerate + ' fps' - }); - } - if (session.TranscodingInfo.TranscodeReasons && session.TranscodingInfo.TranscodeReasons.length) { - sessionStats.push({ - label: globalize.translate('LabelReasonForTranscoding'), - value: session.TranscodingInfo.TranscodeReasons.map(translateReason).join('
') - }); - } - if (session.TranscodingInfo.HardwareAccelerationType) { - sessionStats.push({ - label: globalize.translate('LabelHardwareEncoding'), - value: session.TranscodingInfo.HardwareAccelerationType - }); - } - } - - return sessionStats; - } - - function getDisplayBitrate(bitrate) { - if (bitrate > 1000000) { - return (bitrate / 1000000).toFixed(1) + ' Mbps'; - } else { - return Math.floor(bitrate / 1000) + ' kbps'; - } - } - - function getReadableSize(size) { - if (size >= 1073741824) { - return parseFloat((size / 1073741824).toFixed(1)) + ' GiB'; - } else if (size >= 1048576) { - return parseFloat((size / 1048576).toFixed(1)) + ' MiB'; - } else { - return Math.floor(size / 1024) + ' KiB'; - } - } - - function getMediaSourceStats(session, player) { - const sessionStats = []; - - const mediaSource = playbackManager.currentMediaSource(player) || {}; - const totalBitrate = mediaSource.Bitrate; - const mediaFileSize = mediaSource.Size; - - if (mediaSource.Container) { - sessionStats.push({ - label: globalize.translate('LabelProfileContainer'), - value: mediaSource.Container - }); - } - - if (mediaFileSize) { - sessionStats.push({ - label: globalize.translate('LabelSize'), - value: getReadableSize(mediaFileSize) - }); - } - - if (totalBitrate) { - sessionStats.push({ - label: globalize.translate('LabelBitrate'), - value: getDisplayBitrate(totalBitrate) - }); - } - - const mediaStreams = mediaSource.MediaStreams || []; - const videoStream = mediaStreams.filter(function (s) { - return s.Type === 'Video'; - })[0] || {}; - - const videoCodec = videoStream.Codec; - - const audioStreamIndex = playbackManager.getAudioStreamIndex(player); - const audioStream = playbackManager.audioTracks(player).filter(function (s) { - return s.Type === 'Audio' && s.Index === audioStreamIndex; - })[0] || {}; - - const audioCodec = audioStream.Codec; - const audioChannels = audioStream.Channels; - - const videoInfos = []; - - if (videoCodec) { - videoInfos.push(videoCodec.toUpperCase()); - } - - if (videoStream.Profile) { - videoInfos.push(videoStream.Profile); - } - - if (videoInfos.length) { - sessionStats.push({ - label: globalize.translate('LabelVideoCodec'), - value: videoInfos.join(' ') - }); - } - - if (videoStream.BitRate) { - sessionStats.push({ - label: globalize.translate('LabelVideoBitrate'), - value: getDisplayBitrate(videoStream.BitRate) - }); - } - - if (videoStream.VideoRangeType) { - sessionStats.push({ - label: globalize.translate('LabelVideoRangeType'), - value: videoStream.VideoRangeType - }); - } - - const audioInfos = []; - - if (audioCodec) { - audioInfos.push(audioCodec.toUpperCase()); - } - - if (audioStream.Profile) { - audioInfos.push(audioStream.Profile); - } - - if (audioInfos.length) { - sessionStats.push({ - label: globalize.translate('LabelAudioCodec'), - value: audioInfos.join(' ') - }); - } - - if (audioStream.BitRate) { - sessionStats.push({ - label: globalize.translate('LabelAudioBitrate'), - value: getDisplayBitrate(audioStream.BitRate) - }); - } - + if (displayPlayMethod === 'Transcode') { if (audioChannels) { sessionStats.push({ label: globalize.translate('LabelAudioChannels'), value: audioChannels }); } - - if (audioStream.SampleRate) { + if (totalBitrate) { sessionStats.push({ - label: globalize.translate('LabelAudioSampleRate'), - value: audioStream.SampleRate + ' Hz' + label: globalize.translate('LabelBitrate'), + value: getDisplayBitrate(totalBitrate) }); } - - if (audioStream.BitDepth) { + if (session.TranscodingInfo.CompletionPercentage) { sessionStats.push({ - label: globalize.translate('LabelAudioBitDepth'), - value: audioStream.BitDepth + label: globalize.translate('LabelTranscodingProgress'), + value: session.TranscodingInfo.CompletionPercentage.toFixed(1) + '%' + }); + } + if (session.TranscodingInfo.Framerate) { + sessionStats.push({ + label: globalize.translate('LabelTranscodingFramerate'), + value: session.TranscodingInfo.Framerate + ' fps' + }); + } + if (session.TranscodingInfo.TranscodeReasons && session.TranscodingInfo.TranscodeReasons.length) { + sessionStats.push({ + label: globalize.translate('LabelReasonForTranscoding'), + value: session.TranscodingInfo.TranscodeReasons.map(translateReason).join('
') + }); + } + if (session.TranscodingInfo.HardwareAccelerationType) { + sessionStats.push({ + label: globalize.translate('LabelHardwareEncoding'), + value: session.TranscodingInfo.HardwareAccelerationType }); } - - return sessionStats; } - function getSyncPlayStats() { - const SyncPlay = pluginManager.firstOfType(PluginType.SyncPlay)?.instance; + return sessionStats; +} - if (!SyncPlay?.Manager.isSyncPlayEnabled()) { - return []; - } +function getDisplayBitrate(bitrate) { + if (bitrate > 1000000) { + return (bitrate / 1000000).toFixed(1) + ' Mbps'; + } else { + return Math.floor(bitrate / 1000) + ' kbps'; + } +} - const syncStats = []; - const stats = SyncPlay.Manager.getStats(); +function getReadableSize(size) { + if (size >= 1073741824) { + return parseFloat((size / 1073741824).toFixed(1)) + ' GiB'; + } else if (size >= 1048576) { + return parseFloat((size / 1048576).toFixed(1)) + ' MiB'; + } else { + return Math.floor(size / 1024) + ' KiB'; + } +} - syncStats.push({ - label: globalize.translate('LabelSyncPlayTimeSyncDevice'), - value: stats.TimeSyncDevice +function getMediaSourceStats(session, player) { + const sessionStats = []; + + const mediaSource = playbackManager.currentMediaSource(player) || {}; + const totalBitrate = mediaSource.Bitrate; + const mediaFileSize = mediaSource.Size; + + if (mediaSource.Container) { + sessionStats.push({ + label: globalize.translate('LabelProfileContainer'), + value: mediaSource.Container }); - - syncStats.push({ - // TODO: clean old string 'LabelSyncPlayTimeOffset' from translations. - label: globalize.translate('LabelSyncPlayTimeSyncOffset'), - value: stats.TimeSyncOffset + ' ' + globalize.translate('MillisecondsUnit') - }); - - syncStats.push({ - label: globalize.translate('LabelSyncPlayPlaybackDiff'), - value: stats.PlaybackDiff + ' ' + globalize.translate('MillisecondsUnit') - }); - - syncStats.push({ - label: globalize.translate('LabelSyncPlaySyncMethod'), - value: stats.SyncMethod - }); - - return syncStats; } - function getStats(instance, player) { - const statsPromise = player.getStats ? player.getStats() : Promise.resolve({}); - const sessionPromise = getSession(instance, player); + if (mediaFileSize) { + sessionStats.push({ + label: globalize.translate('LabelSize'), + value: getReadableSize(mediaFileSize) + }); + } - return Promise.all([statsPromise, sessionPromise]).then(function (responses) { - const playerStatsResult = responses[0]; - const playerStats = playerStatsResult.categories || []; - const session = responses[1]; + if (totalBitrate) { + sessionStats.push({ + label: globalize.translate('LabelBitrate'), + value: getDisplayBitrate(totalBitrate) + }); + } - const displayPlayMethod = playMethodHelper.getDisplayPlayMethod(session); - let localizedDisplayMethod = displayPlayMethod; + const mediaStreams = mediaSource.MediaStreams || []; + const videoStream = mediaStreams.filter(function (s) { + return s.Type === 'Video'; + })[0] || {}; - if (displayPlayMethod === 'DirectPlay') { - localizedDisplayMethod = globalize.translate('DirectPlaying'); - } else if (displayPlayMethod === 'Remux') { - localizedDisplayMethod = globalize.translate('Remuxing'); - } else if (displayPlayMethod === 'DirectStream') { - localizedDisplayMethod = globalize.translate('DirectStreaming'); - } else if (displayPlayMethod === 'Transcode') { - localizedDisplayMethod = globalize.translate('Transcoding'); + const videoCodec = videoStream.Codec; + + const audioStreamIndex = playbackManager.getAudioStreamIndex(player); + const audioStream = playbackManager.audioTracks(player).filter(function (s) { + return s.Type === 'Audio' && s.Index === audioStreamIndex; + })[0] || {}; + + const audioCodec = audioStream.Codec; + const audioChannels = audioStream.Channels; + + const videoInfos = []; + + if (videoCodec) { + videoInfos.push(videoCodec.toUpperCase()); + } + + if (videoStream.Profile) { + videoInfos.push(videoStream.Profile); + } + + if (videoInfos.length) { + sessionStats.push({ + label: globalize.translate('LabelVideoCodec'), + value: videoInfos.join(' ') + }); + } + + if (videoStream.BitRate) { + sessionStats.push({ + label: globalize.translate('LabelVideoBitrate'), + value: getDisplayBitrate(videoStream.BitRate) + }); + } + + if (videoStream.VideoRangeType) { + sessionStats.push({ + label: globalize.translate('LabelVideoRangeType'), + value: videoStream.VideoRangeType + }); + } + + const audioInfos = []; + + if (audioCodec) { + audioInfos.push(audioCodec.toUpperCase()); + } + + if (audioStream.Profile) { + audioInfos.push(audioStream.Profile); + } + + if (audioInfos.length) { + sessionStats.push({ + label: globalize.translate('LabelAudioCodec'), + value: audioInfos.join(' ') + }); + } + + if (audioStream.BitRate) { + sessionStats.push({ + label: globalize.translate('LabelAudioBitrate'), + value: getDisplayBitrate(audioStream.BitRate) + }); + } + + if (audioChannels) { + sessionStats.push({ + label: globalize.translate('LabelAudioChannels'), + value: audioChannels + }); + } + + if (audioStream.SampleRate) { + sessionStats.push({ + label: globalize.translate('LabelAudioSampleRate'), + value: audioStream.SampleRate + ' Hz' + }); + } + + if (audioStream.BitDepth) { + sessionStats.push({ + label: globalize.translate('LabelAudioBitDepth'), + value: audioStream.BitDepth + }); + } + + return sessionStats; +} + +function getSyncPlayStats() { + const SyncPlay = pluginManager.firstOfType(PluginType.SyncPlay)?.instance; + + if (!SyncPlay?.Manager.isSyncPlayEnabled()) { + return []; + } + + const syncStats = []; + const stats = SyncPlay.Manager.getStats(); + + syncStats.push({ + label: globalize.translate('LabelSyncPlayTimeSyncDevice'), + value: stats.TimeSyncDevice + }); + + syncStats.push({ + // TODO: clean old string 'LabelSyncPlayTimeOffset' from translations. + label: globalize.translate('LabelSyncPlayTimeSyncOffset'), + value: stats.TimeSyncOffset + ' ' + globalize.translate('MillisecondsUnit') + }); + + syncStats.push({ + label: globalize.translate('LabelSyncPlayPlaybackDiff'), + value: stats.PlaybackDiff + ' ' + globalize.translate('MillisecondsUnit') + }); + + syncStats.push({ + label: globalize.translate('LabelSyncPlaySyncMethod'), + value: stats.SyncMethod + }); + + return syncStats; +} + +function getStats(instance, player) { + const statsPromise = player.getStats ? player.getStats() : Promise.resolve({}); + const sessionPromise = getSession(instance, player); + + return Promise.all([statsPromise, sessionPromise]).then(function (responses) { + const playerStatsResult = responses[0]; + const playerStats = playerStatsResult.categories || []; + const session = responses[1]; + + const displayPlayMethod = playMethodHelper.getDisplayPlayMethod(session); + let localizedDisplayMethod = displayPlayMethod; + + if (displayPlayMethod === 'DirectPlay') { + localizedDisplayMethod = globalize.translate('DirectPlaying'); + } else if (displayPlayMethod === 'Remux') { + localizedDisplayMethod = globalize.translate('Remuxing'); + } else if (displayPlayMethod === 'DirectStream') { + localizedDisplayMethod = globalize.translate('DirectStreaming'); + } else if (displayPlayMethod === 'Transcode') { + localizedDisplayMethod = globalize.translate('Transcoding'); + } + + const baseCategory = { + stats: [], + name: globalize.translate('LabelPlaybackInfo') + }; + + baseCategory.stats.unshift({ + label: globalize.translate('LabelPlayMethod'), + value: localizedDisplayMethod + }); + + baseCategory.stats.unshift({ + label: globalize.translate('LabelPlayer'), + value: player.name + }); + + const categories = []; + + categories.push(baseCategory); + + for (let i = 0, length = playerStats.length; i < length; i++) { + const category = playerStats[i]; + if (category.type === 'audio') { + category.name = globalize.translate('LabelAudioInfo'); + } else if (category.type === 'video') { + category.name = globalize.translate('LabelVideoInfo'); } + categories.push(category); + } - const baseCategory = { - stats: [], - name: globalize.translate('LabelPlaybackInfo') - }; - - baseCategory.stats.unshift({ - label: globalize.translate('LabelPlayMethod'), - value: localizedDisplayMethod - }); - - baseCategory.stats.unshift({ - label: globalize.translate('LabelPlayer'), - value: player.name - }); - - const categories = []; - - categories.push(baseCategory); - - for (let i = 0, length = playerStats.length; i < length; i++) { - const category = playerStats[i]; - if (category.type === 'audio') { - category.name = globalize.translate('LabelAudioInfo'); - } else if (category.type === 'video') { - category.name = globalize.translate('LabelVideoInfo'); - } - categories.push(category); - } - - let localizedTranscodingInfo = globalize.translate('LabelTranscodingInfo'); - if (displayPlayMethod === 'Remux') { - localizedTranscodingInfo = globalize.translate('LabelRemuxingInfo'); - } else if (displayPlayMethod === 'DirectStream') { - localizedTranscodingInfo = globalize.translate('LabelDirectStreamingInfo'); - } - - if (session.TranscodingInfo) { - categories.push({ - stats: getTranscodingStats(session, player, displayPlayMethod), - name: localizedTranscodingInfo - }); - } + let localizedTranscodingInfo = globalize.translate('LabelTranscodingInfo'); + if (displayPlayMethod === 'Remux') { + localizedTranscodingInfo = globalize.translate('LabelRemuxingInfo'); + } else if (displayPlayMethod === 'DirectStream') { + localizedTranscodingInfo = globalize.translate('LabelDirectStreamingInfo'); + } + if (session.TranscodingInfo) { categories.push({ - stats: getMediaSourceStats(session, player), - name: globalize.translate('LabelOriginalMediaInfo') + stats: getTranscodingStats(session, player, displayPlayMethod), + name: localizedTranscodingInfo }); + } - const syncPlayStats = getSyncPlayStats(); - if (syncPlayStats.length > 0) { - categories.push({ - stats: syncPlayStats, - name: globalize.translate('LabelSyncPlayInfo') - }); - } - - return Promise.resolve(categories); + categories.push({ + stats: getMediaSourceStats(session, player), + name: globalize.translate('LabelOriginalMediaInfo') }); + + const syncPlayStats = getSyncPlayStats(); + if (syncPlayStats.length > 0) { + categories.push({ + stats: syncPlayStats, + name: globalize.translate('LabelSyncPlayInfo') + }); + } + + return Promise.resolve(categories); + }); +} + +function renderPlayerStats(instance, player) { + const now = new Date().getTime(); + + if ((now - (instance.lastRender || 0)) < 700) { + return; } - function renderPlayerStats(instance, player) { - const now = new Date().getTime(); + instance.lastRender = now; - if ((now - (instance.lastRender || 0)) < 700) { + getStats(instance, player).then(function (stats) { + const elem = instance.element; + if (!elem) { return; } - instance.lastRender = now; + renderStats(elem, stats); + }); +} - getStats(instance, player).then(function (stats) { - const elem = instance.element; - if (!elem) { - return; - } +function bindEvents(instance, player) { + const localOnTimeUpdate = function () { + renderPlayerStats(instance, player); + }; - renderStats(elem, stats); - }); - } - - function bindEvents(instance, player) { - const localOnTimeUpdate = function () { - renderPlayerStats(instance, player); - }; - - instance.onTimeUpdate = localOnTimeUpdate; - Events.on(player, 'timeupdate', localOnTimeUpdate); - } - - function unbindEvents(instance, player) { - const localOnTimeUpdate = instance.onTimeUpdate; - - if (localOnTimeUpdate) { - Events.off(player, 'timeupdate', localOnTimeUpdate); - } + instance.onTimeUpdate = localOnTimeUpdate; + Events.on(player, 'timeupdate', localOnTimeUpdate); +} + +function unbindEvents(instance, player) { + const localOnTimeUpdate = instance.onTimeUpdate; + + if (localOnTimeUpdate) { + Events.off(player, 'timeupdate', localOnTimeUpdate); } +} class PlayerStats { constructor(options) { @@ -527,6 +525,4 @@ class PlayerStats { } } -/* eslint-enable indent */ - export default PlayerStats; diff --git a/src/components/playlisteditor/playlisteditor.js b/src/components/playlisteditor/playlisteditor.js index 90d835e1c5..91c11010be 100644 --- a/src/components/playlisteditor/playlisteditor.js +++ b/src/components/playlisteditor/playlisteditor.js @@ -18,269 +18,266 @@ import 'material-design-icons-iconfont'; import '../formdialog.scss'; import ServerConnections from '../ServerConnections'; -/* eslint-disable indent */ +let currentServerId; - let currentServerId; +function onSubmit(e) { + const panel = dom.parentWithClass(this, 'dialog'); - function onSubmit(e) { - const panel = dom.parentWithClass(this, 'dialog'); + const playlistId = panel.querySelector('#selectPlaylistToAddTo').value; + const apiClient = ServerConnections.getApiClient(currentServerId); - const playlistId = panel.querySelector('#selectPlaylistToAddTo').value; - const apiClient = ServerConnections.getApiClient(currentServerId); - - if (playlistId) { - userSettings.set('playlisteditor-lastplaylistid', playlistId); - addToPlaylist(apiClient, panel, playlistId); - } else { - createPlaylist(apiClient, panel); - } - - e.preventDefault(); - return false; + if (playlistId) { + userSettings.set('playlisteditor-lastplaylistid', playlistId); + addToPlaylist(apiClient, panel, playlistId); + } else { + createPlaylist(apiClient, panel); } - function createPlaylist(apiClient, dlg) { - loading.show(); + e.preventDefault(); + return false; +} - const url = apiClient.getUrl('Playlists', { - Name: dlg.querySelector('#txtNewPlaylistName').value, - Ids: dlg.querySelector('.fldSelectedItemIds').value || '', - userId: apiClient.getCurrentUserId() +function createPlaylist(apiClient, dlg) { + loading.show(); - }); + const url = apiClient.getUrl('Playlists', { + Name: dlg.querySelector('#txtNewPlaylistName').value, + Ids: dlg.querySelector('.fldSelectedItemIds').value || '', + userId: apiClient.getCurrentUserId() - apiClient.ajax({ - type: 'POST', - url: url, - dataType: 'json', - contentType: 'application/json' - }).then(result => { - loading.hide(); - - const id = result.Id; - dlg.submitted = true; - dialogHelper.close(dlg); - redirectToPlaylist(apiClient, id); - }); - } - - function redirectToPlaylist(apiClient, id) { - appRouter.showItem(id, apiClient.serverId()); - } - - function addToPlaylist(apiClient, dlg, id) { - const itemIds = dlg.querySelector('.fldSelectedItemIds').value || ''; - - if (id === 'queue') { - playbackManager.queue({ - serverId: apiClient.serverId(), - ids: itemIds.split(',') - }); - dlg.submitted = true; - dialogHelper.close(dlg); - return; - } - - loading.show(); - - const url = apiClient.getUrl(`Playlists/${id}/Items`, { - Ids: itemIds, - userId: apiClient.getCurrentUserId() - }); - - apiClient.ajax({ - type: 'POST', - url: url - - }).then(() => { - loading.hide(); - - dlg.submitted = true; - dialogHelper.close(dlg); - }); - } - - function triggerChange(select) { - select.dispatchEvent(new CustomEvent('change', {})); - } - - function populatePlaylists(editorOptions, panel) { - const select = panel.querySelector('#selectPlaylistToAddTo'); + }); + apiClient.ajax({ + type: 'POST', + url: url, + dataType: 'json', + contentType: 'application/json' + }).then(result => { loading.hide(); - panel.querySelector('.newPlaylistInfo').classList.add('hide'); + const id = result.Id; + dlg.submitted = true; + dialogHelper.close(dlg); + redirectToPlaylist(apiClient, id); + }); +} - const options = { - Recursive: true, - IncludeItemTypes: 'Playlist', - SortBy: 'SortName', - EnableTotalRecordCount: false - }; +function redirectToPlaylist(apiClient, id) { + appRouter.showItem(id, apiClient.serverId()); +} - const apiClient = ServerConnections.getApiClient(currentServerId); - const SyncPlay = pluginManager.firstOfType(PluginType.SyncPlay)?.instance; +function addToPlaylist(apiClient, dlg, id) { + const itemIds = dlg.querySelector('.fldSelectedItemIds').value || ''; - apiClient.getItems(apiClient.getCurrentUserId(), options).then(result => { - let html = ''; - - if ((editorOptions.enableAddToPlayQueue !== false && playbackManager.isPlaying()) || SyncPlay?.Manager.isSyncPlayEnabled()) { - html += ``; - } - - html += ``; - - html += result.Items.map(i => { - return ``; - }); - - select.innerHTML = html; - - let defaultValue = editorOptions.defaultValue; - if (!defaultValue) { - defaultValue = userSettings.get('playlisteditor-lastplaylistid') || ''; - } - select.value = defaultValue === 'new' ? '' : defaultValue; - - // If the value is empty set it again, in case we tried to set a lastplaylistid that is no longer valid - if (!select.value) { - select.value = ''; - } - - triggerChange(select); - - loading.hide(); + if (id === 'queue') { + playbackManager.queue({ + serverId: apiClient.serverId(), + ids: itemIds.split(',') }); + dlg.submitted = true; + dialogHelper.close(dlg); + return; } - function getEditorHtml(items) { + loading.show(); + + const url = apiClient.getUrl(`Playlists/${id}/Items`, { + Ids: itemIds, + userId: apiClient.getCurrentUserId() + }); + + apiClient.ajax({ + type: 'POST', + url: url + + }).then(() => { + loading.hide(); + + dlg.submitted = true; + dialogHelper.close(dlg); + }); +} + +function triggerChange(select) { + select.dispatchEvent(new CustomEvent('change', {})); +} + +function populatePlaylists(editorOptions, panel) { + const select = panel.querySelector('#selectPlaylistToAddTo'); + + loading.hide(); + + panel.querySelector('.newPlaylistInfo').classList.add('hide'); + + const options = { + Recursive: true, + IncludeItemTypes: 'Playlist', + SortBy: 'SortName', + EnableTotalRecordCount: false + }; + + const apiClient = ServerConnections.getApiClient(currentServerId); + const SyncPlay = pluginManager.firstOfType(PluginType.SyncPlay)?.instance; + + apiClient.getItems(apiClient.getCurrentUserId(), options).then(result => { let html = ''; - html += '
'; - html += '
'; - html += '
'; + if ((editorOptions.enableAddToPlayQueue !== false && playbackManager.isPlaying()) || SyncPlay?.Manager.isSyncPlayEnabled()) { + html += ``; + } - html += '
'; - let autoFocus = items.length ? ' autofocus' : ''; - html += ``; - html += '
'; + html += ``; - html += '
'; - - html += '
'; - autoFocus = items.length ? '' : ' autofocus'; - html += ``; - html += '
'; - - // newPlaylistInfo - html += '
'; - - html += '
'; - html += ``; - html += '
'; - - html += ''; - - html += '
'; - html += '
'; - html += '
'; - - return html; - } - - function initEditor(content, options, items) { - content.querySelector('#selectPlaylistToAddTo').addEventListener('change', function () { - if (this.value) { - content.querySelector('.newPlaylistInfo').classList.add('hide'); - content.querySelector('#txtNewPlaylistName').removeAttribute('required'); - } else { - content.querySelector('.newPlaylistInfo').classList.remove('hide'); - content.querySelector('#txtNewPlaylistName').setAttribute('required', 'required'); - } + html += result.Items.map(i => { + return ``; }); - content.querySelector('form').addEventListener('submit', onSubmit); + select.innerHTML = html; - content.querySelector('.fldSelectedItemIds', content).value = items.join(','); + let defaultValue = editorOptions.defaultValue; + if (!defaultValue) { + defaultValue = userSettings.get('playlisteditor-lastplaylistid') || ''; + } + select.value = defaultValue === 'new' ? '' : defaultValue; - if (items.length) { - content.querySelector('.fldSelectPlaylist').classList.remove('hide'); - populatePlaylists(options, content); + // If the value is empty set it again, in case we tried to set a lastplaylistid that is no longer valid + if (!select.value) { + select.value = ''; + } + + triggerChange(select); + + loading.hide(); + }); +} + +function getEditorHtml(items) { + let html = ''; + + html += '
'; + html += '
'; + html += '
'; + + html += '
'; + let autoFocus = items.length ? ' autofocus' : ''; + html += ``; + html += '
'; + + html += '
'; + + html += '
'; + autoFocus = items.length ? '' : ' autofocus'; + html += ``; + html += '
'; + + // newPlaylistInfo + html += '
'; + + html += '
'; + html += ``; + html += '
'; + + html += ''; + + html += '
'; + html += '
'; + html += '
'; + + return html; +} + +function initEditor(content, options, items) { + content.querySelector('#selectPlaylistToAddTo').addEventListener('change', function () { + if (this.value) { + content.querySelector('.newPlaylistInfo').classList.add('hide'); + content.querySelector('#txtNewPlaylistName').removeAttribute('required'); } else { - content.querySelector('.fldSelectPlaylist').classList.add('hide'); - - const selectPlaylistToAddTo = content.querySelector('#selectPlaylistToAddTo'); - selectPlaylistToAddTo.innerHTML = ''; - selectPlaylistToAddTo.value = ''; - triggerChange(selectPlaylistToAddTo); + content.querySelector('.newPlaylistInfo').classList.remove('hide'); + content.querySelector('#txtNewPlaylistName').setAttribute('required', 'required'); } - } + }); - function centerFocus(elem, horiz, on) { - import('../../scripts/scrollHelper').then((scrollHelper) => { - const fn = on ? 'on' : 'off'; - scrollHelper.centerFocus[fn](elem, horiz); + content.querySelector('form').addEventListener('submit', onSubmit); + + content.querySelector('.fldSelectedItemIds', content).value = items.join(','); + + if (items.length) { + content.querySelector('.fldSelectPlaylist').classList.remove('hide'); + populatePlaylists(options, content); + } else { + content.querySelector('.fldSelectPlaylist').classList.add('hide'); + + const selectPlaylistToAddTo = content.querySelector('#selectPlaylistToAddTo'); + selectPlaylistToAddTo.innerHTML = ''; + selectPlaylistToAddTo.value = ''; + triggerChange(selectPlaylistToAddTo); + } +} + +function centerFocus(elem, horiz, on) { + import('../../scripts/scrollHelper').then((scrollHelper) => { + const fn = on ? 'on' : 'off'; + scrollHelper.centerFocus[fn](elem, horiz); + }); +} + +export class showEditor { + constructor(options) { + const items = options.items || {}; + currentServerId = options.serverId; + + const dialogOptions = { + removeOnClose: true, + scrollY: false + }; + + if (layoutManager.tv) { + dialogOptions.size = 'fullscreen'; + } else { + dialogOptions.size = 'small'; + } + + const dlg = dialogHelper.createDialog(dialogOptions); + + dlg.classList.add('formDialog'); + + let html = ''; + const title = globalize.translate('HeaderAddToPlaylist'); + + html += '
'; + html += ``; + html += '

'; + html += title; + html += '

'; + + html += '
'; + + html += getEditorHtml(items); + + dlg.innerHTML = html; + + initEditor(dlg, options, items); + + dlg.querySelector('.btnCancel').addEventListener('click', () => { + dialogHelper.close(dlg); + }); + + if (layoutManager.tv) { + centerFocus(dlg.querySelector('.formDialogContent'), false, true); + } + + return dialogHelper.open(dlg).then(() => { + if (layoutManager.tv) { + centerFocus(dlg.querySelector('.formDialogContent'), false, false); + } + + if (dlg.submitted) { + return Promise.resolve(); + } + + return Promise.reject(); }); } +} - export class showEditor { - constructor(options) { - const items = options.items || {}; - currentServerId = options.serverId; - - const dialogOptions = { - removeOnClose: true, - scrollY: false - }; - - if (layoutManager.tv) { - dialogOptions.size = 'fullscreen'; - } else { - dialogOptions.size = 'small'; - } - - const dlg = dialogHelper.createDialog(dialogOptions); - - dlg.classList.add('formDialog'); - - let html = ''; - const title = globalize.translate('HeaderAddToPlaylist'); - - html += '
'; - html += ``; - html += '

'; - html += title; - html += '

'; - - html += '
'; - - html += getEditorHtml(items); - - dlg.innerHTML = html; - - initEditor(dlg, options, items); - - dlg.querySelector('.btnCancel').addEventListener('click', () => { - dialogHelper.close(dlg); - }); - - if (layoutManager.tv) { - centerFocus(dlg.querySelector('.formDialogContent'), false, true); - } - - return dialogHelper.open(dlg).then(() => { - if (layoutManager.tv) { - centerFocus(dlg.querySelector('.formDialogContent'), false, false); - } - - if (dlg.submitted) { - return Promise.resolve(); - } - - return Promise.reject(); - }); - } - } - -/* eslint-enable indent */ export default showEditor; diff --git a/src/components/scrollManager.js b/src/components/scrollManager.js index 6a5aee3645..7417c7643e 100644 --- a/src/components/scrollManager.js +++ b/src/components/scrollManager.js @@ -1,5 +1,3 @@ -/* eslint-disable indent */ - /** * Module for controlling scroll behavior. * @module components/scrollManager @@ -9,50 +7,50 @@ import dom from '../scripts/dom'; import browser from '../scripts/browser'; import layoutManager from './layoutManager'; - /** +/** * Scroll time in ms. */ - const ScrollTime = 270; +const ScrollTime = 270; - /** +/** * Epsilon for comparing values. */ - const Epsilon = 1e-6; +const Epsilon = 1e-6; - // FIXME: Need to scroll to top of page to fully show the top menu. This can be solved by some marker of top most elements or their containers - /** +// FIXME: Need to scroll to top of page to fully show the top menu. This can be solved by some marker of top most elements or their containers +/** * Returns minimum vertical scroll. * Scroll less than that value will be zeroed. * * @return {number} Minimum vertical scroll. */ - function minimumScrollY() { - const topMenu = document.querySelector('.headerTop'); - if (topMenu) { - return topMenu.clientHeight; +function minimumScrollY() { + const topMenu = document.querySelector('.headerTop'); + if (topMenu) { + return topMenu.clientHeight; + } + return 0; +} + +const supportsSmoothScroll = 'scrollBehavior' in document.documentElement.style; + +let supportsScrollToOptions = false; +try { + const elem = document.createElement('div'); + + const opts = Object.defineProperty({}, 'behavior', { + // eslint-disable-next-line getter-return + get: function () { + supportsScrollToOptions = true; } - return 0; - } + }); - const supportsSmoothScroll = 'scrollBehavior' in document.documentElement.style; + elem.scrollTo(opts); +} catch (e) { + console.error('error checking ScrollToOptions support'); +} - let supportsScrollToOptions = false; - try { - const elem = document.createElement('div'); - - const opts = Object.defineProperty({}, 'behavior', { - // eslint-disable-next-line getter-return - get: function () { - supportsScrollToOptions = true; - } - }); - - elem.scrollTo(opts); - } catch (e) { - console.error('error checking ScrollToOptions support'); - } - - /** +/** * Returns value clamped by range [min, max]. * * @param {number} value - Clamped value. @@ -60,16 +58,16 @@ import layoutManager from './layoutManager'; * @param {number} max - Ending of range. * @return {number} Clamped value. */ - function clamp(value, min, max) { - if (value <= min) { - return min; - } else if (value >= max) { - return max; - } - return value; +function clamp(value, min, max) { + if (value <= min) { + return min; + } else if (value >= max) { + return max; } + return value; +} - /** +/** * Returns the required delta to fit range 1 into range 2. * In case of range 1 is bigger than range 2 returns delta to fit most out of range part. * @@ -79,28 +77,28 @@ import layoutManager from './layoutManager'; * @param {number} end2 - Ending of range 2. * @return {number} Delta: <0 move range1 to the left, >0 - to the right. */ - function fitRange(begin1, end1, begin2, end2) { - const delta1 = begin1 - begin2; - const delta2 = end2 - end1; - if (delta1 < 0 && delta1 < delta2) { - return -delta1; - } else if (delta2 < 0) { - return delta2; - } - return 0; +function fitRange(begin1, end1, begin2, end2) { + const delta1 = begin1 - begin2; + const delta2 = end2 - end1; + if (delta1 < 0 && delta1 < delta2) { + return -delta1; + } else if (delta2 < 0) { + return delta2; } + return 0; +} - /** +/** * Ease value. * * @param {number} t - Value in range [0, 1]. * @return {number} Eased value in range [0, 1]. */ - function ease(t) { - return t * (2 - t); // easeOutQuad === ease-out - } +function ease(t) { + return t * (2 - t); // easeOutQuad === ease-out +} - /** +/** * @typedef {Object} Rect * @property {number} left - X coordinate of top-left corner. * @property {number} top - Y coordinate of top-left corner. @@ -108,7 +106,7 @@ import layoutManager from './layoutManager'; * @property {number} height - Height. */ - /** +/** * Document scroll wrapper helps to unify scrolling and fix issues of some browsers. * * webOS 2 Browser: scrolls documentElement (and window), but body has a scroll size @@ -121,157 +119,157 @@ import layoutManager from './layoutManager'; * * Tizen 5 Browser/Native: scrolls documentElement (and window); has a document.scrollingElement */ - class DocumentScroller { - /** +class DocumentScroller { + /** * Horizontal scroll position. * @type {number} */ - get scrollLeft() { - return window.pageXOffset; - } + get scrollLeft() { + return window.pageXOffset; + } - set scrollLeft(val) { - window.scroll(val, window.pageYOffset); - } + set scrollLeft(val) { + window.scroll(val, window.pageYOffset); + } - /** + /** * Vertical scroll position. * @type {number} */ - get scrollTop() { - return window.pageYOffset; - } + get scrollTop() { + return window.pageYOffset; + } - set scrollTop(val) { - window.scroll(window.pageXOffset, val); - } + set scrollTop(val) { + window.scroll(window.pageXOffset, val); + } - /** + /** * Horizontal scroll size (scroll width). * @type {number} */ - get scrollWidth() { - return Math.max(document.documentElement.scrollWidth, document.body.scrollWidth); - } + get scrollWidth() { + return Math.max(document.documentElement.scrollWidth, document.body.scrollWidth); + } - /** + /** * Vertical scroll size (scroll height). * @type {number} */ - get scrollHeight() { - return Math.max(document.documentElement.scrollHeight, document.body.scrollHeight); - } + get scrollHeight() { + return Math.max(document.documentElement.scrollHeight, document.body.scrollHeight); + } - /** + /** * Horizontal client size (client width). * @type {number} */ - get clientWidth() { - return Math.min(document.documentElement.clientWidth, document.body.clientWidth); - } + get clientWidth() { + return Math.min(document.documentElement.clientWidth, document.body.clientWidth); + } - /** + /** * Vertical client size (client height). * @type {number} */ - get clientHeight() { - return Math.min(document.documentElement.clientHeight, document.body.clientHeight); - } + get clientHeight() { + return Math.min(document.documentElement.clientHeight, document.body.clientHeight); + } - /** + /** * Returns attribute value. * @param {string} attributeName - Attibute name. * @return {string} Attibute value. */ - getAttribute(attributeName) { - return document.body.getAttribute(attributeName); - } - - /** - * Returns bounding client rect. - * @return {Rect} Bounding client rect. - */ - getBoundingClientRect() { - // Make valid viewport coordinates: documentElement.getBoundingClientRect returns rect of entire document relative to viewport - return { - left: 0, - top: 0, - width: this.clientWidth, - height: this.clientHeight - }; - } - - /** - * Scrolls window. - * @param {...mixed} args See window.scrollTo. - */ - scrollTo() { - window.scrollTo.apply(window, arguments); - } + getAttribute(attributeName) { + return document.body.getAttribute(attributeName); } /** - * Default (document) scroller. - */ - const documentScroller = new DocumentScroller(); - - const scrollerHints = { - x: { - nameScroll: 'scrollWidth', - nameClient: 'clientWidth', - nameStyle: 'overflowX', - nameScrollMode: 'data-scroll-mode-x' - }, - y: { - nameScroll: 'scrollHeight', - nameClient: 'clientHeight', - nameStyle: 'overflowY', - nameScrollMode: 'data-scroll-mode-y' - } - }; + * Returns bounding client rect. + * @return {Rect} Bounding client rect. + */ + getBoundingClientRect() { + // Make valid viewport coordinates: documentElement.getBoundingClientRect returns rect of entire document relative to viewport + return { + left: 0, + top: 0, + width: this.clientWidth, + height: this.clientHeight + }; + } /** + * Scrolls window. + * @param {...mixed} args See window.scrollTo. + */ + scrollTo() { + window.scrollTo.apply(window, arguments); + } +} + +/** + * Default (document) scroller. + */ +const documentScroller = new DocumentScroller(); + +const scrollerHints = { + x: { + nameScroll: 'scrollWidth', + nameClient: 'clientWidth', + nameStyle: 'overflowX', + nameScrollMode: 'data-scroll-mode-x' + }, + y: { + nameScroll: 'scrollHeight', + nameClient: 'clientHeight', + nameStyle: 'overflowY', + nameScrollMode: 'data-scroll-mode-y' + } +}; + +/** * Returns parent element that can be scrolled. If no such, returns document scroller. * * @param {HTMLElement} element - Element for which parent is being searched. * @param {boolean} vertical - Search for vertical scrollable parent. * @param {HTMLElement|DocumentScroller} Parent element that can be scrolled or document scroller. */ - function getScrollableParent(element, vertical) { - if (element) { - const scrollerHint = vertical ? scrollerHints.y : scrollerHints.x; +function getScrollableParent(element, vertical) { + if (element) { + const scrollerHint = vertical ? scrollerHints.y : scrollerHints.x; - let parent = element.parentElement; + let parent = element.parentElement; - while (parent && parent !== document.body) { - const scrollMode = parent.getAttribute(scrollerHint.nameScrollMode); + while (parent && parent !== document.body) { + const scrollMode = parent.getAttribute(scrollerHint.nameScrollMode); - // Stop on self-scrolled containers - if (scrollMode === 'custom') { - return parent; - } - - const styles = window.getComputedStyle(parent); - - // Stop on fixed parent - if (styles.position === 'fixed') { - return parent; - } - - const overflow = styles[scrollerHint.nameStyle]; - - if (overflow === 'scroll' || overflow === 'auto' && parent[scrollerHint.nameScroll] > parent[scrollerHint.nameClient]) { - return parent; - } - - parent = parent.parentElement; + // Stop on self-scrolled containers + if (scrollMode === 'custom') { + return parent; } - } - return documentScroller; + const styles = window.getComputedStyle(parent); + + // Stop on fixed parent + if (styles.position === 'fixed') { + return parent; + } + + const overflow = styles[scrollerHint.nameStyle]; + + if (overflow === 'scroll' || overflow === 'auto' && parent[scrollerHint.nameScroll] > parent[scrollerHint.nameClient]) { + return parent; + } + + parent = parent.parentElement; + } } - /** + return documentScroller; +} + +/** * @typedef {Object} ScrollerData * @property {number} scrollPos - Current scroll position. * @property {number} scrollSize - Scroll size. @@ -280,34 +278,34 @@ import layoutManager from './layoutManager'; * @property {boolean} custom - Custom scrolling mode. */ - /** +/** * Returns scroller data for specified orientation. * * @param {HTMLElement} scroller - Scroller. * @param {boolean} vertical - Vertical scroller data. * @return {ScrollerData} Scroller data. */ - function getScrollerData(scroller, vertical) { - const data = {}; +function getScrollerData(scroller, vertical) { + const data = {}; - if (!vertical) { - data.scrollPos = scroller.scrollLeft; - data.scrollSize = scroller.scrollWidth; - data.clientSize = scroller.clientWidth; - data.mode = scroller.getAttribute(scrollerHints.x.nameScrollMode); - } else { - data.scrollPos = scroller.scrollTop; - data.scrollSize = scroller.scrollHeight; - data.clientSize = scroller.clientHeight; - data.mode = scroller.getAttribute(scrollerHints.y.nameScrollMode); - } - - data.custom = data.mode === 'custom'; - - return data; + if (!vertical) { + data.scrollPos = scroller.scrollLeft; + data.scrollSize = scroller.scrollWidth; + data.clientSize = scroller.clientWidth; + data.mode = scroller.getAttribute(scrollerHints.x.nameScrollMode); + } else { + data.scrollPos = scroller.scrollTop; + data.scrollSize = scroller.scrollHeight; + data.clientSize = scroller.clientHeight; + data.mode = scroller.getAttribute(scrollerHints.y.nameScrollMode); } - /** + data.custom = data.mode === 'custom'; + + return data; +} + +/** * Returns position of child of scroller for specified orientation. * * @param {HTMLElement} scroller - Scroller. @@ -315,18 +313,18 @@ import layoutManager from './layoutManager'; * @param {boolean} vertical - Vertical scroll. * @return {number} Child position. */ - function getScrollerChildPos(scroller, element, vertical) { - const elementRect = element.getBoundingClientRect(); - const scrollerRect = scroller.getBoundingClientRect(); +function getScrollerChildPos(scroller, element, vertical) { + const elementRect = element.getBoundingClientRect(); + const scrollerRect = scroller.getBoundingClientRect(); - if (!vertical) { - return scroller.scrollLeft + elementRect.left - scrollerRect.left; - } else { - return scroller.scrollTop + elementRect.top - scrollerRect.top; - } + if (!vertical) { + return scroller.scrollLeft + elementRect.left - scrollerRect.left; + } else { + return scroller.scrollTop + elementRect.top - scrollerRect.top; } +} - /** +/** * Returns scroll position for element. * * @param {ScrollerData} scrollerData - Scroller data. @@ -335,47 +333,47 @@ import layoutManager from './layoutManager'; * @param {boolean} centered - Scroll to center. * @return {number} Scroll position. */ - function calcScroll(scrollerData, elementPos, elementSize, centered) { - const maxScroll = scrollerData.scrollSize - scrollerData.clientSize; +function calcScroll(scrollerData, elementPos, elementSize, centered) { + const maxScroll = scrollerData.scrollSize - scrollerData.clientSize; - let scroll; + let scroll; - if (centered) { - scroll = elementPos + (elementSize - scrollerData.clientSize) / 2; - } else { - const delta = fitRange(elementPos, elementPos + elementSize - 1, scrollerData.scrollPos, scrollerData.scrollPos + scrollerData.clientSize - 1); - scroll = scrollerData.scrollPos - delta; - } - - return clamp(Math.round(scroll), 0, maxScroll); + if (centered) { + scroll = elementPos + (elementSize - scrollerData.clientSize) / 2; + } else { + const delta = fitRange(elementPos, elementPos + elementSize - 1, scrollerData.scrollPos, scrollerData.scrollPos + scrollerData.clientSize - 1); + scroll = scrollerData.scrollPos - delta; } - /** + return clamp(Math.round(scroll), 0, maxScroll); +} + +/** * Calls scrollTo function in proper way. * * @param {HTMLElement} scroller - Scroller. * @param {ScrollToOptions} options - Scroll options. */ - function scrollToHelper(scroller, options) { - if ('scrollTo' in scroller) { - if (!supportsScrollToOptions) { - const scrollX = (options.left !== undefined ? options.left : scroller.scrollLeft); - const scrollY = (options.top !== undefined ? options.top : scroller.scrollTop); - scroller.scrollTo(scrollX, scrollY); - } else { - scroller.scrollTo(options); - } - } else if ('scrollLeft' in scroller) { - if (options.left !== undefined) { - scroller.scrollLeft = options.left; - } - if (options.top !== undefined) { - scroller.scrollTop = options.top; - } +function scrollToHelper(scroller, options) { + if ('scrollTo' in scroller) { + if (!supportsScrollToOptions) { + const scrollX = (options.left !== undefined ? options.left : scroller.scrollLeft); + const scrollY = (options.top !== undefined ? options.top : scroller.scrollTop); + scroller.scrollTo(scrollX, scrollY); + } else { + scroller.scrollTo(options); + } + } else if ('scrollLeft' in scroller) { + if (options.left !== undefined) { + scroller.scrollLeft = options.left; + } + if (options.top !== undefined) { + scroller.scrollTop = options.top; } } +} - /** +/** * Performs built-in scroll. * * @param {HTMLElement} xScroller - Horizontal scroller. @@ -384,35 +382,35 @@ import layoutManager from './layoutManager'; * @param {number} scrollY - Vertical coordinate. * @param {boolean} smooth - Smooth scrolling. */ - function builtinScroll(xScroller, scrollX, yScroller, scrollY, smooth) { - const scrollBehavior = smooth ? 'smooth' : 'instant'; +function builtinScroll(xScroller, scrollX, yScroller, scrollY, smooth) { + const scrollBehavior = smooth ? 'smooth' : 'instant'; - if (xScroller !== yScroller) { - if (xScroller) { - scrollToHelper(xScroller, { left: scrollX, behavior: scrollBehavior }); - } - if (yScroller) { - scrollToHelper(yScroller, { top: scrollY, behavior: scrollBehavior }); - } - } else if (xScroller) { - scrollToHelper(xScroller, { left: scrollX, top: scrollY, behavior: scrollBehavior }); + if (xScroller !== yScroller) { + if (xScroller) { + scrollToHelper(xScroller, { left: scrollX, behavior: scrollBehavior }); } + if (yScroller) { + scrollToHelper(yScroller, { top: scrollY, behavior: scrollBehavior }); + } + } else if (xScroller) { + scrollToHelper(xScroller, { left: scrollX, top: scrollY, behavior: scrollBehavior }); } +} - /** +/** * Requested frame for animated scroll. */ - let scrollTimer; +let scrollTimer; - /** +/** * Resets scroll timer to stop scrolling. */ - function resetScrollTimer() { - cancelAnimationFrame(scrollTimer); - scrollTimer = undefined; - } +function resetScrollTimer() { + cancelAnimationFrame(scrollTimer); + scrollTimer = undefined; +} - /** +/** * Performs animated scroll. * * @param {HTMLElement} xScroller - Horizontal scroller. @@ -420,43 +418,43 @@ import layoutManager from './layoutManager'; * @param {HTMLElement} yScroller - Vertical scroller. * @param {number} scrollY - Vertical coordinate. */ - function animateScroll(xScroller, scrollX, yScroller, scrollY) { - const ox = xScroller ? xScroller.scrollLeft : scrollX; - const oy = yScroller ? yScroller.scrollTop : scrollY; - const dx = scrollX - ox; - const dy = scrollY - oy; +function animateScroll(xScroller, scrollX, yScroller, scrollY) { + const ox = xScroller ? xScroller.scrollLeft : scrollX; + const oy = yScroller ? yScroller.scrollTop : scrollY; + const dx = scrollX - ox; + const dy = scrollY - oy; - if (Math.abs(dx) < Epsilon && Math.abs(dy) < Epsilon) { + if (Math.abs(dx) < Epsilon && Math.abs(dy) < Epsilon) { + return; + } + + let start; + + function scrollAnim(currentTimestamp) { + start = start || currentTimestamp; + + let k = Math.min(1, (currentTimestamp - start) / ScrollTime); + + if (k === 1) { + resetScrollTimer(); + builtinScroll(xScroller, scrollX, yScroller, scrollY, false); return; } - let start; + k = ease(k); - function scrollAnim(currentTimestamp) { - start = start || currentTimestamp; + const x = ox + dx * k; + const y = oy + dy * k; - let k = Math.min(1, (currentTimestamp - start) / ScrollTime); - - if (k === 1) { - resetScrollTimer(); - builtinScroll(xScroller, scrollX, yScroller, scrollY, false); - return; - } - - k = ease(k); - - const x = ox + dx * k; - const y = oy + dy * k; - - builtinScroll(xScroller, x, yScroller, y, false); - - scrollTimer = requestAnimationFrame(scrollAnim); - } + builtinScroll(xScroller, x, yScroller, y, false); scrollTimer = requestAnimationFrame(scrollAnim); } - /** + scrollTimer = requestAnimationFrame(scrollAnim); +} + +/** * Performs scroll. * * @param {HTMLElement} xScroller - Horizontal scroller. @@ -465,142 +463,140 @@ import layoutManager from './layoutManager'; * @param {number} scrollY - Vertical coordinate. * @param {boolean} smooth - Smooth scrolling. */ - function doScroll(xScroller, scrollX, yScroller, scrollY, smooth) { - resetScrollTimer(); +function doScroll(xScroller, scrollX, yScroller, scrollY, smooth) { + resetScrollTimer(); - if (smooth && useAnimatedScroll()) { - animateScroll(xScroller, scrollX, yScroller, scrollY); - } else { - builtinScroll(xScroller, scrollX, yScroller, scrollY, smooth); - } + if (smooth && useAnimatedScroll()) { + animateScroll(xScroller, scrollX, yScroller, scrollY); + } else { + builtinScroll(xScroller, scrollX, yScroller, scrollY, smooth); } +} - /** +/** * Returns true if smooth scroll must be used. */ - function useSmoothScroll() { - return !!browser.tizen; - } +function useSmoothScroll() { + return !!browser.tizen; +} - /** +/** * Returns true if animated implementation of smooth scroll must be used. */ - function useAnimatedScroll() { - // Add block to force using (or not) of animated implementation +function useAnimatedScroll() { + // Add block to force using (or not) of animated implementation - return !supportsSmoothScroll; - } + return !supportsSmoothScroll; +} - /** +/** * Returns true if scroll manager is enabled. */ - export function isEnabled() { - return layoutManager.tv; - } +export function isEnabled() { + return layoutManager.tv; +} - /** +/** * Scrolls the document to a given position. * * @param {number} scrollX - Horizontal coordinate. * @param {number} scrollY - Vertical coordinate. * @param {boolean} [smooth=false] - Smooth scrolling. */ - export function scrollTo(scrollX, scrollY, smooth) { - smooth = !!smooth; +export function scrollTo(scrollX, scrollY, smooth) { + smooth = !!smooth; - // Scroller is document itself by default - const scroller = getScrollableParent(null, false); + // Scroller is document itself by default + const scroller = getScrollableParent(null, false); - const xScrollerData = getScrollerData(scroller, false); - const yScrollerData = getScrollerData(scroller, true); + const xScrollerData = getScrollerData(scroller, false); + const yScrollerData = getScrollerData(scroller, true); - scrollX = clamp(Math.round(scrollX), 0, xScrollerData.scrollSize - xScrollerData.clientSize); - scrollY = clamp(Math.round(scrollY), 0, yScrollerData.scrollSize - yScrollerData.clientSize); + scrollX = clamp(Math.round(scrollX), 0, xScrollerData.scrollSize - xScrollerData.clientSize); + scrollY = clamp(Math.round(scrollY), 0, yScrollerData.scrollSize - yScrollerData.clientSize); - doScroll(scroller, scrollX, scroller, scrollY, smooth); - } + doScroll(scroller, scrollX, scroller, scrollY, smooth); +} - /** +/** * Scrolls the document to a given element. * * @param {HTMLElement} element - Target element of scroll task. * @param {boolean} [smooth=false] - Smooth scrolling. */ - export function scrollToElement(element, smooth) { - smooth = !!smooth; +export function scrollToElement(element, smooth) { + smooth = !!smooth; - let scrollCenterX = true; - let scrollCenterY = true; + let scrollCenterX = true; + let scrollCenterY = true; - const offsetParent = element.offsetParent; + const offsetParent = element.offsetParent; - // In Firefox offsetParent.offsetParent is BODY - const isFixed = offsetParent && (!offsetParent.offsetParent || window.getComputedStyle(offsetParent).position === 'fixed'); + // In Firefox offsetParent.offsetParent is BODY + const isFixed = offsetParent && (!offsetParent.offsetParent || window.getComputedStyle(offsetParent).position === 'fixed'); - // Scroll fixed elements to nearest edge (or do not scroll at all) - if (isFixed) { - scrollCenterX = scrollCenterY = false; - } - - let xScroller = getScrollableParent(element, false); - let yScroller = getScrollableParent(element, true); - - const xScrollerData = getScrollerData(xScroller, false); - const yScrollerData = getScrollerData(yScroller, true); - - // Exit, since we have no control over scrolling in this container - if (xScroller === yScroller && (xScrollerData.custom || yScrollerData.custom)) { - return; - } - - // Exit, since we have no control over scrolling in these containers - if (xScrollerData.custom && yScrollerData.custom) { - return; - } - - const elementRect = element.getBoundingClientRect(); - - let scrollX = 0; - let scrollY = 0; - - if (!xScrollerData.custom) { - const xPos = getScrollerChildPos(xScroller, element, false); - scrollX = calcScroll(xScrollerData, xPos, elementRect.width, scrollCenterX); - } else { - xScroller = null; - } - - if (!yScrollerData.custom) { - const yPos = getScrollerChildPos(yScroller, element, true); - scrollY = calcScroll(yScrollerData, yPos, elementRect.height, scrollCenterY); - - // HACK: Scroll to top for top menu because it is hidden - // FIXME: Need a marker to scroll top/bottom - if (isFixed && elementRect.bottom < 0) { - scrollY = 0; - } - - // HACK: Ensure we are at the top - // FIXME: Need a marker to scroll top/bottom - if (scrollY < minimumScrollY() && yScroller === documentScroller) { - scrollY = 0; - } - } else { - yScroller = null; - } - - doScroll(xScroller, scrollX, yScroller, scrollY, smooth); + // Scroll fixed elements to nearest edge (or do not scroll at all) + if (isFixed) { + scrollCenterX = scrollCenterY = false; } - if (isEnabled()) { - dom.addEventListener(window, 'focusin', function(e) { - setTimeout(function() { - scrollToElement(e.target, useSmoothScroll()); - }, 0); - }, { capture: true }); + let xScroller = getScrollableParent(element, false); + let yScroller = getScrollableParent(element, true); + + const xScrollerData = getScrollerData(xScroller, false); + const yScrollerData = getScrollerData(yScroller, true); + + // Exit, since we have no control over scrolling in this container + if (xScroller === yScroller && (xScrollerData.custom || yScrollerData.custom)) { + return; } -/* eslint-enable indent */ + // Exit, since we have no control over scrolling in these containers + if (xScrollerData.custom && yScrollerData.custom) { + return; + } + + const elementRect = element.getBoundingClientRect(); + + let scrollX = 0; + let scrollY = 0; + + if (!xScrollerData.custom) { + const xPos = getScrollerChildPos(xScroller, element, false); + scrollX = calcScroll(xScrollerData, xPos, elementRect.width, scrollCenterX); + } else { + xScroller = null; + } + + if (!yScrollerData.custom) { + const yPos = getScrollerChildPos(yScroller, element, true); + scrollY = calcScroll(yScrollerData, yPos, elementRect.height, scrollCenterY); + + // HACK: Scroll to top for top menu because it is hidden + // FIXME: Need a marker to scroll top/bottom + if (isFixed && elementRect.bottom < 0) { + scrollY = 0; + } + + // HACK: Ensure we are at the top + // FIXME: Need a marker to scroll top/bottom + if (scrollY < minimumScrollY() && yScroller === documentScroller) { + scrollY = 0; + } + } else { + yScroller = null; + } + + doScroll(xScroller, scrollX, yScroller, scrollY, smooth); +} + +if (isEnabled()) { + dom.addEventListener(window, 'focusin', function(e) { + setTimeout(function() { + scrollToElement(e.target, useSmoothScroll()); + }, 0); + }, { capture: true }); +} export default { isEnabled: isEnabled, diff --git a/src/components/shortcuts.js b/src/components/shortcuts.js index 62f43a3436..7c430be567 100644 --- a/src/components/shortcuts.js +++ b/src/components/shortcuts.js @@ -1,5 +1,3 @@ -/* eslint-disable indent */ - /** * Module shortcuts. * @module components/shortcuts @@ -14,385 +12,383 @@ import recordingHelper from './recordingcreator/recordinghelper'; import ServerConnections from './ServerConnections'; import toast from './toast/toast'; - function playAllFromHere(card, serverId, queue) { - const parent = card.parentNode; - const className = card.classList.length ? (`.${card.classList[0]}`) : ''; - const cards = parent.querySelectorAll(`${className}[data-id]`); +function playAllFromHere(card, serverId, queue) { + const parent = card.parentNode; + const className = card.classList.length ? (`.${card.classList[0]}`) : ''; + const cards = parent.querySelectorAll(`${className}[data-id]`); - const ids = []; + const ids = []; - let foundCard = false; - let startIndex; + let foundCard = false; + let startIndex; - for (let i = 0, length = cards.length; i < length; i++) { - if (cards[i] === card) { - foundCard = true; - startIndex = i; - } - if (foundCard || !queue) { - ids.push(cards[i].getAttribute('data-id')); + for (let i = 0, length = cards.length; i < length; i++) { + if (cards[i] === card) { + foundCard = true; + startIndex = i; + } + if (foundCard || !queue) { + ids.push(cards[i].getAttribute('data-id')); + } + } + + const itemsContainer = dom.parentWithClass(card, 'itemsContainer'); + if (itemsContainer && itemsContainer.fetchData) { + const queryOptions = queue ? { StartIndex: startIndex } : {}; + + return itemsContainer.fetchData(queryOptions).then(result => { + if (queue) { + return playbackManager.queue({ + items: result.Items + }); + } else { + return playbackManager.play({ + items: result.Items, + startIndex: startIndex + }); } + }); + } + + if (!ids.length) { + return; + } + + if (queue) { + return playbackManager.queue({ + ids: ids, + serverId: serverId + }); + } else { + return playbackManager.play({ + ids: ids, + serverId: serverId, + startIndex: startIndex + }); + } +} + +function showProgramDialog(item) { + import('./recordingcreator/recordingcreator').then(({ default:recordingCreator }) => { + recordingCreator.show(item.Id, item.ServerId); + }); +} + +function getItem(button) { + button = dom.parentWithAttribute(button, 'data-id'); + const serverId = button.getAttribute('data-serverid'); + const id = button.getAttribute('data-id'); + const type = button.getAttribute('data-type'); + + const apiClient = ServerConnections.getApiClient(serverId); + + if (type === 'Timer') { + return apiClient.getLiveTvTimer(id); + } + if (type === 'SeriesTimer') { + return apiClient.getLiveTvSeriesTimer(id); + } + return apiClient.getItem(apiClient.getCurrentUserId(), id); +} + +function notifyRefreshNeeded(childElement, itemsContainer) { + itemsContainer = itemsContainer || dom.parentWithAttribute(childElement, 'is', 'emby-itemscontainer'); + + if (itemsContainer) { + itemsContainer.notifyRefreshNeeded(true); + } +} + +function showContextMenu(card, options) { + getItem(card).then(item => { + const playlistId = card.getAttribute('data-playlistid'); + const collectionId = card.getAttribute('data-collectionid'); + + if (playlistId) { + const elem = dom.parentWithAttribute(card, 'data-playlistitemid'); + item.PlaylistItemId = elem ? elem.getAttribute('data-playlistitemid') : null; } - const itemsContainer = dom.parentWithClass(card, 'itemsContainer'); - if (itemsContainer && itemsContainer.fetchData) { - const queryOptions = queue ? { StartIndex: startIndex } : {}; + import('./itemContextMenu').then((itemContextMenu) => { + ServerConnections.getApiClient(item.ServerId).getCurrentUser().then(user => { + itemContextMenu.show(Object.assign({ + item: item, + play: true, + queue: true, + playAllFromHere: !item.IsFolder, + queueAllFromHere: !item.IsFolder, + playlistId: playlistId, + collectionId: collectionId, + user: user - return itemsContainer.fetchData(queryOptions).then(result => { - if (queue) { - return playbackManager.queue({ - items: result.Items - }); - } else { - return playbackManager.play({ - items: result.Items, - startIndex: startIndex - }); - } + }, options || {})).then(result => { + if (result.command === 'playallfromhere' || result.command === 'queueallfromhere') { + executeAction(card, options.positionTo, result.command); + } else if (result.updated || result.deleted) { + notifyRefreshNeeded(card, options.itemsContainer); + } + }); }); - } + }); + }); +} - if (!ids.length) { - return; +function getItemInfoFromCard(card) { + return { + Type: card.getAttribute('data-type'), + Id: card.getAttribute('data-id'), + TimerId: card.getAttribute('data-timerid'), + CollectionType: card.getAttribute('data-collectiontype'), + ChannelId: card.getAttribute('data-channelid'), + SeriesId: card.getAttribute('data-seriesid'), + ServerId: card.getAttribute('data-serverid'), + MediaType: card.getAttribute('data-mediatype'), + Path: card.getAttribute('data-path'), + IsFolder: card.getAttribute('data-isfolder') === 'true', + StartDate: card.getAttribute('data-startdate'), + EndDate: card.getAttribute('data-enddate'), + UserData: { + PlaybackPositionTicks: parseInt(card.getAttribute('data-positionticks') || '0', 10) } + }; +} - if (queue) { - return playbackManager.queue({ - ids: ids, +function showPlayMenu(card, target) { + const item = getItemInfoFromCard(card); + + import('./playmenu').then((playMenu) => { + playMenu.show({ + + item: item, + positionTo: target + }); + }); +} + +function executeAction(card, target, action) { + target = target || card; + + let id = card.getAttribute('data-id'); + + if (!id) { + card = dom.parentWithAttribute(card, 'data-id'); + id = card.getAttribute('data-id'); + } + + const item = getItemInfoFromCard(card); + + const serverId = item.ServerId; + const type = item.Type; + + const playableItemId = type === 'Program' ? item.ChannelId : item.Id; + + if (item.MediaType === 'Photo' && action === 'link') { + action = 'play'; + } + + if (action === 'link') { + appRouter.showItem(item, { + context: card.getAttribute('data-context'), + parentId: card.getAttribute('data-parentid') + }); + } else if (action === 'programdialog') { + showProgramDialog(item); + } else if (action === 'instantmix') { + playbackManager.instantMix({ + Id: playableItemId, + ServerId: serverId + }); + } else if (action === 'play' || action === 'resume') { + const startPositionTicks = parseInt(card.getAttribute('data-positionticks') || '0', 10); + + if (playbackManager.canPlay(item)) { + playbackManager.play({ + ids: [playableItemId], + startPositionTicks: startPositionTicks, serverId: serverId }); } else { - return playbackManager.play({ - ids: ids, - serverId: serverId, - startIndex: startIndex + console.warn('Unable to play item', item); + } + } else if (action === 'queue') { + if (playbackManager.isPlaying()) { + playbackManager.queue({ + ids: [playableItemId], + serverId: serverId + }); + toast(globalize.translate('MediaQueued')); + } else { + playbackManager.queue({ + ids: [playableItemId], + serverId: serverId }); } - } + } else if (action === 'playallfromhere') { + playAllFromHere(card, serverId); + } else if (action === 'queueallfromhere') { + playAllFromHere(card, serverId, true); + } else if (action === 'setplaylistindex') { + playbackManager.setCurrentPlaylistItem(card.getAttribute('data-playlistitemid')); + } else if (action === 'record') { + onRecordCommand(serverId, id, type, card.getAttribute('data-timerid'), card.getAttribute('data-seriestimerid')); + } else if (action === 'menu') { + const options = target.getAttribute('data-playoptions') === 'false' ? + { + shuffle: false, + instantMix: false, + play: false, + playAllFromHere: false, + queue: false, + queueAllFromHere: false + } : + {}; - function showProgramDialog(item) { - import('./recordingcreator/recordingcreator').then(({ default:recordingCreator }) => { - recordingCreator.show(item.Id, item.ServerId); + options.positionTo = target; + + showContextMenu(card, options); + } else if (action === 'playmenu') { + showPlayMenu(card, target); + } else if (action === 'edit') { + getItem(target).then(itemToEdit => { + editItem(itemToEdit, serverId); }); + } else if (action === 'playtrailer') { + getItem(target).then(playTrailer); + } else if (action === 'addtoplaylist') { + getItem(target).then(addToPlaylist); + } else if (action === 'custom') { + const customAction = target.getAttribute('data-customaction'); + + card.dispatchEvent(new CustomEvent(`action-${customAction}`, { + detail: { + playlistItemId: card.getAttribute('data-playlistitemid') + }, + cancelable: false, + bubbles: true + })); } +} - function getItem(button) { - button = dom.parentWithAttribute(button, 'data-id'); - const serverId = button.getAttribute('data-serverid'); - const id = button.getAttribute('data-id'); - const type = button.getAttribute('data-type'); +function addToPlaylist(item) { + import('./playlisteditor/playlisteditor').then(({ default: playlistEditor }) => { + new playlistEditor().show({ + items: [item.Id], + serverId: item.ServerId - const apiClient = ServerConnections.getApiClient(serverId); - - if (type === 'Timer') { - return apiClient.getLiveTvTimer(id); - } - if (type === 'SeriesTimer') { - return apiClient.getLiveTvSeriesTimer(id); - } - return apiClient.getItem(apiClient.getCurrentUserId(), id); - } - - function notifyRefreshNeeded(childElement, itemsContainer) { - itemsContainer = itemsContainer || dom.parentWithAttribute(childElement, 'is', 'emby-itemscontainer'); - - if (itemsContainer) { - itemsContainer.notifyRefreshNeeded(true); - } - } - - function showContextMenu(card, options) { - getItem(card).then(item => { - const playlistId = card.getAttribute('data-playlistid'); - const collectionId = card.getAttribute('data-collectionid'); - - if (playlistId) { - const elem = dom.parentWithAttribute(card, 'data-playlistitemid'); - item.PlaylistItemId = elem ? elem.getAttribute('data-playlistitemid') : null; - } - - import('./itemContextMenu').then((itemContextMenu) => { - ServerConnections.getApiClient(item.ServerId).getCurrentUser().then(user => { - itemContextMenu.show(Object.assign({ - item: item, - play: true, - queue: true, - playAllFromHere: !item.IsFolder, - queueAllFromHere: !item.IsFolder, - playlistId: playlistId, - collectionId: collectionId, - user: user - - }, options || {})).then(result => { - if (result.command === 'playallfromhere' || result.command === 'queueallfromhere') { - executeAction(card, options.positionTo, result.command); - } else if (result.updated || result.deleted) { - notifyRefreshNeeded(card, options.itemsContainer); - } - }); - }); - }); }); - } + }); +} - function getItemInfoFromCard(card) { - return { - Type: card.getAttribute('data-type'), - Id: card.getAttribute('data-id'), - TimerId: card.getAttribute('data-timerid'), - CollectionType: card.getAttribute('data-collectiontype'), - ChannelId: card.getAttribute('data-channelid'), - SeriesId: card.getAttribute('data-seriesid'), - ServerId: card.getAttribute('data-serverid'), - MediaType: card.getAttribute('data-mediatype'), - Path: card.getAttribute('data-path'), - IsFolder: card.getAttribute('data-isfolder') === 'true', - StartDate: card.getAttribute('data-startdate'), - EndDate: card.getAttribute('data-enddate'), - UserData: { - PlaybackPositionTicks: parseInt(card.getAttribute('data-positionticks') || '0', 10) - } - }; - } +function playTrailer(item) { + const apiClient = ServerConnections.getApiClient(item.ServerId); - function showPlayMenu(card, target) { - const item = getItemInfoFromCard(card); + apiClient.getLocalTrailers(apiClient.getCurrentUserId(), item.Id).then(trailers => { + playbackManager.play({ items: trailers }); + }); +} - import('./playmenu').then((playMenu) => { - playMenu.show({ +function editItem(item, serverId) { + const apiClient = ServerConnections.getApiClient(serverId); - item: item, - positionTo: target - }); - }); - } + return new Promise((resolve, reject) => { + const currentServerId = apiClient.serverInfo().Id; - function executeAction(card, target, action) { - target = target || card; - - let id = card.getAttribute('data-id'); - - if (!id) { - card = dom.parentWithAttribute(card, 'data-id'); - id = card.getAttribute('data-id'); - } - - const item = getItemInfoFromCard(card); - - const serverId = item.ServerId; - const type = item.Type; - - const playableItemId = type === 'Program' ? item.ChannelId : item.Id; - - if (item.MediaType === 'Photo' && action === 'link') { - action = 'play'; - } - - if (action === 'link') { - appRouter.showItem(item, { - context: card.getAttribute('data-context'), - parentId: card.getAttribute('data-parentid') - }); - } else if (action === 'programdialog') { - showProgramDialog(item); - } else if (action === 'instantmix') { - playbackManager.instantMix({ - Id: playableItemId, - ServerId: serverId - }); - } else if (action === 'play' || action === 'resume') { - const startPositionTicks = parseInt(card.getAttribute('data-positionticks') || '0', 10); - - if (playbackManager.canPlay(item)) { - playbackManager.play({ - ids: [playableItemId], - startPositionTicks: startPositionTicks, - serverId: serverId + if (item.Type === 'Timer') { + if (item.ProgramId) { + import('./recordingcreator/recordingcreator').then(({ default: recordingCreator }) => { + recordingCreator.show(item.ProgramId, currentServerId).then(resolve, reject); }); } else { - console.warn('Unable to play item', item); - } - } else if (action === 'queue') { - if (playbackManager.isPlaying()) { - playbackManager.queue({ - ids: [playableItemId], - serverId: serverId - }); - toast(globalize.translate('MediaQueued')); - } else { - playbackManager.queue({ - ids: [playableItemId], - serverId: serverId + import('./recordingcreator/recordingeditor').then(({ default: recordingEditor }) => { + recordingEditor.show(item.Id, currentServerId).then(resolve, reject); }); } - } else if (action === 'playallfromhere') { - playAllFromHere(card, serverId); - } else if (action === 'queueallfromhere') { - playAllFromHere(card, serverId, true); - } else if (action === 'setplaylistindex') { - playbackManager.setCurrentPlaylistItem(card.getAttribute('data-playlistitemid')); - } else if (action === 'record') { - onRecordCommand(serverId, id, type, card.getAttribute('data-timerid'), card.getAttribute('data-seriestimerid')); - } else if (action === 'menu') { - const options = target.getAttribute('data-playoptions') === 'false' ? - { - shuffle: false, - instantMix: false, - play: false, - playAllFromHere: false, - queue: false, - queueAllFromHere: false - } : - {}; - - options.positionTo = target; - - showContextMenu(card, options); - } else if (action === 'playmenu') { - showPlayMenu(card, target); - } else if (action === 'edit') { - getItem(target).then(itemToEdit => { - editItem(itemToEdit, serverId); + } else { + import('./metadataEditor/metadataEditor').then(({ default: metadataEditor }) => { + metadataEditor.show(item.Id, currentServerId).then(resolve, reject); }); - } else if (action === 'playtrailer') { - getItem(target).then(playTrailer); - } else if (action === 'addtoplaylist') { - getItem(target).then(addToPlaylist); - } else if (action === 'custom') { - const customAction = target.getAttribute('data-customaction'); + } + }); +} - card.dispatchEvent(new CustomEvent(`action-${customAction}`, { - detail: { - playlistItemId: card.getAttribute('data-playlistitemid') - }, - cancelable: false, - bubbles: true - })); +function onRecordCommand(serverId, id, type, timerId, seriesTimerId) { + if (type === 'Program' || timerId || seriesTimerId) { + const programId = type === 'Program' ? id : null; + recordingHelper.toggleRecording(serverId, programId, timerId, seriesTimerId); + } +} + +export function onClick(e) { + const card = dom.parentWithClass(e.target, 'itemAction'); + + if (card) { + let actionElement = card; + let action = actionElement.getAttribute('data-action'); + + if (!action) { + actionElement = dom.parentWithAttribute(actionElement, 'data-action'); + if (actionElement) { + action = actionElement.getAttribute('data-action'); + } + } + + if (action) { + executeAction(card, actionElement, action); + + e.preventDefault(); + e.stopPropagation(); + return false; } } +} - function addToPlaylist(item) { - import('./playlisteditor/playlisteditor').then(({ default: playlistEditor }) => { - new playlistEditor().show({ - items: [item.Id], - serverId: item.ServerId +function onCommand(e) { + const cmd = e.detail.command; - }); - }); - } - - function playTrailer(item) { - const apiClient = ServerConnections.getApiClient(item.ServerId); - - apiClient.getLocalTrailers(apiClient.getCurrentUserId(), item.Id).then(trailers => { - playbackManager.play({ items: trailers }); - }); - } - - function editItem(item, serverId) { - const apiClient = ServerConnections.getApiClient(serverId); - - return new Promise((resolve, reject) => { - const currentServerId = apiClient.serverInfo().Id; - - if (item.Type === 'Timer') { - if (item.ProgramId) { - import('./recordingcreator/recordingcreator').then(({ default: recordingCreator }) => { - recordingCreator.show(item.ProgramId, currentServerId).then(resolve, reject); - }); - } else { - import('./recordingcreator/recordingeditor').then(({ default: recordingEditor }) => { - recordingEditor.show(item.Id, currentServerId).then(resolve, reject); - }); - } - } else { - import('./metadataEditor/metadataEditor').then(({ default: metadataEditor }) => { - metadataEditor.show(item.Id, currentServerId).then(resolve, reject); - }); - } - }); - } - - function onRecordCommand(serverId, id, type, timerId, seriesTimerId) { - if (type === 'Program' || timerId || seriesTimerId) { - const programId = type === 'Program' ? id : null; - recordingHelper.toggleRecording(serverId, programId, timerId, seriesTimerId); - } - } - - export function onClick(e) { - const card = dom.parentWithClass(e.target, 'itemAction'); + if (cmd === 'play' || cmd === 'resume' || cmd === 'record' || cmd === 'menu' || cmd === 'info') { + const target = e.target; + const card = dom.parentWithClass(target, 'itemAction') || dom.parentWithAttribute(target, 'data-id'); if (card) { - let actionElement = card; - let action = actionElement.getAttribute('data-action'); - - if (!action) { - actionElement = dom.parentWithAttribute(actionElement, 'data-action'); - if (actionElement) { - action = actionElement.getAttribute('data-action'); - } - } - - if (action) { - executeAction(card, actionElement, action); - - e.preventDefault(); - e.stopPropagation(); - return false; - } + e.preventDefault(); + e.stopPropagation(); + executeAction(card, card, cmd); } } +} - function onCommand(e) { - const cmd = e.detail.command; +export function on(context, options) { + options = options || {}; - if (cmd === 'play' || cmd === 'resume' || cmd === 'record' || cmd === 'menu' || cmd === 'info') { - const target = e.target; - const card = dom.parentWithClass(target, 'itemAction') || dom.parentWithAttribute(target, 'data-id'); - - if (card) { - e.preventDefault(); - e.stopPropagation(); - executeAction(card, card, cmd); - } - } + if (options.click !== false) { + context.addEventListener('click', onClick); } - export function on(context, options) { - options = options || {}; + if (options.command !== false) { + inputManager.on(context, onCommand); + } +} - if (options.click !== false) { - context.addEventListener('click', onClick); - } +export function off(context, options) { + options = options || {}; - if (options.command !== false) { - inputManager.on(context, onCommand); - } + context.removeEventListener('click', onClick); + + if (options.command !== false) { + inputManager.off(context, onCommand); + } +} + +export function getShortcutAttributesHtml(item, serverId) { + let html = `data-id="${item.Id}" data-serverid="${serverId || item.ServerId}" data-type="${item.Type}" data-mediatype="${item.MediaType}" data-channelid="${item.ChannelId}" data-isfolder="${item.IsFolder}"`; + + const collectionType = item.CollectionType; + if (collectionType) { + html += ` data-collectiontype="${collectionType}"`; } - export function off(context, options) { - options = options || {}; - - context.removeEventListener('click', onClick); - - if (options.command !== false) { - inputManager.off(context, onCommand); - } - } - - export function getShortcutAttributesHtml(item, serverId) { - let html = `data-id="${item.Id}" data-serverid="${serverId || item.ServerId}" data-type="${item.Type}" data-mediatype="${item.MediaType}" data-channelid="${item.ChannelId}" data-isfolder="${item.IsFolder}"`; - - const collectionType = item.CollectionType; - if (collectionType) { - html += ` data-collectiontype="${collectionType}"`; - } - - return html; - } - -/* eslint-enable indent */ + return html; +} export default { on: on, diff --git a/src/components/upnextdialog/upnextdialog.js b/src/components/upnextdialog/upnextdialog.js index bfb3ca18cd..2434744f24 100644 --- a/src/components/upnextdialog/upnextdialog.js +++ b/src/components/upnextdialog/upnextdialog.js @@ -10,199 +10,197 @@ import './upnextdialog.scss'; import '../../elements/emby-button/emby-button'; import '../../styles/flexstyles.scss'; -/* eslint-disable indent */ +const transitionEndEventName = dom.whichTransitionEvent(); - const transitionEndEventName = dom.whichTransitionEvent(); +function getHtml() { + let html = ''; - function getHtml() { - let html = ''; + html += '
'; - html += '
'; + html += '

 

'; - html += '

 

'; + html += '

'; - html += '

'; + html += '
'; + html += '
'; - html += '
'; - html += '
'; + html += '
'; - html += '
'; + html += ''; - html += ''; + html += ''; - html += ''; + // buttons + html += '
'; - // buttons - html += '
'; + // main + html += '
'; - // main - html += '
'; + return html; +} - return html; +function setNextVideoText() { + const instance = this; + + const elem = instance.options.parent; + + const secondsRemaining = Math.max(Math.round(getTimeRemainingMs(instance) / 1000), 0); + + console.debug('up next seconds remaining: ' + secondsRemaining); + + const timeText = '' + globalize.translate('HeaderSecondsValue', secondsRemaining) + ''; + + const nextVideoText = instance.itemType === 'Episode' ? + globalize.translate('HeaderNextEpisodePlayingInValue', timeText) : + globalize.translate('HeaderNextVideoPlayingInValue', timeText); + + elem.querySelector('.upNextDialog-nextVideoText').innerHTML = nextVideoText; +} + +function fillItem(item) { + const instance = this; + + const elem = instance.options.parent; + + elem.querySelector('.upNextDialog-mediainfo').innerHTML = mediaInfo.getPrimaryMediaInfoHtml(item, { + criticRating: true, + originalAirDate: false, + starRating: true, + subtitles: false + }); + + let title = itemHelper.getDisplayName(item); + if (item.SeriesName) { + title = item.SeriesName + ' - ' + title; } - function setNextVideoText() { - const instance = this; + elem.querySelector('.upNextDialog-title').innerText = title || ''; - const elem = instance.options.parent; + instance.itemType = item.Type; - const secondsRemaining = Math.max(Math.round(getTimeRemainingMs(instance) / 1000), 0); + instance.show(); +} - console.debug('up next seconds remaining: ' + secondsRemaining); +function clearCountdownTextTimeout(instance) { + if (instance._countdownTextTimeout) { + clearInterval(instance._countdownTextTimeout); + instance._countdownTextTimeout = null; + } +} - const timeText = '' + globalize.translate('HeaderSecondsValue', secondsRemaining) + ''; +async function onStartNowClick() { + const options = this.options; - const nextVideoText = instance.itemType === 'Episode' ? - globalize.translate('HeaderNextEpisodePlayingInValue', timeText) : - globalize.translate('HeaderNextVideoPlayingInValue', timeText); + if (options) { + const player = options.player; - elem.querySelector('.upNextDialog-nextVideoText').innerHTML = nextVideoText; + await this.hide(); + + playbackManager.nextTrack(player); + } +} + +function init(instance, options) { + options.parent.innerHTML = getHtml(); + + options.parent.classList.add('hide'); + options.parent.classList.add('upNextDialog'); + options.parent.classList.add('upNextDialog-hidden'); + + fillItem.call(instance, options.nextItem); + + options.parent.querySelector('.btnHide').addEventListener('click', instance.hide.bind(instance)); + options.parent.querySelector('.btnStartNow').addEventListener('click', onStartNowClick.bind(instance)); +} + +function clearHideAnimationEventListeners(instance, elem) { + const fn = instance._onHideAnimationComplete; + + if (fn) { + dom.removeEventListener(elem, transitionEndEventName, fn, { + once: true + }); + } +} + +function onHideAnimationComplete(e) { + const instance = this; + const elem = e.target; + + elem.classList.add('hide'); + + clearHideAnimationEventListeners(instance, elem); + Events.trigger(instance, 'hide'); +} + +async function hideComingUpNext() { + const instance = this; + clearCountdownTextTimeout(this); + + if (!instance.options) { + return; } - function fillItem(item) { - const instance = this; + const elem = instance.options.parent; - const elem = instance.options.parent; + if (!elem) { + return; + } - elem.querySelector('.upNextDialog-mediainfo').innerHTML = mediaInfo.getPrimaryMediaInfoHtml(item, { - criticRating: true, - originalAirDate: false, - starRating: true, - subtitles: false + clearHideAnimationEventListeners(this, elem); + + if (elem.classList.contains('upNextDialog-hidden')) { + return; + } + + const fn = onHideAnimationComplete.bind(instance); + instance._onHideAnimationComplete = fn; + + const transitionEvent = await new Promise((resolve) => { + dom.addEventListener(elem, transitionEndEventName, resolve, { + once: true }); - let title = itemHelper.getDisplayName(item); - if (item.SeriesName) { - title = item.SeriesName + ' - ' + title; - } + // trigger a reflow to force it to animate again + void elem.offsetWidth; - elem.querySelector('.upNextDialog-title').innerText = title || ''; + elem.classList.add('upNextDialog-hidden'); + }); - instance.itemType = item.Type; + instance._onHideAnimationComplete(transitionEvent); +} - instance.show(); - } +function getTimeRemainingMs(instance) { + const options = instance.options; + if (options) { + const runtimeTicks = playbackManager.duration(options.player); - function clearCountdownTextTimeout(instance) { - if (instance._countdownTextTimeout) { - clearInterval(instance._countdownTextTimeout); - instance._countdownTextTimeout = null; + if (runtimeTicks) { + const timeRemainingTicks = runtimeTicks - playbackManager.currentTime(options.player) * 10000; + + return Math.round(timeRemainingTicks / 10000); } } - async function onStartNowClick() { - const options = this.options; + return 0; +} - if (options) { - const player = options.player; +function startComingUpNextHideTimer(instance) { + const timeRemainingMs = getTimeRemainingMs(instance); - await this.hide(); - - playbackManager.nextTrack(player); - } + if (timeRemainingMs <= 0) { + return; } - function init(instance, options) { - options.parent.innerHTML = getHtml(); + setNextVideoText.call(instance); + clearCountdownTextTimeout(instance); - options.parent.classList.add('hide'); - options.parent.classList.add('upNextDialog'); - options.parent.classList.add('upNextDialog-hidden'); - - fillItem.call(instance, options.nextItem); - - options.parent.querySelector('.btnHide').addEventListener('click', instance.hide.bind(instance)); - options.parent.querySelector('.btnStartNow').addEventListener('click', onStartNowClick.bind(instance)); - } - - function clearHideAnimationEventListeners(instance, elem) { - const fn = instance._onHideAnimationComplete; - - if (fn) { - dom.removeEventListener(elem, transitionEndEventName, fn, { - once: true - }); - } - } - - function onHideAnimationComplete(e) { - const instance = this; - const elem = e.target; - - elem.classList.add('hide'); - - clearHideAnimationEventListeners(instance, elem); - Events.trigger(instance, 'hide'); - } - - async function hideComingUpNext() { - const instance = this; - clearCountdownTextTimeout(this); - - if (!instance.options) { - return; - } - - const elem = instance.options.parent; - - if (!elem) { - return; - } - - clearHideAnimationEventListeners(this, elem); - - if (elem.classList.contains('upNextDialog-hidden')) { - return; - } - - const fn = onHideAnimationComplete.bind(instance); - instance._onHideAnimationComplete = fn; - - const transitionEvent = await new Promise((resolve) => { - dom.addEventListener(elem, transitionEndEventName, resolve, { - once: true - }); - - // trigger a reflow to force it to animate again - void elem.offsetWidth; - - elem.classList.add('upNextDialog-hidden'); - }); - - instance._onHideAnimationComplete(transitionEvent); - } - - function getTimeRemainingMs(instance) { - const options = instance.options; - if (options) { - const runtimeTicks = playbackManager.duration(options.player); - - if (runtimeTicks) { - const timeRemainingTicks = runtimeTicks - playbackManager.currentTime(options.player) * 10000; - - return Math.round(timeRemainingTicks / 10000); - } - } - - return 0; - } - - function startComingUpNextHideTimer(instance) { - const timeRemainingMs = getTimeRemainingMs(instance); - - if (timeRemainingMs <= 0) { - return; - } - - setNextVideoText.call(instance); - clearCountdownTextTimeout(instance); - - instance._countdownTextTimeout = setInterval(setNextVideoText.bind(instance), 400); - } + instance._countdownTextTimeout = setInterval(setNextVideoText.bind(instance), 400); +} class UpNextDialog { constructor(options) { @@ -243,4 +241,3 @@ class UpNextDialog { export default UpNextDialog; -/* eslint-enable indent */ diff --git a/src/components/viewContainer.js b/src/components/viewContainer.js index c65b5f8a03..a5547517a7 100644 --- a/src/components/viewContainer.js +++ b/src/components/viewContainer.js @@ -10,248 +10,244 @@ const getMainAnimatedPages = () => { return mainAnimatedPages; }; -/* eslint-disable indent */ - - function setControllerClass(view, options) { - if (options.controllerFactory) { - return Promise.resolve(); - } - - let controllerUrl = view.getAttribute('data-controller'); - - if (controllerUrl) { - if (controllerUrl.indexOf('__plugin/') === 0) { - controllerUrl = controllerUrl.substring('__plugin/'.length); - } - - controllerUrl = Dashboard.getPluginUrl(controllerUrl); - const apiUrl = ApiClient.getUrl('/web/' + controllerUrl); - return importModule(apiUrl).then((ControllerFactory) => { - options.controllerFactory = ControllerFactory; - }); - } - +function setControllerClass(view, options) { + if (options.controllerFactory) { return Promise.resolve(); } - export function loadView(options) { - if (!options.cancel) { - const selected = selectedPageIndex; - const previousAnimatable = selected === -1 ? null : allPages[selected]; - let pageIndex = selected + 1; + let controllerUrl = view.getAttribute('data-controller'); - if (pageIndex >= pageContainerCount) { - pageIndex = 0; + if (controllerUrl) { + if (controllerUrl.indexOf('__plugin/') === 0) { + controllerUrl = controllerUrl.substring('__plugin/'.length); + } + + controllerUrl = Dashboard.getPluginUrl(controllerUrl); + const apiUrl = ApiClient.getUrl('/web/' + controllerUrl); + return importModule(apiUrl).then((ControllerFactory) => { + options.controllerFactory = ControllerFactory; + }); + } + + return Promise.resolve(); +} + +export function loadView(options) { + if (!options.cancel) { + const selected = selectedPageIndex; + const previousAnimatable = selected === -1 ? null : allPages[selected]; + let pageIndex = selected + 1; + + if (pageIndex >= pageContainerCount) { + pageIndex = 0; + } + + const isPluginpage = options.url.includes('configurationpage'); + const newViewInfo = normalizeNewView(options, isPluginpage); + const newView = newViewInfo.elem; + + const currentPage = allPages[pageIndex]; + + if (currentPage) { + triggerDestroy(currentPage); + } + + let view = newView; + + if (typeof view == 'string') { + view = document.createElement('div'); + view.innerHTML = newView; + } + + view.classList.add('mainAnimatedPage'); + + if (!getMainAnimatedPages()) { + console.warn('[viewContainer] main animated pages element is not present'); + return; + } + + if (currentPage) { + if (newViewInfo.hasScript && window.$) { + mainAnimatedPages.removeChild(currentPage); + view = $(view).appendTo(mainAnimatedPages)[0]; + } else { + mainAnimatedPages.replaceChild(view, currentPage); } - - const isPluginpage = options.url.includes('configurationpage'); - const newViewInfo = normalizeNewView(options, isPluginpage); - const newView = newViewInfo.elem; - - const currentPage = allPages[pageIndex]; - - if (currentPage) { - triggerDestroy(currentPage); + } else { + if (newViewInfo.hasScript && window.$) { + view = $(view).appendTo(mainAnimatedPages)[0]; + } else { + mainAnimatedPages.appendChild(view); } + } - let view = newView; + if (options.type) { + view.setAttribute('data-type', options.type); + } - if (typeof view == 'string') { - view = document.createElement('div'); - view.innerHTML = newView; - } + const properties = []; - view.classList.add('mainAnimatedPage'); + if (options.fullscreen) { + properties.push('fullscreen'); + } - if (!getMainAnimatedPages()) { - console.warn('[viewContainer] main animated pages element is not present'); + if (properties.length) { + view.setAttribute('data-properties', properties.join(',')); + } + + allPages[pageIndex] = view; + + return setControllerClass(view, options) + // Timeout for polyfilled CustomElements (webOS 1.2) + .then(() => new Promise((resolve) => setTimeout(resolve, 0))) + .then(() => { + if (onBeforeChange) { + onBeforeChange(view, false, options); + } + + beforeAnimate(allPages, pageIndex, selected); + selectedPageIndex = pageIndex; + currentUrls[pageIndex] = options.url; + + if (!options.cancel && previousAnimatable) { + afterAnimate(allPages, pageIndex); + } + + if (window.$) { + $.mobile = $.mobile || {}; + $.mobile.activePage = view; + } + + return view; + }); + } +} + +function parseHtml(html, hasScript) { + if (hasScript) { + html = html + .replaceAll('\x3c!----\x3e', ''); + } + + const wrapper = document.createElement('div'); + wrapper.innerHTML = html; + return wrapper.querySelector('div[data-role="page"]'); +} + +function normalizeNewView(options, isPluginpage) { + const viewHtml = options.view; + + if (viewHtml.indexOf('data-role="page"') === -1) { + return viewHtml; + } + + let hasScript = viewHtml.indexOf(' new Promise((resolve) => setTimeout(resolve, 0))) - .then(() => { - if (onBeforeChange) { - onBeforeChange(view, false, options); - } - - beforeAnimate(allPages, pageIndex, selected); - selectedPageIndex = pageIndex; - currentUrls[pageIndex] = options.url; - - if (!options.cancel && previousAnimatable) { - afterAnimate(allPages, pageIndex); - } - - if (window.$) { - $.mobile = $.mobile || {}; - $.mobile.activePage = view; - } - - return view; - }); - } - } - - function parseHtml(html, hasScript) { - if (hasScript) { - html = html - .replaceAll('\x3c!----\x3e', ''); - } - - const wrapper = document.createElement('div'); - wrapper.innerHTML = html; - return wrapper.querySelector('div[data-role="page"]'); - } - - function normalizeNewView(options, isPluginpage) { - const viewHtml = options.view; - - if (viewHtml.indexOf('data-role="page"') === -1) { - return viewHtml; - } - - let hasScript = viewHtml.indexOf(' { + if (onBeforeChange) { + onBeforeChange(view, true, options); } - const selected = selectedPageIndex; - const previousAnimatable = selected === -1 ? null : allPages[selected]; - return setControllerClass(view, options).then(() => { - if (onBeforeChange) { - onBeforeChange(view, true, options); - } + beforeAnimate(allPages, index, selected); + animatable.classList.remove('hide'); + selectedPageIndex = index; - beforeAnimate(allPages, index, selected); - animatable.classList.remove('hide'); - selectedPageIndex = index; + if (!options.cancel && previousAnimatable) { + afterAnimate(allPages, index); + } - if (!options.cancel && previousAnimatable) { - afterAnimate(allPages, index); - } + if (window.$) { + $.mobile = $.mobile || {}; + $.mobile.activePage = view; + } - if (window.$) { - $.mobile = $.mobile || {}; - $.mobile.activePage = view; - } - - return view; - }); - } + return view; + }); } - - return Promise.reject(); } - function triggerDestroy(view) { - view.dispatchEvent(new CustomEvent('viewdestroy', {})); - } + return Promise.reject(); +} - export function reset() { - allPages = []; - currentUrls = []; - if (mainAnimatedPages) mainAnimatedPages.innerHTML = ''; - selectedPageIndex = -1; - } +function triggerDestroy(view) { + view.dispatchEvent(new CustomEvent('viewdestroy', {})); +} - let onBeforeChange; - let mainAnimatedPages; - let allPages = []; - let currentUrls = []; - const pageContainerCount = 3; - let selectedPageIndex = -1; - reset(); - getMainAnimatedPages()?.classList.remove('hide'); +export function reset() { + allPages = []; + currentUrls = []; + if (mainAnimatedPages) mainAnimatedPages.innerHTML = ''; + selectedPageIndex = -1; +} -/* eslint-enable indent */ +let onBeforeChange; +let mainAnimatedPages; +let allPages = []; +let currentUrls = []; +const pageContainerCount = 3; +let selectedPageIndex = -1; +reset(); +getMainAnimatedPages()?.classList.remove('hide'); export default { loadView: loadView, diff --git a/src/controllers/dashboard/apikeys.js b/src/controllers/dashboard/apikeys.js index 0946f10e60..c36aac650f 100644 --- a/src/controllers/dashboard/apikeys.js +++ b/src/controllers/dashboard/apikeys.js @@ -6,85 +6,82 @@ import '../../elements/emby-button/emby-button'; import confirm from '../../components/confirm/confirm'; import { pageIdOn } from '../../utils/dashboard'; -/* eslint-disable indent */ +function revoke(page, key) { + confirm(globalize.translate('MessageConfirmRevokeApiKey'), globalize.translate('HeaderConfirmRevokeApiKey')).then(function () { + loading.show(); + ApiClient.ajax({ + type: 'DELETE', + url: ApiClient.getUrl('Auth/Keys/' + key) + }).then(function () { + loadData(page); + }); + }); +} - function revoke(page, key) { - confirm(globalize.translate('MessageConfirmRevokeApiKey'), globalize.translate('HeaderConfirmRevokeApiKey')).then(function () { - loading.show(); +function renderKeys(page, keys) { + const rows = keys.map(function (item) { + let html = ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += item.AccessToken; + html += ''; + html += ''; + html += item.AppName || ''; + html += ''; + html += ''; + const date = datetime.parseISO8601Date(item.DateCreated, true); + html += datetime.toLocaleDateString(date) + ' ' + datetime.getDisplayTime(date); + html += ''; + html += ''; + return html; + }).join(''); + page.querySelector('.resultBody').innerHTML = rows; + loading.hide(); +} + +function loadData(page) { + loading.show(); + ApiClient.getJSON(ApiClient.getUrl('Auth/Keys')).then(function (result) { + renderKeys(page, result.Items); + }); +} + +function showNewKeyPrompt(page) { + import('../../components/prompt/prompt').then(({ default: prompt }) => { + prompt({ + title: globalize.translate('HeaderNewApiKey'), + label: globalize.translate('LabelAppName'), + description: globalize.translate('LabelAppNameExample') + }).then(function (value) { ApiClient.ajax({ - type: 'DELETE', - url: ApiClient.getUrl('Auth/Keys/' + key) + type: 'POST', + url: ApiClient.getUrl('Auth/Keys', { + App: value + }) }).then(function () { loadData(page); }); }); - } - - function renderKeys(page, keys) { - const rows = keys.map(function (item) { - let html = ''; - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - html += item.AccessToken; - html += ''; - html += ''; - html += item.AppName || ''; - html += ''; - html += ''; - const date = datetime.parseISO8601Date(item.DateCreated, true); - html += datetime.toLocaleDateString(date) + ' ' + datetime.getDisplayTime(date); - html += ''; - html += ''; - return html; - }).join(''); - page.querySelector('.resultBody').innerHTML = rows; - loading.hide(); - } - - function loadData(page) { - loading.show(); - ApiClient.getJSON(ApiClient.getUrl('Auth/Keys')).then(function (result) { - renderKeys(page, result.Items); - }); - } - - function showNewKeyPrompt(page) { - import('../../components/prompt/prompt').then(({ default: prompt }) => { - prompt({ - title: globalize.translate('HeaderNewApiKey'), - label: globalize.translate('LabelAppName'), - description: globalize.translate('LabelAppNameExample') - }).then(function (value) { - ApiClient.ajax({ - type: 'POST', - url: ApiClient.getUrl('Auth/Keys', { - App: value - }) - }).then(function () { - loadData(page); - }); - }); - }); - } - - pageIdOn('pageinit', 'apiKeysPage', function () { - const page = this; - page.querySelector('.btnNewKey').addEventListener('click', function () { - showNewKeyPrompt(page); - }); - page.querySelector('.tblApiKeys').addEventListener('click', function (e) { - const btnRevoke = dom.parentWithClass(e.target, 'btnRevoke'); - - if (btnRevoke) { - revoke(page, btnRevoke.getAttribute('data-token')); - } - }); - }); - pageIdOn('pagebeforeshow', 'apiKeysPage', function () { - loadData(this); }); +} + +pageIdOn('pageinit', 'apiKeysPage', function () { + const page = this; + page.querySelector('.btnNewKey').addEventListener('click', function () { + showNewKeyPrompt(page); + }); + page.querySelector('.tblApiKeys').addEventListener('click', function (e) { + const btnRevoke = dom.parentWithClass(e.target, 'btnRevoke'); + + if (btnRevoke) { + revoke(page, btnRevoke.getAttribute('data-token')); + } + }); +}); +pageIdOn('pagebeforeshow', 'apiKeysPage', function () { + loadData(this); +}); -/* eslint-enable indent */ diff --git a/src/controllers/dashboard/dashboard.js b/src/controllers/dashboard/dashboard.js index d2db85156a..95912a55d6 100644 --- a/src/controllers/dashboard/dashboard.js +++ b/src/controllers/dashboard/dashboard.js @@ -24,826 +24,823 @@ import ServerConnections from '../../components/ServerConnections'; import alert from '../../components/alert'; import confirm from '../../components/confirm/confirm'; -/* eslint-disable indent */ +function showPlaybackInfo(btn, session) { + let title; + const text = []; + const displayPlayMethod = playMethodHelper.getDisplayPlayMethod(session); - function showPlaybackInfo(btn, session) { - let title; - const text = []; - const displayPlayMethod = playMethodHelper.getDisplayPlayMethod(session); + if (displayPlayMethod === 'Remux') { + title = globalize.translate('Remuxing'); + text.push(globalize.translate('RemuxHelp1')); + text.push('
'); + text.push(globalize.translate('RemuxHelp2')); + } else if (displayPlayMethod === 'DirectStream') { + title = globalize.translate('DirectStreaming'); + text.push(globalize.translate('DirectStreamHelp1')); + text.push('
'); + text.push(globalize.translate('DirectStreamHelp2')); + } else if (displayPlayMethod === 'DirectPlay') { + title = globalize.translate('DirectPlaying'); + text.push(globalize.translate('DirectPlayHelp')); + } else if (displayPlayMethod === 'Transcode') { + title = globalize.translate('Transcoding'); + text.push(globalize.translate('MediaIsBeingConverted')); + text.push(DashboardPage.getSessionNowPlayingStreamInfo(session)); - if (displayPlayMethod === 'Remux') { - title = globalize.translate('Remuxing'); - text.push(globalize.translate('RemuxHelp1')); + if (session.TranscodingInfo && session.TranscodingInfo.TranscodeReasons && session.TranscodingInfo.TranscodeReasons.length) { text.push('
'); - text.push(globalize.translate('RemuxHelp2')); - } else if (displayPlayMethod === 'DirectStream') { - title = globalize.translate('DirectStreaming'); - text.push(globalize.translate('DirectStreamHelp1')); - text.push('
'); - text.push(globalize.translate('DirectStreamHelp2')); - } else if (displayPlayMethod === 'DirectPlay') { - title = globalize.translate('DirectPlaying'); - text.push(globalize.translate('DirectPlayHelp')); - } else if (displayPlayMethod === 'Transcode') { - title = globalize.translate('Transcoding'); - text.push(globalize.translate('MediaIsBeingConverted')); - text.push(DashboardPage.getSessionNowPlayingStreamInfo(session)); - - if (session.TranscodingInfo && session.TranscodingInfo.TranscodeReasons && session.TranscodingInfo.TranscodeReasons.length) { - text.push('
'); - text.push(globalize.translate('LabelReasonForTranscoding')); - session.TranscodingInfo.TranscodeReasons.forEach(function (transcodeReason) { - text.push(globalize.translate(transcodeReason)); - }); - } - } - - alert({ - text: text.join('
'), - title: title - }); - } - - function showSendMessageForm(btn, session) { - import('../../components/prompt/prompt').then(({ default: prompt }) => { - prompt({ - title: globalize.translate('HeaderSendMessage'), - label: globalize.translate('LabelMessageText'), - confirmText: globalize.translate('ButtonSend') - }).then(function (text) { - if (text) { - ServerConnections.getApiClient(session.ServerId).sendMessageCommand(session.Id, { - Text: text, - TimeoutMs: 5e3 - }); - } + text.push(globalize.translate('LabelReasonForTranscoding')); + session.TranscodingInfo.TranscodeReasons.forEach(function (transcodeReason) { + text.push(globalize.translate(transcodeReason)); }); - }); + } } - function showOptionsMenu(btn, session) { - import('../../components/actionSheet/actionSheet').then(({ default: actionsheet }) => { - const menuItems = []; + alert({ + text: text.join('
'), + title: title + }); +} - if (session.ServerId && session.DeviceId !== ServerConnections.deviceId()) { - menuItems.push({ - name: globalize.translate('SendMessage'), - id: 'sendmessage' +function showSendMessageForm(btn, session) { + import('../../components/prompt/prompt').then(({ default: prompt }) => { + prompt({ + title: globalize.translate('HeaderSendMessage'), + label: globalize.translate('LabelMessageText'), + confirmText: globalize.translate('ButtonSend') + }).then(function (text) { + if (text) { + ServerConnections.getApiClient(session.ServerId).sendMessageCommand(session.Id, { + Text: text, + TimeoutMs: 5e3 }); } + }); + }); +} - if (session.TranscodingInfo && session.TranscodingInfo.TranscodeReasons && session.TranscodingInfo.TranscodeReasons.length) { - menuItems.push({ - name: globalize.translate('ViewPlaybackInfo'), - id: 'transcodinginfo' - }); - } +function showOptionsMenu(btn, session) { + import('../../components/actionSheet/actionSheet').then(({ default: actionsheet }) => { + const menuItems = []; - return actionsheet.show({ - items: menuItems, - positionTo: btn - }).then(function (id) { - switch (id) { - case 'sendmessage': - showSendMessageForm(btn, session); - break; - case 'transcodinginfo': - showPlaybackInfo(btn, session); - break; - } + if (session.ServerId && session.DeviceId !== ServerConnections.deviceId()) { + menuItems.push({ + name: globalize.translate('SendMessage'), + id: 'sendmessage' }); - }); - } - - function onActiveDevicesClick(evt) { - const btn = dom.parentWithClass(evt.target, 'sessionCardButton'); - - if (btn) { - const card = dom.parentWithClass(btn, 'card'); - - if (card) { - const sessionId = card.id; - const session = (DashboardPage.sessionsList || []).filter(function (dashboardSession) { - return 'session' + dashboardSession.Id === sessionId; - })[0]; - - if (session) { - if (btn.classList.contains('btnCardOptions')) { - showOptionsMenu(btn, session); - } else if (btn.classList.contains('btnSessionInfo')) { - showPlaybackInfo(btn, session); - } else if (btn.classList.contains('btnSessionSendMessage')) { - showSendMessageForm(btn, session); - } else if (btn.classList.contains('btnSessionStop')) { - ServerConnections.getApiClient(session.ServerId).sendPlayStateCommand(session.Id, 'Stop'); - } else if (btn.classList.contains('btnSessionPlayPause') && session.PlayState) { - ServerConnections.getApiClient(session.ServerId).sendPlayStateCommand(session.Id, 'PlayPause'); - } - } - } - } - } - - function filterSessions(sessions) { - const list = []; - const minActiveDate = new Date().getTime() - 9e5; - - for (let i = 0, length = sessions.length; i < length; i++) { - const session = sessions[i]; - - if (!session.NowPlayingItem && !session.UserId) { - continue; - } - - if (datetime.parseISO8601Date(session.LastActivityDate, true).getTime() >= minActiveDate) { - list.push(session); - } } - return list; - } - - function refreshActiveRecordings(view, apiClient) { - apiClient.getLiveTvRecordings({ - UserId: Dashboard.getCurrentUserId(), - IsInProgress: true, - Fields: 'CanDelete,PrimaryImageAspectRatio', - EnableTotalRecordCount: false, - EnableImageTypes: 'Primary,Thumb,Backdrop' - }).then(function (result) { - const itemsContainer = view.querySelector('.activeRecordingItems'); - - if (!result.Items.length) { - view.querySelector('.activeRecordingsSection').classList.add('hide'); - itemsContainer.innerHTML = ''; - return; - } - - view.querySelector('.activeRecordingsSection').classList.remove('hide'); - itemsContainer.innerHTML = cardBuilder.getCardsHtml({ - items: result.Items, - shape: 'auto', - defaultShape: 'backdrop', - showTitle: true, - showParentTitle: true, - coverImage: true, - cardLayout: false, - centerText: true, - preferThumb: 'auto', - overlayText: false, - overlayMoreButton: true, - action: 'none', - centerPlayButton: true + if (session.TranscodingInfo && session.TranscodingInfo.TranscodeReasons && session.TranscodingInfo.TranscodeReasons.length) { + menuItems.push({ + name: globalize.translate('ViewPlaybackInfo'), + id: 'transcodinginfo' }); - imageLoader.lazyChildren(itemsContainer); - }); - } - - function reloadSystemInfo(view, apiClient) { - apiClient.getSystemInfo().then(function (systemInfo) { - view.querySelector('#serverName').innerText = globalize.translate('DashboardServerName', systemInfo.ServerName); - view.querySelector('#versionNumber').innerText = globalize.translate('DashboardVersionNumber', systemInfo.Version); - - if (systemInfo.CanSelfRestart) { - view.querySelector('#btnRestartServer').classList.remove('hide'); - } else { - view.querySelector('#btnRestartServer').classList.add('hide'); - } - - view.querySelector('#cachePath').innerText = systemInfo.CachePath; - view.querySelector('#logPath').innerText = systemInfo.LogPath; - view.querySelector('#transcodePath').innerText = systemInfo.TranscodingTempPath; - view.querySelector('#metadataPath').innerText = systemInfo.InternalMetadataPath; - view.querySelector('#webPath').innerText = systemInfo.WebPath; - }); - } - - function renderInfo(view, sessions) { - sessions = filterSessions(sessions); - renderActiveConnections(view, sessions); - loading.hide(); - } - - function pollForInfo(view, apiClient) { - apiClient.getSessions({ - ActiveWithinSeconds: 960 - }).then(function (sessions) { - renderInfo(view, sessions); - }); - apiClient.getScheduledTasks().then(function (tasks) { - renderRunningTasks(view, tasks); - }); - } - - function renderActiveConnections(view, sessions) { - let html = ''; - DashboardPage.sessionsList = sessions; - const parentElement = view.querySelector('.activeDevices'); - const cardElem = parentElement.querySelector('.card'); - - if (cardElem) { - cardElem.classList.add('deadSession'); } - for (let i = 0, length = sessions.length; i < length; i++) { - const session = sessions[i]; - const rowId = 'session' + session.Id; - const elem = view.querySelector('#' + rowId); + return actionsheet.show({ + items: menuItems, + positionTo: btn + }).then(function (id) { + switch (id) { + case 'sendmessage': + showSendMessageForm(btn, session); + break; + case 'transcodinginfo': + showPlaybackInfo(btn, session); + break; + } + }); + }); +} - if (elem) { - DashboardPage.updateSession(elem, session); - } else { - const nowPlayingItem = session.NowPlayingItem; - const className = 'scalableCard card activeSession backdropCard backdropCard-scalable'; - const imgUrl = DashboardPage.getNowPlayingImageUrl(nowPlayingItem); +function onActiveDevicesClick(evt) { + const btn = dom.parentWithClass(evt.target, 'sessionCardButton'); - html += '
'; - html += '
'; - html += '
'; - html += '
'; - html += `
`; + if (btn) { + const card = dom.parentWithClass(btn, 'card'); - if (imgUrl) { - html += '
"; - } else { - html += '
'; + if (card) { + const sessionId = card.id; + const session = (DashboardPage.sessionsList || []).filter(function (dashboardSession) { + return 'session' + dashboardSession.Id === sessionId; + })[0]; + + if (session) { + if (btn.classList.contains('btnCardOptions')) { + showOptionsMenu(btn, session); + } else if (btn.classList.contains('btnSessionInfo')) { + showPlaybackInfo(btn, session); + } else if (btn.classList.contains('btnSessionSendMessage')) { + showSendMessageForm(btn, session); + } else if (btn.classList.contains('btnSessionStop')) { + ServerConnections.getApiClient(session.ServerId).sendPlayStateCommand(session.Id, 'Stop'); + } else if (btn.classList.contains('btnSessionPlayPause') && session.PlayState) { + ServerConnections.getApiClient(session.ServerId).sendPlayStateCommand(session.Id, 'PlayPause'); } - - html += `
`; - html += '
'; - const clientImage = DashboardPage.getClientImage(session); - - if (clientImage) { - html += clientImage; - } - - html += '
'; - html += '
' + escapeHtml(session.DeviceName) + '
'; - html += '
' + escapeHtml(DashboardPage.getAppSecondaryText(session)) + '
'; - html += '
'; - html += '
'; - - html += '
'; - const nowPlayingName = DashboardPage.getNowPlayingName(session); - html += '
'; - html += '' + nowPlayingName.html + ''; - html += '
'; - html += '
' + escapeHtml(DashboardPage.getSessionNowPlayingTime(session)) + '
'; - html += '
'; - - let percent = 100 * session?.PlayState?.PositionTicks / nowPlayingItem?.RunTimeTicks; - html += indicators.getProgressHtml(percent || 0, { - containerClass: 'playbackProgress' - }); - - percent = session?.TranscodingInfo?.CompletionPercentage?.toFixed(1); - html += indicators.getProgressHtml(percent || 0, { - containerClass: 'transcodingProgress' - }); - - html += indicators.getProgressHtml(100, { - containerClass: 'backgroundProgress' - }); - - html += '
'; - html += '
'; - html += '
'; - html += '
'; - html += '
'; - - let btnCssClass = session.ServerId && session.NowPlayingItem && session.SupportsRemoteControl ? '' : ' hide'; - const playIcon = session.PlayState.IsPaused ? 'pause' : 'play_arrow'; - - html += ''; - html += ''; - html += ''; - - btnCssClass = session.ServerId && session.SupportedCommands.indexOf('DisplayMessage') !== -1 && session.DeviceId !== ServerConnections.deviceId() ? '' : ' hide'; - html += ''; - html += '
'; - - html += '
'; - const userImage = DashboardPage.getUserImage(session); - html += userImage ? '
" : '
'; - html += '
'; - html += DashboardPage.getUsersHtml(session); - - html += '
'; - html += '
'; - html += '
'; - html += '
'; - html += '
'; } } + } +} - parentElement.insertAdjacentHTML('beforeend', html); - const deadSessionElem = parentElement.querySelector('.deadSession'); +function filterSessions(sessions) { + const list = []; + const minActiveDate = new Date().getTime() - 9e5; - if (deadSessionElem) { - deadSessionElem.parentNode.removeChild(deadSessionElem); + for (let i = 0, length = sessions.length; i < length; i++) { + const session = sessions[i]; + + if (!session.NowPlayingItem && !session.UserId) { + continue; + } + + if (datetime.parseISO8601Date(session.LastActivityDate, true).getTime() >= minActiveDate) { + list.push(session); } } - function renderRunningTasks(view, tasks) { - let html = ''; - tasks = tasks.filter(function (task) { - if (task.State != 'Idle') { - return !task.IsHidden; - } + return list; +} - return false; +function refreshActiveRecordings(view, apiClient) { + apiClient.getLiveTvRecordings({ + UserId: Dashboard.getCurrentUserId(), + IsInProgress: true, + Fields: 'CanDelete,PrimaryImageAspectRatio', + EnableTotalRecordCount: false, + EnableImageTypes: 'Primary,Thumb,Backdrop' + }).then(function (result) { + const itemsContainer = view.querySelector('.activeRecordingItems'); + + if (!result.Items.length) { + view.querySelector('.activeRecordingsSection').classList.add('hide'); + itemsContainer.innerHTML = ''; + return; + } + + view.querySelector('.activeRecordingsSection').classList.remove('hide'); + itemsContainer.innerHTML = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'auto', + defaultShape: 'backdrop', + showTitle: true, + showParentTitle: true, + coverImage: true, + cardLayout: false, + centerText: true, + preferThumb: 'auto', + overlayText: false, + overlayMoreButton: true, + action: 'none', + centerPlayButton: true }); + imageLoader.lazyChildren(itemsContainer); + }); +} - if (tasks.length) { - view.querySelector('.runningTasksContainer').classList.remove('hide'); +function reloadSystemInfo(view, apiClient) { + apiClient.getSystemInfo().then(function (systemInfo) { + view.querySelector('#serverName').innerText = globalize.translate('DashboardServerName', systemInfo.ServerName); + view.querySelector('#versionNumber').innerText = globalize.translate('DashboardVersionNumber', systemInfo.Version); + + if (systemInfo.CanSelfRestart) { + view.querySelector('#btnRestartServer').classList.remove('hide'); } else { - view.querySelector('.runningTasksContainer').classList.add('hide'); + view.querySelector('#btnRestartServer').classList.add('hide'); } - for (let i = 0, length = tasks.length; i < length; i++) { - const task = tasks[i]; - html += '

'; - html += task.Name + '
'; + view.querySelector('#cachePath').innerText = systemInfo.CachePath; + view.querySelector('#logPath').innerText = systemInfo.LogPath; + view.querySelector('#transcodePath').innerText = systemInfo.TranscodingTempPath; + view.querySelector('#metadataPath').innerText = systemInfo.InternalMetadataPath; + view.querySelector('#webPath').innerText = systemInfo.WebPath; + }); +} - if (task.State === 'Running') { - const progress = (task.CurrentProgressPercentage || 0).toFixed(1); - html += ''; - html += progress + '%'; - html += ''; - html += "" + progress + '%'; - html += ''; - } else if (task.State === 'Cancelling') { - html += '' + globalize.translate('LabelStopping') + ''; - } +function renderInfo(view, sessions) { + sessions = filterSessions(sessions); + renderActiveConnections(view, sessions); + loading.hide(); +} - html += '

'; - } +function pollForInfo(view, apiClient) { + apiClient.getSessions({ + ActiveWithinSeconds: 960 + }).then(function (sessions) { + renderInfo(view, sessions); + }); + apiClient.getScheduledTasks().then(function (tasks) { + renderRunningTasks(view, tasks); + }); +} - view.querySelector('#divRunningTasks').innerHTML = html; +function renderActiveConnections(view, sessions) { + let html = ''; + DashboardPage.sessionsList = sessions; + const parentElement = view.querySelector('.activeDevices'); + const cardElem = parentElement.querySelector('.card'); + + if (cardElem) { + cardElem.classList.add('deadSession'); } - window.DashboardPage = { - startInterval: function (apiClient) { - apiClient.sendMessage('SessionsStart', '0,1500'); - apiClient.sendMessage('ScheduledTasksInfoStart', '0,1000'); - }, - stopInterval: function (apiClient) { - apiClient.sendMessage('SessionsStop'); - apiClient.sendMessage('ScheduledTasksInfoStop'); - }, - getSessionNowPlayingStreamInfo: function (session) { - let html = ''; - let showTranscodingInfo = false; - const displayPlayMethod = playMethodHelper.getDisplayPlayMethod(session); + for (let i = 0, length = sessions.length; i < length; i++) { + const session = sessions[i]; + const rowId = 'session' + session.Id; + const elem = view.querySelector('#' + rowId); - if (displayPlayMethod === 'DirectPlay') { - html += globalize.translate('DirectPlaying'); - } else if (displayPlayMethod === 'Remux') { - html += globalize.translate('Remuxing'); - } else if (displayPlayMethod === 'DirectStream') { - html += globalize.translate('DirectStreaming'); - } else if (displayPlayMethod === 'Transcode') { - if (session.TranscodingInfo && session.TranscodingInfo.Framerate) { - html += `${globalize.translate('Framerate')}: ${session.TranscodingInfo.Framerate}fps`; - } - - showTranscodingInfo = true; - } - - if (showTranscodingInfo) { - const line = []; - - if (session.TranscodingInfo) { - if (session.TranscodingInfo.Bitrate) { - if (session.TranscodingInfo.Bitrate > 1e6) { - line.push((session.TranscodingInfo.Bitrate / 1e6).toFixed(1) + ' Mbps'); - } else { - line.push(Math.floor(session.TranscodingInfo.Bitrate / 1e3) + ' Kbps'); - } - } - - if (session.TranscodingInfo.Container) { - line.push(session.TranscodingInfo.Container.toUpperCase()); - } - - if (session.TranscodingInfo.VideoCodec) { - line.push(session.TranscodingInfo.VideoCodec.toUpperCase()); - } - - if (session.TranscodingInfo.AudioCodec && session.TranscodingInfo.AudioCodec != session.TranscodingInfo.Container) { - line.push(session.TranscodingInfo.AudioCodec.toUpperCase()); - } - } - - if (line.length) { - html += '

' + line.join(' '); - } - } - - return html; - }, - getSessionNowPlayingTime: function (session) { + if (elem) { + DashboardPage.updateSession(elem, session); + } else { const nowPlayingItem = session.NowPlayingItem; - let html = ''; + const className = 'scalableCard card activeSession backdropCard backdropCard-scalable'; + const imgUrl = DashboardPage.getNowPlayingImageUrl(nowPlayingItem); - if (nowPlayingItem) { - if (session.PlayState.PositionTicks) { - html += datetime.getDisplayRunningTime(session.PlayState.PositionTicks); - } else { - html += '0:00'; - } - - html += ' / '; - - if (nowPlayingItem.RunTimeTicks) { - html += datetime.getDisplayRunningTime(nowPlayingItem.RunTimeTicks); - } else { - html += '0:00'; - } - } - - return html; - }, - getAppSecondaryText: function (session) { - return session.Client + ' ' + session.ApplicationVersion; - }, - getNowPlayingName: function (session) { - let imgUrl = ''; - const nowPlayingItem = session.NowPlayingItem; - // FIXME: It seems that, sometimes, server sends date in the future, so date-fns displays messages like 'in less than a minute'. We should fix - // how dates are returned by the server when the session is active and show something like 'Active now', instead of past/future sentences - if (!nowPlayingItem) { - return { - html: globalize.translate('LastSeen', formatDistanceToNow(Date.parse(session.LastActivityDate), getLocaleWithSuffix())), - image: imgUrl - }; - } - - let topText = escapeHtml(itemHelper.getDisplayName(nowPlayingItem)); - let bottomText = ''; - - if (nowPlayingItem.Artists && nowPlayingItem.Artists.length) { - bottomText = topText; - topText = escapeHtml(nowPlayingItem.Artists[0]); - } else { - if (nowPlayingItem.SeriesName || nowPlayingItem.Album) { - bottomText = topText; - topText = escapeHtml(nowPlayingItem.SeriesName || nowPlayingItem.Album); - } else if (nowPlayingItem.ProductionYear) { - bottomText = nowPlayingItem.ProductionYear; - } - } - - if (nowPlayingItem.ImageTags && nowPlayingItem.ImageTags.Logo) { - imgUrl = ApiClient.getScaledImageUrl(nowPlayingItem.Id, { - tag: nowPlayingItem.ImageTags.Logo, - maxHeight: 24, - maxWidth: 130, - type: 'Logo' - }); - } else if (nowPlayingItem.ParentLogoImageTag) { - imgUrl = ApiClient.getScaledImageUrl(nowPlayingItem.ParentLogoItemId, { - tag: nowPlayingItem.ParentLogoImageTag, - maxHeight: 24, - maxWidth: 130, - type: 'Logo' - }); - } + html += '
'; + html += '
'; + html += '
'; + html += '
'; + html += `
`; if (imgUrl) { - topText = ''; - } - - return { - html: bottomText ? topText + '
' + bottomText : topText, - image: imgUrl - }; - }, - getUsersHtml: function (session) { - const html = []; - - if (session.UserId) { - html.push(escapeHtml(session.UserName)); - } - - for (let i = 0, length = session.AdditionalUsers.length; i < length; i++) { - html.push(escapeHtml(session.AdditionalUsers[i].UserName)); - } - - return html.join(', '); - }, - getUserImage: function (session) { - if (session.UserId && session.UserPrimaryImageTag) { - return ApiClient.getUserImageUrl(session.UserId, { - tag: session.UserPrimaryImageTag, - type: 'Primary' - }); - } - - return null; - }, - updateSession: function (row, session) { - row.classList.remove('deadSession'); - const nowPlayingItem = session.NowPlayingItem; - - if (nowPlayingItem) { - row.classList.add('playingSession'); - row.querySelector('.btnSessionInfo').classList.remove('hide'); + html += '
"; } else { - row.classList.remove('playingSession'); - row.querySelector('.btnSessionInfo').classList.add('hide'); + html += '
'; } - if (session.ServerId && session.SupportedCommands.indexOf('DisplayMessage') !== -1) { - row.querySelector('.btnSessionSendMessage').classList.remove('hide'); - } else { - row.querySelector('.btnSessionSendMessage').classList.add('hide'); + html += `
`; + html += '
'; + const clientImage = DashboardPage.getClientImage(session); + + if (clientImage) { + html += clientImage; } - const btnSessionPlayPause = row.querySelector('.btnSessionPlayPause'); + html += '
'; + html += '
' + escapeHtml(session.DeviceName) + '
'; + html += '
' + escapeHtml(DashboardPage.getAppSecondaryText(session)) + '
'; + html += '
'; + html += '
'; - if (session.ServerId && nowPlayingItem && session.SupportsRemoteControl) { - btnSessionPlayPause.classList.remove('hide'); - row.querySelector('.btnSessionStop').classList.remove('hide'); - } else { - btnSessionPlayPause.classList.add('hide'); - row.querySelector('.btnSessionStop').classList.add('hide'); - } - - const btnSessionPlayPauseIcon = btnSessionPlayPause.querySelector('.material-icons'); - btnSessionPlayPauseIcon.classList.remove('play_arrow', 'pause'); - btnSessionPlayPauseIcon.classList.add(session.PlayState && session.PlayState.IsPaused ? 'play_arrow' : 'pause'); - - row.querySelector('.sessionNowPlayingTime').innerText = DashboardPage.getSessionNowPlayingTime(session); - row.querySelector('.sessionUserName').innerHTML = DashboardPage.getUsersHtml(session); - row.querySelector('.sessionAppSecondaryText').innerText = DashboardPage.getAppSecondaryText(session); + html += '
'; const nowPlayingName = DashboardPage.getNowPlayingName(session); - const nowPlayingInfoElem = row.querySelector('.sessionNowPlayingInfo'); - - if (!(nowPlayingName.image && nowPlayingName.image == nowPlayingInfoElem.getAttribute('data-imgsrc'))) { - nowPlayingInfoElem.innerHTML = nowPlayingName.html; - nowPlayingInfoElem.setAttribute('data-imgsrc', nowPlayingName.image || ''); - } - - const playbackProgressElem = row.querySelector('.playbackProgress'); - const transcodingProgress = row.querySelector('.transcodingProgress'); + html += '
'; + html += '' + nowPlayingName.html + ''; + html += '
'; + html += '
' + escapeHtml(DashboardPage.getSessionNowPlayingTime(session)) + '
'; + html += '
'; let percent = 100 * session?.PlayState?.PositionTicks / nowPlayingItem?.RunTimeTicks; - playbackProgressElem.outerHTML = indicators.getProgressHtml(percent || 0, { + html += indicators.getProgressHtml(percent || 0, { containerClass: 'playbackProgress' }); percent = session?.TranscodingInfo?.CompletionPercentage?.toFixed(1); - transcodingProgress.outerHTML = indicators.getProgressHtml(percent || 0, { + html += indicators.getProgressHtml(percent || 0, { containerClass: 'transcodingProgress' }); - const imgUrl = DashboardPage.getNowPlayingImageUrl(nowPlayingItem) || ''; - const imgElem = row.querySelector('.sessionNowPlayingContent'); - - if (imgUrl != imgElem.getAttribute('data-src')) { - imgElem.style.backgroundImage = imgUrl ? "url('" + imgUrl + "')" : ''; - imgElem.setAttribute('data-src', imgUrl); - - if (imgUrl) { - imgElem.classList.add('sessionNowPlayingContent-withbackground'); - row.querySelector('.sessionNowPlayingInnerContent').classList.add('darkenContent'); - } else { - imgElem.classList.remove('sessionNowPlayingContent-withbackground'); - row.querySelector('.sessionNowPlayingInnerContent').classList.remove('darkenContent'); - } - } - }, - getClientImage: function (connection) { - const iconUrl = imageHelper.getDeviceIcon(connection); - return ""; - }, - getNowPlayingImageUrl: function (item) { - /* Screen width is multiplied by 0.2, as the there is currently no way to get the width of - elements that aren't created yet. */ - if (item && item.BackdropImageTags && item.BackdropImageTags.length) { - return ApiClient.getScaledImageUrl(item.Id, { - maxWidth: Math.round(dom.getScreenWidth() * 0.20), - type: 'Backdrop', - tag: item.BackdropImageTags[0] - }); - } - - if (item && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length) { - return ApiClient.getScaledImageUrl(item.ParentBackdropItemId, { - maxWidth: Math.round(dom.getScreenWidth() * 0.20), - type: 'Backdrop', - tag: item.ParentBackdropImageTags[0] - }); - } - - if (item && item.BackdropImageTag) { - return ApiClient.getScaledImageUrl(item.BackdropItemId, { - maxWidth: Math.round(dom.getScreenWidth() * 0.20), - type: 'Backdrop', - tag: item.BackdropImageTag - }); - } - - const imageTags = (item || {}).ImageTags || {}; - - if (item && imageTags.Thumb) { - return ApiClient.getScaledImageUrl(item.Id, { - maxWidth: Math.round(dom.getScreenWidth() * 0.20), - type: 'Thumb', - tag: imageTags.Thumb - }); - } - - if (item && item.ParentThumbImageTag) { - return ApiClient.getScaledImageUrl(item.ParentThumbItemId, { - maxWidth: Math.round(dom.getScreenWidth() * 0.20), - type: 'Thumb', - tag: item.ParentThumbImageTag - }); - } - - if (item && item.ThumbImageTag) { - return ApiClient.getScaledImageUrl(item.ThumbItemId, { - maxWidth: Math.round(dom.getScreenWidth() * 0.20), - type: 'Thumb', - tag: item.ThumbImageTag - }); - } - - if (item && imageTags.Primary) { - return ApiClient.getScaledImageUrl(item.Id, { - maxWidth: Math.round(dom.getScreenWidth() * 0.20), - type: 'Primary', - tag: imageTags.Primary - }); - } - - if (item && item.PrimaryImageTag) { - return ApiClient.getScaledImageUrl(item.PrimaryImageItemId, { - maxWidth: Math.round(dom.getScreenWidth() * 0.20), - type: 'Primary', - tag: item.PrimaryImageTag - }); - } - - if (item && item.AlbumPrimaryImageTag) { - return ApiClient.getScaledImageUrl(item.AlbumId, { - maxWidth: Math.round(dom.getScreenWidth() * 0.20), - type: 'Primary', - tag: item.AlbumPrimaryImageTag - }); - } - - return null; - }, - systemUpdateTaskKey: 'SystemUpdateTask', - stopTask: function (btn, id) { - const page = dom.parentWithClass(btn, 'page'); - ApiClient.stopScheduledTask(id).then(function () { - pollForInfo(page, ApiClient); - }); - }, - restart: function (btn) { - confirm({ - title: globalize.translate('Restart'), - text: globalize.translate('MessageConfirmRestart'), - confirmText: globalize.translate('Restart'), - primary: 'delete' - }).then(function () { - const page = dom.parentWithClass(btn, 'page'); - page.querySelector('#btnRestartServer').disabled = true; - page.querySelector('#btnShutdown').disabled = true; - ApiClient.restartServer(); - }); - }, - shutdown: function (btn) { - confirm({ - title: globalize.translate('ButtonShutdown'), - text: globalize.translate('MessageConfirmShutdown'), - confirmText: globalize.translate('ButtonShutdown'), - primary: 'delete' - }).then(function () { - const page = dom.parentWithClass(btn, 'page'); - page.querySelector('#btnRestartServer').disabled = true; - page.querySelector('#btnShutdown').disabled = true; - ApiClient.shutdownServer(); + html += indicators.getProgressHtml(100, { + containerClass: 'backgroundProgress' }); + + html += '
'; + html += '
'; + html += '
'; + html += '
'; + html += '
'; + + let btnCssClass = session.ServerId && session.NowPlayingItem && session.SupportsRemoteControl ? '' : ' hide'; + const playIcon = session.PlayState.IsPaused ? 'pause' : 'play_arrow'; + + html += ''; + html += ''; + html += ''; + + btnCssClass = session.ServerId && session.SupportedCommands.indexOf('DisplayMessage') !== -1 && session.DeviceId !== ServerConnections.deviceId() ? '' : ' hide'; + html += ''; + html += '
'; + + html += '
'; + const userImage = DashboardPage.getUserImage(session); + html += userImage ? '
" : '
'; + html += '
'; + html += DashboardPage.getUsersHtml(session); + + html += '
'; + html += '
'; + html += '
'; + html += '
'; + html += '
'; } - }; - export default function (view) { - function onRestartRequired(evt, apiClient) { - console.debug('onRestartRequired not implemented', evt, apiClient); - } - - function onServerShuttingDown(evt, apiClient) { - console.debug('onServerShuttingDown not implemented', evt, apiClient); - } - - function onServerRestarting(evt, apiClient) { - console.debug('onServerRestarting not implemented', evt, apiClient); - } - - function onPackageInstall(_, apiClient) { - if (apiClient.serverId() === serverId) { - pollForInfo(view, apiClient); - reloadSystemInfo(view, apiClient); - } - } - - function onSessionsUpdate(evt, apiClient, info) { - if (apiClient.serverId() === serverId) { - renderInfo(view, info); - } - } - - function onScheduledTasksUpdate(evt, apiClient, info) { - if (apiClient.serverId() === serverId) { - renderRunningTasks(view, info); - } - } - - const serverId = ApiClient.serverId(); - view.querySelector('.activeDevices').addEventListener('click', onActiveDevicesClick); - view.addEventListener('viewshow', function () { - const page = this; - const apiClient = ApiClient; - - if (apiClient) { - loading.show(); - pollForInfo(page, apiClient); - DashboardPage.startInterval(apiClient); - Events.on(serverNotifications, 'RestartRequired', onRestartRequired); - Events.on(serverNotifications, 'ServerShuttingDown', onServerShuttingDown); - Events.on(serverNotifications, 'ServerRestarting', onServerRestarting); - Events.on(serverNotifications, 'PackageInstalling', onPackageInstall); - Events.on(serverNotifications, 'PackageInstallationCompleted', onPackageInstall); - Events.on(serverNotifications, 'Sessions', onSessionsUpdate); - Events.on(serverNotifications, 'ScheduledTasksInfo', onScheduledTasksUpdate); - DashboardPage.lastAppUpdateCheck = null; - reloadSystemInfo(page, ApiClient); - - if (!page.userActivityLog) { - page.userActivityLog = new ActivityLog({ - serverId: ApiClient.serverId(), - element: page.querySelector('.userActivityItems') - }); - } - - if (!page.serverActivityLog) { - page.serverActivityLog = new ActivityLog({ - serverId: ApiClient.serverId(), - element: page.querySelector('.serverActivityItems') - }); - } - - refreshActiveRecordings(view, apiClient); - loading.hide(); - } - - taskButton({ - mode: 'on', - taskKey: 'RefreshLibrary', - button: page.querySelector('.btnRefresh') - }); - }); - view.addEventListener('viewbeforehide', function () { - const apiClient = ApiClient; - const page = this; - - Events.off(serverNotifications, 'RestartRequired', onRestartRequired); - Events.off(serverNotifications, 'ServerShuttingDown', onServerShuttingDown); - Events.off(serverNotifications, 'ServerRestarting', onServerRestarting); - Events.off(serverNotifications, 'PackageInstalling', onPackageInstall); - Events.off(serverNotifications, 'PackageInstallationCompleted', onPackageInstall); - Events.off(serverNotifications, 'Sessions', onSessionsUpdate); - Events.off(serverNotifications, 'ScheduledTasksInfo', onScheduledTasksUpdate); - - if (apiClient) { - DashboardPage.stopInterval(apiClient); - } - - taskButton({ - mode: 'off', - taskKey: 'RefreshLibrary', - button: page.querySelector('.btnRefresh') - }); - }); - view.addEventListener('viewdestroy', function () { - const page = this; - const userActivityLog = page.userActivityLog; - - if (userActivityLog) { - userActivityLog.destroy(); - } - - const serverActivityLog = page.serverActivityLog; - - if (serverActivityLog) { - serverActivityLog.destroy(); - } - }); } -/* eslint-enable indent */ + parentElement.insertAdjacentHTML('beforeend', html); + const deadSessionElem = parentElement.querySelector('.deadSession'); + + if (deadSessionElem) { + deadSessionElem.parentNode.removeChild(deadSessionElem); + } +} + +function renderRunningTasks(view, tasks) { + let html = ''; + tasks = tasks.filter(function (task) { + if (task.State != 'Idle') { + return !task.IsHidden; + } + + return false; + }); + + if (tasks.length) { + view.querySelector('.runningTasksContainer').classList.remove('hide'); + } else { + view.querySelector('.runningTasksContainer').classList.add('hide'); + } + + for (let i = 0, length = tasks.length; i < length; i++) { + const task = tasks[i]; + html += '

'; + html += task.Name + '
'; + + if (task.State === 'Running') { + const progress = (task.CurrentProgressPercentage || 0).toFixed(1); + html += ''; + html += progress + '%'; + html += ''; + html += "" + progress + '%'; + html += ''; + } else if (task.State === 'Cancelling') { + html += '' + globalize.translate('LabelStopping') + ''; + } + + html += '

'; + } + + view.querySelector('#divRunningTasks').innerHTML = html; +} + +window.DashboardPage = { + startInterval: function (apiClient) { + apiClient.sendMessage('SessionsStart', '0,1500'); + apiClient.sendMessage('ScheduledTasksInfoStart', '0,1000'); + }, + stopInterval: function (apiClient) { + apiClient.sendMessage('SessionsStop'); + apiClient.sendMessage('ScheduledTasksInfoStop'); + }, + getSessionNowPlayingStreamInfo: function (session) { + let html = ''; + let showTranscodingInfo = false; + const displayPlayMethod = playMethodHelper.getDisplayPlayMethod(session); + + if (displayPlayMethod === 'DirectPlay') { + html += globalize.translate('DirectPlaying'); + } else if (displayPlayMethod === 'Remux') { + html += globalize.translate('Remuxing'); + } else if (displayPlayMethod === 'DirectStream') { + html += globalize.translate('DirectStreaming'); + } else if (displayPlayMethod === 'Transcode') { + if (session.TranscodingInfo && session.TranscodingInfo.Framerate) { + html += `${globalize.translate('Framerate')}: ${session.TranscodingInfo.Framerate}fps`; + } + + showTranscodingInfo = true; + } + + if (showTranscodingInfo) { + const line = []; + + if (session.TranscodingInfo) { + if (session.TranscodingInfo.Bitrate) { + if (session.TranscodingInfo.Bitrate > 1e6) { + line.push((session.TranscodingInfo.Bitrate / 1e6).toFixed(1) + ' Mbps'); + } else { + line.push(Math.floor(session.TranscodingInfo.Bitrate / 1e3) + ' Kbps'); + } + } + + if (session.TranscodingInfo.Container) { + line.push(session.TranscodingInfo.Container.toUpperCase()); + } + + if (session.TranscodingInfo.VideoCodec) { + line.push(session.TranscodingInfo.VideoCodec.toUpperCase()); + } + + if (session.TranscodingInfo.AudioCodec && session.TranscodingInfo.AudioCodec != session.TranscodingInfo.Container) { + line.push(session.TranscodingInfo.AudioCodec.toUpperCase()); + } + } + + if (line.length) { + html += '

' + line.join(' '); + } + } + + return html; + }, + getSessionNowPlayingTime: function (session) { + const nowPlayingItem = session.NowPlayingItem; + let html = ''; + + if (nowPlayingItem) { + if (session.PlayState.PositionTicks) { + html += datetime.getDisplayRunningTime(session.PlayState.PositionTicks); + } else { + html += '0:00'; + } + + html += ' / '; + + if (nowPlayingItem.RunTimeTicks) { + html += datetime.getDisplayRunningTime(nowPlayingItem.RunTimeTicks); + } else { + html += '0:00'; + } + } + + return html; + }, + getAppSecondaryText: function (session) { + return session.Client + ' ' + session.ApplicationVersion; + }, + getNowPlayingName: function (session) { + let imgUrl = ''; + const nowPlayingItem = session.NowPlayingItem; + // FIXME: It seems that, sometimes, server sends date in the future, so date-fns displays messages like 'in less than a minute'. We should fix + // how dates are returned by the server when the session is active and show something like 'Active now', instead of past/future sentences + if (!nowPlayingItem) { + return { + html: globalize.translate('LastSeen', formatDistanceToNow(Date.parse(session.LastActivityDate), getLocaleWithSuffix())), + image: imgUrl + }; + } + + let topText = escapeHtml(itemHelper.getDisplayName(nowPlayingItem)); + let bottomText = ''; + + if (nowPlayingItem.Artists && nowPlayingItem.Artists.length) { + bottomText = topText; + topText = escapeHtml(nowPlayingItem.Artists[0]); + } else { + if (nowPlayingItem.SeriesName || nowPlayingItem.Album) { + bottomText = topText; + topText = escapeHtml(nowPlayingItem.SeriesName || nowPlayingItem.Album); + } else if (nowPlayingItem.ProductionYear) { + bottomText = nowPlayingItem.ProductionYear; + } + } + + if (nowPlayingItem.ImageTags && nowPlayingItem.ImageTags.Logo) { + imgUrl = ApiClient.getScaledImageUrl(nowPlayingItem.Id, { + tag: nowPlayingItem.ImageTags.Logo, + maxHeight: 24, + maxWidth: 130, + type: 'Logo' + }); + } else if (nowPlayingItem.ParentLogoImageTag) { + imgUrl = ApiClient.getScaledImageUrl(nowPlayingItem.ParentLogoItemId, { + tag: nowPlayingItem.ParentLogoImageTag, + maxHeight: 24, + maxWidth: 130, + type: 'Logo' + }); + } + + if (imgUrl) { + topText = ''; + } + + return { + html: bottomText ? topText + '
' + bottomText : topText, + image: imgUrl + }; + }, + getUsersHtml: function (session) { + const html = []; + + if (session.UserId) { + html.push(escapeHtml(session.UserName)); + } + + for (let i = 0, length = session.AdditionalUsers.length; i < length; i++) { + html.push(escapeHtml(session.AdditionalUsers[i].UserName)); + } + + return html.join(', '); + }, + getUserImage: function (session) { + if (session.UserId && session.UserPrimaryImageTag) { + return ApiClient.getUserImageUrl(session.UserId, { + tag: session.UserPrimaryImageTag, + type: 'Primary' + }); + } + + return null; + }, + updateSession: function (row, session) { + row.classList.remove('deadSession'); + const nowPlayingItem = session.NowPlayingItem; + + if (nowPlayingItem) { + row.classList.add('playingSession'); + row.querySelector('.btnSessionInfo').classList.remove('hide'); + } else { + row.classList.remove('playingSession'); + row.querySelector('.btnSessionInfo').classList.add('hide'); + } + + if (session.ServerId && session.SupportedCommands.indexOf('DisplayMessage') !== -1) { + row.querySelector('.btnSessionSendMessage').classList.remove('hide'); + } else { + row.querySelector('.btnSessionSendMessage').classList.add('hide'); + } + + const btnSessionPlayPause = row.querySelector('.btnSessionPlayPause'); + + if (session.ServerId && nowPlayingItem && session.SupportsRemoteControl) { + btnSessionPlayPause.classList.remove('hide'); + row.querySelector('.btnSessionStop').classList.remove('hide'); + } else { + btnSessionPlayPause.classList.add('hide'); + row.querySelector('.btnSessionStop').classList.add('hide'); + } + + const btnSessionPlayPauseIcon = btnSessionPlayPause.querySelector('.material-icons'); + btnSessionPlayPauseIcon.classList.remove('play_arrow', 'pause'); + btnSessionPlayPauseIcon.classList.add(session.PlayState && session.PlayState.IsPaused ? 'play_arrow' : 'pause'); + + row.querySelector('.sessionNowPlayingTime').innerText = DashboardPage.getSessionNowPlayingTime(session); + row.querySelector('.sessionUserName').innerHTML = DashboardPage.getUsersHtml(session); + row.querySelector('.sessionAppSecondaryText').innerText = DashboardPage.getAppSecondaryText(session); + const nowPlayingName = DashboardPage.getNowPlayingName(session); + const nowPlayingInfoElem = row.querySelector('.sessionNowPlayingInfo'); + + if (!(nowPlayingName.image && nowPlayingName.image == nowPlayingInfoElem.getAttribute('data-imgsrc'))) { + nowPlayingInfoElem.innerHTML = nowPlayingName.html; + nowPlayingInfoElem.setAttribute('data-imgsrc', nowPlayingName.image || ''); + } + + const playbackProgressElem = row.querySelector('.playbackProgress'); + const transcodingProgress = row.querySelector('.transcodingProgress'); + + let percent = 100 * session?.PlayState?.PositionTicks / nowPlayingItem?.RunTimeTicks; + playbackProgressElem.outerHTML = indicators.getProgressHtml(percent || 0, { + containerClass: 'playbackProgress' + }); + + percent = session?.TranscodingInfo?.CompletionPercentage?.toFixed(1); + transcodingProgress.outerHTML = indicators.getProgressHtml(percent || 0, { + containerClass: 'transcodingProgress' + }); + + const imgUrl = DashboardPage.getNowPlayingImageUrl(nowPlayingItem) || ''; + const imgElem = row.querySelector('.sessionNowPlayingContent'); + + if (imgUrl != imgElem.getAttribute('data-src')) { + imgElem.style.backgroundImage = imgUrl ? "url('" + imgUrl + "')" : ''; + imgElem.setAttribute('data-src', imgUrl); + + if (imgUrl) { + imgElem.classList.add('sessionNowPlayingContent-withbackground'); + row.querySelector('.sessionNowPlayingInnerContent').classList.add('darkenContent'); + } else { + imgElem.classList.remove('sessionNowPlayingContent-withbackground'); + row.querySelector('.sessionNowPlayingInnerContent').classList.remove('darkenContent'); + } + } + }, + getClientImage: function (connection) { + const iconUrl = imageHelper.getDeviceIcon(connection); + return ""; + }, + getNowPlayingImageUrl: function (item) { + /* Screen width is multiplied by 0.2, as the there is currently no way to get the width of + elements that aren't created yet. */ + if (item && item.BackdropImageTags && item.BackdropImageTags.length) { + return ApiClient.getScaledImageUrl(item.Id, { + maxWidth: Math.round(dom.getScreenWidth() * 0.20), + type: 'Backdrop', + tag: item.BackdropImageTags[0] + }); + } + + if (item && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length) { + return ApiClient.getScaledImageUrl(item.ParentBackdropItemId, { + maxWidth: Math.round(dom.getScreenWidth() * 0.20), + type: 'Backdrop', + tag: item.ParentBackdropImageTags[0] + }); + } + + if (item && item.BackdropImageTag) { + return ApiClient.getScaledImageUrl(item.BackdropItemId, { + maxWidth: Math.round(dom.getScreenWidth() * 0.20), + type: 'Backdrop', + tag: item.BackdropImageTag + }); + } + + const imageTags = (item || {}).ImageTags || {}; + + if (item && imageTags.Thumb) { + return ApiClient.getScaledImageUrl(item.Id, { + maxWidth: Math.round(dom.getScreenWidth() * 0.20), + type: 'Thumb', + tag: imageTags.Thumb + }); + } + + if (item && item.ParentThumbImageTag) { + return ApiClient.getScaledImageUrl(item.ParentThumbItemId, { + maxWidth: Math.round(dom.getScreenWidth() * 0.20), + type: 'Thumb', + tag: item.ParentThumbImageTag + }); + } + + if (item && item.ThumbImageTag) { + return ApiClient.getScaledImageUrl(item.ThumbItemId, { + maxWidth: Math.round(dom.getScreenWidth() * 0.20), + type: 'Thumb', + tag: item.ThumbImageTag + }); + } + + if (item && imageTags.Primary) { + return ApiClient.getScaledImageUrl(item.Id, { + maxWidth: Math.round(dom.getScreenWidth() * 0.20), + type: 'Primary', + tag: imageTags.Primary + }); + } + + if (item && item.PrimaryImageTag) { + return ApiClient.getScaledImageUrl(item.PrimaryImageItemId, { + maxWidth: Math.round(dom.getScreenWidth() * 0.20), + type: 'Primary', + tag: item.PrimaryImageTag + }); + } + + if (item && item.AlbumPrimaryImageTag) { + return ApiClient.getScaledImageUrl(item.AlbumId, { + maxWidth: Math.round(dom.getScreenWidth() * 0.20), + type: 'Primary', + tag: item.AlbumPrimaryImageTag + }); + } + + return null; + }, + systemUpdateTaskKey: 'SystemUpdateTask', + stopTask: function (btn, id) { + const page = dom.parentWithClass(btn, 'page'); + ApiClient.stopScheduledTask(id).then(function () { + pollForInfo(page, ApiClient); + }); + }, + restart: function (btn) { + confirm({ + title: globalize.translate('Restart'), + text: globalize.translate('MessageConfirmRestart'), + confirmText: globalize.translate('Restart'), + primary: 'delete' + }).then(function () { + const page = dom.parentWithClass(btn, 'page'); + page.querySelector('#btnRestartServer').disabled = true; + page.querySelector('#btnShutdown').disabled = true; + ApiClient.restartServer(); + }); + }, + shutdown: function (btn) { + confirm({ + title: globalize.translate('ButtonShutdown'), + text: globalize.translate('MessageConfirmShutdown'), + confirmText: globalize.translate('ButtonShutdown'), + primary: 'delete' + }).then(function () { + const page = dom.parentWithClass(btn, 'page'); + page.querySelector('#btnRestartServer').disabled = true; + page.querySelector('#btnShutdown').disabled = true; + ApiClient.shutdownServer(); + }); + } +}; +export default function (view) { + function onRestartRequired(evt, apiClient) { + console.debug('onRestartRequired not implemented', evt, apiClient); + } + + function onServerShuttingDown(evt, apiClient) { + console.debug('onServerShuttingDown not implemented', evt, apiClient); + } + + function onServerRestarting(evt, apiClient) { + console.debug('onServerRestarting not implemented', evt, apiClient); + } + + function onPackageInstall(_, apiClient) { + if (apiClient.serverId() === serverId) { + pollForInfo(view, apiClient); + reloadSystemInfo(view, apiClient); + } + } + + function onSessionsUpdate(evt, apiClient, info) { + if (apiClient.serverId() === serverId) { + renderInfo(view, info); + } + } + + function onScheduledTasksUpdate(evt, apiClient, info) { + if (apiClient.serverId() === serverId) { + renderRunningTasks(view, info); + } + } + + const serverId = ApiClient.serverId(); + view.querySelector('.activeDevices').addEventListener('click', onActiveDevicesClick); + view.addEventListener('viewshow', function () { + const page = this; + const apiClient = ApiClient; + + if (apiClient) { + loading.show(); + pollForInfo(page, apiClient); + DashboardPage.startInterval(apiClient); + Events.on(serverNotifications, 'RestartRequired', onRestartRequired); + Events.on(serverNotifications, 'ServerShuttingDown', onServerShuttingDown); + Events.on(serverNotifications, 'ServerRestarting', onServerRestarting); + Events.on(serverNotifications, 'PackageInstalling', onPackageInstall); + Events.on(serverNotifications, 'PackageInstallationCompleted', onPackageInstall); + Events.on(serverNotifications, 'Sessions', onSessionsUpdate); + Events.on(serverNotifications, 'ScheduledTasksInfo', onScheduledTasksUpdate); + DashboardPage.lastAppUpdateCheck = null; + reloadSystemInfo(page, ApiClient); + + if (!page.userActivityLog) { + page.userActivityLog = new ActivityLog({ + serverId: ApiClient.serverId(), + element: page.querySelector('.userActivityItems') + }); + } + + if (!page.serverActivityLog) { + page.serverActivityLog = new ActivityLog({ + serverId: ApiClient.serverId(), + element: page.querySelector('.serverActivityItems') + }); + } + + refreshActiveRecordings(view, apiClient); + loading.hide(); + } + + taskButton({ + mode: 'on', + taskKey: 'RefreshLibrary', + button: page.querySelector('.btnRefresh') + }); + }); + view.addEventListener('viewbeforehide', function () { + const apiClient = ApiClient; + const page = this; + + Events.off(serverNotifications, 'RestartRequired', onRestartRequired); + Events.off(serverNotifications, 'ServerShuttingDown', onServerShuttingDown); + Events.off(serverNotifications, 'ServerRestarting', onServerRestarting); + Events.off(serverNotifications, 'PackageInstalling', onPackageInstall); + Events.off(serverNotifications, 'PackageInstallationCompleted', onPackageInstall); + Events.off(serverNotifications, 'Sessions', onSessionsUpdate); + Events.off(serverNotifications, 'ScheduledTasksInfo', onScheduledTasksUpdate); + + if (apiClient) { + DashboardPage.stopInterval(apiClient); + } + + taskButton({ + mode: 'off', + taskKey: 'RefreshLibrary', + button: page.querySelector('.btnRefresh') + }); + }); + view.addEventListener('viewdestroy', function () { + const page = this; + const userActivityLog = page.userActivityLog; + + if (userActivityLog) { + userActivityLog.destroy(); + } + + const serverActivityLog = page.serverActivityLog; + + if (serverActivityLog) { + serverActivityLog.destroy(); + } + }); +} + diff --git a/src/controllers/dashboard/devices/device.js b/src/controllers/dashboard/devices/device.js index 4a4da4bd7e..e911b361af 100644 --- a/src/controllers/dashboard/devices/device.js +++ b/src/controllers/dashboard/devices/device.js @@ -5,53 +5,50 @@ import '../../../elements/emby-button/emby-button'; import Dashboard from '../../../utils/dashboard'; import { getParameterByName } from '../../../utils/url.ts'; -/* eslint-disable indent */ +function load(page, device, deviceOptions) { + page.querySelector('#txtCustomName', page).value = deviceOptions.CustomName || ''; + page.querySelector('.reportedName', page).innerText = device.Name || ''; +} - function load(page, device, deviceOptions) { - page.querySelector('#txtCustomName', page).value = deviceOptions.CustomName || ''; - page.querySelector('.reportedName', page).innerText = device.Name || ''; - } +function loadData() { + const page = this; + loading.show(); + const id = getParameterByName('id'); + const promise1 = ApiClient.getJSON(ApiClient.getUrl('Devices/Info', { + Id: id + })); + const promise2 = ApiClient.getJSON(ApiClient.getUrl('Devices/Options', { + Id: id + })); + Promise.all([promise1, promise2]).then(function (responses) { + load(page, responses[0], responses[1]); + loading.hide(); + }); +} - function loadData() { - const page = this; - loading.show(); - const id = getParameterByName('id'); - const promise1 = ApiClient.getJSON(ApiClient.getUrl('Devices/Info', { +function save(page) { + const id = getParameterByName('id'); + ApiClient.ajax({ + url: ApiClient.getUrl('Devices/Options', { Id: id - })); - const promise2 = ApiClient.getJSON(ApiClient.getUrl('Devices/Options', { - Id: id - })); - Promise.all([promise1, promise2]).then(function (responses) { - load(page, responses[0], responses[1]); - loading.hide(); - }); - } + }), + type: 'POST', + data: JSON.stringify({ + CustomName: page.querySelector('#txtCustomName').value + }), + contentType: 'application/json' + }).then(Dashboard.processServerConfigurationUpdateResult); +} - function save(page) { - const id = getParameterByName('id'); - ApiClient.ajax({ - url: ApiClient.getUrl('Devices/Options', { - Id: id - }), - type: 'POST', - data: JSON.stringify({ - CustomName: page.querySelector('#txtCustomName').value - }), - contentType: 'application/json' - }).then(Dashboard.processServerConfigurationUpdateResult); - } +function onSubmit(e) { + const form = this; + save(dom.parentWithClass(form, 'page')); + e.preventDefault(); + return false; +} - function onSubmit(e) { - const form = this; - save(dom.parentWithClass(form, 'page')); - e.preventDefault(); - return false; - } +export default function (view) { + view.querySelector('form').addEventListener('submit', onSubmit); + view.addEventListener('viewshow', loadData); +} - export default function (view) { - view.querySelector('form').addEventListener('submit', onSubmit); - view.addEventListener('viewshow', loadData); - } - -/* eslint-enable indent */ diff --git a/src/controllers/dashboard/devices/devices.js b/src/controllers/dashboard/devices/devices.js index 348d6d2be1..8413c42dee 100644 --- a/src/controllers/dashboard/devices/devices.js +++ b/src/controllers/dashboard/devices/devices.js @@ -12,159 +12,157 @@ import '../../../components/cardbuilder/card.scss'; import Dashboard from '../../../utils/dashboard'; import confirm from '../../../components/confirm/confirm'; -/* eslint-disable indent */ +// Local cache of loaded +let deviceIds = []; - // Local cache of loaded - let deviceIds = []; +function canDelete(deviceId) { + return deviceId !== ApiClient.deviceId(); +} - function canDelete(deviceId) { - return deviceId !== ApiClient.deviceId(); - } +function deleteAllDevices(page) { + const msg = globalize.translate('DeleteDevicesConfirmation'); - function deleteAllDevices(page) { - const msg = globalize.translate('DeleteDevicesConfirmation'); + confirm({ + text: msg, + title: globalize.translate('HeaderDeleteDevices'), + confirmText: globalize.translate('Delete'), + primary: 'delete' + }).then(async () => { + loading.show(); + await Promise.all( + deviceIds.filter(canDelete).map((id) => ApiClient.deleteDevice(id)) + ); + loadData(page); + }); +} - confirm({ - text: msg, - title: globalize.translate('HeaderDeleteDevices'), - confirmText: globalize.translate('Delete'), - primary: 'delete' - }).then(async () => { - loading.show(); - await Promise.all( - deviceIds.filter(canDelete).map((id) => ApiClient.deleteDevice(id)) - ); - loadData(page); +function deleteDevice(page, id) { + const msg = globalize.translate('DeleteDeviceConfirmation'); + + confirm({ + text: msg, + title: globalize.translate('HeaderDeleteDevice'), + confirmText: globalize.translate('Delete'), + primary: 'delete' + }).then(async () => { + loading.show(); + await ApiClient.deleteDevice(id); + loadData(page); + }); +} + +function showDeviceMenu(view, btn, deviceId) { + const menuItems = [{ + name: globalize.translate('Edit'), + id: 'open', + icon: 'mode_edit' + }]; + + if (canDelete(deviceId)) { + menuItems.push({ + name: globalize.translate('Delete'), + id: 'delete', + icon: 'delete' }); } - function deleteDevice(page, id) { - const msg = globalize.translate('DeleteDeviceConfirmation'); + import('../../../components/actionSheet/actionSheet').then(({ default: actionsheet }) => { + actionsheet.show({ + items: menuItems, + positionTo: btn, + callback: function (id) { + switch (id) { + case 'open': + Dashboard.navigate('device.html?id=' + deviceId); + break; - confirm({ - text: msg, - title: globalize.translate('HeaderDeleteDevice'), - confirmText: globalize.translate('Delete'), - primary: 'delete' - }).then(async () => { - loading.show(); - await ApiClient.deleteDevice(id); - loadData(page); + case 'delete': + deleteDevice(view, deviceId); + } + } }); - } + }); +} - function showDeviceMenu(view, btn, deviceId) { - const menuItems = [{ - name: globalize.translate('Edit'), - id: 'open', - icon: 'mode_edit' - }]; +function load(page, devices) { + const localeWithSuffix = getLocaleWithSuffix(); - if (canDelete(deviceId)) { - menuItems.push({ - name: globalize.translate('Delete'), - id: 'delete', - icon: 'delete' - }); + let html = ''; + html += devices.map(function (device) { + let deviceHtml = ''; + deviceHtml += "
"; + deviceHtml += '
'; + deviceHtml += ''; + deviceHtml += '
'; - case 'delete': - deleteDevice(view, deviceId); - } - } - }); - }); - } - - function load(page, devices) { - const localeWithSuffix = getLocaleWithSuffix(); - - let html = ''; - html += devices.map(function (device) { - let deviceHtml = ''; - deviceHtml += "
"; - deviceHtml += '
'; - deviceHtml += '
'; - deviceHtml += '
'; - deviceHtml += ``; - const iconUrl = imageHelper.getDeviceIcon(device); - - if (iconUrl) { - deviceHtml += '
"; - deviceHtml += '
'; - } else { - deviceHtml += ''; - } - - deviceHtml += '
'; + if (canDelete(device.Id)) { + if (globalize.getIsRTL()) + deviceHtml += '
'; + else + deviceHtml += '
'; + deviceHtml += ''; deviceHtml += '
'; - deviceHtml += '
'; + } - if (canDelete(device.Id)) { - if (globalize.getIsRTL()) - deviceHtml += '
'; - else - deviceHtml += '
'; - deviceHtml += ''; - deviceHtml += '
'; - } + deviceHtml += "
"; + deviceHtml += escapeHtml(device.Name); + deviceHtml += '
'; + deviceHtml += "
"; + deviceHtml += escapeHtml(device.AppName + ' ' + device.AppVersion); + deviceHtml += '
'; + deviceHtml += "
"; - deviceHtml += "
"; - deviceHtml += escapeHtml(device.Name); - deviceHtml += '
'; - deviceHtml += "
"; - deviceHtml += escapeHtml(device.AppName + ' ' + device.AppVersion); - deviceHtml += '
'; - deviceHtml += "
"; + if (device.LastUserName) { + deviceHtml += escapeHtml(device.LastUserName); + deviceHtml += ', ' + formatDistanceToNow(Date.parse(device.DateLastActivity), localeWithSuffix); + } - if (device.LastUserName) { - deviceHtml += escapeHtml(device.LastUserName); - deviceHtml += ', ' + formatDistanceToNow(Date.parse(device.DateLastActivity), localeWithSuffix); - } + deviceHtml += ' '; + deviceHtml += '
'; + deviceHtml += '
'; + deviceHtml += '
'; + deviceHtml += '
'; + return deviceHtml; + }).join(''); + page.querySelector('.devicesList').innerHTML = html; +} - deviceHtml += ' '; - deviceHtml += '
'; - deviceHtml += '
'; - deviceHtml += '
'; - deviceHtml += '
'; - return deviceHtml; - }).join(''); - page.querySelector('.devicesList').innerHTML = html; - } +function loadData(page) { + loading.show(); + ApiClient.getJSON(ApiClient.getUrl('Devices')).then(function (result) { + load(page, result.Items); + deviceIds = result.Items.map((device) => device.Id); + loading.hide(); + }); +} - function loadData(page) { - loading.show(); - ApiClient.getJSON(ApiClient.getUrl('Devices')).then(function (result) { - load(page, result.Items); - deviceIds = result.Items.map((device) => device.Id); - loading.hide(); - }); - } +export default function (view) { + view.querySelector('.devicesList').addEventListener('click', function (e) { + const btnDeviceMenu = dom.parentWithClass(e.target, 'btnDeviceMenu'); - export default function (view) { - view.querySelector('.devicesList').addEventListener('click', function (e) { - const btnDeviceMenu = dom.parentWithClass(e.target, 'btnDeviceMenu'); + if (btnDeviceMenu) { + showDeviceMenu(view, btnDeviceMenu, btnDeviceMenu.getAttribute('data-id')); + } + }); + view.addEventListener('viewshow', function () { + loadData(this); + }); - if (btnDeviceMenu) { - showDeviceMenu(view, btnDeviceMenu, btnDeviceMenu.getAttribute('data-id')); - } - }); - view.addEventListener('viewshow', function () { - loadData(this); - }); + view.querySelector('#deviceDeleteAll').addEventListener('click', function() { + deleteAllDevices(view); + }); +} - view.querySelector('#deviceDeleteAll').addEventListener('click', function() { - deleteAllDevices(view); - }); - } -/* eslint-enable indent */ diff --git a/src/controllers/dashboard/dlna/profile.js b/src/controllers/dashboard/dlna/profile.js index 4d942f7cdc..dd3e3ad957 100644 --- a/src/controllers/dashboard/dlna/profile.js +++ b/src/controllers/dashboard/dlna/profile.js @@ -11,829 +11,826 @@ import Dashboard from '../../../utils/dashboard'; import toast from '../../../components/toast/toast'; import { getParameterByName } from '../../../utils/url.ts'; -/* eslint-disable indent */ - - function loadProfile(page) { - loading.show(); - const promise1 = getProfile(); - const promise2 = ApiClient.getUsers(); - Promise.all([promise1, promise2]).then(function (responses) { - currentProfile = responses[0]; - renderProfile(page, currentProfile, responses[1]); - loading.hide(); - }); - } - - function getProfile() { - const id = getParameterByName('id'); - const url = id ? 'Dlna/Profiles/' + id : 'Dlna/Profiles/Default'; - return ApiClient.getJSON(ApiClient.getUrl(url)); - } - - function renderProfile(page, profile, users) { - $('#txtName', page).val(profile.Name); - $('.chkMediaType', page).each(function () { - this.checked = (profile.SupportedMediaTypes || '').split(',').indexOf(this.getAttribute('data-value')) != -1; - }); - $('#chkEnableAlbumArtInDidl', page).prop('checked', profile.EnableAlbumArtInDidl); - $('#chkEnableSingleImageLimit', page).prop('checked', profile.EnableSingleAlbumArtLimit); - renderXmlDocumentAttributes(page, profile.XmlRootAttributes || []); - const idInfo = profile.Identification || {}; - renderIdentificationHeaders(page, idInfo.Headers || []); - renderSubtitleProfiles(page, profile.SubtitleProfiles || []); - $('#txtInfoFriendlyName', page).val(profile.FriendlyName || ''); - $('#txtInfoModelName', page).val(profile.ModelName || ''); - $('#txtInfoModelNumber', page).val(profile.ModelNumber || ''); - $('#txtInfoModelDescription', page).val(profile.ModelDescription || ''); - $('#txtInfoModelUrl', page).val(profile.ModelUrl || ''); - $('#txtInfoManufacturer', page).val(profile.Manufacturer || ''); - $('#txtInfoManufacturerUrl', page).val(profile.ManufacturerUrl || ''); - $('#txtInfoSerialNumber', page).val(profile.SerialNumber || ''); - $('#txtIdFriendlyName', page).val(idInfo.FriendlyName || ''); - $('#txtIdModelName', page).val(idInfo.ModelName || ''); - $('#txtIdModelNumber', page).val(idInfo.ModelNumber || ''); - $('#txtIdModelDescription', page).val(idInfo.ModelDescription || ''); - $('#txtIdModelUrl', page).val(idInfo.ModelUrl || ''); - $('#txtIdManufacturer', page).val(idInfo.Manufacturer || ''); - $('#txtIdManufacturerUrl', page).val(idInfo.ManufacturerUrl || ''); - $('#txtIdSerialNumber', page).val(idInfo.SerialNumber || ''); - $('#txtIdDeviceDescription', page).val(idInfo.DeviceDescription || ''); - $('#txtAlbumArtPn', page).val(profile.AlbumArtPn || ''); - $('#txtAlbumArtMaxWidth', page).val(profile.MaxAlbumArtWidth || ''); - $('#txtAlbumArtMaxHeight', page).val(profile.MaxAlbumArtHeight || ''); - $('#txtIconMaxWidth', page).val(profile.MaxIconWidth || ''); - $('#txtIconMaxHeight', page).val(profile.MaxIconHeight || ''); - $('#chkIgnoreTranscodeByteRangeRequests', page).prop('checked', profile.IgnoreTranscodeByteRangeRequests); - $('#txtMaxAllowedBitrate', page).val(profile.MaxStreamingBitrate || ''); - $('#txtMusicStreamingTranscodingBitrate', page).val(profile.MusicStreamingTranscodingBitrate || ''); - $('#chkRequiresPlainFolders', page).prop('checked', profile.RequiresPlainFolders); - $('#chkRequiresPlainVideoItems', page).prop('checked', profile.RequiresPlainVideoItems); - $('#txtProtocolInfo', page).val(profile.ProtocolInfo || ''); - $('#txtXDlnaCap', page).val(profile.XDlnaCap || ''); - $('#txtXDlnaDoc', page).val(profile.XDlnaDoc || ''); - $('#txtSonyAggregationFlags', page).val(profile.SonyAggregationFlags || ''); - profile.DirectPlayProfiles = profile.DirectPlayProfiles || []; - profile.TranscodingProfiles = profile.TranscodingProfiles || []; - profile.ContainerProfiles = profile.ContainerProfiles || []; - profile.CodecProfiles = profile.CodecProfiles || []; - profile.ResponseProfiles = profile.ResponseProfiles || []; - const usersHtml = '' + users.map(function (u) { - return ''; - }).join(''); - $('#selectUser', page).html(usersHtml).val(profile.UserId || ''); - renderSubProfiles(page, profile); - } - - function renderIdentificationHeaders(page, headers) { - let index = 0; - const html = '
' + headers.map(function (h) { - let li = '
'; - li += ''; - li += '
'; - li += '

' + escapeHtml(h.Name + ': ' + (h.Value || '')) + '

'; - li += '
' + escapeHtml(h.Match || '') + '
'; - li += '
'; - li += ''; - li += '
'; - index++; - return li; - }).join('') + '
'; - const elem = $('.httpHeaderIdentificationList', page).html(html).trigger('create'); - $('.btnDeleteIdentificationHeader', elem).on('click', function () { - const itemIndex = parseInt(this.getAttribute('data-index'), 10); - currentProfile.Identification.Headers.splice(itemIndex, 1); - renderIdentificationHeaders(page, currentProfile.Identification.Headers); - }); - } - - function openPopup(elem) { - elem.classList.remove('hide'); - } - - function closePopup(elem) { - elem.classList.add('hide'); - } - - function editIdentificationHeader(page, header) { - isSubProfileNew = header == null; - header = header || {}; - currentSubProfile = header; - const popup = $('#identificationHeaderPopup', page); - $('#txtIdentificationHeaderName', popup).val(header.Name || ''); - $('#txtIdentificationHeaderValue', popup).val(header.Value || ''); - $('#selectMatchType', popup).val(header.Match || 'Equals'); - openPopup(popup[0]); - } - - function saveIdentificationHeader(page) { - currentSubProfile.Name = $('#txtIdentificationHeaderName', page).val(); - currentSubProfile.Value = $('#txtIdentificationHeaderValue', page).val(); - currentSubProfile.Match = $('#selectMatchType', page).val(); - - if (isSubProfileNew) { - currentProfile.Identification = currentProfile.Identification || {}; - currentProfile.Identification.Headers = currentProfile.Identification.Headers || []; - currentProfile.Identification.Headers.push(currentSubProfile); - } - - renderIdentificationHeaders(page, currentProfile.Identification.Headers); - currentSubProfile = null; - closePopup($('#identificationHeaderPopup', page)[0]); - } - - function renderXmlDocumentAttributes(page, attribute) { - const html = '
' + attribute.map(function (h) { - let li = '
'; - li += ''; - li += '
'; - li += '

' + escapeHtml(h.Name + ' = ' + (h.Value || '')) + '

'; - li += '
'; - li += ''; - li += '
'; - return li; - }).join('') + '
'; - const elem = $('.xmlDocumentAttributeList', page).html(html).trigger('create'); - $('.btnDeleteXmlAttribute', elem).on('click', function () { - const itemIndex = parseInt(this.getAttribute('data-index'), 10); - currentProfile.XmlRootAttributes.splice(itemIndex, 1); - renderXmlDocumentAttributes(page, currentProfile.XmlRootAttributes); - }); - } - - function editXmlDocumentAttribute(page, attribute) { - isSubProfileNew = attribute == null; - attribute = attribute || {}; - currentSubProfile = attribute; - const popup = $('#xmlAttributePopup', page); - $('#txtXmlAttributeName', popup).val(attribute.Name || ''); - $('#txtXmlAttributeValue', popup).val(attribute.Value || ''); - openPopup(popup[0]); - } - - function saveXmlDocumentAttribute(page) { - currentSubProfile.Name = $('#txtXmlAttributeName', page).val(); - currentSubProfile.Value = $('#txtXmlAttributeValue', page).val(); - - if (isSubProfileNew) { - currentProfile.XmlRootAttributes.push(currentSubProfile); - } - - renderXmlDocumentAttributes(page, currentProfile.XmlRootAttributes); - currentSubProfile = null; - closePopup($('#xmlAttributePopup', page)[0]); - } - - function renderSubtitleProfiles(page, profiles) { - let index = 0; - const html = '
' + profiles.map(function (h) { - let li = '
'; - li += ''; - li += '
'; - li += '

' + escapeHtml(h.Format || '') + '

'; - li += '
'; - li += ''; - li += '
'; - index++; - return li; - }).join('') + '
'; - const elem = $('.subtitleProfileList', page).html(html).trigger('create'); - $('.btnDeleteProfile', elem).on('click', function () { - const itemIndex = parseInt(this.getAttribute('data-index'), 10); - currentProfile.SubtitleProfiles.splice(itemIndex, 1); - renderSubtitleProfiles(page, currentProfile.SubtitleProfiles); - }); - $('.lnkEditSubProfile', elem).on('click', function () { - const itemIndex = parseInt(this.getAttribute('data-index'), 10); - editSubtitleProfile(page, currentProfile.SubtitleProfiles[itemIndex]); - }); - } - - function editSubtitleProfile(page, profile) { - isSubProfileNew = profile == null; - profile = profile || {}; - currentSubProfile = profile; - const popup = $('#subtitleProfilePopup', page); - $('#txtSubtitleProfileFormat', popup).val(profile.Format || ''); - $('#selectSubtitleProfileMethod', popup).val(profile.Method || ''); - $('#selectSubtitleProfileDidlMode', popup).val(profile.DidlMode || ''); - openPopup(popup[0]); - } - - function saveSubtitleProfile(page) { - currentSubProfile.Format = $('#txtSubtitleProfileFormat', page).val(); - currentSubProfile.Method = $('#selectSubtitleProfileMethod', page).val(); - currentSubProfile.DidlMode = $('#selectSubtitleProfileDidlMode', page).val(); - - if (isSubProfileNew) { - currentProfile.SubtitleProfiles.push(currentSubProfile); - } - - renderSubtitleProfiles(page, currentProfile.SubtitleProfiles); - currentSubProfile = null; - closePopup($('#subtitleProfilePopup', page)[0]); - } - - function renderSubProfiles(page, profile) { - renderDirectPlayProfiles(page, profile.DirectPlayProfiles); - renderTranscodingProfiles(page, profile.TranscodingProfiles); - renderContainerProfiles(page, profile.ContainerProfiles); - renderCodecProfiles(page, profile.CodecProfiles); - renderResponseProfiles(page, profile.ResponseProfiles); - } - - function saveDirectPlayProfile(page) { - currentSubProfile.Type = $('#selectDirectPlayProfileType', page).val(); - currentSubProfile.Container = $('#txtDirectPlayContainer', page).val(); - currentSubProfile.AudioCodec = $('#txtDirectPlayAudioCodec', page).val(); - currentSubProfile.VideoCodec = $('#txtDirectPlayVideoCodec', page).val(); - - if (isSubProfileNew) { - currentProfile.DirectPlayProfiles.push(currentSubProfile); - } - - renderSubProfiles(page, currentProfile); - currentSubProfile = null; - closePopup($('#popupEditDirectPlayProfile', page)[0]); - } - - function renderDirectPlayProfiles(page, profiles) { - let html = ''; - html += ''; - const elem = $('.directPlayProfiles', page).html(html).trigger('create'); - $('.btnDeleteProfile', elem).on('click', function () { - const index = this.getAttribute('data-profileindex'); - deleteDirectPlayProfile(page, index); - }); - $('.lnkEditSubProfile', elem).on('click', function () { - const index = parseInt(this.getAttribute('data-profileindex'), 10); - editDirectPlayProfile(page, currentProfile.DirectPlayProfiles[index]); - }); - } - - function deleteDirectPlayProfile(page, index) { - currentProfile.DirectPlayProfiles.splice(index, 1); - renderDirectPlayProfiles(page, currentProfile.DirectPlayProfiles); - } - - function editDirectPlayProfile(page, directPlayProfile) { - isSubProfileNew = directPlayProfile == null; - directPlayProfile = directPlayProfile || {}; - currentSubProfile = directPlayProfile; - const popup = $('#popupEditDirectPlayProfile', page); - $('#selectDirectPlayProfileType', popup).val(directPlayProfile.Type || 'Video').trigger('change'); - $('#txtDirectPlayContainer', popup).val(directPlayProfile.Container || ''); - $('#txtDirectPlayAudioCodec', popup).val(directPlayProfile.AudioCodec || ''); - $('#txtDirectPlayVideoCodec', popup).val(directPlayProfile.VideoCodec || ''); - openPopup(popup[0]); - } - - function renderTranscodingProfiles(page, profiles) { - let html = ''; - html += ''; - const elem = $('.transcodingProfiles', page).html(html).trigger('create'); - $('.btnDeleteProfile', elem).on('click', function () { - const index = this.getAttribute('data-profileindex'); - deleteTranscodingProfile(page, index); - }); - $('.lnkEditSubProfile', elem).on('click', function () { - const index = parseInt(this.getAttribute('data-profileindex'), 10); - editTranscodingProfile(page, currentProfile.TranscodingProfiles[index]); - }); - } - - function editTranscodingProfile(page, transcodingProfile) { - isSubProfileNew = transcodingProfile == null; - transcodingProfile = transcodingProfile || {}; - currentSubProfile = transcodingProfile; - const popup = $('#transcodingProfilePopup', page); - $('#selectTranscodingProfileType', popup).val(transcodingProfile.Type || 'Video').trigger('change'); - $('#txtTranscodingContainer', popup).val(transcodingProfile.Container || ''); - $('#txtTranscodingAudioCodec', popup).val(transcodingProfile.AudioCodec || ''); - $('#txtTranscodingVideoCodec', popup).val(transcodingProfile.VideoCodec || ''); - $('#selectTranscodingProtocol', popup).val(transcodingProfile.Protocol || 'Http'); - $('#chkEnableMpegtsM2TsMode', popup).prop('checked', transcodingProfile.EnableMpegtsM2TsMode || false); - $('#chkEstimateContentLength', popup).prop('checked', transcodingProfile.EstimateContentLength || false); - $('#chkReportByteRangeRequests', popup).prop('checked', transcodingProfile.TranscodeSeekInfo == 'Bytes'); - $('.radioTabButton:first', popup).trigger('click'); - openPopup(popup[0]); - } - - function deleteTranscodingProfile(page, index) { - currentProfile.TranscodingProfiles.splice(index, 1); - renderTranscodingProfiles(page, currentProfile.TranscodingProfiles); - } - - function saveTranscodingProfile(page) { - currentSubProfile.Type = $('#selectTranscodingProfileType', page).val(); - currentSubProfile.Container = $('#txtTranscodingContainer', page).val(); - currentSubProfile.AudioCodec = $('#txtTranscodingAudioCodec', page).val(); - currentSubProfile.VideoCodec = $('#txtTranscodingVideoCodec', page).val(); - currentSubProfile.Protocol = $('#selectTranscodingProtocol', page).val(); - currentSubProfile.Context = 'Streaming'; - currentSubProfile.EnableMpegtsM2TsMode = $('#chkEnableMpegtsM2TsMode', page).is(':checked'); - currentSubProfile.EstimateContentLength = $('#chkEstimateContentLength', page).is(':checked'); - currentSubProfile.TranscodeSeekInfo = $('#chkReportByteRangeRequests', page).is(':checked') ? 'Bytes' : 'Auto'; - - if (isSubProfileNew) { - currentProfile.TranscodingProfiles.push(currentSubProfile); - } - - renderSubProfiles(page, currentProfile); - currentSubProfile = null; - closePopup($('#transcodingProfilePopup', page)[0]); - } - - function renderContainerProfiles(page, profiles) { - let html = ''; - html += ''; - const elem = $('.containerProfiles', page).html(html).trigger('create'); - $('.btnDeleteProfile', elem).on('click', function () { - const index = this.getAttribute('data-profileindex'); - deleteContainerProfile(page, index); - }); - $('.lnkEditSubProfile', elem).on('click', function () { - const index = parseInt(this.getAttribute('data-profileindex'), 10); - editContainerProfile(page, currentProfile.ContainerProfiles[index]); - }); - } - - function deleteContainerProfile(page, index) { - currentProfile.ContainerProfiles.splice(index, 1); - renderContainerProfiles(page, currentProfile.ContainerProfiles); - } - - function editContainerProfile(page, containerProfile) { - isSubProfileNew = containerProfile == null; - containerProfile = containerProfile || {}; - currentSubProfile = containerProfile; - const popup = $('#containerProfilePopup', page); - $('#selectContainerProfileType', popup).val(containerProfile.Type || 'Video').trigger('change'); - $('#txtContainerProfileContainer', popup).val(containerProfile.Container || ''); - $('.radioTabButton:first', popup).trigger('click'); - openPopup(popup[0]); - } - - function saveContainerProfile(page) { - currentSubProfile.Type = $('#selectContainerProfileType', page).val(); - currentSubProfile.Container = $('#txtContainerProfileContainer', page).val(); - - if (isSubProfileNew) { - currentProfile.ContainerProfiles.push(currentSubProfile); - } - - renderSubProfiles(page, currentProfile); - currentSubProfile = null; - closePopup($('#containerProfilePopup', page)[0]); - } - - function renderCodecProfiles(page, profiles) { - let html = ''; - html += ''; - const elem = $('.codecProfiles', page).html(html).trigger('create'); - $('.btnDeleteProfile', elem).on('click', function () { - const index = this.getAttribute('data-profileindex'); - deleteCodecProfile(page, index); - }); - $('.lnkEditSubProfile', elem).on('click', function () { - const index = parseInt(this.getAttribute('data-profileindex'), 10); - editCodecProfile(page, currentProfile.CodecProfiles[index]); - }); - } - - function deleteCodecProfile(page, index) { - currentProfile.CodecProfiles.splice(index, 1); - renderCodecProfiles(page, currentProfile.CodecProfiles); - } - - function editCodecProfile(page, codecProfile) { - isSubProfileNew = codecProfile == null; - codecProfile = codecProfile || {}; - currentSubProfile = codecProfile; - const popup = $('#codecProfilePopup', page); - $('#selectCodecProfileType', popup).val(codecProfile.Type || 'Video').trigger('change'); - $('#txtCodecProfileCodec', popup).val(codecProfile.Codec || ''); - $('.radioTabButton:first', popup).trigger('click'); - openPopup(popup[0]); - } - - function saveCodecProfile(page) { - currentSubProfile.Type = $('#selectCodecProfileType', page).val(); - currentSubProfile.Codec = $('#txtCodecProfileCodec', page).val(); - - if (isSubProfileNew) { - currentProfile.CodecProfiles.push(currentSubProfile); - } - - renderSubProfiles(page, currentProfile); - currentSubProfile = null; - closePopup($('#codecProfilePopup', page)[0]); - } - - function renderResponseProfiles(page, profiles) { - let html = ''; - html += ''; - const elem = $('.mediaProfiles', page).html(html).trigger('create'); - $('.btnDeleteProfile', elem).on('click', function () { - const index = this.getAttribute('data-profileindex'); - deleteResponseProfile(page, index); - }); - $('.lnkEditSubProfile', elem).on('click', function () { - const index = parseInt(this.getAttribute('data-profileindex'), 10); - editResponseProfile(page, currentProfile.ResponseProfiles[index]); - }); - } - - function deleteResponseProfile(page, index) { - currentProfile.ResponseProfiles.splice(index, 1); - renderResponseProfiles(page, currentProfile.ResponseProfiles); - } - - function editResponseProfile(page, responseProfile) { - isSubProfileNew = responseProfile == null; - responseProfile = responseProfile || {}; - currentSubProfile = responseProfile; - const popup = $('#responseProfilePopup', page); - $('#selectResponseProfileType', popup).val(responseProfile.Type || 'Video').trigger('change'); - $('#txtResponseProfileContainer', popup).val(responseProfile.Container || ''); - $('#txtResponseProfileAudioCodec', popup).val(responseProfile.AudioCodec || ''); - $('#txtResponseProfileVideoCodec', popup).val(responseProfile.VideoCodec || ''); - $('.radioTabButton:first', popup).trigger('click'); - openPopup(popup[0]); - } - - function saveResponseProfile(page) { - currentSubProfile.Type = $('#selectResponseProfileType', page).val(); - currentSubProfile.Container = $('#txtResponseProfileContainer', page).val(); - currentSubProfile.AudioCodec = $('#txtResponseProfileAudioCodec', page).val(); - currentSubProfile.VideoCodec = $('#txtResponseProfileVideoCodec', page).val(); - - if (isSubProfileNew) { - currentProfile.ResponseProfiles.push(currentSubProfile); - } - - renderSubProfiles(page, currentProfile); - currentSubProfile = null; - closePopup($('#responseProfilePopup', page)[0]); - } - - function saveProfile(page, profile) { - updateProfile(page, profile); - const id = getParameterByName('id'); - - if (id) { - ApiClient.ajax({ - type: 'POST', - url: ApiClient.getUrl('Dlna/Profiles/' + id), - data: JSON.stringify(profile), - contentType: 'application/json' - }).then(function () { - toast(globalize.translate('SettingsSaved')); - }, Dashboard.processErrorResponse); - } else { - ApiClient.ajax({ - type: 'POST', - url: ApiClient.getUrl('Dlna/Profiles'), - data: JSON.stringify(profile), - contentType: 'application/json' - }).then(function () { - Dashboard.navigate('dlnaprofiles.html'); - }, Dashboard.processErrorResponse); - } - +function loadProfile(page) { + loading.show(); + const promise1 = getProfile(); + const promise2 = ApiClient.getUsers(); + Promise.all([promise1, promise2]).then(function (responses) { + currentProfile = responses[0]; + renderProfile(page, currentProfile, responses[1]); loading.hide(); - } - - function updateProfile(page, profile) { - profile.Name = $('#txtName', page).val(); - profile.EnableAlbumArtInDidl = $('#chkEnableAlbumArtInDidl', page).is(':checked'); - profile.EnableSingleAlbumArtLimit = $('#chkEnableSingleImageLimit', page).is(':checked'); - profile.SupportedMediaTypes = $('.chkMediaType:checked', page).get().map(function (c) { - return c.getAttribute('data-value'); - }).join(','); - profile.Identification = profile.Identification || {}; - profile.FriendlyName = $('#txtInfoFriendlyName', page).val(); - profile.ModelName = $('#txtInfoModelName', page).val(); - profile.ModelNumber = $('#txtInfoModelNumber', page).val(); - profile.ModelDescription = $('#txtInfoModelDescription', page).val(); - profile.ModelUrl = $('#txtInfoModelUrl', page).val(); - profile.Manufacturer = $('#txtInfoManufacturer', page).val(); - profile.ManufacturerUrl = $('#txtInfoManufacturerUrl', page).val(); - profile.SerialNumber = $('#txtInfoSerialNumber', page).val(); - profile.Identification.FriendlyName = $('#txtIdFriendlyName', page).val(); - profile.Identification.ModelName = $('#txtIdModelName', page).val(); - profile.Identification.ModelNumber = $('#txtIdModelNumber', page).val(); - profile.Identification.ModelDescription = $('#txtIdModelDescription', page).val(); - profile.Identification.ModelUrl = $('#txtIdModelUrl', page).val(); - profile.Identification.Manufacturer = $('#txtIdManufacturer', page).val(); - profile.Identification.ManufacturerUrl = $('#txtIdManufacturerUrl', page).val(); - profile.Identification.SerialNumber = $('#txtIdSerialNumber', page).val(); - profile.Identification.DeviceDescription = $('#txtIdDeviceDescription', page).val(); - profile.AlbumArtPn = $('#txtAlbumArtPn', page).val(); - profile.MaxAlbumArtWidth = $('#txtAlbumArtMaxWidth', page).val(); - profile.MaxAlbumArtHeight = $('#txtAlbumArtMaxHeight', page).val(); - profile.MaxIconWidth = $('#txtIconMaxWidth', page).val(); - profile.MaxIconHeight = $('#txtIconMaxHeight', page).val(); - profile.RequiresPlainFolders = $('#chkRequiresPlainFolders', page).is(':checked'); - profile.RequiresPlainVideoItems = $('#chkRequiresPlainVideoItems', page).is(':checked'); - profile.IgnoreTranscodeByteRangeRequests = $('#chkIgnoreTranscodeByteRangeRequests', page).is(':checked'); - profile.MaxStreamingBitrate = $('#txtMaxAllowedBitrate', page).val(); - profile.MusicStreamingTranscodingBitrate = $('#txtMusicStreamingTranscodingBitrate', page).val(); - profile.ProtocolInfo = $('#txtProtocolInfo', page).val(); - profile.XDlnaCap = $('#txtXDlnaCap', page).val(); - profile.XDlnaDoc = $('#txtXDlnaDoc', page).val(); - profile.SonyAggregationFlags = $('#txtSonyAggregationFlags', page).val(); - profile.UserId = $('#selectUser', page).val(); - } - - let currentProfile; - let currentSubProfile; - let isSubProfileNew; - const allText = globalize.translate('All'); - - $(document).on('pageinit', '#dlnaProfilePage', function () { - const page = this; - $('.radioTabButton', page).on('click', function () { - $(this).siblings().removeClass('ui-btn-active'); - $(this).addClass('ui-btn-active'); - const value = this.tagName == 'A' ? this.getAttribute('data-value') : this.value; - const elem = $('.' + value, page); - elem.siblings('.tabContent').hide(); - elem.show(); - }); - $('#selectDirectPlayProfileType', page).on('change', function () { - if (this.value == 'Video') { - $('#fldDirectPlayVideoCodec', page).show(); - } else { - $('#fldDirectPlayVideoCodec', page).hide(); - } - - if (this.value == 'Photo') { - $('#fldDirectPlayAudioCodec', page).hide(); - } else { - $('#fldDirectPlayAudioCodec', page).show(); - } - }); - $('#selectTranscodingProfileType', page).on('change', function () { - if (this.value == 'Video') { - $('#fldTranscodingVideoCodec', page).show(); - $('#fldTranscodingProtocol', page).show(); - $('#fldEnableMpegtsM2TsMode', page).show(); - } else { - $('#fldTranscodingVideoCodec', page).hide(); - $('#fldTranscodingProtocol', page).hide(); - $('#fldEnableMpegtsM2TsMode', page).hide(); - } - - if (this.value == 'Photo') { - $('#fldTranscodingAudioCodec', page).hide(); - $('#fldEstimateContentLength', page).hide(); - $('#fldReportByteRangeRequests', page).hide(); - } else { - $('#fldTranscodingAudioCodec', page).show(); - $('#fldEstimateContentLength', page).show(); - $('#fldReportByteRangeRequests', page).show(); - } - }); - $('#selectResponseProfileType', page).on('change', function () { - if (this.value == 'Video') { - $('#fldResponseProfileVideoCodec', page).show(); - } else { - $('#fldResponseProfileVideoCodec', page).hide(); - } - - if (this.value == 'Photo') { - $('#fldResponseProfileAudioCodec', page).hide(); - } else { - $('#fldResponseProfileAudioCodec', page).show(); - } - }); - $('.btnAddDirectPlayProfile', page).on('click', function () { - editDirectPlayProfile(page); - }); - $('.btnAddTranscodingProfile', page).on('click', function () { - editTranscodingProfile(page); - }); - $('.btnAddContainerProfile', page).on('click', function () { - editContainerProfile(page); - }); - $('.btnAddCodecProfile', page).on('click', function () { - editCodecProfile(page); - }); - $('.btnAddResponseProfile', page).on('click', function () { - editResponseProfile(page); - }); - $('.btnAddIdentificationHttpHeader', page).on('click', function () { - editIdentificationHeader(page); - }); - $('.btnAddXmlDocumentAttribute', page).on('click', function () { - editXmlDocumentAttribute(page); - }); - $('.btnAddSubtitleProfile', page).on('click', function () { - editSubtitleProfile(page); - }); - $('.dlnaProfileForm').off('submit', DlnaProfilePage.onSubmit).on('submit', DlnaProfilePage.onSubmit); - $('.editDirectPlayProfileForm').off('submit', DlnaProfilePage.onDirectPlayFormSubmit).on('submit', DlnaProfilePage.onDirectPlayFormSubmit); - $('.transcodingProfileForm').off('submit', DlnaProfilePage.onTranscodingProfileFormSubmit).on('submit', DlnaProfilePage.onTranscodingProfileFormSubmit); - $('.containerProfileForm').off('submit', DlnaProfilePage.onContainerProfileFormSubmit).on('submit', DlnaProfilePage.onContainerProfileFormSubmit); - $('.codecProfileForm').off('submit', DlnaProfilePage.onCodecProfileFormSubmit).on('submit', DlnaProfilePage.onCodecProfileFormSubmit); - $('.editResponseProfileForm').off('submit', DlnaProfilePage.onResponseProfileFormSubmit).on('submit', DlnaProfilePage.onResponseProfileFormSubmit); - $('.identificationHeaderForm').off('submit', DlnaProfilePage.onIdentificationHeaderFormSubmit).on('submit', DlnaProfilePage.onIdentificationHeaderFormSubmit); - $('.xmlAttributeForm').off('submit', DlnaProfilePage.onXmlAttributeFormSubmit).on('submit', DlnaProfilePage.onXmlAttributeFormSubmit); - $('.subtitleProfileForm').off('submit', DlnaProfilePage.onSubtitleProfileFormSubmit).on('submit', DlnaProfilePage.onSubtitleProfileFormSubmit); - }).on('pageshow', '#dlnaProfilePage', function () { - const page = this; - $('#radioInfo', page).trigger('click'); - loadProfile(page); }); - window.DlnaProfilePage = { - onSubmit: function () { - loading.show(); - saveProfile($(this).parents('.page'), currentProfile); - return false; - }, - onDirectPlayFormSubmit: function () { - saveDirectPlayProfile($(this).parents('.page')); - return false; - }, - onTranscodingProfileFormSubmit: function () { - saveTranscodingProfile($(this).parents('.page')); - return false; - }, - onContainerProfileFormSubmit: function () { - saveContainerProfile($(this).parents('.page')); - return false; - }, - onCodecProfileFormSubmit: function () { - saveCodecProfile($(this).parents('.page')); - return false; - }, - onResponseProfileFormSubmit: function () { - saveResponseProfile($(this).parents('.page')); - return false; - }, - onIdentificationHeaderFormSubmit: function () { - saveIdentificationHeader($(this).parents('.page')); - return false; - }, - onXmlAttributeFormSubmit: function () { - saveXmlDocumentAttribute($(this).parents('.page')); - return false; - }, - onSubtitleProfileFormSubmit: function () { - saveSubtitleProfile($(this).parents('.page')); - return false; - } - }; +} + +function getProfile() { + const id = getParameterByName('id'); + const url = id ? 'Dlna/Profiles/' + id : 'Dlna/Profiles/Default'; + return ApiClient.getJSON(ApiClient.getUrl(url)); +} + +function renderProfile(page, profile, users) { + $('#txtName', page).val(profile.Name); + $('.chkMediaType', page).each(function () { + this.checked = (profile.SupportedMediaTypes || '').split(',').indexOf(this.getAttribute('data-value')) != -1; + }); + $('#chkEnableAlbumArtInDidl', page).prop('checked', profile.EnableAlbumArtInDidl); + $('#chkEnableSingleImageLimit', page).prop('checked', profile.EnableSingleAlbumArtLimit); + renderXmlDocumentAttributes(page, profile.XmlRootAttributes || []); + const idInfo = profile.Identification || {}; + renderIdentificationHeaders(page, idInfo.Headers || []); + renderSubtitleProfiles(page, profile.SubtitleProfiles || []); + $('#txtInfoFriendlyName', page).val(profile.FriendlyName || ''); + $('#txtInfoModelName', page).val(profile.ModelName || ''); + $('#txtInfoModelNumber', page).val(profile.ModelNumber || ''); + $('#txtInfoModelDescription', page).val(profile.ModelDescription || ''); + $('#txtInfoModelUrl', page).val(profile.ModelUrl || ''); + $('#txtInfoManufacturer', page).val(profile.Manufacturer || ''); + $('#txtInfoManufacturerUrl', page).val(profile.ManufacturerUrl || ''); + $('#txtInfoSerialNumber', page).val(profile.SerialNumber || ''); + $('#txtIdFriendlyName', page).val(idInfo.FriendlyName || ''); + $('#txtIdModelName', page).val(idInfo.ModelName || ''); + $('#txtIdModelNumber', page).val(idInfo.ModelNumber || ''); + $('#txtIdModelDescription', page).val(idInfo.ModelDescription || ''); + $('#txtIdModelUrl', page).val(idInfo.ModelUrl || ''); + $('#txtIdManufacturer', page).val(idInfo.Manufacturer || ''); + $('#txtIdManufacturerUrl', page).val(idInfo.ManufacturerUrl || ''); + $('#txtIdSerialNumber', page).val(idInfo.SerialNumber || ''); + $('#txtIdDeviceDescription', page).val(idInfo.DeviceDescription || ''); + $('#txtAlbumArtPn', page).val(profile.AlbumArtPn || ''); + $('#txtAlbumArtMaxWidth', page).val(profile.MaxAlbumArtWidth || ''); + $('#txtAlbumArtMaxHeight', page).val(profile.MaxAlbumArtHeight || ''); + $('#txtIconMaxWidth', page).val(profile.MaxIconWidth || ''); + $('#txtIconMaxHeight', page).val(profile.MaxIconHeight || ''); + $('#chkIgnoreTranscodeByteRangeRequests', page).prop('checked', profile.IgnoreTranscodeByteRangeRequests); + $('#txtMaxAllowedBitrate', page).val(profile.MaxStreamingBitrate || ''); + $('#txtMusicStreamingTranscodingBitrate', page).val(profile.MusicStreamingTranscodingBitrate || ''); + $('#chkRequiresPlainFolders', page).prop('checked', profile.RequiresPlainFolders); + $('#chkRequiresPlainVideoItems', page).prop('checked', profile.RequiresPlainVideoItems); + $('#txtProtocolInfo', page).val(profile.ProtocolInfo || ''); + $('#txtXDlnaCap', page).val(profile.XDlnaCap || ''); + $('#txtXDlnaDoc', page).val(profile.XDlnaDoc || ''); + $('#txtSonyAggregationFlags', page).val(profile.SonyAggregationFlags || ''); + profile.DirectPlayProfiles = profile.DirectPlayProfiles || []; + profile.TranscodingProfiles = profile.TranscodingProfiles || []; + profile.ContainerProfiles = profile.ContainerProfiles || []; + profile.CodecProfiles = profile.CodecProfiles || []; + profile.ResponseProfiles = profile.ResponseProfiles || []; + const usersHtml = '' + users.map(function (u) { + return ''; + }).join(''); + $('#selectUser', page).html(usersHtml).val(profile.UserId || ''); + renderSubProfiles(page, profile); +} + +function renderIdentificationHeaders(page, headers) { + let index = 0; + const html = '
' + headers.map(function (h) { + let li = '
'; + li += ''; + li += '
'; + li += '

' + escapeHtml(h.Name + ': ' + (h.Value || '')) + '

'; + li += '
' + escapeHtml(h.Match || '') + '
'; + li += '
'; + li += ''; + li += '
'; + index++; + return li; + }).join('') + '
'; + const elem = $('.httpHeaderIdentificationList', page).html(html).trigger('create'); + $('.btnDeleteIdentificationHeader', elem).on('click', function () { + const itemIndex = parseInt(this.getAttribute('data-index'), 10); + currentProfile.Identification.Headers.splice(itemIndex, 1); + renderIdentificationHeaders(page, currentProfile.Identification.Headers); + }); +} + +function openPopup(elem) { + elem.classList.remove('hide'); +} + +function closePopup(elem) { + elem.classList.add('hide'); +} + +function editIdentificationHeader(page, header) { + isSubProfileNew = header == null; + header = header || {}; + currentSubProfile = header; + const popup = $('#identificationHeaderPopup', page); + $('#txtIdentificationHeaderName', popup).val(header.Name || ''); + $('#txtIdentificationHeaderValue', popup).val(header.Value || ''); + $('#selectMatchType', popup).val(header.Match || 'Equals'); + openPopup(popup[0]); +} + +function saveIdentificationHeader(page) { + currentSubProfile.Name = $('#txtIdentificationHeaderName', page).val(); + currentSubProfile.Value = $('#txtIdentificationHeaderValue', page).val(); + currentSubProfile.Match = $('#selectMatchType', page).val(); + + if (isSubProfileNew) { + currentProfile.Identification = currentProfile.Identification || {}; + currentProfile.Identification.Headers = currentProfile.Identification.Headers || []; + currentProfile.Identification.Headers.push(currentSubProfile); + } + + renderIdentificationHeaders(page, currentProfile.Identification.Headers); + currentSubProfile = null; + closePopup($('#identificationHeaderPopup', page)[0]); +} + +function renderXmlDocumentAttributes(page, attribute) { + const html = '
' + attribute.map(function (h) { + let li = '
'; + li += ''; + li += '
'; + li += '

' + escapeHtml(h.Name + ' = ' + (h.Value || '')) + '

'; + li += '
'; + li += ''; + li += '
'; + return li; + }).join('') + '
'; + const elem = $('.xmlDocumentAttributeList', page).html(html).trigger('create'); + $('.btnDeleteXmlAttribute', elem).on('click', function () { + const itemIndex = parseInt(this.getAttribute('data-index'), 10); + currentProfile.XmlRootAttributes.splice(itemIndex, 1); + renderXmlDocumentAttributes(page, currentProfile.XmlRootAttributes); + }); +} + +function editXmlDocumentAttribute(page, attribute) { + isSubProfileNew = attribute == null; + attribute = attribute || {}; + currentSubProfile = attribute; + const popup = $('#xmlAttributePopup', page); + $('#txtXmlAttributeName', popup).val(attribute.Name || ''); + $('#txtXmlAttributeValue', popup).val(attribute.Value || ''); + openPopup(popup[0]); +} + +function saveXmlDocumentAttribute(page) { + currentSubProfile.Name = $('#txtXmlAttributeName', page).val(); + currentSubProfile.Value = $('#txtXmlAttributeValue', page).val(); + + if (isSubProfileNew) { + currentProfile.XmlRootAttributes.push(currentSubProfile); + } + + renderXmlDocumentAttributes(page, currentProfile.XmlRootAttributes); + currentSubProfile = null; + closePopup($('#xmlAttributePopup', page)[0]); +} + +function renderSubtitleProfiles(page, profiles) { + let index = 0; + const html = '
' + profiles.map(function (h) { + let li = '
'; + li += ''; + li += '
'; + li += '

' + escapeHtml(h.Format || '') + '

'; + li += '
'; + li += ''; + li += '
'; + index++; + return li; + }).join('') + '
'; + const elem = $('.subtitleProfileList', page).html(html).trigger('create'); + $('.btnDeleteProfile', elem).on('click', function () { + const itemIndex = parseInt(this.getAttribute('data-index'), 10); + currentProfile.SubtitleProfiles.splice(itemIndex, 1); + renderSubtitleProfiles(page, currentProfile.SubtitleProfiles); + }); + $('.lnkEditSubProfile', elem).on('click', function () { + const itemIndex = parseInt(this.getAttribute('data-index'), 10); + editSubtitleProfile(page, currentProfile.SubtitleProfiles[itemIndex]); + }); +} + +function editSubtitleProfile(page, profile) { + isSubProfileNew = profile == null; + profile = profile || {}; + currentSubProfile = profile; + const popup = $('#subtitleProfilePopup', page); + $('#txtSubtitleProfileFormat', popup).val(profile.Format || ''); + $('#selectSubtitleProfileMethod', popup).val(profile.Method || ''); + $('#selectSubtitleProfileDidlMode', popup).val(profile.DidlMode || ''); + openPopup(popup[0]); +} + +function saveSubtitleProfile(page) { + currentSubProfile.Format = $('#txtSubtitleProfileFormat', page).val(); + currentSubProfile.Method = $('#selectSubtitleProfileMethod', page).val(); + currentSubProfile.DidlMode = $('#selectSubtitleProfileDidlMode', page).val(); + + if (isSubProfileNew) { + currentProfile.SubtitleProfiles.push(currentSubProfile); + } + + renderSubtitleProfiles(page, currentProfile.SubtitleProfiles); + currentSubProfile = null; + closePopup($('#subtitleProfilePopup', page)[0]); +} + +function renderSubProfiles(page, profile) { + renderDirectPlayProfiles(page, profile.DirectPlayProfiles); + renderTranscodingProfiles(page, profile.TranscodingProfiles); + renderContainerProfiles(page, profile.ContainerProfiles); + renderCodecProfiles(page, profile.CodecProfiles); + renderResponseProfiles(page, profile.ResponseProfiles); +} + +function saveDirectPlayProfile(page) { + currentSubProfile.Type = $('#selectDirectPlayProfileType', page).val(); + currentSubProfile.Container = $('#txtDirectPlayContainer', page).val(); + currentSubProfile.AudioCodec = $('#txtDirectPlayAudioCodec', page).val(); + currentSubProfile.VideoCodec = $('#txtDirectPlayVideoCodec', page).val(); + + if (isSubProfileNew) { + currentProfile.DirectPlayProfiles.push(currentSubProfile); + } + + renderSubProfiles(page, currentProfile); + currentSubProfile = null; + closePopup($('#popupEditDirectPlayProfile', page)[0]); +} + +function renderDirectPlayProfiles(page, profiles) { + let html = ''; + html += ''; + const elem = $('.directPlayProfiles', page).html(html).trigger('create'); + $('.btnDeleteProfile', elem).on('click', function () { + const index = this.getAttribute('data-profileindex'); + deleteDirectPlayProfile(page, index); + }); + $('.lnkEditSubProfile', elem).on('click', function () { + const index = parseInt(this.getAttribute('data-profileindex'), 10); + editDirectPlayProfile(page, currentProfile.DirectPlayProfiles[index]); + }); +} + +function deleteDirectPlayProfile(page, index) { + currentProfile.DirectPlayProfiles.splice(index, 1); + renderDirectPlayProfiles(page, currentProfile.DirectPlayProfiles); +} + +function editDirectPlayProfile(page, directPlayProfile) { + isSubProfileNew = directPlayProfile == null; + directPlayProfile = directPlayProfile || {}; + currentSubProfile = directPlayProfile; + const popup = $('#popupEditDirectPlayProfile', page); + $('#selectDirectPlayProfileType', popup).val(directPlayProfile.Type || 'Video').trigger('change'); + $('#txtDirectPlayContainer', popup).val(directPlayProfile.Container || ''); + $('#txtDirectPlayAudioCodec', popup).val(directPlayProfile.AudioCodec || ''); + $('#txtDirectPlayVideoCodec', popup).val(directPlayProfile.VideoCodec || ''); + openPopup(popup[0]); +} + +function renderTranscodingProfiles(page, profiles) { + let html = ''; + html += ''; + const elem = $('.transcodingProfiles', page).html(html).trigger('create'); + $('.btnDeleteProfile', elem).on('click', function () { + const index = this.getAttribute('data-profileindex'); + deleteTranscodingProfile(page, index); + }); + $('.lnkEditSubProfile', elem).on('click', function () { + const index = parseInt(this.getAttribute('data-profileindex'), 10); + editTranscodingProfile(page, currentProfile.TranscodingProfiles[index]); + }); +} + +function editTranscodingProfile(page, transcodingProfile) { + isSubProfileNew = transcodingProfile == null; + transcodingProfile = transcodingProfile || {}; + currentSubProfile = transcodingProfile; + const popup = $('#transcodingProfilePopup', page); + $('#selectTranscodingProfileType', popup).val(transcodingProfile.Type || 'Video').trigger('change'); + $('#txtTranscodingContainer', popup).val(transcodingProfile.Container || ''); + $('#txtTranscodingAudioCodec', popup).val(transcodingProfile.AudioCodec || ''); + $('#txtTranscodingVideoCodec', popup).val(transcodingProfile.VideoCodec || ''); + $('#selectTranscodingProtocol', popup).val(transcodingProfile.Protocol || 'Http'); + $('#chkEnableMpegtsM2TsMode', popup).prop('checked', transcodingProfile.EnableMpegtsM2TsMode || false); + $('#chkEstimateContentLength', popup).prop('checked', transcodingProfile.EstimateContentLength || false); + $('#chkReportByteRangeRequests', popup).prop('checked', transcodingProfile.TranscodeSeekInfo == 'Bytes'); + $('.radioTabButton:first', popup).trigger('click'); + openPopup(popup[0]); +} + +function deleteTranscodingProfile(page, index) { + currentProfile.TranscodingProfiles.splice(index, 1); + renderTranscodingProfiles(page, currentProfile.TranscodingProfiles); +} + +function saveTranscodingProfile(page) { + currentSubProfile.Type = $('#selectTranscodingProfileType', page).val(); + currentSubProfile.Container = $('#txtTranscodingContainer', page).val(); + currentSubProfile.AudioCodec = $('#txtTranscodingAudioCodec', page).val(); + currentSubProfile.VideoCodec = $('#txtTranscodingVideoCodec', page).val(); + currentSubProfile.Protocol = $('#selectTranscodingProtocol', page).val(); + currentSubProfile.Context = 'Streaming'; + currentSubProfile.EnableMpegtsM2TsMode = $('#chkEnableMpegtsM2TsMode', page).is(':checked'); + currentSubProfile.EstimateContentLength = $('#chkEstimateContentLength', page).is(':checked'); + currentSubProfile.TranscodeSeekInfo = $('#chkReportByteRangeRequests', page).is(':checked') ? 'Bytes' : 'Auto'; + + if (isSubProfileNew) { + currentProfile.TranscodingProfiles.push(currentSubProfile); + } + + renderSubProfiles(page, currentProfile); + currentSubProfile = null; + closePopup($('#transcodingProfilePopup', page)[0]); +} + +function renderContainerProfiles(page, profiles) { + let html = ''; + html += ''; + const elem = $('.containerProfiles', page).html(html).trigger('create'); + $('.btnDeleteProfile', elem).on('click', function () { + const index = this.getAttribute('data-profileindex'); + deleteContainerProfile(page, index); + }); + $('.lnkEditSubProfile', elem).on('click', function () { + const index = parseInt(this.getAttribute('data-profileindex'), 10); + editContainerProfile(page, currentProfile.ContainerProfiles[index]); + }); +} + +function deleteContainerProfile(page, index) { + currentProfile.ContainerProfiles.splice(index, 1); + renderContainerProfiles(page, currentProfile.ContainerProfiles); +} + +function editContainerProfile(page, containerProfile) { + isSubProfileNew = containerProfile == null; + containerProfile = containerProfile || {}; + currentSubProfile = containerProfile; + const popup = $('#containerProfilePopup', page); + $('#selectContainerProfileType', popup).val(containerProfile.Type || 'Video').trigger('change'); + $('#txtContainerProfileContainer', popup).val(containerProfile.Container || ''); + $('.radioTabButton:first', popup).trigger('click'); + openPopup(popup[0]); +} + +function saveContainerProfile(page) { + currentSubProfile.Type = $('#selectContainerProfileType', page).val(); + currentSubProfile.Container = $('#txtContainerProfileContainer', page).val(); + + if (isSubProfileNew) { + currentProfile.ContainerProfiles.push(currentSubProfile); + } + + renderSubProfiles(page, currentProfile); + currentSubProfile = null; + closePopup($('#containerProfilePopup', page)[0]); +} + +function renderCodecProfiles(page, profiles) { + let html = ''; + html += ''; + const elem = $('.codecProfiles', page).html(html).trigger('create'); + $('.btnDeleteProfile', elem).on('click', function () { + const index = this.getAttribute('data-profileindex'); + deleteCodecProfile(page, index); + }); + $('.lnkEditSubProfile', elem).on('click', function () { + const index = parseInt(this.getAttribute('data-profileindex'), 10); + editCodecProfile(page, currentProfile.CodecProfiles[index]); + }); +} + +function deleteCodecProfile(page, index) { + currentProfile.CodecProfiles.splice(index, 1); + renderCodecProfiles(page, currentProfile.CodecProfiles); +} + +function editCodecProfile(page, codecProfile) { + isSubProfileNew = codecProfile == null; + codecProfile = codecProfile || {}; + currentSubProfile = codecProfile; + const popup = $('#codecProfilePopup', page); + $('#selectCodecProfileType', popup).val(codecProfile.Type || 'Video').trigger('change'); + $('#txtCodecProfileCodec', popup).val(codecProfile.Codec || ''); + $('.radioTabButton:first', popup).trigger('click'); + openPopup(popup[0]); +} + +function saveCodecProfile(page) { + currentSubProfile.Type = $('#selectCodecProfileType', page).val(); + currentSubProfile.Codec = $('#txtCodecProfileCodec', page).val(); + + if (isSubProfileNew) { + currentProfile.CodecProfiles.push(currentSubProfile); + } + + renderSubProfiles(page, currentProfile); + currentSubProfile = null; + closePopup($('#codecProfilePopup', page)[0]); +} + +function renderResponseProfiles(page, profiles) { + let html = ''; + html += ''; + const elem = $('.mediaProfiles', page).html(html).trigger('create'); + $('.btnDeleteProfile', elem).on('click', function () { + const index = this.getAttribute('data-profileindex'); + deleteResponseProfile(page, index); + }); + $('.lnkEditSubProfile', elem).on('click', function () { + const index = parseInt(this.getAttribute('data-profileindex'), 10); + editResponseProfile(page, currentProfile.ResponseProfiles[index]); + }); +} + +function deleteResponseProfile(page, index) { + currentProfile.ResponseProfiles.splice(index, 1); + renderResponseProfiles(page, currentProfile.ResponseProfiles); +} + +function editResponseProfile(page, responseProfile) { + isSubProfileNew = responseProfile == null; + responseProfile = responseProfile || {}; + currentSubProfile = responseProfile; + const popup = $('#responseProfilePopup', page); + $('#selectResponseProfileType', popup).val(responseProfile.Type || 'Video').trigger('change'); + $('#txtResponseProfileContainer', popup).val(responseProfile.Container || ''); + $('#txtResponseProfileAudioCodec', popup).val(responseProfile.AudioCodec || ''); + $('#txtResponseProfileVideoCodec', popup).val(responseProfile.VideoCodec || ''); + $('.radioTabButton:first', popup).trigger('click'); + openPopup(popup[0]); +} + +function saveResponseProfile(page) { + currentSubProfile.Type = $('#selectResponseProfileType', page).val(); + currentSubProfile.Container = $('#txtResponseProfileContainer', page).val(); + currentSubProfile.AudioCodec = $('#txtResponseProfileAudioCodec', page).val(); + currentSubProfile.VideoCodec = $('#txtResponseProfileVideoCodec', page).val(); + + if (isSubProfileNew) { + currentProfile.ResponseProfiles.push(currentSubProfile); + } + + renderSubProfiles(page, currentProfile); + currentSubProfile = null; + closePopup($('#responseProfilePopup', page)[0]); +} + +function saveProfile(page, profile) { + updateProfile(page, profile); + const id = getParameterByName('id'); + + if (id) { + ApiClient.ajax({ + type: 'POST', + url: ApiClient.getUrl('Dlna/Profiles/' + id), + data: JSON.stringify(profile), + contentType: 'application/json' + }).then(function () { + toast(globalize.translate('SettingsSaved')); + }, Dashboard.processErrorResponse); + } else { + ApiClient.ajax({ + type: 'POST', + url: ApiClient.getUrl('Dlna/Profiles'), + data: JSON.stringify(profile), + contentType: 'application/json' + }).then(function () { + Dashboard.navigate('dlnaprofiles.html'); + }, Dashboard.processErrorResponse); + } + + loading.hide(); +} + +function updateProfile(page, profile) { + profile.Name = $('#txtName', page).val(); + profile.EnableAlbumArtInDidl = $('#chkEnableAlbumArtInDidl', page).is(':checked'); + profile.EnableSingleAlbumArtLimit = $('#chkEnableSingleImageLimit', page).is(':checked'); + profile.SupportedMediaTypes = $('.chkMediaType:checked', page).get().map(function (c) { + return c.getAttribute('data-value'); + }).join(','); + profile.Identification = profile.Identification || {}; + profile.FriendlyName = $('#txtInfoFriendlyName', page).val(); + profile.ModelName = $('#txtInfoModelName', page).val(); + profile.ModelNumber = $('#txtInfoModelNumber', page).val(); + profile.ModelDescription = $('#txtInfoModelDescription', page).val(); + profile.ModelUrl = $('#txtInfoModelUrl', page).val(); + profile.Manufacturer = $('#txtInfoManufacturer', page).val(); + profile.ManufacturerUrl = $('#txtInfoManufacturerUrl', page).val(); + profile.SerialNumber = $('#txtInfoSerialNumber', page).val(); + profile.Identification.FriendlyName = $('#txtIdFriendlyName', page).val(); + profile.Identification.ModelName = $('#txtIdModelName', page).val(); + profile.Identification.ModelNumber = $('#txtIdModelNumber', page).val(); + profile.Identification.ModelDescription = $('#txtIdModelDescription', page).val(); + profile.Identification.ModelUrl = $('#txtIdModelUrl', page).val(); + profile.Identification.Manufacturer = $('#txtIdManufacturer', page).val(); + profile.Identification.ManufacturerUrl = $('#txtIdManufacturerUrl', page).val(); + profile.Identification.SerialNumber = $('#txtIdSerialNumber', page).val(); + profile.Identification.DeviceDescription = $('#txtIdDeviceDescription', page).val(); + profile.AlbumArtPn = $('#txtAlbumArtPn', page).val(); + profile.MaxAlbumArtWidth = $('#txtAlbumArtMaxWidth', page).val(); + profile.MaxAlbumArtHeight = $('#txtAlbumArtMaxHeight', page).val(); + profile.MaxIconWidth = $('#txtIconMaxWidth', page).val(); + profile.MaxIconHeight = $('#txtIconMaxHeight', page).val(); + profile.RequiresPlainFolders = $('#chkRequiresPlainFolders', page).is(':checked'); + profile.RequiresPlainVideoItems = $('#chkRequiresPlainVideoItems', page).is(':checked'); + profile.IgnoreTranscodeByteRangeRequests = $('#chkIgnoreTranscodeByteRangeRequests', page).is(':checked'); + profile.MaxStreamingBitrate = $('#txtMaxAllowedBitrate', page).val(); + profile.MusicStreamingTranscodingBitrate = $('#txtMusicStreamingTranscodingBitrate', page).val(); + profile.ProtocolInfo = $('#txtProtocolInfo', page).val(); + profile.XDlnaCap = $('#txtXDlnaCap', page).val(); + profile.XDlnaDoc = $('#txtXDlnaDoc', page).val(); + profile.SonyAggregationFlags = $('#txtSonyAggregationFlags', page).val(); + profile.UserId = $('#selectUser', page).val(); +} + +let currentProfile; +let currentSubProfile; +let isSubProfileNew; +const allText = globalize.translate('All'); + +$(document).on('pageinit', '#dlnaProfilePage', function () { + const page = this; + $('.radioTabButton', page).on('click', function () { + $(this).siblings().removeClass('ui-btn-active'); + $(this).addClass('ui-btn-active'); + const value = this.tagName == 'A' ? this.getAttribute('data-value') : this.value; + const elem = $('.' + value, page); + elem.siblings('.tabContent').hide(); + elem.show(); + }); + $('#selectDirectPlayProfileType', page).on('change', function () { + if (this.value == 'Video') { + $('#fldDirectPlayVideoCodec', page).show(); + } else { + $('#fldDirectPlayVideoCodec', page).hide(); + } + + if (this.value == 'Photo') { + $('#fldDirectPlayAudioCodec', page).hide(); + } else { + $('#fldDirectPlayAudioCodec', page).show(); + } + }); + $('#selectTranscodingProfileType', page).on('change', function () { + if (this.value == 'Video') { + $('#fldTranscodingVideoCodec', page).show(); + $('#fldTranscodingProtocol', page).show(); + $('#fldEnableMpegtsM2TsMode', page).show(); + } else { + $('#fldTranscodingVideoCodec', page).hide(); + $('#fldTranscodingProtocol', page).hide(); + $('#fldEnableMpegtsM2TsMode', page).hide(); + } + + if (this.value == 'Photo') { + $('#fldTranscodingAudioCodec', page).hide(); + $('#fldEstimateContentLength', page).hide(); + $('#fldReportByteRangeRequests', page).hide(); + } else { + $('#fldTranscodingAudioCodec', page).show(); + $('#fldEstimateContentLength', page).show(); + $('#fldReportByteRangeRequests', page).show(); + } + }); + $('#selectResponseProfileType', page).on('change', function () { + if (this.value == 'Video') { + $('#fldResponseProfileVideoCodec', page).show(); + } else { + $('#fldResponseProfileVideoCodec', page).hide(); + } + + if (this.value == 'Photo') { + $('#fldResponseProfileAudioCodec', page).hide(); + } else { + $('#fldResponseProfileAudioCodec', page).show(); + } + }); + $('.btnAddDirectPlayProfile', page).on('click', function () { + editDirectPlayProfile(page); + }); + $('.btnAddTranscodingProfile', page).on('click', function () { + editTranscodingProfile(page); + }); + $('.btnAddContainerProfile', page).on('click', function () { + editContainerProfile(page); + }); + $('.btnAddCodecProfile', page).on('click', function () { + editCodecProfile(page); + }); + $('.btnAddResponseProfile', page).on('click', function () { + editResponseProfile(page); + }); + $('.btnAddIdentificationHttpHeader', page).on('click', function () { + editIdentificationHeader(page); + }); + $('.btnAddXmlDocumentAttribute', page).on('click', function () { + editXmlDocumentAttribute(page); + }); + $('.btnAddSubtitleProfile', page).on('click', function () { + editSubtitleProfile(page); + }); + $('.dlnaProfileForm').off('submit', DlnaProfilePage.onSubmit).on('submit', DlnaProfilePage.onSubmit); + $('.editDirectPlayProfileForm').off('submit', DlnaProfilePage.onDirectPlayFormSubmit).on('submit', DlnaProfilePage.onDirectPlayFormSubmit); + $('.transcodingProfileForm').off('submit', DlnaProfilePage.onTranscodingProfileFormSubmit).on('submit', DlnaProfilePage.onTranscodingProfileFormSubmit); + $('.containerProfileForm').off('submit', DlnaProfilePage.onContainerProfileFormSubmit).on('submit', DlnaProfilePage.onContainerProfileFormSubmit); + $('.codecProfileForm').off('submit', DlnaProfilePage.onCodecProfileFormSubmit).on('submit', DlnaProfilePage.onCodecProfileFormSubmit); + $('.editResponseProfileForm').off('submit', DlnaProfilePage.onResponseProfileFormSubmit).on('submit', DlnaProfilePage.onResponseProfileFormSubmit); + $('.identificationHeaderForm').off('submit', DlnaProfilePage.onIdentificationHeaderFormSubmit).on('submit', DlnaProfilePage.onIdentificationHeaderFormSubmit); + $('.xmlAttributeForm').off('submit', DlnaProfilePage.onXmlAttributeFormSubmit).on('submit', DlnaProfilePage.onXmlAttributeFormSubmit); + $('.subtitleProfileForm').off('submit', DlnaProfilePage.onSubtitleProfileFormSubmit).on('submit', DlnaProfilePage.onSubtitleProfileFormSubmit); +}).on('pageshow', '#dlnaProfilePage', function () { + const page = this; + $('#radioInfo', page).trigger('click'); + loadProfile(page); +}); +window.DlnaProfilePage = { + onSubmit: function () { + loading.show(); + saveProfile($(this).parents('.page'), currentProfile); + return false; + }, + onDirectPlayFormSubmit: function () { + saveDirectPlayProfile($(this).parents('.page')); + return false; + }, + onTranscodingProfileFormSubmit: function () { + saveTranscodingProfile($(this).parents('.page')); + return false; + }, + onContainerProfileFormSubmit: function () { + saveContainerProfile($(this).parents('.page')); + return false; + }, + onCodecProfileFormSubmit: function () { + saveCodecProfile($(this).parents('.page')); + return false; + }, + onResponseProfileFormSubmit: function () { + saveResponseProfile($(this).parents('.page')); + return false; + }, + onIdentificationHeaderFormSubmit: function () { + saveIdentificationHeader($(this).parents('.page')); + return false; + }, + onXmlAttributeFormSubmit: function () { + saveXmlDocumentAttribute($(this).parents('.page')); + return false; + }, + onSubtitleProfileFormSubmit: function () { + saveSubtitleProfile($(this).parents('.page')); + return false; + } +}; -/* eslint-enable indent */ diff --git a/src/controllers/dashboard/dlna/profiles.js b/src/controllers/dashboard/dlna/profiles.js index 9a79883e9e..a7c8f045c4 100644 --- a/src/controllers/dashboard/dlna/profiles.js +++ b/src/controllers/dashboard/dlna/profiles.js @@ -7,90 +7,87 @@ import '../../../components/listview/listview.scss'; import '../../../elements/emby-button/emby-button'; import confirm from '../../../components/confirm/confirm'; -/* eslint-disable indent */ - - function loadProfiles(page) { - loading.show(); - ApiClient.getJSON(ApiClient.getUrl('Dlna/ProfileInfos')).then(function (result) { - renderUserProfiles(page, result); - renderSystemProfiles(page, result); - loading.hide(); - }); - } - - function renderUserProfiles(page, profiles) { - renderProfiles(page, page.querySelector('.customProfiles'), profiles.filter(function (p) { - return p.Type == 'User'; - })); - } - - function renderSystemProfiles(page, profiles) { - renderProfiles(page, page.querySelector('.systemProfiles'), profiles.filter(function (p) { - return p.Type == 'System'; - })); - } - - function renderProfiles(page, element, profiles) { - let html = ''; - - if (profiles.length) { - html += '
'; - } - - for (let i = 0, length = profiles.length; i < length; i++) { - const profile = profiles[i]; - html += '
'; - html += ''; - html += ''; - - if (profile.Type == 'User') { - html += ''; - } - - html += '
'; - } - - if (profiles.length) { - html += '
'; - } - - element.innerHTML = html; - $('.btnDeleteProfile', element).on('click', function () { - const id = this.getAttribute('data-profileid'); - deleteProfile(page, id); - }); - } - - function deleteProfile(page, id) { - confirm(globalize.translate('MessageConfirmProfileDeletion'), globalize.translate('HeaderConfirmProfileDeletion')).then(function () { - loading.show(); - ApiClient.ajax({ - type: 'DELETE', - url: ApiClient.getUrl('Dlna/Profiles/' + id) - }).then(function () { - loading.hide(); - loadProfiles(page); - }); - }); - } - - function getTabs() { - return [{ - href: '#/dlnasettings.html', - name: globalize.translate('Settings') - }, { - href: '#/dlnaprofiles.html', - name: globalize.translate('TabProfiles') - }]; - } - - $(document).on('pageshow', '#dlnaProfilesPage', function () { - libraryMenu.setTabs('dlna', 1, getTabs); - loadProfiles(this); +function loadProfiles(page) { + loading.show(); + ApiClient.getJSON(ApiClient.getUrl('Dlna/ProfileInfos')).then(function (result) { + renderUserProfiles(page, result); + renderSystemProfiles(page, result); + loading.hide(); }); +} + +function renderUserProfiles(page, profiles) { + renderProfiles(page, page.querySelector('.customProfiles'), profiles.filter(function (p) { + return p.Type == 'User'; + })); +} + +function renderSystemProfiles(page, profiles) { + renderProfiles(page, page.querySelector('.systemProfiles'), profiles.filter(function (p) { + return p.Type == 'System'; + })); +} + +function renderProfiles(page, element, profiles) { + let html = ''; + + if (profiles.length) { + html += '
'; + } + + for (let i = 0, length = profiles.length; i < length; i++) { + const profile = profiles[i]; + html += '
'; + html += ''; + html += ''; + + if (profile.Type == 'User') { + html += ''; + } + + html += '
'; + } + + if (profiles.length) { + html += '
'; + } + + element.innerHTML = html; + $('.btnDeleteProfile', element).on('click', function () { + const id = this.getAttribute('data-profileid'); + deleteProfile(page, id); + }); +} + +function deleteProfile(page, id) { + confirm(globalize.translate('MessageConfirmProfileDeletion'), globalize.translate('HeaderConfirmProfileDeletion')).then(function () { + loading.show(); + ApiClient.ajax({ + type: 'DELETE', + url: ApiClient.getUrl('Dlna/Profiles/' + id) + }).then(function () { + loading.hide(); + loadProfiles(page); + }); + }); +} + +function getTabs() { + return [{ + href: '#/dlnasettings.html', + name: globalize.translate('Settings') + }, { + href: '#/dlnaprofiles.html', + name: globalize.translate('TabProfiles') + }]; +} + +$(document).on('pageshow', '#dlnaProfilesPage', function () { + libraryMenu.setTabs('dlna', 1, getTabs); + loadProfiles(this); +}); -/* eslint-enable indent */ diff --git a/src/controllers/dashboard/dlna/settings.js b/src/controllers/dashboard/dlna/settings.js index 9988d7d610..fcc7d7d100 100644 --- a/src/controllers/dashboard/dlna/settings.js +++ b/src/controllers/dashboard/dlna/settings.js @@ -5,59 +5,56 @@ import libraryMenu from '../../../scripts/libraryMenu'; import globalize from '../../../scripts/globalize'; import Dashboard from '../../../utils/dashboard'; -/* eslint-disable indent */ +function loadPage(page, config, users) { + page.querySelector('#chkEnablePlayTo').checked = config.EnablePlayTo; + page.querySelector('#chkEnableDlnaDebugLogging').checked = config.EnableDebugLog; + $('#txtClientDiscoveryInterval', page).val(config.ClientDiscoveryIntervalSeconds); + $('#chkEnableServer', page).prop('checked', config.EnableServer); + $('#chkBlastAliveMessages', page).prop('checked', config.BlastAliveMessages); + $('#txtBlastInterval', page).val(config.BlastAliveMessageIntervalSeconds); + const usersHtml = users.map(function (u) { + return ''; + }).join(''); + $('#selectUser', page).html(usersHtml).val(config.DefaultUserId || ''); + loading.hide(); +} - function loadPage(page, config, users) { - page.querySelector('#chkEnablePlayTo').checked = config.EnablePlayTo; - page.querySelector('#chkEnableDlnaDebugLogging').checked = config.EnableDebugLog; - $('#txtClientDiscoveryInterval', page).val(config.ClientDiscoveryIntervalSeconds); - $('#chkEnableServer', page).prop('checked', config.EnableServer); - $('#chkBlastAliveMessages', page).prop('checked', config.BlastAliveMessages); - $('#txtBlastInterval', page).val(config.BlastAliveMessageIntervalSeconds); - const usersHtml = users.map(function (u) { - return ''; - }).join(''); - $('#selectUser', page).html(usersHtml).val(config.DefaultUserId || ''); - loading.hide(); - } - - function onSubmit() { - loading.show(); - const form = this; - ApiClient.getNamedConfiguration('dlna').then(function (config) { - config.EnablePlayTo = form.querySelector('#chkEnablePlayTo').checked; - config.EnableDebugLog = form.querySelector('#chkEnableDlnaDebugLogging').checked; - config.ClientDiscoveryIntervalSeconds = $('#txtClientDiscoveryInterval', form).val(); - config.EnableServer = $('#chkEnableServer', form).is(':checked'); - config.BlastAliveMessages = $('#chkBlastAliveMessages', form).is(':checked'); - config.BlastAliveMessageIntervalSeconds = $('#txtBlastInterval', form).val(); - config.DefaultUserId = $('#selectUser', form).val(); - ApiClient.updateNamedConfiguration('dlna', config).then(Dashboard.processServerConfigurationUpdateResult); - }); - return false; - } - - function getTabs() { - return [{ - href: '#/dlnasettings.html', - name: globalize.translate('Settings') - }, { - href: '#/dlnaprofiles.html', - name: globalize.translate('TabProfiles') - }]; - } - - $(document).on('pageinit', '#dlnaSettingsPage', function () { - $('.dlnaSettingsForm').off('submit', onSubmit).on('submit', onSubmit); - }).on('pageshow', '#dlnaSettingsPage', function () { - libraryMenu.setTabs('dlna', 0, getTabs); - loading.show(); - const page = this; - const promise1 = ApiClient.getNamedConfiguration('dlna'); - const promise2 = ApiClient.getUsers(); - Promise.all([promise1, promise2]).then(function (responses) { - loadPage(page, responses[0], responses[1]); - }); +function onSubmit() { + loading.show(); + const form = this; + ApiClient.getNamedConfiguration('dlna').then(function (config) { + config.EnablePlayTo = form.querySelector('#chkEnablePlayTo').checked; + config.EnableDebugLog = form.querySelector('#chkEnableDlnaDebugLogging').checked; + config.ClientDiscoveryIntervalSeconds = $('#txtClientDiscoveryInterval', form).val(); + config.EnableServer = $('#chkEnableServer', form).is(':checked'); + config.BlastAliveMessages = $('#chkBlastAliveMessages', form).is(':checked'); + config.BlastAliveMessageIntervalSeconds = $('#txtBlastInterval', form).val(); + config.DefaultUserId = $('#selectUser', form).val(); + ApiClient.updateNamedConfiguration('dlna', config).then(Dashboard.processServerConfigurationUpdateResult); }); + return false; +} + +function getTabs() { + return [{ + href: '#/dlnasettings.html', + name: globalize.translate('Settings') + }, { + href: '#/dlnaprofiles.html', + name: globalize.translate('TabProfiles') + }]; +} + +$(document).on('pageinit', '#dlnaSettingsPage', function () { + $('.dlnaSettingsForm').off('submit', onSubmit).on('submit', onSubmit); +}).on('pageshow', '#dlnaSettingsPage', function () { + libraryMenu.setTabs('dlna', 0, getTabs); + loading.show(); + const page = this; + const promise1 = ApiClient.getNamedConfiguration('dlna'); + const promise2 = ApiClient.getUsers(); + Promise.all([promise1, promise2]).then(function (responses) { + loadPage(page, responses[0], responses[1]); + }); +}); -/* eslint-enable indent */ diff --git a/src/controllers/dashboard/encodingsettings.js b/src/controllers/dashboard/encodingsettings.js index f122d6da2a..38ca954d0e 100644 --- a/src/controllers/dashboard/encodingsettings.js +++ b/src/controllers/dashboard/encodingsettings.js @@ -6,297 +6,294 @@ import libraryMenu from '../../scripts/libraryMenu'; import Dashboard from '../../utils/dashboard'; import alert from '../../components/alert'; -/* eslint-disable indent */ +function loadPage(page, config, systemInfo) { + Array.prototype.forEach.call(page.querySelectorAll('.chkDecodeCodec'), function (c) { + c.checked = (config.HardwareDecodingCodecs || []).indexOf(c.getAttribute('data-codec')) !== -1; + }); + page.querySelector('#chkDecodingColorDepth10Hevc').checked = config.EnableDecodingColorDepth10Hevc; + page.querySelector('#chkDecodingColorDepth10Vp9').checked = config.EnableDecodingColorDepth10Vp9; + page.querySelector('#chkEnhancedNvdecDecoder').checked = config.EnableEnhancedNvdecDecoder; + page.querySelector('#chkSystemNativeHwDecoder').checked = config.PreferSystemNativeHwDecoder; + page.querySelector('#chkIntelLpH264HwEncoder').checked = config.EnableIntelLowPowerH264HwEncoder; + page.querySelector('#chkIntelLpHevcHwEncoder').checked = config.EnableIntelLowPowerHevcHwEncoder; + page.querySelector('#chkHardwareEncoding').checked = config.EnableHardwareEncoding; + page.querySelector('#chkAllowHevcEncoding').checked = config.AllowHevcEncoding; + $('#selectVideoDecoder', page).val(config.HardwareAccelerationType); + $('#selectThreadCount', page).val(config.EncodingThreadCount); + page.querySelector('#chkEnableAudioVbr').checked = config.EnableAudioVbr; + $('#txtDownMixAudioBoost', page).val(config.DownMixAudioBoost); + $('#selectStereoDownmixAlgorithm').val(config.DownMixStereoAlgorithm || 'None'); + page.querySelector('#txtMaxMuxingQueueSize').value = config.MaxMuxingQueueSize || ''; + page.querySelector('.txtEncoderPath').value = config.EncoderAppPathDisplay || ''; + $('#txtTranscodingTempPath', page).val(systemInfo.TranscodingTempPath || ''); + page.querySelector('#txtFallbackFontPath').value = config.FallbackFontPath || ''; + page.querySelector('#chkEnableFallbackFont').checked = config.EnableFallbackFont; + $('#txtVaapiDevice', page).val(config.VaapiDevice || ''); + page.querySelector('#chkTonemapping').checked = config.EnableTonemapping; + page.querySelector('#chkVppTonemapping').checked = config.EnableVppTonemapping; + page.querySelector('#selectTonemappingAlgorithm').value = config.TonemappingAlgorithm; + page.querySelector('#selectTonemappingRange').value = config.TonemappingRange; + page.querySelector('#txtTonemappingDesat').value = config.TonemappingDesat; + page.querySelector('#txtTonemappingThreshold').value = config.TonemappingThreshold; + page.querySelector('#txtTonemappingPeak').value = config.TonemappingPeak; + page.querySelector('#txtTonemappingParam').value = config.TonemappingParam || ''; + page.querySelector('#txtVppTonemappingBrightness').value = config.VppTonemappingBrightness; + page.querySelector('#txtVppTonemappingContrast').value = config.VppTonemappingContrast; + page.querySelector('#selectEncoderPreset').value = config.EncoderPreset || ''; + page.querySelector('#txtH264Crf').value = config.H264Crf || ''; + page.querySelector('#txtH265Crf').value = config.H265Crf || ''; + page.querySelector('#selectDeinterlaceMethod').value = config.DeinterlaceMethod || ''; + page.querySelector('#chkDoubleRateDeinterlacing').checked = config.DeinterlaceDoubleRate; + page.querySelector('#chkEnableSubtitleExtraction').checked = config.EnableSubtitleExtraction || false; + page.querySelector('#chkEnableThrottling').checked = config.EnableThrottling || false; + page.querySelector('#selectVideoDecoder').dispatchEvent(new CustomEvent('change', { + bubbles: true + })); + loading.hide(); +} - function loadPage(page, config, systemInfo) { - Array.prototype.forEach.call(page.querySelectorAll('.chkDecodeCodec'), function (c) { - c.checked = (config.HardwareDecodingCodecs || []).indexOf(c.getAttribute('data-codec')) !== -1; - }); - page.querySelector('#chkDecodingColorDepth10Hevc').checked = config.EnableDecodingColorDepth10Hevc; - page.querySelector('#chkDecodingColorDepth10Vp9').checked = config.EnableDecodingColorDepth10Vp9; - page.querySelector('#chkEnhancedNvdecDecoder').checked = config.EnableEnhancedNvdecDecoder; - page.querySelector('#chkSystemNativeHwDecoder').checked = config.PreferSystemNativeHwDecoder; - page.querySelector('#chkIntelLpH264HwEncoder').checked = config.EnableIntelLowPowerH264HwEncoder; - page.querySelector('#chkIntelLpHevcHwEncoder').checked = config.EnableIntelLowPowerHevcHwEncoder; - page.querySelector('#chkHardwareEncoding').checked = config.EnableHardwareEncoding; - page.querySelector('#chkAllowHevcEncoding').checked = config.AllowHevcEncoding; - $('#selectVideoDecoder', page).val(config.HardwareAccelerationType); - $('#selectThreadCount', page).val(config.EncodingThreadCount); - page.querySelector('#chkEnableAudioVbr').checked = config.EnableAudioVbr; - $('#txtDownMixAudioBoost', page).val(config.DownMixAudioBoost); - $('#selectStereoDownmixAlgorithm').val(config.DownMixStereoAlgorithm || 'None'); - page.querySelector('#txtMaxMuxingQueueSize').value = config.MaxMuxingQueueSize || ''; - page.querySelector('.txtEncoderPath').value = config.EncoderAppPathDisplay || ''; - $('#txtTranscodingTempPath', page).val(systemInfo.TranscodingTempPath || ''); - page.querySelector('#txtFallbackFontPath').value = config.FallbackFontPath || ''; - page.querySelector('#chkEnableFallbackFont').checked = config.EnableFallbackFont; - $('#txtVaapiDevice', page).val(config.VaapiDevice || ''); - page.querySelector('#chkTonemapping').checked = config.EnableTonemapping; - page.querySelector('#chkVppTonemapping').checked = config.EnableVppTonemapping; - page.querySelector('#selectTonemappingAlgorithm').value = config.TonemappingAlgorithm; - page.querySelector('#selectTonemappingRange').value = config.TonemappingRange; - page.querySelector('#txtTonemappingDesat').value = config.TonemappingDesat; - page.querySelector('#txtTonemappingThreshold').value = config.TonemappingThreshold; - page.querySelector('#txtTonemappingPeak').value = config.TonemappingPeak; - page.querySelector('#txtTonemappingParam').value = config.TonemappingParam || ''; - page.querySelector('#txtVppTonemappingBrightness').value = config.VppTonemappingBrightness; - page.querySelector('#txtVppTonemappingContrast').value = config.VppTonemappingContrast; - page.querySelector('#selectEncoderPreset').value = config.EncoderPreset || ''; - page.querySelector('#txtH264Crf').value = config.H264Crf || ''; - page.querySelector('#txtH265Crf').value = config.H265Crf || ''; - page.querySelector('#selectDeinterlaceMethod').value = config.DeinterlaceMethod || ''; - page.querySelector('#chkDoubleRateDeinterlacing').checked = config.DeinterlaceDoubleRate; - page.querySelector('#chkEnableSubtitleExtraction').checked = config.EnableSubtitleExtraction || false; - page.querySelector('#chkEnableThrottling').checked = config.EnableThrottling || false; - page.querySelector('#selectVideoDecoder').dispatchEvent(new CustomEvent('change', { - bubbles: true - })); - loading.hide(); - } +function onSaveEncodingPathFailure() { + loading.hide(); + alert(globalize.translate('FFmpegSavePathNotFound')); +} - function onSaveEncodingPathFailure() { - loading.hide(); - alert(globalize.translate('FFmpegSavePathNotFound')); - } +function updateEncoder(form) { + return ApiClient.getSystemInfo().then(function () { + return ApiClient.ajax({ + url: ApiClient.getUrl('System/MediaEncoder/Path'), + type: 'POST', + data: JSON.stringify({ + Path: form.querySelector('.txtEncoderPath').value, + PathType: 'Custom' + }), + contentType: 'application/json' + }).then(Dashboard.processServerConfigurationUpdateResult, onSaveEncodingPathFailure); + }); +} - function updateEncoder(form) { - return ApiClient.getSystemInfo().then(function () { - return ApiClient.ajax({ - url: ApiClient.getUrl('System/MediaEncoder/Path'), - type: 'POST', - data: JSON.stringify({ - Path: form.querySelector('.txtEncoderPath').value, - PathType: 'Custom' - }), - contentType: 'application/json' - }).then(Dashboard.processServerConfigurationUpdateResult, onSaveEncodingPathFailure); - }); - } +function onSubmit() { + const form = this; - function onSubmit() { - const form = this; - - const onDecoderConfirmed = function () { - loading.show(); - ApiClient.getNamedConfiguration('encoding').then(function (config) { - config.EnableAudioVbr = form.querySelector('#chkEnableAudioVbr').checked; - config.DownMixAudioBoost = $('#txtDownMixAudioBoost', form).val(); - config.DownMixStereoAlgorithm = $('#selectStereoDownmixAlgorithm', form).val() || 'None'; - config.MaxMuxingQueueSize = form.querySelector('#txtMaxMuxingQueueSize').value; - config.TranscodingTempPath = $('#txtTranscodingTempPath', form).val(); - config.FallbackFontPath = form.querySelector('#txtFallbackFontPath').value; - config.EnableFallbackFont = form.querySelector('#txtFallbackFontPath').value ? form.querySelector('#chkEnableFallbackFont').checked : false; - config.EncodingThreadCount = $('#selectThreadCount', form).val(); - config.HardwareAccelerationType = $('#selectVideoDecoder', form).val(); - config.VaapiDevice = $('#txtVaapiDevice', form).val(); - config.EnableTonemapping = form.querySelector('#chkTonemapping').checked; - config.EnableVppTonemapping = form.querySelector('#chkVppTonemapping').checked; - config.TonemappingAlgorithm = form.querySelector('#selectTonemappingAlgorithm').value; - config.TonemappingRange = form.querySelector('#selectTonemappingRange').value; - config.TonemappingDesat = form.querySelector('#txtTonemappingDesat').value; - config.TonemappingThreshold = form.querySelector('#txtTonemappingThreshold').value; - config.TonemappingPeak = form.querySelector('#txtTonemappingPeak').value; - config.TonemappingParam = form.querySelector('#txtTonemappingParam').value || '0'; - config.VppTonemappingBrightness = form.querySelector('#txtVppTonemappingBrightness').value; - config.VppTonemappingContrast = form.querySelector('#txtVppTonemappingContrast').value; - config.EncoderPreset = form.querySelector('#selectEncoderPreset').value; - config.H264Crf = parseInt(form.querySelector('#txtH264Crf').value || '0', 10); - config.H265Crf = parseInt(form.querySelector('#txtH265Crf').value || '0', 10); - config.DeinterlaceMethod = form.querySelector('#selectDeinterlaceMethod').value; - config.DeinterlaceDoubleRate = form.querySelector('#chkDoubleRateDeinterlacing').checked; - config.EnableSubtitleExtraction = form.querySelector('#chkEnableSubtitleExtraction').checked; - config.EnableThrottling = form.querySelector('#chkEnableThrottling').checked; - config.HardwareDecodingCodecs = Array.prototype.map.call(Array.prototype.filter.call(form.querySelectorAll('.chkDecodeCodec'), function (c) { - return c.checked; - }), function (c) { - return c.getAttribute('data-codec'); - }); - config.EnableDecodingColorDepth10Hevc = form.querySelector('#chkDecodingColorDepth10Hevc').checked; - config.EnableDecodingColorDepth10Vp9 = form.querySelector('#chkDecodingColorDepth10Vp9').checked; - config.EnableEnhancedNvdecDecoder = form.querySelector('#chkEnhancedNvdecDecoder').checked; - config.PreferSystemNativeHwDecoder = form.querySelector('#chkSystemNativeHwDecoder').checked; - config.EnableIntelLowPowerH264HwEncoder = form.querySelector('#chkIntelLpH264HwEncoder').checked; - config.EnableIntelLowPowerHevcHwEncoder = form.querySelector('#chkIntelLpHevcHwEncoder').checked; - config.EnableHardwareEncoding = form.querySelector('#chkHardwareEncoding').checked; - config.AllowHevcEncoding = form.querySelector('#chkAllowHevcEncoding').checked; - ApiClient.updateNamedConfiguration('encoding', config).then(function () { - updateEncoder(form); - }, function () { - alert(globalize.translate('ErrorDefault')); - Dashboard.processServerConfigurationUpdateResult(); - }); - }); - }; - - if ($('#selectVideoDecoder', form).val()) { - alert({ - title: globalize.translate('TitleHardwareAcceleration'), - text: globalize.translate('HardwareAccelerationWarning') - }).then(onDecoderConfirmed); - } else { - onDecoderConfirmed(); - } - - return false; - } - - function setDecodingCodecsVisible(context, value) { - value = value || ''; - let any; - Array.prototype.forEach.call(context.querySelectorAll('.chkDecodeCodec'), function (c) { - if (c.getAttribute('data-types').split(',').indexOf(value) === -1) { - dom.parentWithTag(c, 'LABEL').classList.add('hide'); - } else { - dom.parentWithTag(c, 'LABEL').classList.remove('hide'); - any = true; - } - }); - - if (any) { - context.querySelector('.decodingCodecsList').classList.remove('hide'); - } else { - context.querySelector('.decodingCodecsList').classList.add('hide'); - } - } - - function getTabs() { - return [{ - href: '#/encodingsettings.html', - name: globalize.translate('Transcoding') - }, { - href: '#/playbackconfiguration.html', - name: globalize.translate('ButtonResume') - }, { - href: '#/streamingsettings.html', - name: globalize.translate('TabStreaming') - }]; - } - - let systemInfo; - function getSystemInfo() { - return systemInfo ? Promise.resolve(systemInfo) : ApiClient.getPublicSystemInfo().then( - info => { - systemInfo = info; - return info; - } - ); - } - - $(document).on('pageinit', '#encodingSettingsPage', function () { - const page = this; - getSystemInfo(); - page.querySelector('#selectVideoDecoder').addEventListener('change', function () { - if (this.value == 'vaapi') { - page.querySelector('.fldVaapiDevice').classList.remove('hide'); - page.querySelector('#txtVaapiDevice').setAttribute('required', 'required'); - } else { - page.querySelector('.fldVaapiDevice').classList.add('hide'); - page.querySelector('#txtVaapiDevice').removeAttribute('required'); - } - - if (this.value == 'amf' || this.value == 'nvenc' || this.value == 'qsv' || this.value == 'vaapi' || this.value == 'videotoolbox') { - page.querySelector('.fld10bitHevcVp9HwDecoding').classList.remove('hide'); - } else { - page.querySelector('.fld10bitHevcVp9HwDecoding').classList.add('hide'); - } - - if (this.value == 'amf' || this.value == 'nvenc' || this.value == 'qsv' || this.value == 'vaapi') { - page.querySelector('.tonemappingOptions').classList.remove('hide'); - } else { - page.querySelector('.tonemappingOptions').classList.add('hide'); - } - - if (this.value == 'qsv' || this.value == 'vaapi') { - page.querySelector('.fldIntelLp').classList.remove('hide'); - } else { - page.querySelector('.fldIntelLp').classList.add('hide'); - } - - if (systemInfo.OperatingSystem.toLowerCase() === 'linux' && (this.value == 'qsv' || this.value == 'vaapi')) { - page.querySelector('.vppTonemappingOptions').classList.remove('hide'); - } else { - page.querySelector('.vppTonemappingOptions').classList.add('hide'); - } - - if (this.value == 'qsv') { - page.querySelector('.fldSysNativeHwDecoder').classList.remove('hide'); - } else { - page.querySelector('.fldSysNativeHwDecoder').classList.add('hide'); - } - - if (this.value == 'nvenc') { - page.querySelector('.fldEnhancedNvdec').classList.remove('hide'); - } else { - page.querySelector('.fldEnhancedNvdec').classList.add('hide'); - } - - if (this.value) { - page.querySelector('.hardwareAccelerationOptions').classList.remove('hide'); - } else { - page.querySelector('.hardwareAccelerationOptions').classList.add('hide'); - } - - setDecodingCodecsVisible(page, this.value); - }); - $('#btnSelectEncoderPath', page).on('click.selectDirectory', function () { - import('../../components/directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => { - const picker = new DirectoryBrowser(); - picker.show({ - includeFiles: true, - callback: function (path) { - if (path) { - $('.txtEncoderPath', page).val(path); - } - - picker.close(); - } - }); - }); - }); - $('#btnSelectTranscodingTempPath', page).on('click.selectDirectory', function () { - import('../../components/directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => { - const picker = new DirectoryBrowser(); - picker.show({ - callback: function (path) { - if (path) { - $('#txtTranscodingTempPath', page).val(path); - } - - picker.close(); - }, - validateWriteable: true, - header: globalize.translate('HeaderSelectTranscodingPath'), - instruction: globalize.translate('HeaderSelectTranscodingPathHelp') - }); - }); - }); - $('#btnSelectFallbackFontPath', page).on('click.selectDirectory', function () { - import('../../components/directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => { - const picker = new DirectoryBrowser(); - picker.show({ - includeDirectories: true, - callback: function (path) { - if (path) { - page.querySelector('#txtFallbackFontPath').value = path; - } - - picker.close(); - }, - header: globalize.translate('HeaderSelectFallbackFontPath'), - instruction: globalize.translate('HeaderSelectFallbackFontPathHelp') - }); - }); - }); - $('.encodingSettingsForm').off('submit', onSubmit).on('submit', onSubmit); - }).on('pageshow', '#encodingSettingsPage', function () { + const onDecoderConfirmed = function () { loading.show(); - libraryMenu.setTabs('playback', 0, getTabs); - const page = this; ApiClient.getNamedConfiguration('encoding').then(function (config) { - ApiClient.getSystemInfo().then(function (fetchedSystemInfo) { - loadPage(page, config, fetchedSystemInfo); + config.EnableAudioVbr = form.querySelector('#chkEnableAudioVbr').checked; + config.DownMixAudioBoost = $('#txtDownMixAudioBoost', form).val(); + config.DownMixStereoAlgorithm = $('#selectStereoDownmixAlgorithm', form).val() || 'None'; + config.MaxMuxingQueueSize = form.querySelector('#txtMaxMuxingQueueSize').value; + config.TranscodingTempPath = $('#txtTranscodingTempPath', form).val(); + config.FallbackFontPath = form.querySelector('#txtFallbackFontPath').value; + config.EnableFallbackFont = form.querySelector('#txtFallbackFontPath').value ? form.querySelector('#chkEnableFallbackFont').checked : false; + config.EncodingThreadCount = $('#selectThreadCount', form).val(); + config.HardwareAccelerationType = $('#selectVideoDecoder', form).val(); + config.VaapiDevice = $('#txtVaapiDevice', form).val(); + config.EnableTonemapping = form.querySelector('#chkTonemapping').checked; + config.EnableVppTonemapping = form.querySelector('#chkVppTonemapping').checked; + config.TonemappingAlgorithm = form.querySelector('#selectTonemappingAlgorithm').value; + config.TonemappingRange = form.querySelector('#selectTonemappingRange').value; + config.TonemappingDesat = form.querySelector('#txtTonemappingDesat').value; + config.TonemappingThreshold = form.querySelector('#txtTonemappingThreshold').value; + config.TonemappingPeak = form.querySelector('#txtTonemappingPeak').value; + config.TonemappingParam = form.querySelector('#txtTonemappingParam').value || '0'; + config.VppTonemappingBrightness = form.querySelector('#txtVppTonemappingBrightness').value; + config.VppTonemappingContrast = form.querySelector('#txtVppTonemappingContrast').value; + config.EncoderPreset = form.querySelector('#selectEncoderPreset').value; + config.H264Crf = parseInt(form.querySelector('#txtH264Crf').value || '0', 10); + config.H265Crf = parseInt(form.querySelector('#txtH265Crf').value || '0', 10); + config.DeinterlaceMethod = form.querySelector('#selectDeinterlaceMethod').value; + config.DeinterlaceDoubleRate = form.querySelector('#chkDoubleRateDeinterlacing').checked; + config.EnableSubtitleExtraction = form.querySelector('#chkEnableSubtitleExtraction').checked; + config.EnableThrottling = form.querySelector('#chkEnableThrottling').checked; + config.HardwareDecodingCodecs = Array.prototype.map.call(Array.prototype.filter.call(form.querySelectorAll('.chkDecodeCodec'), function (c) { + return c.checked; + }), function (c) { + return c.getAttribute('data-codec'); + }); + config.EnableDecodingColorDepth10Hevc = form.querySelector('#chkDecodingColorDepth10Hevc').checked; + config.EnableDecodingColorDepth10Vp9 = form.querySelector('#chkDecodingColorDepth10Vp9').checked; + config.EnableEnhancedNvdecDecoder = form.querySelector('#chkEnhancedNvdecDecoder').checked; + config.PreferSystemNativeHwDecoder = form.querySelector('#chkSystemNativeHwDecoder').checked; + config.EnableIntelLowPowerH264HwEncoder = form.querySelector('#chkIntelLpH264HwEncoder').checked; + config.EnableIntelLowPowerHevcHwEncoder = form.querySelector('#chkIntelLpHevcHwEncoder').checked; + config.EnableHardwareEncoding = form.querySelector('#chkHardwareEncoding').checked; + config.AllowHevcEncoding = form.querySelector('#chkAllowHevcEncoding').checked; + ApiClient.updateNamedConfiguration('encoding', config).then(function () { + updateEncoder(form); + }, function () { + alert(globalize.translate('ErrorDefault')); + Dashboard.processServerConfigurationUpdateResult(); + }); + }); + }; + + if ($('#selectVideoDecoder', form).val()) { + alert({ + title: globalize.translate('TitleHardwareAcceleration'), + text: globalize.translate('HardwareAccelerationWarning') + }).then(onDecoderConfirmed); + } else { + onDecoderConfirmed(); + } + + return false; +} + +function setDecodingCodecsVisible(context, value) { + value = value || ''; + let any; + Array.prototype.forEach.call(context.querySelectorAll('.chkDecodeCodec'), function (c) { + if (c.getAttribute('data-types').split(',').indexOf(value) === -1) { + dom.parentWithTag(c, 'LABEL').classList.add('hide'); + } else { + dom.parentWithTag(c, 'LABEL').classList.remove('hide'); + any = true; + } + }); + + if (any) { + context.querySelector('.decodingCodecsList').classList.remove('hide'); + } else { + context.querySelector('.decodingCodecsList').classList.add('hide'); + } +} + +function getTabs() { + return [{ + href: '#/encodingsettings.html', + name: globalize.translate('Transcoding') + }, { + href: '#/playbackconfiguration.html', + name: globalize.translate('ButtonResume') + }, { + href: '#/streamingsettings.html', + name: globalize.translate('TabStreaming') + }]; +} + +let systemInfo; +function getSystemInfo() { + return systemInfo ? Promise.resolve(systemInfo) : ApiClient.getPublicSystemInfo().then( + info => { + systemInfo = info; + return info; + } + ); +} + +$(document).on('pageinit', '#encodingSettingsPage', function () { + const page = this; + getSystemInfo(); + page.querySelector('#selectVideoDecoder').addEventListener('change', function () { + if (this.value == 'vaapi') { + page.querySelector('.fldVaapiDevice').classList.remove('hide'); + page.querySelector('#txtVaapiDevice').setAttribute('required', 'required'); + } else { + page.querySelector('.fldVaapiDevice').classList.add('hide'); + page.querySelector('#txtVaapiDevice').removeAttribute('required'); + } + + if (this.value == 'amf' || this.value == 'nvenc' || this.value == 'qsv' || this.value == 'vaapi' || this.value == 'videotoolbox') { + page.querySelector('.fld10bitHevcVp9HwDecoding').classList.remove('hide'); + } else { + page.querySelector('.fld10bitHevcVp9HwDecoding').classList.add('hide'); + } + + if (this.value == 'amf' || this.value == 'nvenc' || this.value == 'qsv' || this.value == 'vaapi') { + page.querySelector('.tonemappingOptions').classList.remove('hide'); + } else { + page.querySelector('.tonemappingOptions').classList.add('hide'); + } + + if (this.value == 'qsv' || this.value == 'vaapi') { + page.querySelector('.fldIntelLp').classList.remove('hide'); + } else { + page.querySelector('.fldIntelLp').classList.add('hide'); + } + + if (systemInfo.OperatingSystem.toLowerCase() === 'linux' && (this.value == 'qsv' || this.value == 'vaapi')) { + page.querySelector('.vppTonemappingOptions').classList.remove('hide'); + } else { + page.querySelector('.vppTonemappingOptions').classList.add('hide'); + } + + if (this.value == 'qsv') { + page.querySelector('.fldSysNativeHwDecoder').classList.remove('hide'); + } else { + page.querySelector('.fldSysNativeHwDecoder').classList.add('hide'); + } + + if (this.value == 'nvenc') { + page.querySelector('.fldEnhancedNvdec').classList.remove('hide'); + } else { + page.querySelector('.fldEnhancedNvdec').classList.add('hide'); + } + + if (this.value) { + page.querySelector('.hardwareAccelerationOptions').classList.remove('hide'); + } else { + page.querySelector('.hardwareAccelerationOptions').classList.add('hide'); + } + + setDecodingCodecsVisible(page, this.value); + }); + $('#btnSelectEncoderPath', page).on('click.selectDirectory', function () { + import('../../components/directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => { + const picker = new DirectoryBrowser(); + picker.show({ + includeFiles: true, + callback: function (path) { + if (path) { + $('.txtEncoderPath', page).val(path); + } + + picker.close(); + } }); }); }); + $('#btnSelectTranscodingTempPath', page).on('click.selectDirectory', function () { + import('../../components/directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => { + const picker = new DirectoryBrowser(); + picker.show({ + callback: function (path) { + if (path) { + $('#txtTranscodingTempPath', page).val(path); + } + + picker.close(); + }, + validateWriteable: true, + header: globalize.translate('HeaderSelectTranscodingPath'), + instruction: globalize.translate('HeaderSelectTranscodingPathHelp') + }); + }); + }); + $('#btnSelectFallbackFontPath', page).on('click.selectDirectory', function () { + import('../../components/directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => { + const picker = new DirectoryBrowser(); + picker.show({ + includeDirectories: true, + callback: function (path) { + if (path) { + page.querySelector('#txtFallbackFontPath').value = path; + } + + picker.close(); + }, + header: globalize.translate('HeaderSelectFallbackFontPath'), + instruction: globalize.translate('HeaderSelectFallbackFontPathHelp') + }); + }); + }); + $('.encodingSettingsForm').off('submit', onSubmit).on('submit', onSubmit); +}).on('pageshow', '#encodingSettingsPage', function () { + loading.show(); + libraryMenu.setTabs('playback', 0, getTabs); + const page = this; + ApiClient.getNamedConfiguration('encoding').then(function (config) { + ApiClient.getSystemInfo().then(function (fetchedSystemInfo) { + loadPage(page, config, fetchedSystemInfo); + }); + }); +}); -/* eslint-enable indent */ diff --git a/src/controllers/dashboard/general.js b/src/controllers/dashboard/general.js index ed7268748f..098d2244bd 100644 --- a/src/controllers/dashboard/general.js +++ b/src/controllers/dashboard/general.js @@ -10,110 +10,107 @@ import '../../elements/emby-button/emby-button'; import Dashboard from '../../utils/dashboard'; import alert from '../../components/alert'; -/* eslint-disable indent */ +function loadPage(page, config, languageOptions, systemInfo) { + page.querySelector('#txtServerName').value = systemInfo.ServerName; + page.querySelector('#txtCachePath').value = systemInfo.CachePath || ''; + page.querySelector('#chkQuickConnectAvailable').checked = config.QuickConnectAvailable === true; + $('#txtMetadataPath', page).val(systemInfo.InternalMetadataPath || ''); + $('#txtMetadataNetworkPath', page).val(systemInfo.MetadataNetworkPath || ''); + $('#selectLocalizationLanguage', page).html(languageOptions.map(function (language) { + return ''; + })).val(config.UICulture); + page.querySelector('#txtParallelImageEncodingLimit').value = config.ParallelImageEncodingLimit || ''; - function loadPage(page, config, languageOptions, systemInfo) { - page.querySelector('#txtServerName').value = systemInfo.ServerName; - page.querySelector('#txtCachePath').value = systemInfo.CachePath || ''; - page.querySelector('#chkQuickConnectAvailable').checked = config.QuickConnectAvailable === true; - $('#txtMetadataPath', page).val(systemInfo.InternalMetadataPath || ''); - $('#txtMetadataNetworkPath', page).val(systemInfo.MetadataNetworkPath || ''); - $('#selectLocalizationLanguage', page).html(languageOptions.map(function (language) { - return ''; - })).val(config.UICulture); - page.querySelector('#txtParallelImageEncodingLimit').value = config.ParallelImageEncodingLimit || ''; + loading.hide(); +} - loading.hide(); - } +function onSubmit() { + loading.show(); + const form = this; + $(form).parents('.page'); + ApiClient.getServerConfiguration().then(function (config) { + config.ServerName = $('#txtServerName', form).val(); + config.UICulture = $('#selectLocalizationLanguage', form).val(); + config.CachePath = form.querySelector('#txtCachePath').value; + config.MetadataPath = $('#txtMetadataPath', form).val(); + config.MetadataNetworkPath = $('#txtMetadataNetworkPath', form).val(); + config.QuickConnectAvailable = form.querySelector('#chkQuickConnectAvailable').checked; + config.ParallelImageEncodingLimit = parseInt(form.querySelector('#txtParallelImageEncodingLimit').value || '0', 10); - function onSubmit() { - loading.show(); - const form = this; - $(form).parents('.page'); - ApiClient.getServerConfiguration().then(function (config) { - config.ServerName = $('#txtServerName', form).val(); - config.UICulture = $('#selectLocalizationLanguage', form).val(); - config.CachePath = form.querySelector('#txtCachePath').value; - config.MetadataPath = $('#txtMetadataPath', form).val(); - config.MetadataNetworkPath = $('#txtMetadataNetworkPath', form).val(); - config.QuickConnectAvailable = form.querySelector('#chkQuickConnectAvailable').checked; - config.ParallelImageEncodingLimit = parseInt(form.querySelector('#txtParallelImageEncodingLimit').value || '0', 10); + ApiClient.updateServerConfiguration(config).then(function() { + ApiClient.getNamedConfiguration(brandingConfigKey).then(function(brandingConfig) { + brandingConfig.LoginDisclaimer = form.querySelector('#txtLoginDisclaimer').value; + brandingConfig.CustomCss = form.querySelector('#txtCustomCss').value; + brandingConfig.SplashscreenEnabled = form.querySelector('#chkSplashScreenAvailable').checked; - ApiClient.updateServerConfiguration(config).then(function() { - ApiClient.getNamedConfiguration(brandingConfigKey).then(function(brandingConfig) { - brandingConfig.LoginDisclaimer = form.querySelector('#txtLoginDisclaimer').value; - brandingConfig.CustomCss = form.querySelector('#txtCustomCss').value; - brandingConfig.SplashscreenEnabled = form.querySelector('#chkSplashScreenAvailable').checked; - - ApiClient.updateNamedConfiguration(brandingConfigKey, brandingConfig).then(function () { - Dashboard.processServerConfigurationUpdateResult(); - }); - }); - }, function () { - alert(globalize.translate('ErrorDefault')); - Dashboard.processServerConfigurationUpdateResult(); - }); - }); - return false; - } - - const brandingConfigKey = 'branding'; - export default function (view) { - $('#btnSelectCachePath', view).on('click.selectDirectory', function () { - import('../../components/directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => { - const picker = new DirectoryBrowser(); - picker.show({ - callback: function (path) { - if (path) { - view.querySelector('#txtCachePath').value = path; - } - - picker.close(); - }, - validateWriteable: true, - header: globalize.translate('HeaderSelectServerCachePath'), - instruction: globalize.translate('HeaderSelectServerCachePathHelp') + ApiClient.updateNamedConfiguration(brandingConfigKey, brandingConfig).then(function () { + Dashboard.processServerConfigurationUpdateResult(); }); }); + }, function () { + alert(globalize.translate('ErrorDefault')); + Dashboard.processServerConfigurationUpdateResult(); }); - $('#btnSelectMetadataPath', view).on('click.selectDirectory', function () { - import('../../components/directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => { - const picker = new DirectoryBrowser(); - picker.show({ - path: $('#txtMetadataPath', view).val(), - networkSharePath: $('#txtMetadataNetworkPath', view).val(), - callback: function (path, networkPath) { - if (path) { - $('#txtMetadataPath', view).val(path); - } + }); + return false; +} - if (networkPath) { - $('#txtMetadataNetworkPath', view).val(networkPath); - } +const brandingConfigKey = 'branding'; +export default function (view) { + $('#btnSelectCachePath', view).on('click.selectDirectory', function () { + import('../../components/directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => { + const picker = new DirectoryBrowser(); + picker.show({ + callback: function (path) { + if (path) { + view.querySelector('#txtCachePath').value = path; + } - picker.close(); - }, - validateWriteable: true, - header: globalize.translate('HeaderSelectMetadataPath'), - instruction: globalize.translate('HeaderSelectMetadataPathHelp'), - enableNetworkSharePath: true - }); + picker.close(); + }, + validateWriteable: true, + header: globalize.translate('HeaderSelectServerCachePath'), + instruction: globalize.translate('HeaderSelectServerCachePathHelp') }); }); - $('.dashboardGeneralForm', view).off('submit', onSubmit).on('submit', onSubmit); - view.addEventListener('viewshow', function () { - const promiseConfig = ApiClient.getServerConfiguration(); - const promiseLanguageOptions = ApiClient.getJSON(ApiClient.getUrl('Localization/Options')); - const promiseSystemInfo = ApiClient.getSystemInfo(); - Promise.all([promiseConfig, promiseLanguageOptions, promiseSystemInfo]).then(function (responses) { - loadPage(view, responses[0], responses[1], responses[2]); - }); - ApiClient.getNamedConfiguration(brandingConfigKey).then(function (config) { - view.querySelector('#txtLoginDisclaimer').value = config.LoginDisclaimer || ''; - view.querySelector('#txtCustomCss').value = config.CustomCss || ''; - view.querySelector('#chkSplashScreenAvailable').checked = config.SplashscreenEnabled === true; + }); + $('#btnSelectMetadataPath', view).on('click.selectDirectory', function () { + import('../../components/directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => { + const picker = new DirectoryBrowser(); + picker.show({ + path: $('#txtMetadataPath', view).val(), + networkSharePath: $('#txtMetadataNetworkPath', view).val(), + callback: function (path, networkPath) { + if (path) { + $('#txtMetadataPath', view).val(path); + } + + if (networkPath) { + $('#txtMetadataNetworkPath', view).val(networkPath); + } + + picker.close(); + }, + validateWriteable: true, + header: globalize.translate('HeaderSelectMetadataPath'), + instruction: globalize.translate('HeaderSelectMetadataPathHelp'), + enableNetworkSharePath: true }); }); - } + }); + $('.dashboardGeneralForm', view).off('submit', onSubmit).on('submit', onSubmit); + view.addEventListener('viewshow', function () { + const promiseConfig = ApiClient.getServerConfiguration(); + const promiseLanguageOptions = ApiClient.getJSON(ApiClient.getUrl('Localization/Options')); + const promiseSystemInfo = ApiClient.getSystemInfo(); + Promise.all([promiseConfig, promiseLanguageOptions, promiseSystemInfo]).then(function (responses) { + loadPage(view, responses[0], responses[1], responses[2]); + }); + ApiClient.getNamedConfiguration(brandingConfigKey).then(function (config) { + view.querySelector('#txtLoginDisclaimer').value = config.LoginDisclaimer || ''; + view.querySelector('#txtCustomCss').value = config.CustomCss || ''; + view.querySelector('#chkSplashScreenAvailable').checked = config.SplashscreenEnabled === true; + }); + }); +} -/* eslint-enable indent */ diff --git a/src/controllers/dashboard/library.js b/src/controllers/dashboard/library.js index 62cb3edf42..f9d80fa3fc 100644 --- a/src/controllers/dashboard/library.js +++ b/src/controllers/dashboard/library.js @@ -12,399 +12,396 @@ import Dashboard, { pageClassOn, pageIdOn } from '../../utils/dashboard'; import confirm from '../../components/confirm/confirm'; import cardBuilder from '../../components/cardbuilder/cardBuilder'; -/* eslint-disable indent */ - - function addVirtualFolder(page) { - import('../../components/mediaLibraryCreator/mediaLibraryCreator').then(({ default: medialibrarycreator }) => { - new medialibrarycreator({ - collectionTypeOptions: getCollectionTypeOptions().filter(function (f) { - return !f.hidden; - }), - refresh: shouldRefreshLibraryAfterChanges(page) - }).then(function (hasChanges) { - if (hasChanges) { - reloadLibrary(page); - } - }); - }); - } - - function editVirtualFolder(page, virtualFolder) { - import('../../components/mediaLibraryEditor/mediaLibraryEditor').then(({ default: medialibraryeditor }) => { - new medialibraryeditor({ - refresh: shouldRefreshLibraryAfterChanges(page), - library: virtualFolder - }).then(function (hasChanges) { - if (hasChanges) { - reloadLibrary(page); - } - }); - }); - } - - function deleteVirtualFolder(page, virtualFolder) { - let msg = globalize.translate('MessageAreYouSureYouWishToRemoveMediaFolder'); - - if (virtualFolder.Locations.length) { - msg += '

' + globalize.translate('MessageTheFollowingLocationWillBeRemovedFromLibrary') + '

'; - msg += virtualFolder.Locations.join('
'); - } - - confirm({ - text: msg, - title: globalize.translate('HeaderRemoveMediaFolder'), - confirmText: globalize.translate('Delete'), - primary: 'delete' - }).then(function () { - const refreshAfterChange = shouldRefreshLibraryAfterChanges(page); - ApiClient.removeVirtualFolder(virtualFolder.Name, refreshAfterChange).then(function () { +function addVirtualFolder(page) { + import('../../components/mediaLibraryCreator/mediaLibraryCreator').then(({ default: medialibrarycreator }) => { + new medialibrarycreator({ + collectionTypeOptions: getCollectionTypeOptions().filter(function (f) { + return !f.hidden; + }), + refresh: shouldRefreshLibraryAfterChanges(page) + }).then(function (hasChanges) { + if (hasChanges) { reloadLibrary(page); - }); + } }); + }); +} + +function editVirtualFolder(page, virtualFolder) { + import('../../components/mediaLibraryEditor/mediaLibraryEditor').then(({ default: medialibraryeditor }) => { + new medialibraryeditor({ + refresh: shouldRefreshLibraryAfterChanges(page), + library: virtualFolder + }).then(function (hasChanges) { + if (hasChanges) { + reloadLibrary(page); + } + }); + }); +} + +function deleteVirtualFolder(page, virtualFolder) { + let msg = globalize.translate('MessageAreYouSureYouWishToRemoveMediaFolder'); + + if (virtualFolder.Locations.length) { + msg += '

' + globalize.translate('MessageTheFollowingLocationWillBeRemovedFromLibrary') + '

'; + msg += virtualFolder.Locations.join('
'); } - function refreshVirtualFolder(page, virtualFolder) { - import('../../components/refreshdialog/refreshdialog').then(({ default: refreshDialog }) => { - new refreshDialog({ - itemIds: [virtualFolder.ItemId], - serverId: ApiClient.serverId(), - mode: 'scan' - }).show(); + confirm({ + text: msg, + title: globalize.translate('HeaderRemoveMediaFolder'), + confirmText: globalize.translate('Delete'), + primary: 'delete' + }).then(function () { + const refreshAfterChange = shouldRefreshLibraryAfterChanges(page); + ApiClient.removeVirtualFolder(virtualFolder.Name, refreshAfterChange).then(function () { + reloadLibrary(page); }); - } + }); +} - function renameVirtualFolder(page, virtualFolder) { - import('../../components/prompt/prompt').then(({ default: prompt }) => { - prompt({ - label: globalize.translate('LabelNewName'), - description: globalize.translate('MessageRenameMediaFolder'), - confirmText: globalize.translate('ButtonRename') - }).then(function (newName) { - if (newName && newName != virtualFolder.Name) { - const refreshAfterChange = shouldRefreshLibraryAfterChanges(page); - ApiClient.renameVirtualFolder(virtualFolder.Name, newName, refreshAfterChange).then(function () { - reloadLibrary(page); - }); +function refreshVirtualFolder(page, virtualFolder) { + import('../../components/refreshdialog/refreshdialog').then(({ default: refreshDialog }) => { + new refreshDialog({ + itemIds: [virtualFolder.ItemId], + serverId: ApiClient.serverId(), + mode: 'scan' + }).show(); + }); +} + +function renameVirtualFolder(page, virtualFolder) { + import('../../components/prompt/prompt').then(({ default: prompt }) => { + prompt({ + label: globalize.translate('LabelNewName'), + description: globalize.translate('MessageRenameMediaFolder'), + confirmText: globalize.translate('ButtonRename') + }).then(function (newName) { + if (newName && newName != virtualFolder.Name) { + const refreshAfterChange = shouldRefreshLibraryAfterChanges(page); + ApiClient.renameVirtualFolder(virtualFolder.Name, newName, refreshAfterChange).then(function () { + reloadLibrary(page); + }); + } + }); + }); +} + +function showCardMenu(page, elem, virtualFolders) { + const card = dom.parentWithClass(elem, 'card'); + const index = parseInt(card.getAttribute('data-index'), 10); + const virtualFolder = virtualFolders[index]; + const menuItems = []; + menuItems.push({ + name: globalize.translate('EditImages'), + id: 'editimages', + icon: 'photo' + }); + menuItems.push({ + name: globalize.translate('ManageLibrary'), + id: 'edit', + icon: 'folder' + }); + menuItems.push({ + name: globalize.translate('ButtonRename'), + id: 'rename', + icon: 'mode_edit' + }); + menuItems.push({ + name: globalize.translate('ScanLibrary'), + id: 'refresh', + icon: 'refresh' + }); + menuItems.push({ + name: globalize.translate('ButtonRemove'), + id: 'delete', + icon: 'delete' + }); + + import('../../components/actionSheet/actionSheet').then((actionsheet) => { + actionsheet.show({ + items: menuItems, + positionTo: elem, + callback: function (resultId) { + switch (resultId) { + case 'edit': + editVirtualFolder(page, virtualFolder); + break; + + case 'editimages': + editImages(page, virtualFolder); + break; + + case 'rename': + renameVirtualFolder(page, virtualFolder); + break; + + case 'delete': + deleteVirtualFolder(page, virtualFolder); + break; + + case 'refresh': + refreshVirtualFolder(page, virtualFolder); } - }); + } }); + }); +} + +function reloadLibrary(page) { + loading.show(); + ApiClient.getVirtualFolders().then(function (result) { + reloadVirtualFolders(page, result); + }); +} + +function shouldRefreshLibraryAfterChanges(page) { + return page.id === 'mediaLibraryPage'; +} + +function reloadVirtualFolders(page, virtualFolders) { + let html = ''; + virtualFolders.push({ + Name: globalize.translate('ButtonAddMediaLibrary'), + icon: 'add_circle', + Locations: [], + showType: false, + showLocations: false, + showMenu: false, + showNameWithIcon: false, + elementId: 'addLibrary' + }); + + for (let i = 0; i < virtualFolders.length; i++) { + const virtualFolder = virtualFolders[i]; + html += getVirtualFolderHtml(page, virtualFolder, i); } - function showCardMenu(page, elem, virtualFolders) { - const card = dom.parentWithClass(elem, 'card'); + const divVirtualFolders = page.querySelector('#divVirtualFolders'); + divVirtualFolders.innerHTML = html; + divVirtualFolders.classList.add('itemsContainer'); + divVirtualFolders.classList.add('vertical-wrap'); + $('.btnCardMenu', divVirtualFolders).on('click', function () { + showCardMenu(page, this, virtualFolders); + }); + divVirtualFolders.querySelector('#addLibrary').addEventListener('click', function () { + addVirtualFolder(page); + }); + $('.editLibrary', divVirtualFolders).on('click', function () { + const card = $(this).parents('.card')[0]; const index = parseInt(card.getAttribute('data-index'), 10); const virtualFolder = virtualFolders[index]; - const menuItems = []; - menuItems.push({ - name: globalize.translate('EditImages'), - id: 'editimages', - icon: 'photo' - }); - menuItems.push({ - name: globalize.translate('ManageLibrary'), - id: 'edit', - icon: 'folder' - }); - menuItems.push({ - name: globalize.translate('ButtonRename'), - id: 'rename', - icon: 'mode_edit' - }); - menuItems.push({ - name: globalize.translate('ScanLibrary'), - id: 'refresh', - icon: 'refresh' - }); - menuItems.push({ - name: globalize.translate('ButtonRemove'), - id: 'delete', - icon: 'delete' - }); - import('../../components/actionSheet/actionSheet').then((actionsheet) => { - actionsheet.show({ - items: menuItems, - positionTo: elem, - callback: function (resultId) { - switch (resultId) { - case 'edit': - editVirtualFolder(page, virtualFolder); - break; - - case 'editimages': - editImages(page, virtualFolder); - break; - - case 'rename': - renameVirtualFolder(page, virtualFolder); - break; - - case 'delete': - deleteVirtualFolder(page, virtualFolder); - break; - - case 'refresh': - refreshVirtualFolder(page, virtualFolder); - } - } - }); - }); - } - - function reloadLibrary(page) { - loading.show(); - ApiClient.getVirtualFolders().then(function (result) { - reloadVirtualFolders(page, result); - }); - } - - function shouldRefreshLibraryAfterChanges(page) { - return page.id === 'mediaLibraryPage'; - } - - function reloadVirtualFolders(page, virtualFolders) { - let html = ''; - virtualFolders.push({ - Name: globalize.translate('ButtonAddMediaLibrary'), - icon: 'add_circle', - Locations: [], - showType: false, - showLocations: false, - showMenu: false, - showNameWithIcon: false, - elementId: 'addLibrary' - }); - - for (let i = 0; i < virtualFolders.length; i++) { - const virtualFolder = virtualFolders[i]; - html += getVirtualFolderHtml(page, virtualFolder, i); + if (virtualFolder.ItemId) { + editVirtualFolder(page, virtualFolder); } + }); + loading.hide(); +} - const divVirtualFolders = page.querySelector('#divVirtualFolders'); - divVirtualFolders.innerHTML = html; - divVirtualFolders.classList.add('itemsContainer'); - divVirtualFolders.classList.add('vertical-wrap'); - $('.btnCardMenu', divVirtualFolders).on('click', function () { - showCardMenu(page, this, virtualFolders); +function editImages(page, virtualFolder) { + import('../../components/imageeditor/imageeditor').then((imageEditor) => { + imageEditor.show({ + itemId: virtualFolder.ItemId, + serverId: ApiClient.serverId() + }).then(function () { + reloadLibrary(page); }); - divVirtualFolders.querySelector('#addLibrary').addEventListener('click', function () { - addVirtualFolder(page); - }); - $('.editLibrary', divVirtualFolders).on('click', function () { - const card = $(this).parents('.card')[0]; - const index = parseInt(card.getAttribute('data-index'), 10); - const virtualFolder = virtualFolders[index]; + }); +} - if (virtualFolder.ItemId) { - editVirtualFolder(page, virtualFolder); - } - }); - loading.hide(); +function getLink(text, url) { + return globalize.translate(text, '', ''); +} + +function getCollectionTypeOptions() { + return [{ + name: '', + value: '' + }, { + name: globalize.translate('Movies'), + value: 'movies', + message: getLink('MovieLibraryHelp', 'https://jellyfin.org/docs/general/server/media/movies') + }, { + name: globalize.translate('TabMusic'), + value: 'music', + message: getLink('MusicLibraryHelp', 'https://jellyfin.org/docs/general/server/media/music') + }, { + name: globalize.translate('Shows'), + value: 'tvshows', + message: getLink('TvLibraryHelp', 'https://jellyfin.org/docs/general/server/media/shows') + }, { + name: globalize.translate('Books'), + value: 'books', + message: getLink('BookLibraryHelp', 'https://jellyfin.org/docs/general/server/media/books') + }, { + name: globalize.translate('HomeVideosPhotos'), + value: 'homevideos' + }, { + name: globalize.translate('MusicVideos'), + value: 'musicvideos' + }, { + name: globalize.translate('MixedMoviesShows'), + value: 'mixed', + message: globalize.translate('MessageUnsetContentHelp') + }]; +} + +function getVirtualFolderHtml(page, virtualFolder, index) { + let html = ''; + let style = ''; + + if (page.classList.contains('wizardPage')) { + style += 'min-width:33.3%;'; } - function editImages(page, virtualFolder) { - import('../../components/imageeditor/imageeditor').then((imageEditor) => { - imageEditor.show({ - itemId: virtualFolder.ItemId, - serverId: ApiClient.serverId() - }).then(function () { - reloadLibrary(page); - }); + const elementId = virtualFolder.elementId ? `id="${virtualFolder.elementId}" ` : ''; + html += '
'; + + html += '
'; + html += '
'; + html += '
'; + html += '
'; + let imgUrl = ''; + + if (virtualFolder.PrimaryImageItemId) { + imgUrl = ApiClient.getScaledImageUrl(virtualFolder.PrimaryImageItemId, { + maxWidth: Math.round(dom.getScreenWidth() * 0.40), + type: 'Primary' }); } - function getLink(text, url) { - return globalize.translate(text, '', ''); + let hasCardImageContainer; + + if (imgUrl) { + html += `
`; + html += ``; + hasCardImageContainer = true; + } else if (!virtualFolder.showNameWithIcon) { + html += `
`; + html += ''; + hasCardImageContainer = true; } - function getCollectionTypeOptions() { - return [{ - name: '', - value: '' - }, { - name: globalize.translate('Movies'), - value: 'movies', - message: getLink('MovieLibraryHelp', 'https://jellyfin.org/docs/general/server/media/movies') - }, { - name: globalize.translate('TabMusic'), - value: 'music', - message: getLink('MusicLibraryHelp', 'https://jellyfin.org/docs/general/server/media/music') - }, { - name: globalize.translate('Shows'), - value: 'tvshows', - message: getLink('TvLibraryHelp', 'https://jellyfin.org/docs/general/server/media/shows') - }, { - name: globalize.translate('Books'), - value: 'books', - message: getLink('BookLibraryHelp', 'https://jellyfin.org/docs/general/server/media/books') - }, { - name: globalize.translate('HomeVideosPhotos'), - value: 'homevideos' - }, { - name: globalize.translate('MusicVideos'), - value: 'musicvideos' - }, { - name: globalize.translate('MixedMoviesShows'), - value: 'mixed', - message: globalize.translate('MessageUnsetContentHelp') - }]; - } - - function getVirtualFolderHtml(page, virtualFolder, index) { - let html = ''; - let style = ''; - - if (page.classList.contains('wizardPage')) { - style += 'min-width:33.3%;'; - } - - const elementId = virtualFolder.elementId ? `id="${virtualFolder.elementId}" ` : ''; - html += '
'; - - html += '
'; - html += '
'; - html += '
'; - html += '
'; - let imgUrl = ''; - - if (virtualFolder.PrimaryImageItemId) { - imgUrl = ApiClient.getScaledImageUrl(virtualFolder.PrimaryImageItemId, { - maxWidth: Math.round(dom.getScreenWidth() * 0.40), - type: 'Primary' - }); - } - - let hasCardImageContainer; - - if (imgUrl) { - html += `
`; - html += ``; - hasCardImageContainer = true; - } else if (!virtualFolder.showNameWithIcon) { - html += `
`; - html += ''; - hasCardImageContainer = true; - } - - if (hasCardImageContainer) { - html += '
'; - html += '
'; - html += '
'; - html += '
'; - } - - if (!imgUrl && virtualFolder.showNameWithIcon) { - html += '

'; - html += ''; - - if (virtualFolder.showNameWithIcon) { - html += '
'; - html += escapeHtml(virtualFolder.Name); - html += '
'; - } - - html += '

'; - } - + if (hasCardImageContainer) { + html += '
'; + html += '
'; html += '
'; html += '
'; - html += '
'; // always show menu unless explicitly hidden + } - if (virtualFolder.showMenu !== false) { - let dirTextAlign = 'right'; - if (globalize.getIsRTL()) - dirTextAlign = 'left'; - html += '
'; - html += ''; - html += '
'; - } - - html += "
"; + if (!imgUrl && virtualFolder.showNameWithIcon) { + html += '

'; + html += ''; if (virtualFolder.showNameWithIcon) { - html += ' '; - } else { + html += '
'; html += escapeHtml(virtualFolder.Name); + html += '
'; } + html += '

'; + } + + html += '
'; + html += '
'; + html += '
'; // always show menu unless explicitly hidden + + if (virtualFolder.showMenu !== false) { + let dirTextAlign = 'right'; + if (globalize.getIsRTL()) + dirTextAlign = 'left'; + html += '
'; + html += ''; html += '
'; - let typeName = getCollectionTypeOptions().filter(function (t) { - return t.value == virtualFolder.CollectionType; - })[0]; - typeName = typeName ? typeName.name : globalize.translate('Other'); + } + + html += "
"; + + if (virtualFolder.showNameWithIcon) { + html += ' '; + } else { + html += escapeHtml(virtualFolder.Name); + } + + html += '
'; + let typeName = getCollectionTypeOptions().filter(function (t) { + return t.value == virtualFolder.CollectionType; + })[0]; + typeName = typeName ? typeName.name : globalize.translate('Other'); + html += "
"; + + if (virtualFolder.showType === false) { + html += ' '; + } else { + html += typeName; + } + + html += '
'; + + if (virtualFolder.showLocations === false) { html += "
"; - - if (virtualFolder.showType === false) { - html += ' '; - } else { - html += typeName; - } - + html += ' '; html += '
'; - - if (virtualFolder.showLocations === false) { - html += "
"; - html += ' '; - html += '
'; - } else if (virtualFolder.Locations.length && virtualFolder.Locations.length === 1) { - html += "
"; - html += virtualFolder.Locations[0]; - html += '
'; - } else { - html += "
"; - html += globalize.translate('NumLocationsValue', virtualFolder.Locations.length); - html += '
'; - } - + } else if (virtualFolder.Locations.length && virtualFolder.Locations.length === 1) { + html += "
"; + html += virtualFolder.Locations[0]; html += '
'; + } else { + html += "
"; + html += globalize.translate('NumLocationsValue', virtualFolder.Locations.length); html += '
'; - html += '
'; - return html; } - function getTabs() { - return [{ - href: '#/library.html', - name: globalize.translate('HeaderLibraries') - }, { - href: '#/librarydisplay.html', - name: globalize.translate('Display') - }, { - href: '#/metadataimages.html', - name: globalize.translate('Metadata') - }, { - href: '#/metadatanfo.html', - name: globalize.translate('TabNfoSettings') - }]; + html += '
'; + html += '
'; + html += '
'; + return html; +} + +function getTabs() { + return [{ + href: '#/library.html', + name: globalize.translate('HeaderLibraries') + }, { + href: '#/librarydisplay.html', + name: globalize.translate('Display') + }, { + href: '#/metadataimages.html', + name: globalize.translate('Metadata') + }, { + href: '#/metadatanfo.html', + name: globalize.translate('TabNfoSettings') + }]; +} + +window.WizardLibraryPage = { + next: function () { + Dashboard.navigate('wizardsettings.html'); } +}; +pageClassOn('pageshow', 'mediaLibraryPage', function () { + reloadLibrary(this); +}); +pageIdOn('pageshow', 'mediaLibraryPage', function () { + libraryMenu.setTabs('librarysetup', 0, getTabs); - window.WizardLibraryPage = { - next: function () { - Dashboard.navigate('wizardsettings.html'); - } - }; - pageClassOn('pageshow', 'mediaLibraryPage', function () { - reloadLibrary(this); + const page = this; + taskButton({ + mode: 'on', + progressElem: page.querySelector('.refreshProgress'), + taskKey: 'RefreshLibrary', + button: page.querySelector('.btnRefresh') }); - pageIdOn('pageshow', 'mediaLibraryPage', function () { - libraryMenu.setTabs('librarysetup', 0, getTabs); +}); +pageIdOn('pagebeforehide', 'mediaLibraryPage', function () { + const page = this; + taskButton({ + mode: 'off', + progressElem: page.querySelector('.refreshProgress'), + taskKey: 'RefreshLibrary', + button: page.querySelector('.btnRefresh') + }); +}); - const page = this; - taskButton({ - mode: 'on', - progressElem: page.querySelector('.refreshProgress'), - taskKey: 'RefreshLibrary', - button: page.querySelector('.btnRefresh') - }); - }); - pageIdOn('pagebeforehide', 'mediaLibraryPage', function () { - const page = this; - taskButton({ - mode: 'off', - progressElem: page.querySelector('.refreshProgress'), - taskKey: 'RefreshLibrary', - button: page.querySelector('.btnRefresh') - }); - }); - -/* eslint-enable indent */ diff --git a/src/controllers/dashboard/librarydisplay.js b/src/controllers/dashboard/librarydisplay.js index 4691158386..b418984fb7 100644 --- a/src/controllers/dashboard/librarydisplay.js +++ b/src/controllers/dashboard/librarydisplay.js @@ -5,70 +5,67 @@ import '../../elements/emby-checkbox/emby-checkbox'; import '../../elements/emby-button/emby-button'; import Dashboard from '../../utils/dashboard'; -/* eslint-disable indent */ +function getTabs() { + return [{ + href: '#/library.html', + name: globalize.translate('HeaderLibraries') + }, { + href: '#/librarydisplay.html', + name: globalize.translate('Display') + }, { + href: '#/metadataimages.html', + name: globalize.translate('Metadata') + }, { + href: '#/metadatanfo.html', + name: globalize.translate('TabNfoSettings') + }]; +} - function getTabs() { - return [{ - href: '#/library.html', - name: globalize.translate('HeaderLibraries') - }, { - href: '#/librarydisplay.html', - name: globalize.translate('Display') - }, { - href: '#/metadataimages.html', - name: globalize.translate('Metadata') - }, { - href: '#/metadatanfo.html', - name: globalize.translate('TabNfoSettings') - }]; - } - - export default function(view) { - function loadData() { - ApiClient.getServerConfiguration().then(function(config) { - view.querySelector('.chkFolderView').checked = config.EnableFolderView; - view.querySelector('.chkGroupMoviesIntoCollections').checked = config.EnableGroupingIntoCollections; - view.querySelector('.chkDisplaySpecialsWithinSeasons').checked = config.DisplaySpecialsWithinSeasons; - view.querySelector('.chkExternalContentInSuggestions').checked = config.EnableExternalContentInSuggestions; - view.querySelector('#chkSaveMetadataHidden').checked = config.SaveMetadataHidden; - }); - ApiClient.getNamedConfiguration('metadata').then(function(metadata) { - view.querySelector('#selectDateAdded').selectedIndex = metadata.UseFileCreationTimeForDateAdded ? 1 : 0; - }); - } - - view.querySelector('form').addEventListener('submit', function(e) { - loading.show(); - const form = this; - ApiClient.getServerConfiguration().then(function(config) { - config.EnableFolderView = form.querySelector('.chkFolderView').checked; - config.EnableGroupingIntoCollections = form.querySelector('.chkGroupMoviesIntoCollections').checked; - config.DisplaySpecialsWithinSeasons = form.querySelector('.chkDisplaySpecialsWithinSeasons').checked; - config.EnableExternalContentInSuggestions = form.querySelector('.chkExternalContentInSuggestions').checked; - config.SaveMetadataHidden = form.querySelector('#chkSaveMetadataHidden').checked; - ApiClient.updateServerConfiguration(config).then(Dashboard.processServerConfigurationUpdateResult); - }); - ApiClient.getNamedConfiguration('metadata').then(function(config) { - config.UseFileCreationTimeForDateAdded = $('#selectDateAdded', form).val() === '1'; - ApiClient.updateNamedConfiguration('metadata', config); - }); - - e.preventDefault(); - loading.hide(); - return false; +export default function(view) { + function loadData() { + ApiClient.getServerConfiguration().then(function(config) { + view.querySelector('.chkFolderView').checked = config.EnableFolderView; + view.querySelector('.chkGroupMoviesIntoCollections').checked = config.EnableGroupingIntoCollections; + view.querySelector('.chkDisplaySpecialsWithinSeasons').checked = config.DisplaySpecialsWithinSeasons; + view.querySelector('.chkExternalContentInSuggestions').checked = config.EnableExternalContentInSuggestions; + view.querySelector('#chkSaveMetadataHidden').checked = config.SaveMetadataHidden; }); - - view.addEventListener('viewshow', function() { - libraryMenu.setTabs('librarysetup', 1, getTabs); - loadData(); - ApiClient.getSystemInfo().then(function(info) { - if (info.OperatingSystem === 'Windows') { - view.querySelector('.fldSaveMetadataHidden').classList.remove('hide'); - } else { - view.querySelector('.fldSaveMetadataHidden').classList.add('hide'); - } - }); + ApiClient.getNamedConfiguration('metadata').then(function(metadata) { + view.querySelector('#selectDateAdded').selectedIndex = metadata.UseFileCreationTimeForDateAdded ? 1 : 0; }); } -/* eslint-enable indent */ + view.querySelector('form').addEventListener('submit', function(e) { + loading.show(); + const form = this; + ApiClient.getServerConfiguration().then(function(config) { + config.EnableFolderView = form.querySelector('.chkFolderView').checked; + config.EnableGroupingIntoCollections = form.querySelector('.chkGroupMoviesIntoCollections').checked; + config.DisplaySpecialsWithinSeasons = form.querySelector('.chkDisplaySpecialsWithinSeasons').checked; + config.EnableExternalContentInSuggestions = form.querySelector('.chkExternalContentInSuggestions').checked; + config.SaveMetadataHidden = form.querySelector('#chkSaveMetadataHidden').checked; + ApiClient.updateServerConfiguration(config).then(Dashboard.processServerConfigurationUpdateResult); + }); + ApiClient.getNamedConfiguration('metadata').then(function(config) { + config.UseFileCreationTimeForDateAdded = $('#selectDateAdded', form).val() === '1'; + ApiClient.updateNamedConfiguration('metadata', config); + }); + + e.preventDefault(); + loading.hide(); + return false; + }); + + view.addEventListener('viewshow', function() { + libraryMenu.setTabs('librarysetup', 1, getTabs); + loadData(); + ApiClient.getSystemInfo().then(function(info) { + if (info.OperatingSystem === 'Windows') { + view.querySelector('.fldSaveMetadataHidden').classList.remove('hide'); + } else { + view.querySelector('.fldSaveMetadataHidden').classList.add('hide'); + } + }); + }); +} + diff --git a/src/controllers/dashboard/logs.js b/src/controllers/dashboard/logs.js index 23a6d43853..86dcfccd7e 100644 --- a/src/controllers/dashboard/logs.js +++ b/src/controllers/dashboard/logs.js @@ -7,61 +7,57 @@ import '../../styles/flexstyles.scss'; import Dashboard from '../../utils/dashboard'; import alert from '../../components/alert'; -/* eslint-disable indent */ +function onSubmit(event) { + event.preventDefault(); + loading.show(); + const form = this; + ApiClient.getServerConfiguration().then(function (config) { + config.EnableSlowResponseWarning = form.querySelector('#chkSlowResponseWarning').checked; + config.SlowResponseThresholdMs = form.querySelector('#txtSlowResponseWarning').value; + ApiClient.updateServerConfiguration(config).then(function() { + Dashboard.processServerConfigurationUpdateResult(); + }, function () { + alert(globalize.translate('ErrorDefault')); + Dashboard.processServerConfigurationUpdateResult(); + }); + }); + return false; +} - function onSubmit(event) { - event.preventDefault(); +export default function(view) { + view.querySelector('.logsForm').addEventListener('submit', onSubmit); + view.addEventListener('viewbeforeshow', function() { loading.show(); - const form = this; - ApiClient.getServerConfiguration().then(function (config) { - config.EnableSlowResponseWarning = form.querySelector('#chkSlowResponseWarning').checked; - config.SlowResponseThresholdMs = form.querySelector('#txtSlowResponseWarning').value; - ApiClient.updateServerConfiguration(config).then(function() { - Dashboard.processServerConfigurationUpdateResult(); - }, function () { - alert(globalize.translate('ErrorDefault')); - Dashboard.processServerConfigurationUpdateResult(); - }); + const apiClient = ApiClient; + apiClient.getJSON(apiClient.getUrl('System/Logs')).then(function(logs) { + let html = ''; + html += '
'; + html += logs.map(function(log) { + let logUrl = apiClient.getUrl('System/Logs/Log', { + name: log.Name + }); + logUrl += '&api_key=' + apiClient.accessToken(); + let logHtml = ''; + logHtml += ''; + logHtml += '
'; + logHtml += "

" + log.Name + '

'; + const date = datetime.parseISO8601Date(log.DateModified, true); + let text = datetime.toLocaleDateString(date); + text += ' ' + datetime.getDisplayTime(date); + logHtml += '
' + text + '
'; + logHtml += '
'; + logHtml += '
'; + return logHtml; + }).join(''); + html += '
'; + view.querySelector('.serverLogs').innerHTML = html; }); - return false; - } - export default function(view) { - view.querySelector('.logsForm').addEventListener('submit', onSubmit); - view.addEventListener('viewbeforeshow', function() { - loading.show(); - const apiClient = ApiClient; - apiClient.getJSON(apiClient.getUrl('System/Logs')).then(function(logs) { - let html = ''; - html += '
'; - html += logs.map(function(log) { - let logUrl = apiClient.getUrl('System/Logs/Log', { - name: log.Name - }); - logUrl += '&api_key=' + apiClient.accessToken(); - let logHtml = ''; - logHtml += ''; - logHtml += '
'; - logHtml += "

" + log.Name + '

'; - const date = datetime.parseISO8601Date(log.DateModified, true); - let text = datetime.toLocaleDateString(date); - text += ' ' + datetime.getDisplayTime(date); - logHtml += '
' + text + '
'; - logHtml += '
'; - logHtml += '
'; - return logHtml; - }).join(''); - html += '
'; - view.querySelector('.serverLogs').innerHTML = html; - }); - - apiClient.getServerConfiguration().then(function (config) { - view.querySelector('#chkSlowResponseWarning').checked = config.EnableSlowResponseWarning; - view.querySelector('#txtSlowResponseWarning').value = config.SlowResponseThresholdMs; - }); - - loading.hide(); + apiClient.getServerConfiguration().then(function (config) { + view.querySelector('#chkSlowResponseWarning').checked = config.EnableSlowResponseWarning; + view.querySelector('#txtSlowResponseWarning').value = config.SlowResponseThresholdMs; }); - } -/* eslint-enable indent */ + loading.hide(); + }); +} diff --git a/src/controllers/dashboard/metadataImages.js b/src/controllers/dashboard/metadataImages.js index 383143715b..15dcbd8812 100644 --- a/src/controllers/dashboard/metadataImages.js +++ b/src/controllers/dashboard/metadataImages.js @@ -30,88 +30,85 @@ function populateImageResolutionOptions(select) { select.innerHTML = html; } -/* eslint-disable indent */ - - function populateLanguages(select) { - return ApiClient.getCultures().then(function(languages) { - let html = ''; - html += ""; - for (let i = 0, length = languages.length; i < length; i++) { - const culture = languages[i]; - html += "'; - } - select.innerHTML = html; - }); - } - - function populateCountries(select) { - return ApiClient.getCountries().then(function(allCountries) { - let html = ''; - html += ""; - for (let i = 0, length = allCountries.length; i < length; i++) { - const culture = allCountries[i]; - html += "'; - } - select.innerHTML = html; - }); - } - - function loadPage(page) { - const promises = [ - ApiClient.getServerConfiguration(), - populateLanguages(page.querySelector('#selectLanguage')), - populateCountries(page.querySelector('#selectCountry')) - ]; - - populateImageResolutionOptions(page.querySelector('#txtChapterImageResolution')); - - Promise.all(promises).then(function(responses) { - const config = responses[0]; - page.querySelector('#selectLanguage').value = config.PreferredMetadataLanguage || ''; - page.querySelector('#selectCountry').value = config.MetadataCountryCode || ''; - page.querySelector('#valDummyChapterDuration').value = config.DummyChapterDuration || ''; - page.querySelector('#valDummyChapterCount').value = config.DummyChapterCount || ''; - page.querySelector('#txtChapterImageResolution').value = config.ChapterImageResolution || ''; - loading.hide(); - }); - } - - function onSubmit() { - const form = this; - loading.show(); - ApiClient.getServerConfiguration().then(function(config) { - config.PreferredMetadataLanguage = form.querySelector('#selectLanguage').value; - config.MetadataCountryCode = form.querySelector('#selectCountry').value; - config.DummyChapterDuration = form.querySelector('#valDummyChapterDuration').value; - config.DummyChapterCount = form.querySelector('#valDummyChapterCount').value; - config.ChapterImageResolution = form.querySelector('#txtChapterImageResolution').value; - ApiClient.updateServerConfiguration(config).then(Dashboard.processServerConfigurationUpdateResult); - }); - return false; - } - - function getTabs() { - return [{ - href: '#/library.html', - name: globalize.translate('HeaderLibraries') - }, { - href: '#/librarydisplay.html', - name: globalize.translate('Display') - }, { - href: '#/metadataimages.html', - name: globalize.translate('Metadata') - }, { - href: '#/metadatanfo.html', - name: globalize.translate('TabNfoSettings') - }]; - } - - $(document).on('pageinit', '#metadataImagesConfigurationPage', function() { - $('.metadataImagesConfigurationForm').off('submit', onSubmit).on('submit', onSubmit); - }).on('pageshow', '#metadataImagesConfigurationPage', function() { - libraryMenu.setTabs('metadata', 2, getTabs); - loading.show(); - loadPage(this); +function populateLanguages(select) { + return ApiClient.getCultures().then(function(languages) { + let html = ''; + html += ""; + for (let i = 0, length = languages.length; i < length; i++) { + const culture = languages[i]; + html += "'; + } + select.innerHTML = html; }); +} + +function populateCountries(select) { + return ApiClient.getCountries().then(function(allCountries) { + let html = ''; + html += ""; + for (let i = 0, length = allCountries.length; i < length; i++) { + const culture = allCountries[i]; + html += "'; + } + select.innerHTML = html; + }); +} + +function loadPage(page) { + const promises = [ + ApiClient.getServerConfiguration(), + populateLanguages(page.querySelector('#selectLanguage')), + populateCountries(page.querySelector('#selectCountry')) + ]; + + populateImageResolutionOptions(page.querySelector('#txtChapterImageResolution')); + + Promise.all(promises).then(function(responses) { + const config = responses[0]; + page.querySelector('#selectLanguage').value = config.PreferredMetadataLanguage || ''; + page.querySelector('#selectCountry').value = config.MetadataCountryCode || ''; + page.querySelector('#valDummyChapterDuration').value = config.DummyChapterDuration || ''; + page.querySelector('#valDummyChapterCount').value = config.DummyChapterCount || ''; + page.querySelector('#txtChapterImageResolution').value = config.ChapterImageResolution || ''; + loading.hide(); + }); +} + +function onSubmit() { + const form = this; + loading.show(); + ApiClient.getServerConfiguration().then(function(config) { + config.PreferredMetadataLanguage = form.querySelector('#selectLanguage').value; + config.MetadataCountryCode = form.querySelector('#selectCountry').value; + config.DummyChapterDuration = form.querySelector('#valDummyChapterDuration').value; + config.DummyChapterCount = form.querySelector('#valDummyChapterCount').value; + config.ChapterImageResolution = form.querySelector('#txtChapterImageResolution').value; + ApiClient.updateServerConfiguration(config).then(Dashboard.processServerConfigurationUpdateResult); + }); + return false; +} + +function getTabs() { + return [{ + href: '#/library.html', + name: globalize.translate('HeaderLibraries') + }, { + href: '#/librarydisplay.html', + name: globalize.translate('Display') + }, { + href: '#/metadataimages.html', + name: globalize.translate('Metadata') + }, { + href: '#/metadatanfo.html', + name: globalize.translate('TabNfoSettings') + }]; +} + +$(document).on('pageinit', '#metadataImagesConfigurationPage', function() { + $('.metadataImagesConfigurationForm').off('submit', onSubmit).on('submit', onSubmit); +}).on('pageshow', '#metadataImagesConfigurationPage', function() { + libraryMenu.setTabs('metadata', 2, getTabs); + loading.show(); + loadPage(this); +}); -/* eslint-enable indent */ diff --git a/src/controllers/dashboard/metadatanfo.js b/src/controllers/dashboard/metadatanfo.js index 33cad75f7f..38e2ec71fe 100644 --- a/src/controllers/dashboard/metadatanfo.js +++ b/src/controllers/dashboard/metadatanfo.js @@ -6,74 +6,71 @@ import globalize from '../../scripts/globalize'; import Dashboard from '../../utils/dashboard'; import alert from '../../components/alert'; -/* eslint-disable indent */ +function loadPage(page, config, users) { + let html = ''; + html += users.map(function (user) { + return ''; + }).join(''); + $('#selectUser', page).html(html).val(config.UserId || ''); + $('#selectReleaseDateFormat', page).val(config.ReleaseDateFormat); + page.querySelector('#chkSaveImagePaths').checked = config.SaveImagePathsInNfo; + page.querySelector('#chkEnablePathSubstitution').checked = config.EnablePathSubstitution; + page.querySelector('#chkEnableExtraThumbs').checked = config.EnableExtraThumbsDuplication; + loading.hide(); +} - function loadPage(page, config, users) { - let html = ''; - html += users.map(function (user) { - return ''; - }).join(''); - $('#selectUser', page).html(html).val(config.UserId || ''); - $('#selectReleaseDateFormat', page).val(config.ReleaseDateFormat); - page.querySelector('#chkSaveImagePaths').checked = config.SaveImagePathsInNfo; - page.querySelector('#chkEnablePathSubstitution').checked = config.EnablePathSubstitution; - page.querySelector('#chkEnableExtraThumbs').checked = config.EnableExtraThumbsDuplication; - loading.hide(); - } - - function onSubmit() { - loading.show(); - const form = this; - ApiClient.getNamedConfiguration(metadataKey).then(function (config) { - config.UserId = $('#selectUser', form).val() || null; - config.ReleaseDateFormat = $('#selectReleaseDateFormat', form).val(); - config.SaveImagePathsInNfo = form.querySelector('#chkSaveImagePaths').checked; - config.EnablePathSubstitution = form.querySelector('#chkEnablePathSubstitution').checked; - config.EnableExtraThumbsDuplication = form.querySelector('#chkEnableExtraThumbs').checked; - ApiClient.updateNamedConfiguration(metadataKey, config).then(function () { - Dashboard.processServerConfigurationUpdateResult(); - showConfirmMessage(); - }); - }); - return false; - } - - function showConfirmMessage() { - const msg = []; - msg.push(globalize.translate('MetadataSettingChangeHelp')); - alert({ - text: msg.join('

') - }); - } - - function getTabs() { - return [{ - href: '#/library.html', - name: globalize.translate('HeaderLibraries') - }, { - href: '#/librarydisplay.html', - name: globalize.translate('Display') - }, { - href: '#/metadataimages.html', - name: globalize.translate('Metadata') - }, { - href: '#/metadatanfo.html', - name: globalize.translate('TabNfoSettings') - }]; - } - - const metadataKey = 'xbmcmetadata'; - $(document).on('pageinit', '#metadataNfoPage', function () { - $('.metadataNfoForm').off('submit', onSubmit).on('submit', onSubmit); - }).on('pageshow', '#metadataNfoPage', function () { - libraryMenu.setTabs('metadata', 3, getTabs); - loading.show(); - const page = this; - const promise1 = ApiClient.getUsers(); - const promise2 = ApiClient.getNamedConfiguration(metadataKey); - Promise.all([promise1, promise2]).then(function (responses) { - loadPage(page, responses[1], responses[0]); +function onSubmit() { + loading.show(); + const form = this; + ApiClient.getNamedConfiguration(metadataKey).then(function (config) { + config.UserId = $('#selectUser', form).val() || null; + config.ReleaseDateFormat = $('#selectReleaseDateFormat', form).val(); + config.SaveImagePathsInNfo = form.querySelector('#chkSaveImagePaths').checked; + config.EnablePathSubstitution = form.querySelector('#chkEnablePathSubstitution').checked; + config.EnableExtraThumbsDuplication = form.querySelector('#chkEnableExtraThumbs').checked; + ApiClient.updateNamedConfiguration(metadataKey, config).then(function () { + Dashboard.processServerConfigurationUpdateResult(); + showConfirmMessage(); }); }); + return false; +} + +function showConfirmMessage() { + const msg = []; + msg.push(globalize.translate('MetadataSettingChangeHelp')); + alert({ + text: msg.join('

') + }); +} + +function getTabs() { + return [{ + href: '#/library.html', + name: globalize.translate('HeaderLibraries') + }, { + href: '#/librarydisplay.html', + name: globalize.translate('Display') + }, { + href: '#/metadataimages.html', + name: globalize.translate('Metadata') + }, { + href: '#/metadatanfo.html', + name: globalize.translate('TabNfoSettings') + }]; +} + +const metadataKey = 'xbmcmetadata'; +$(document).on('pageinit', '#metadataNfoPage', function () { + $('.metadataNfoForm').off('submit', onSubmit).on('submit', onSubmit); +}).on('pageshow', '#metadataNfoPage', function () { + libraryMenu.setTabs('metadata', 3, getTabs); + loading.show(); + const page = this; + const promise1 = ApiClient.getUsers(); + const promise2 = ApiClient.getNamedConfiguration(metadataKey); + Promise.all([promise1, promise2]).then(function (responses) { + loadPage(page, responses[1], responses[0]); + }); +}); -/* eslint-enable indent */ diff --git a/src/controllers/dashboard/networking.js b/src/controllers/dashboard/networking.js index a98715705c..57a8a51b53 100644 --- a/src/controllers/dashboard/networking.js +++ b/src/controllers/dashboard/networking.js @@ -5,205 +5,202 @@ import '../../elements/emby-select/emby-select'; import Dashboard from '../../utils/dashboard'; import alert from '../../components/alert'; -/* eslint-disable indent */ +function onSubmit(e) { + const form = this; + const localAddress = form.querySelector('#txtLocalAddress').value; + const enableUpnp = form.querySelector('#chkEnableUpnp').checked; + confirmSelections(localAddress, enableUpnp, function () { + const validationResult = getValidationAlert(form); - function onSubmit(e) { - const form = this; - const localAddress = form.querySelector('#txtLocalAddress').value; - const enableUpnp = form.querySelector('#chkEnableUpnp').checked; - confirmSelections(localAddress, enableUpnp, function () { - const validationResult = getValidationAlert(form); - - if (validationResult) { - showAlertText(validationResult); - return; - } - - validateHttps(form).then(function () { - loading.show(); - ApiClient.getNamedConfiguration('network').then(function (config) { - config.LocalNetworkSubnets = form.querySelector('#txtLanNetworks').value.split(',').map(function (s) { - return s.trim(); - }).filter(function (s) { - return s.length > 0; - }); - config.RemoteIPFilter = form.querySelector('#txtExternalAddressFilter').value.split(',').map(function (s) { - return s.trim(); - }).filter(function (s) { - return s.length > 0; - }); - config.KnownProxies = form.querySelector('#txtKnownProxies').value.split(',').map(function (s) { - return s.trim(); - }).filter(function (s) { - return s.length > 0; - }); - config.LocalNetworkAddresses = form.querySelector('#txtLocalAddress').value.split(',').map(function (s) { - return s.trim(); - }).filter(function (s) { - return s.length > 0; - }); - - config.PublishedServerUriBySubnet = form.querySelector('#txtPublishedServer').value.split(',').map(function (s) { - return s.trim(); - }).filter(function (s) { - return s.length > 0; - }); - - config.IsRemoteIPFilterBlacklist = form.querySelector('#selectExternalAddressFilterMode').value === 'blacklist'; - config.PublicPort = form.querySelector('#txtPublicPort').value; - config.PublicHttpsPort = form.querySelector('#txtPublicHttpsPort').value; - config.HttpServerPortNumber = form.querySelector('#txtPortNumber').value; - config.HttpsPortNumber = form.querySelector('#txtHttpsPort').value; - config.EnableHttps = form.querySelector('#chkEnableHttps').checked; - config.RequireHttps = form.querySelector('#chkRequireHttps').checked; - config.EnableUPnP = enableUpnp; - config.BaseUrl = form.querySelector('#txtBaseUrl').value; - config.EnableRemoteAccess = form.querySelector('#chkRemoteAccess').checked; - config.CertificatePath = form.querySelector('#txtCertificatePath').value || null; - config.CertificatePassword = form.querySelector('#txtCertPassword').value || null; - config.UPnPCreateHttpPortMap = form.querySelector('#chkCreateHttpPortMap').checked; - config.AutoDiscovery = form.querySelector('#chkAutodiscovery').checked; - config.AutoDiscoveryTracing = form.querySelector('#chkAutodiscoveryTracing').checked; - config.EnableIPV6 = form.querySelector('#chkEnableIP6').checked; - config.EnableIPV4 = form.querySelector('#chkEnableIP4').checked; - config.UPnPCreateHttpPortMap = form.querySelector('#chkCreateHttpPortMap').checked; - config.UDPPortRange = form.querySelector('#txtUDPPortRange').value; - config.HDHomerunPortRange = form.querySelector('#txtHDHomerunPortRange').value; - config.EnableSSDPTracing = form.querySelector('#chkEnableSSDPTracing').checked; - config.SSDPTracingFilter = form.querySelector('#txtSSDPTracingFilter').value; - ApiClient.updateNamedConfiguration('network', config).then(Dashboard.processServerConfigurationUpdateResult, Dashboard.processErrorResponse); - }); - }); - }); - e.preventDefault(); - } - - function triggerChange(select) { - const evt = document.createEvent('HTMLEvents'); - evt.initEvent('change', false, true); - select.dispatchEvent(evt); - } - - function getValidationAlert(form) { - if (form.querySelector('#txtPublicPort').value === form.querySelector('#txtPublicHttpsPort').value) { - return 'The public http and https ports must be different.'; + if (validationResult) { + showAlertText(validationResult); + return; } - if (form.querySelector('#txtPortNumber').value === form.querySelector('#txtHttpsPort').value) { - return 'The http and https ports must be different.'; - } - - if (!form.querySelector('#chkEnableIP6').checked && !form.querySelector('#chkEnableIP4').checked) { - return 'Either IPv4 or IPv6 need to be checked.'; - } - - return null; - } - - function validateHttps(form) { - const certPath = form.querySelector('#txtCertificatePath').value || null; - const httpsEnabled = form.querySelector('#chkEnableHttps').checked; - - if (httpsEnabled && !certPath) { - return showAlertText({ - title: globalize.translate('TitleHostingSettings'), - text: globalize.translate('HttpsRequiresCert') - }).then(Promise.reject); - } - - return Promise.resolve(); - } - - function showAlertText(options) { - return new Promise(function (resolve, reject) { - alert(options).then(resolve, reject); - }); - } - - function confirmSelections(localAddress, enableUpnp, callback) { - if (localAddress || !enableUpnp) { - showAlertText({ - title: globalize.translate('TitleHostingSettings'), - text: globalize.translate('SettingsWarning') - }).then(callback); - } else { - callback(); - } - } - - export default function (view) { - function loadPage(page, config) { - page.querySelector('#txtPortNumber').value = config.HttpServerPortNumber; - page.querySelector('#txtPublicPort').value = config.PublicPort; - page.querySelector('#txtPublicHttpsPort').value = config.PublicHttpsPort; - page.querySelector('#txtLocalAddress').value = (config.LocalNetworkAddresses || []).join(', '); - page.querySelector('#txtLanNetworks').value = (config.LocalNetworkSubnets || []).join(', '); - page.querySelector('#txtKnownProxies').value = (config.KnownProxies || []).join(', '); - page.querySelector('#txtExternalAddressFilter').value = (config.RemoteIPFilter || []).join(', '); - page.querySelector('#selectExternalAddressFilterMode').value = config.IsRemoteIPFilterBlacklist ? 'blacklist' : 'whitelist'; - page.querySelector('#chkRemoteAccess').checked = config.EnableRemoteAccess == null || config.EnableRemoteAccess; - page.querySelector('#txtHttpsPort').value = config.HttpsPortNumber; - page.querySelector('#chkEnableHttps').checked = config.EnableHttps; - page.querySelector('#chkRequireHttps').checked = config.RequireHttps; - page.querySelector('#txtBaseUrl').value = config.BaseUrl || ''; - const txtCertificatePath = page.querySelector('#txtCertificatePath'); - txtCertificatePath.value = config.CertificatePath || ''; - page.querySelector('#txtCertPassword').value = config.CertificatePassword || ''; - page.querySelector('#chkEnableUpnp').checked = config.EnableUPnP; - triggerChange(page.querySelector('#chkRemoteAccess')); - page.querySelector('#chkCreateHttpPortMap').checked = config.UPnPCreateHttpPortMap; - page.querySelector('#chkAutodiscovery').checked = config.AutoDiscovery; - page.querySelector('#chkAutodiscoveryTracing').checked = config.AutoDiscoveryTracing; - page.querySelector('#chkEnableIP6').checked = config.EnableIPV6; - page.querySelector('#chkEnableIP4').checked = config.EnableIPV4; - page.querySelector('#chkCreateHttpPortMap').checked = config.UPnPCreateHttpPortMap; - page.querySelector('#txtUDPPortRange').value = config.UDPPortRange || ''; - page.querySelector('#txtHDHomerunPortRange').checked = config.HDHomerunPortRange || ''; - page.querySelector('#chkEnableSSDPTracing').checked = config.EnableSSDPTracing; - page.querySelector('#txtSSDPTracingFilter').value = config.SSDPTracingFilter || ''; - page.querySelector('#txtPublishedServer').value = (config.PublishedServerUriBySubnet || []).join(', '); - loading.hide(); - } - - view.querySelector('#chkRemoteAccess').addEventListener('change', function () { - if (this.checked) { - view.querySelector('.fldExternalAddressFilter').classList.remove('hide'); - view.querySelector('.fldExternalAddressFilterMode').classList.remove('hide'); - view.querySelector('.fldPublicPort').classList.remove('hide'); - view.querySelector('.fldPublicHttpsPort').classList.remove('hide'); - view.querySelector('.fldEnableUpnp').classList.remove('hide'); - } else { - view.querySelector('.fldExternalAddressFilter').classList.add('hide'); - view.querySelector('.fldExternalAddressFilterMode').classList.add('hide'); - view.querySelector('.fldPublicPort').classList.add('hide'); - view.querySelector('.fldPublicHttpsPort').classList.add('hide'); - view.querySelector('.fldEnableUpnp').classList.add('hide'); - } - }); - view.querySelector('#btnSelectCertPath').addEventListener('click', function () { - import('../../components/directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => { - const picker = new DirectoryBrowser(); - picker.show({ - includeFiles: true, - includeDirectories: true, - callback: function (path) { - if (path) { - view.querySelector('#txtCertificatePath').value = path; - } - - picker.close(); - }, - header: globalize.translate('HeaderSelectCertificatePath') - }); - }); - }); - view.querySelector('.dashboardHostingForm').addEventListener('submit', onSubmit); - view.addEventListener('viewshow', function () { + validateHttps(form).then(function () { loading.show(); ApiClient.getNamedConfiguration('network').then(function (config) { - loadPage(view, config); + config.LocalNetworkSubnets = form.querySelector('#txtLanNetworks').value.split(',').map(function (s) { + return s.trim(); + }).filter(function (s) { + return s.length > 0; + }); + config.RemoteIPFilter = form.querySelector('#txtExternalAddressFilter').value.split(',').map(function (s) { + return s.trim(); + }).filter(function (s) { + return s.length > 0; + }); + config.KnownProxies = form.querySelector('#txtKnownProxies').value.split(',').map(function (s) { + return s.trim(); + }).filter(function (s) { + return s.length > 0; + }); + config.LocalNetworkAddresses = form.querySelector('#txtLocalAddress').value.split(',').map(function (s) { + return s.trim(); + }).filter(function (s) { + return s.length > 0; + }); + + config.PublishedServerUriBySubnet = form.querySelector('#txtPublishedServer').value.split(',').map(function (s) { + return s.trim(); + }).filter(function (s) { + return s.length > 0; + }); + + config.IsRemoteIPFilterBlacklist = form.querySelector('#selectExternalAddressFilterMode').value === 'blacklist'; + config.PublicPort = form.querySelector('#txtPublicPort').value; + config.PublicHttpsPort = form.querySelector('#txtPublicHttpsPort').value; + config.HttpServerPortNumber = form.querySelector('#txtPortNumber').value; + config.HttpsPortNumber = form.querySelector('#txtHttpsPort').value; + config.EnableHttps = form.querySelector('#chkEnableHttps').checked; + config.RequireHttps = form.querySelector('#chkRequireHttps').checked; + config.EnableUPnP = enableUpnp; + config.BaseUrl = form.querySelector('#txtBaseUrl').value; + config.EnableRemoteAccess = form.querySelector('#chkRemoteAccess').checked; + config.CertificatePath = form.querySelector('#txtCertificatePath').value || null; + config.CertificatePassword = form.querySelector('#txtCertPassword').value || null; + config.UPnPCreateHttpPortMap = form.querySelector('#chkCreateHttpPortMap').checked; + config.AutoDiscovery = form.querySelector('#chkAutodiscovery').checked; + config.AutoDiscoveryTracing = form.querySelector('#chkAutodiscoveryTracing').checked; + config.EnableIPV6 = form.querySelector('#chkEnableIP6').checked; + config.EnableIPV4 = form.querySelector('#chkEnableIP4').checked; + config.UPnPCreateHttpPortMap = form.querySelector('#chkCreateHttpPortMap').checked; + config.UDPPortRange = form.querySelector('#txtUDPPortRange').value; + config.HDHomerunPortRange = form.querySelector('#txtHDHomerunPortRange').value; + config.EnableSSDPTracing = form.querySelector('#chkEnableSSDPTracing').checked; + config.SSDPTracingFilter = form.querySelector('#txtSSDPTracingFilter').value; + ApiClient.updateNamedConfiguration('network', config).then(Dashboard.processServerConfigurationUpdateResult, Dashboard.processErrorResponse); }); }); + }); + e.preventDefault(); +} + +function triggerChange(select) { + const evt = document.createEvent('HTMLEvents'); + evt.initEvent('change', false, true); + select.dispatchEvent(evt); +} + +function getValidationAlert(form) { + if (form.querySelector('#txtPublicPort').value === form.querySelector('#txtPublicHttpsPort').value) { + return 'The public http and https ports must be different.'; } -/* eslint-enable indent */ + if (form.querySelector('#txtPortNumber').value === form.querySelector('#txtHttpsPort').value) { + return 'The http and https ports must be different.'; + } + + if (!form.querySelector('#chkEnableIP6').checked && !form.querySelector('#chkEnableIP4').checked) { + return 'Either IPv4 or IPv6 need to be checked.'; + } + + return null; +} + +function validateHttps(form) { + const certPath = form.querySelector('#txtCertificatePath').value || null; + const httpsEnabled = form.querySelector('#chkEnableHttps').checked; + + if (httpsEnabled && !certPath) { + return showAlertText({ + title: globalize.translate('TitleHostingSettings'), + text: globalize.translate('HttpsRequiresCert') + }).then(Promise.reject); + } + + return Promise.resolve(); +} + +function showAlertText(options) { + return new Promise(function (resolve, reject) { + alert(options).then(resolve, reject); + }); +} + +function confirmSelections(localAddress, enableUpnp, callback) { + if (localAddress || !enableUpnp) { + showAlertText({ + title: globalize.translate('TitleHostingSettings'), + text: globalize.translate('SettingsWarning') + }).then(callback); + } else { + callback(); + } +} + +export default function (view) { + function loadPage(page, config) { + page.querySelector('#txtPortNumber').value = config.HttpServerPortNumber; + page.querySelector('#txtPublicPort').value = config.PublicPort; + page.querySelector('#txtPublicHttpsPort').value = config.PublicHttpsPort; + page.querySelector('#txtLocalAddress').value = (config.LocalNetworkAddresses || []).join(', '); + page.querySelector('#txtLanNetworks').value = (config.LocalNetworkSubnets || []).join(', '); + page.querySelector('#txtKnownProxies').value = (config.KnownProxies || []).join(', '); + page.querySelector('#txtExternalAddressFilter').value = (config.RemoteIPFilter || []).join(', '); + page.querySelector('#selectExternalAddressFilterMode').value = config.IsRemoteIPFilterBlacklist ? 'blacklist' : 'whitelist'; + page.querySelector('#chkRemoteAccess').checked = config.EnableRemoteAccess == null || config.EnableRemoteAccess; + page.querySelector('#txtHttpsPort').value = config.HttpsPortNumber; + page.querySelector('#chkEnableHttps').checked = config.EnableHttps; + page.querySelector('#chkRequireHttps').checked = config.RequireHttps; + page.querySelector('#txtBaseUrl').value = config.BaseUrl || ''; + const txtCertificatePath = page.querySelector('#txtCertificatePath'); + txtCertificatePath.value = config.CertificatePath || ''; + page.querySelector('#txtCertPassword').value = config.CertificatePassword || ''; + page.querySelector('#chkEnableUpnp').checked = config.EnableUPnP; + triggerChange(page.querySelector('#chkRemoteAccess')); + page.querySelector('#chkCreateHttpPortMap').checked = config.UPnPCreateHttpPortMap; + page.querySelector('#chkAutodiscovery').checked = config.AutoDiscovery; + page.querySelector('#chkAutodiscoveryTracing').checked = config.AutoDiscoveryTracing; + page.querySelector('#chkEnableIP6').checked = config.EnableIPV6; + page.querySelector('#chkEnableIP4').checked = config.EnableIPV4; + page.querySelector('#chkCreateHttpPortMap').checked = config.UPnPCreateHttpPortMap; + page.querySelector('#txtUDPPortRange').value = config.UDPPortRange || ''; + page.querySelector('#txtHDHomerunPortRange').checked = config.HDHomerunPortRange || ''; + page.querySelector('#chkEnableSSDPTracing').checked = config.EnableSSDPTracing; + page.querySelector('#txtSSDPTracingFilter').value = config.SSDPTracingFilter || ''; + page.querySelector('#txtPublishedServer').value = (config.PublishedServerUriBySubnet || []).join(', '); + loading.hide(); + } + + view.querySelector('#chkRemoteAccess').addEventListener('change', function () { + if (this.checked) { + view.querySelector('.fldExternalAddressFilter').classList.remove('hide'); + view.querySelector('.fldExternalAddressFilterMode').classList.remove('hide'); + view.querySelector('.fldPublicPort').classList.remove('hide'); + view.querySelector('.fldPublicHttpsPort').classList.remove('hide'); + view.querySelector('.fldEnableUpnp').classList.remove('hide'); + } else { + view.querySelector('.fldExternalAddressFilter').classList.add('hide'); + view.querySelector('.fldExternalAddressFilterMode').classList.add('hide'); + view.querySelector('.fldPublicPort').classList.add('hide'); + view.querySelector('.fldPublicHttpsPort').classList.add('hide'); + view.querySelector('.fldEnableUpnp').classList.add('hide'); + } + }); + view.querySelector('#btnSelectCertPath').addEventListener('click', function () { + import('../../components/directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => { + const picker = new DirectoryBrowser(); + picker.show({ + includeFiles: true, + includeDirectories: true, + callback: function (path) { + if (path) { + view.querySelector('#txtCertificatePath').value = path; + } + + picker.close(); + }, + header: globalize.translate('HeaderSelectCertificatePath') + }); + }); + }); + view.querySelector('.dashboardHostingForm').addEventListener('submit', onSubmit); + view.addEventListener('viewshow', function () { + loading.show(); + ApiClient.getNamedConfiguration('network').then(function (config) { + loadPage(view, config); + }); + }); +} + diff --git a/src/controllers/dashboard/playback.js b/src/controllers/dashboard/playback.js index 90df298120..f24e77efd9 100644 --- a/src/controllers/dashboard/playback.js +++ b/src/controllers/dashboard/playback.js @@ -4,55 +4,52 @@ import libraryMenu from '../../scripts/libraryMenu'; import globalize from '../../scripts/globalize'; import Dashboard from '../../utils/dashboard'; -/* eslint-disable indent */ +function loadPage(page, config) { + $('#txtMinResumePct', page).val(config.MinResumePct); + $('#txtMaxResumePct', page).val(config.MaxResumePct); + $('#txtMinAudiobookResume', page).val(config.MinAudiobookResume); + $('#txtMaxAudiobookResume', page).val(config.MaxAudiobookResume); + $('#txtMinResumeDuration', page).val(config.MinResumeDurationSeconds); + loading.hide(); +} - function loadPage(page, config) { - $('#txtMinResumePct', page).val(config.MinResumePct); - $('#txtMaxResumePct', page).val(config.MaxResumePct); - $('#txtMinAudiobookResume', page).val(config.MinAudiobookResume); - $('#txtMaxAudiobookResume', page).val(config.MaxAudiobookResume); - $('#txtMinResumeDuration', page).val(config.MinResumeDurationSeconds); - loading.hide(); - } +function onSubmit() { + loading.show(); + const form = this; + ApiClient.getServerConfiguration().then(function (config) { + config.MinResumePct = $('#txtMinResumePct', form).val(); + config.MaxResumePct = $('#txtMaxResumePct', form).val(); + config.MinAudiobookResume = $('#txtMinAudiobookResume', form).val(); + config.MaxAudiobookResume = $('#txtMaxAudiobookResume', form).val(); + config.MinResumeDurationSeconds = $('#txtMinResumeDuration', form).val(); - function onSubmit() { - loading.show(); - const form = this; - ApiClient.getServerConfiguration().then(function (config) { - config.MinResumePct = $('#txtMinResumePct', form).val(); - config.MaxResumePct = $('#txtMaxResumePct', form).val(); - config.MinAudiobookResume = $('#txtMinAudiobookResume', form).val(); - config.MaxAudiobookResume = $('#txtMaxAudiobookResume', form).val(); - config.MinResumeDurationSeconds = $('#txtMinResumeDuration', form).val(); - - ApiClient.updateServerConfiguration(config).then(Dashboard.processServerConfigurationUpdateResult); - }); - - return false; - } - - function getTabs() { - return [{ - href: '#/encodingsettings.html', - name: globalize.translate('Transcoding') - }, { - href: '#/playbackconfiguration.html', - name: globalize.translate('ButtonResume') - }, { - href: '#/streamingsettings.html', - name: globalize.translate('TabStreaming') - }]; - } - - $(document).on('pageinit', '#playbackConfigurationPage', function () { - $('.playbackConfigurationForm').off('submit', onSubmit).on('submit', onSubmit); - }).on('pageshow', '#playbackConfigurationPage', function () { - loading.show(); - libraryMenu.setTabs('playback', 1, getTabs); - const page = this; - ApiClient.getServerConfiguration().then(function (config) { - loadPage(page, config); - }); + ApiClient.updateServerConfiguration(config).then(Dashboard.processServerConfigurationUpdateResult); }); -/* eslint-enable indent */ + return false; +} + +function getTabs() { + return [{ + href: '#/encodingsettings.html', + name: globalize.translate('Transcoding') + }, { + href: '#/playbackconfiguration.html', + name: globalize.translate('ButtonResume') + }, { + href: '#/streamingsettings.html', + name: globalize.translate('TabStreaming') + }]; +} + +$(document).on('pageinit', '#playbackConfigurationPage', function () { + $('.playbackConfigurationForm').off('submit', onSubmit).on('submit', onSubmit); +}).on('pageshow', '#playbackConfigurationPage', function () { + loading.show(); + libraryMenu.setTabs('playback', 1, getTabs); + const page = this; + ApiClient.getServerConfiguration().then(function (config) { + loadPage(page, config); + }); +}); + diff --git a/src/controllers/dashboard/scheduledtasks/scheduledtask.js b/src/controllers/dashboard/scheduledtasks/scheduledtask.js index 64db2e4292..909bbfffe3 100644 --- a/src/controllers/dashboard/scheduledtasks/scheduledtask.js +++ b/src/controllers/dashboard/scheduledtasks/scheduledtask.js @@ -9,238 +9,235 @@ import '../../../elements/emby-select/emby-select'; import confirm from '../../../components/confirm/confirm'; import { getParameterByName } from '../../../utils/url.ts'; -/* eslint-disable indent */ +function fillTimeOfDay(select) { + const options = []; - function fillTimeOfDay(select) { - const options = []; - - for (let i = 0; i < 86400000; i += 900000) { - options.push({ - name: ScheduledTaskPage.getDisplayTime(i * 10000), - value: i * 10000 - }); - } - - select.innerHTML = options.map(function (o) { - return ''; - }).join(''); + for (let i = 0; i < 86400000; i += 900000) { + options.push({ + name: ScheduledTaskPage.getDisplayTime(i * 10000), + value: i * 10000 + }); } - Array.prototype.remove = function (from, to) { - const rest = this.slice((to || from) + 1 || this.length); - this.length = from < 0 ? this.length + from : from; - return this.push.apply(this, rest); - }; + select.innerHTML = options.map(function (o) { + return ''; + }).join(''); +} - const ScheduledTaskPage = { - refreshScheduledTask: function (view) { - loading.show(); - const id = getParameterByName('id'); - ApiClient.getScheduledTask(id).then(function (task) { - ScheduledTaskPage.loadScheduledTask(view, task); - }); - }, - loadScheduledTask: function (view, task) { - $('.taskName', view).html(task.Name); - $('#pTaskDescription', view).html(task.Description); +Array.prototype.remove = function (from, to) { + const rest = this.slice((to || from) + 1 || this.length); + this.length = from < 0 ? this.length + from : from; + return this.push.apply(this, rest); +}; - import('../../../components/listview/listview.scss').then(() => { - ScheduledTaskPage.loadTaskTriggers(view, task); - }); +const ScheduledTaskPage = { + refreshScheduledTask: function (view) { + loading.show(); + const id = getParameterByName('id'); + ApiClient.getScheduledTask(id).then(function (task) { + ScheduledTaskPage.loadScheduledTask(view, task); + }); + }, + loadScheduledTask: function (view, task) { + $('.taskName', view).html(task.Name); + $('#pTaskDescription', view).html(task.Description); - loading.hide(); - }, - loadTaskTriggers: function (context, task) { - let html = ''; - html += '
'; + import('../../../components/listview/listview.scss').then(() => { + ScheduledTaskPage.loadTaskTriggers(view, task); + }); - for (let i = 0, length = task.Triggers.length; i < length; i++) { - const trigger = task.Triggers[i]; + loading.hide(); + }, + loadTaskTriggers: function (context, task) { + let html = ''; + html += '
'; - html += '
'; - html += ''; - if (trigger.MaxRuntimeMs) { - html += '
'; + for (let i = 0, length = task.Triggers.length; i < length; i++) { + const trigger = task.Triggers[i]; + + html += '
'; + html += ''; + if (trigger.MaxRuntimeMs) { + html += '
'; + } else { + html += '
'; + } + html += "
" + ScheduledTaskPage.getTriggerFriendlyName(trigger) + '
'; + if (trigger.MaxRuntimeMs) { + html += '
'; + const hours = trigger.MaxRuntimeTicks / 36e9; + if (hours == 1) { + html += globalize.translate('ValueTimeLimitSingleHour'); } else { - html += '
'; + html += globalize.translate('ValueTimeLimitMultiHour', hours); } - html += "
" + ScheduledTaskPage.getTriggerFriendlyName(trigger) + '
'; - if (trigger.MaxRuntimeMs) { - html += '
'; - const hours = trigger.MaxRuntimeTicks / 36e9; - if (hours == 1) { - html += globalize.translate('ValueTimeLimitSingleHour'); - } else { - html += globalize.translate('ValueTimeLimitMultiHour', hours); - } - html += '
'; - } - - html += '
'; - html += ''; html += '
'; } html += '
'; - context.querySelector('.taskTriggers').innerHTML = html; - }, - // TODO: Replace this mess with date-fns and remove datetime completely - getTriggerFriendlyName: function (trigger) { - if (trigger.Type == 'DailyTrigger') { - return globalize.translate('DailyAt', ScheduledTaskPage.getDisplayTime(trigger.TimeOfDayTicks)); - } - - if (trigger.Type == 'WeeklyTrigger') { - // TODO: The day of week isn't localised as well - return globalize.translate('WeeklyAt', trigger.DayOfWeek, ScheduledTaskPage.getDisplayTime(trigger.TimeOfDayTicks)); - } - - if (trigger.Type == 'SystemEventTrigger' && trigger.SystemEvent == 'WakeFromSleep') { - return globalize.translate('OnWakeFromSleep'); - } - - if (trigger.Type == 'IntervalTrigger') { - const hours = trigger.IntervalTicks / 36e9; - - if (hours == 0.25) { - return globalize.translate('EveryXMinutes', '15'); - } - if (hours == 0.5) { - return globalize.translate('EveryXMinutes', '30'); - } - if (hours == 0.75) { - return globalize.translate('EveryXMinutes', '45'); - } - if (hours == 1) { - return globalize.translate('EveryHour'); - } - - return globalize.translate('EveryXHours', hours); - } - - if (trigger.Type == 'StartupTrigger') { - return globalize.translate('OnApplicationStartup'); - } - - return trigger.Type; - }, - getDisplayTime: function (ticks) { - const ms = ticks / 1e4; - const now = new Date(); - now.setHours(0, 0, 0, 0); - now.setTime(now.getTime() + ms); - return datetime.getDisplayTime(now); - }, - showAddTriggerPopup: function (view) { - $('#selectTriggerType', view).val('DailyTrigger'); - view.querySelector('#selectTriggerType').dispatchEvent(new CustomEvent('change', {})); - $('#popupAddTrigger', view).removeClass('hide'); - }, - confirmDeleteTrigger: function (view, index) { - confirm(globalize.translate('MessageDeleteTaskTrigger'), globalize.translate('HeaderDeleteTaskTrigger')).then(function () { - ScheduledTaskPage.deleteTrigger(view, index); - }); - }, - deleteTrigger: function (view, index) { - loading.show(); - const id = getParameterByName('id'); - ApiClient.getScheduledTask(id).then(function (task) { - task.Triggers.remove(index); - ApiClient.updateScheduledTaskTriggers(task.Id, task.Triggers).then(function () { - ScheduledTaskPage.refreshScheduledTask(view); - }); - }); - }, - refreshTriggerFields: function (page, triggerType) { - if (triggerType == 'DailyTrigger') { - $('#fldTimeOfDay', page).show(); - $('#fldDayOfWeek', page).hide(); - $('#fldSelectSystemEvent', page).hide(); - $('#fldSelectInterval', page).hide(); - $('#selectTimeOfDay', page).attr('required', 'required'); - } else if (triggerType == 'WeeklyTrigger') { - $('#fldTimeOfDay', page).show(); - $('#fldDayOfWeek', page).show(); - $('#fldSelectSystemEvent', page).hide(); - $('#fldSelectInterval', page).hide(); - $('#selectTimeOfDay', page).attr('required', 'required'); - } else if (triggerType == 'SystemEventTrigger') { - $('#fldTimeOfDay', page).hide(); - $('#fldDayOfWeek', page).hide(); - $('#fldSelectSystemEvent', page).show(); - $('#fldSelectInterval', page).hide(); - $('#selectTimeOfDay', page).removeAttr('required'); - } else if (triggerType == 'IntervalTrigger') { - $('#fldTimeOfDay', page).hide(); - $('#fldDayOfWeek', page).hide(); - $('#fldSelectSystemEvent', page).hide(); - $('#fldSelectInterval', page).show(); - $('#selectTimeOfDay', page).removeAttr('required'); - } else if (triggerType == 'StartupTrigger') { - $('#fldTimeOfDay', page).hide(); - $('#fldDayOfWeek', page).hide(); - $('#fldSelectSystemEvent', page).hide(); - $('#fldSelectInterval', page).hide(); - $('#selectTimeOfDay', page).removeAttr('required'); - } - }, - getTriggerToAdd: function (page) { - const trigger = { - Type: $('#selectTriggerType', page).val() - }; - - if (trigger.Type == 'DailyTrigger') { - trigger.TimeOfDayTicks = $('#selectTimeOfDay', page).val(); - } else if (trigger.Type == 'WeeklyTrigger') { - trigger.DayOfWeek = $('#selectDayOfWeek', page).val(); - trigger.TimeOfDayTicks = $('#selectTimeOfDay', page).val(); - } else if (trigger.Type == 'SystemEventTrigger') { - trigger.SystemEvent = $('#selectSystemEvent', page).val(); - } else if (trigger.Type == 'IntervalTrigger') { - trigger.IntervalTicks = $('#selectInterval', page).val(); - } - - let timeLimit = $('#txtTimeLimit', page).val() || '0'; - timeLimit = parseFloat(timeLimit) * 3600000; - - trigger.MaxRuntimeMs = timeLimit || null; - - return trigger; - } - }; - export default function (view) { - function onSubmit(e) { - loading.show(); - const id = getParameterByName('id'); - ApiClient.getScheduledTask(id).then(function (task) { - task.Triggers.push(ScheduledTaskPage.getTriggerToAdd(view)); - ApiClient.updateScheduledTaskTriggers(task.Id, task.Triggers).then(function () { - $('#popupAddTrigger').addClass('hide'); - ScheduledTaskPage.refreshScheduledTask(view); - }); - }); - e.preventDefault(); + html += ''; + html += '
'; } - view.querySelector('.addTriggerForm').addEventListener('submit', onSubmit); - fillTimeOfDay(view.querySelector('#selectTimeOfDay')); - $(view.querySelector('#popupAddTrigger').parentNode).trigger('create'); - view.querySelector('.selectTriggerType').addEventListener('change', function () { - ScheduledTaskPage.refreshTriggerFields(view, this.value); - }); - view.querySelector('.btnAddTrigger').addEventListener('click', function () { - ScheduledTaskPage.showAddTriggerPopup(view); - }); - view.addEventListener('click', function (e) { - const btnDeleteTrigger = dom.parentWithClass(e.target, 'btnDeleteTrigger'); + html += '
'; + context.querySelector('.taskTriggers').innerHTML = html; + }, + // TODO: Replace this mess with date-fns and remove datetime completely + getTriggerFriendlyName: function (trigger) { + if (trigger.Type == 'DailyTrigger') { + return globalize.translate('DailyAt', ScheduledTaskPage.getDisplayTime(trigger.TimeOfDayTicks)); + } - if (btnDeleteTrigger) { - ScheduledTaskPage.confirmDeleteTrigger(view, parseInt(btnDeleteTrigger.getAttribute('data-index'), 10)); + if (trigger.Type == 'WeeklyTrigger') { + // TODO: The day of week isn't localised as well + return globalize.translate('WeeklyAt', trigger.DayOfWeek, ScheduledTaskPage.getDisplayTime(trigger.TimeOfDayTicks)); + } + + if (trigger.Type == 'SystemEventTrigger' && trigger.SystemEvent == 'WakeFromSleep') { + return globalize.translate('OnWakeFromSleep'); + } + + if (trigger.Type == 'IntervalTrigger') { + const hours = trigger.IntervalTicks / 36e9; + + if (hours == 0.25) { + return globalize.translate('EveryXMinutes', '15'); } + if (hours == 0.5) { + return globalize.translate('EveryXMinutes', '30'); + } + if (hours == 0.75) { + return globalize.translate('EveryXMinutes', '45'); + } + if (hours == 1) { + return globalize.translate('EveryHour'); + } + + return globalize.translate('EveryXHours', hours); + } + + if (trigger.Type == 'StartupTrigger') { + return globalize.translate('OnApplicationStartup'); + } + + return trigger.Type; + }, + getDisplayTime: function (ticks) { + const ms = ticks / 1e4; + const now = new Date(); + now.setHours(0, 0, 0, 0); + now.setTime(now.getTime() + ms); + return datetime.getDisplayTime(now); + }, + showAddTriggerPopup: function (view) { + $('#selectTriggerType', view).val('DailyTrigger'); + view.querySelector('#selectTriggerType').dispatchEvent(new CustomEvent('change', {})); + $('#popupAddTrigger', view).removeClass('hide'); + }, + confirmDeleteTrigger: function (view, index) { + confirm(globalize.translate('MessageDeleteTaskTrigger'), globalize.translate('HeaderDeleteTaskTrigger')).then(function () { + ScheduledTaskPage.deleteTrigger(view, index); }); - view.addEventListener('viewshow', function () { - ScheduledTaskPage.refreshScheduledTask(view); + }, + deleteTrigger: function (view, index) { + loading.show(); + const id = getParameterByName('id'); + ApiClient.getScheduledTask(id).then(function (task) { + task.Triggers.remove(index); + ApiClient.updateScheduledTaskTriggers(task.Id, task.Triggers).then(function () { + ScheduledTaskPage.refreshScheduledTask(view); + }); }); + }, + refreshTriggerFields: function (page, triggerType) { + if (triggerType == 'DailyTrigger') { + $('#fldTimeOfDay', page).show(); + $('#fldDayOfWeek', page).hide(); + $('#fldSelectSystemEvent', page).hide(); + $('#fldSelectInterval', page).hide(); + $('#selectTimeOfDay', page).attr('required', 'required'); + } else if (triggerType == 'WeeklyTrigger') { + $('#fldTimeOfDay', page).show(); + $('#fldDayOfWeek', page).show(); + $('#fldSelectSystemEvent', page).hide(); + $('#fldSelectInterval', page).hide(); + $('#selectTimeOfDay', page).attr('required', 'required'); + } else if (triggerType == 'SystemEventTrigger') { + $('#fldTimeOfDay', page).hide(); + $('#fldDayOfWeek', page).hide(); + $('#fldSelectSystemEvent', page).show(); + $('#fldSelectInterval', page).hide(); + $('#selectTimeOfDay', page).removeAttr('required'); + } else if (triggerType == 'IntervalTrigger') { + $('#fldTimeOfDay', page).hide(); + $('#fldDayOfWeek', page).hide(); + $('#fldSelectSystemEvent', page).hide(); + $('#fldSelectInterval', page).show(); + $('#selectTimeOfDay', page).removeAttr('required'); + } else if (triggerType == 'StartupTrigger') { + $('#fldTimeOfDay', page).hide(); + $('#fldDayOfWeek', page).hide(); + $('#fldSelectSystemEvent', page).hide(); + $('#fldSelectInterval', page).hide(); + $('#selectTimeOfDay', page).removeAttr('required'); + } + }, + getTriggerToAdd: function (page) { + const trigger = { + Type: $('#selectTriggerType', page).val() + }; + + if (trigger.Type == 'DailyTrigger') { + trigger.TimeOfDayTicks = $('#selectTimeOfDay', page).val(); + } else if (trigger.Type == 'WeeklyTrigger') { + trigger.DayOfWeek = $('#selectDayOfWeek', page).val(); + trigger.TimeOfDayTicks = $('#selectTimeOfDay', page).val(); + } else if (trigger.Type == 'SystemEventTrigger') { + trigger.SystemEvent = $('#selectSystemEvent', page).val(); + } else if (trigger.Type == 'IntervalTrigger') { + trigger.IntervalTicks = $('#selectInterval', page).val(); + } + + let timeLimit = $('#txtTimeLimit', page).val() || '0'; + timeLimit = parseFloat(timeLimit) * 3600000; + + trigger.MaxRuntimeMs = timeLimit || null; + + return trigger; + } +}; +export default function (view) { + function onSubmit(e) { + loading.show(); + const id = getParameterByName('id'); + ApiClient.getScheduledTask(id).then(function (task) { + task.Triggers.push(ScheduledTaskPage.getTriggerToAdd(view)); + ApiClient.updateScheduledTaskTriggers(task.Id, task.Triggers).then(function () { + $('#popupAddTrigger').addClass('hide'); + ScheduledTaskPage.refreshScheduledTask(view); + }); + }); + e.preventDefault(); } -/* eslint-enable indent */ + view.querySelector('.addTriggerForm').addEventListener('submit', onSubmit); + fillTimeOfDay(view.querySelector('#selectTimeOfDay')); + $(view.querySelector('#popupAddTrigger').parentNode).trigger('create'); + view.querySelector('.selectTriggerType').addEventListener('change', function () { + ScheduledTaskPage.refreshTriggerFields(view, this.value); + }); + view.querySelector('.btnAddTrigger').addEventListener('click', function () { + ScheduledTaskPage.showAddTriggerPopup(view); + }); + view.addEventListener('click', function (e) { + const btnDeleteTrigger = dom.parentWithClass(e.target, 'btnDeleteTrigger'); + + if (btnDeleteTrigger) { + ScheduledTaskPage.confirmDeleteTrigger(view, parseInt(btnDeleteTrigger.getAttribute('data-index'), 10)); + } + }); + view.addEventListener('viewshow', function () { + ScheduledTaskPage.refreshScheduledTask(view); + }); +} + diff --git a/src/controllers/dashboard/scheduledtasks/scheduledtasks.js b/src/controllers/dashboard/scheduledtasks/scheduledtasks.js index 0c8f0dca37..c074331aa1 100644 --- a/src/controllers/dashboard/scheduledtasks/scheduledtasks.js +++ b/src/controllers/dashboard/scheduledtasks/scheduledtasks.js @@ -9,194 +9,191 @@ import Events from '../../../utils/events.ts'; import '../../../components/listview/listview.scss'; import '../../../elements/emby-button/emby-button'; -/* eslint-disable indent */ +function reloadList(page) { + ApiClient.getScheduledTasks({ + isHidden: false + }).then(function(tasks) { + populateList(page, tasks); + loading.hide(); + }); +} - function reloadList(page) { - ApiClient.getScheduledTasks({ - isHidden: false - }).then(function(tasks) { - populateList(page, tasks); - loading.hide(); - }); - } +function populateList(page, tasks) { + tasks = tasks.sort(function(a, b) { + a = a.Category + ' ' + a.Name; + b = b.Category + ' ' + b.Name; + if (a > b) { + return 1; + } else if (a < b) { + return -1; + } else { + return 0; + } + }); - function populateList(page, tasks) { - tasks = tasks.sort(function(a, b) { - a = a.Category + ' ' + a.Name; - b = b.Category + ' ' + b.Name; - if (a > b) { - return 1; - } else if (a < b) { - return -1; - } else { - return 0; + let currentCategory; + let html = ''; + for (let i = 0; i < tasks.length; i++) { + const task = tasks[i]; + if (task.Category != currentCategory) { + currentCategory = task.Category; + if (currentCategory) { + html += '
'; + html += '
'; } - }); + html += '
'; + html += '
'; + html += '

'; + html += currentCategory; + html += '

'; + if (i === 0) { + html += '' + globalize.translate('Help') + ''; + } + html += '
'; + html += '
'; + } + html += '
'; + html += ""; + html += ''; + html += ''; + html += '
'; + let textAlignStyle = 'left'; + if (globalize.getIsRTL()) + textAlignStyle = 'right'; + html += ""; + html += "

" + task.Name + '

'; + html += "
" + getTaskProgressHtml(task) + '
'; + html += '
'; + html += '
'; + if (task.State === 'Running') { + html += ''; + } else if (task.State === 'Idle') { + html += ''; + } + html += '
'; + } + if (tasks.length) { + html += '
'; + html += '
'; + } + page.querySelector('.divScheduledTasks').innerHTML = html; +} - let currentCategory; - let html = ''; +function getTaskProgressHtml(task) { + let html = ''; + if (task.State === 'Idle') { + if (task.LastExecutionResult) { + const endtime = Date.parse(task.LastExecutionResult.EndTimeUtc); + const starttime = Date.parse(task.LastExecutionResult.StartTimeUtc); + html += globalize.translate('LabelScheduledTaskLastRan', formatDistanceToNow(endtime, getLocaleWithSuffix()), + formatDistance(starttime, endtime, { locale: getLocale() })); + if (task.LastExecutionResult.Status === 'Failed') { + html += " (" + globalize.translate('LabelFailed') + ')'; + } else if (task.LastExecutionResult.Status === 'Cancelled') { + html += " (" + globalize.translate('LabelCancelled') + ')'; + } else if (task.LastExecutionResult.Status === 'Aborted') { + html += " " + globalize.translate('LabelAbortedByServerShutdown') + ''; + } + } + } else if (task.State === 'Running') { + const progress = (task.CurrentProgressPercentage || 0).toFixed(1); + html += '
'; + html += '
'; + html += '
'; + html += '
'; + html += '
'; + html += "" + progress + '%'; + html += '
'; + } else { + html += "" + globalize.translate('LabelStopping') + ''; + } + return html; +} + +function setTaskButtonIcon(button, icon) { + const inner = button.querySelector('.material-icons'); + inner.classList.remove('stop', 'play_arrow'); + inner.classList.add(icon); +} + +function updateTaskButton(elem, state) { + if (state === 'Running') { + elem.classList.remove('btnStartTask'); + elem.classList.add('btnStopTask'); + setTaskButtonIcon(elem, 'stop'); + elem.title = globalize.translate('ButtonStop'); + } else if (state === 'Idle') { + elem.classList.add('btnStartTask'); + elem.classList.remove('btnStopTask'); + setTaskButtonIcon(elem, 'play_arrow'); + elem.title = globalize.translate('ButtonStart'); + } + $(elem).parents('.listItem')[0].setAttribute('data-status', state); +} + +export default function(view) { + function updateTasks(tasks) { for (let i = 0; i < tasks.length; i++) { const task = tasks[i]; - if (task.Category != currentCategory) { - currentCategory = task.Category; - if (currentCategory) { - html += '
'; - html += '
'; - } - html += '
'; - html += '
'; - html += '

'; - html += currentCategory; - html += '

'; - if (i === 0) { - html += '' + globalize.translate('Help') + ''; - } - html += '
'; - html += '
'; - } - html += '
'; - html += ""; - html += ''; - html += ''; - html += '
'; - let textAlignStyle = 'left'; - if (globalize.getIsRTL()) - textAlignStyle = 'right'; - html += ""; - html += "

" + task.Name + '

'; - html += "
" + getTaskProgressHtml(task) + '
'; - html += '
'; - html += '
'; - if (task.State === 'Running') { - html += ''; - } else if (task.State === 'Idle') { - html += ''; - } - html += '
'; + view.querySelector('#taskProgress' + task.Id).innerHTML = getTaskProgressHtml(task); + updateTaskButton(view.querySelector('#btnTask' + task.Id), task.State); } - if (tasks.length) { - html += '
'; - html += '
'; - } - page.querySelector('.divScheduledTasks').innerHTML = html; } - function getTaskProgressHtml(task) { - let html = ''; - if (task.State === 'Idle') { - if (task.LastExecutionResult) { - const endtime = Date.parse(task.LastExecutionResult.EndTimeUtc); - const starttime = Date.parse(task.LastExecutionResult.StartTimeUtc); - html += globalize.translate('LabelScheduledTaskLastRan', formatDistanceToNow(endtime, getLocaleWithSuffix()), - formatDistance(starttime, endtime, { locale: getLocale() })); - if (task.LastExecutionResult.Status === 'Failed') { - html += " (" + globalize.translate('LabelFailed') + ')'; - } else if (task.LastExecutionResult.Status === 'Cancelled') { - html += " (" + globalize.translate('LabelCancelled') + ')'; - } else if (task.LastExecutionResult.Status === 'Aborted') { - html += " " + globalize.translate('LabelAbortedByServerShutdown') + ''; - } - } - } else if (task.State === 'Running') { - const progress = (task.CurrentProgressPercentage || 0).toFixed(1); - html += '
'; - html += '
'; - html += '
'; - html += '
'; - html += '
'; - html += "" + progress + '%'; - html += '
'; - } else { - html += "" + globalize.translate('LabelStopping') + ''; - } - return html; - } - - function setTaskButtonIcon(button, icon) { - const inner = button.querySelector('.material-icons'); - inner.classList.remove('stop', 'play_arrow'); - inner.classList.add(icon); - } - - function updateTaskButton(elem, state) { - if (state === 'Running') { - elem.classList.remove('btnStartTask'); - elem.classList.add('btnStopTask'); - setTaskButtonIcon(elem, 'stop'); - elem.title = globalize.translate('ButtonStop'); - } else if (state === 'Idle') { - elem.classList.add('btnStartTask'); - elem.classList.remove('btnStopTask'); - setTaskButtonIcon(elem, 'play_arrow'); - elem.title = globalize.translate('ButtonStart'); - } - $(elem).parents('.listItem')[0].setAttribute('data-status', state); - } - - export default function(view) { - function updateTasks(tasks) { - for (let i = 0; i < tasks.length; i++) { - const task = tasks[i]; - view.querySelector('#taskProgress' + task.Id).innerHTML = getTaskProgressHtml(task); - updateTaskButton(view.querySelector('#btnTask' + task.Id), task.State); - } - } - - function onPollIntervalFired() { - if (!ApiClient.isMessageChannelOpen()) { - reloadList(view); - } - } - - function onScheduledTasksUpdate(e, apiClient, info) { - if (apiClient.serverId() === serverId) { - updateTasks(info); - } - } - - function startInterval() { - ApiClient.sendMessage('ScheduledTasksInfoStart', '1000,1000'); - pollInterval && clearInterval(pollInterval); - pollInterval = setInterval(onPollIntervalFired, 1e4); - } - - function stopInterval() { - ApiClient.sendMessage('ScheduledTasksInfoStop'); - pollInterval && clearInterval(pollInterval); - } - - let pollInterval; - const serverId = ApiClient.serverId(); - - $('.divScheduledTasks', view).on('click', '.btnStartTask', function() { - const button = this; - const id = button.getAttribute('data-taskid'); - ApiClient.startScheduledTask(id).then(function() { - updateTaskButton(button, 'Running'); - reloadList(view); - }); - }); - - $('.divScheduledTasks', view).on('click', '.btnStopTask', function() { - const button = this; - const id = button.getAttribute('data-taskid'); - ApiClient.stopScheduledTask(id).then(function() { - updateTaskButton(button, ''); - reloadList(view); - }); - }); - - view.addEventListener('viewbeforehide', function() { - Events.off(serverNotifications, 'ScheduledTasksInfo', onScheduledTasksUpdate); - stopInterval(); - }); - - view.addEventListener('viewshow', function() { - loading.show(); - startInterval(); + function onPollIntervalFired() { + if (!ApiClient.isMessageChannelOpen()) { reloadList(view); - Events.on(serverNotifications, 'ScheduledTasksInfo', onScheduledTasksUpdate); - }); + } } -/* eslint-enable indent */ + function onScheduledTasksUpdate(e, apiClient, info) { + if (apiClient.serverId() === serverId) { + updateTasks(info); + } + } + + function startInterval() { + ApiClient.sendMessage('ScheduledTasksInfoStart', '1000,1000'); + pollInterval && clearInterval(pollInterval); + pollInterval = setInterval(onPollIntervalFired, 1e4); + } + + function stopInterval() { + ApiClient.sendMessage('ScheduledTasksInfoStop'); + pollInterval && clearInterval(pollInterval); + } + + let pollInterval; + const serverId = ApiClient.serverId(); + + $('.divScheduledTasks', view).on('click', '.btnStartTask', function() { + const button = this; + const id = button.getAttribute('data-taskid'); + ApiClient.startScheduledTask(id).then(function() { + updateTaskButton(button, 'Running'); + reloadList(view); + }); + }); + + $('.divScheduledTasks', view).on('click', '.btnStopTask', function() { + const button = this; + const id = button.getAttribute('data-taskid'); + ApiClient.stopScheduledTask(id).then(function() { + updateTaskButton(button, ''); + reloadList(view); + }); + }); + + view.addEventListener('viewbeforehide', function() { + Events.off(serverNotifications, 'ScheduledTasksInfo', onScheduledTasksUpdate); + stopInterval(); + }); + + view.addEventListener('viewshow', function() { + loading.show(); + startInterval(); + reloadList(view); + Events.on(serverNotifications, 'ScheduledTasksInfo', onScheduledTasksUpdate); + }); +} + diff --git a/src/controllers/dashboard/serveractivity.js b/src/controllers/dashboard/serveractivity.js index e8471999dd..72e84a89d5 100644 --- a/src/controllers/dashboard/serveractivity.js +++ b/src/controllers/dashboard/serveractivity.js @@ -2,34 +2,31 @@ import ActivityLog from '../../components/activitylog'; import globalize from '../../scripts/globalize'; import { toBoolean } from '../../utils/string.ts'; -/* eslint-disable indent */ +export default function (view, params) { + let activityLog; - export default function (view, params) { - let activityLog; - - if (toBoolean(params.useractivity, true)) { - view.querySelector('.activityItems').setAttribute('data-useractivity', 'true'); - view.querySelector('.sectionTitle').innerHTML = globalize.translate('HeaderActivity'); - } else { - view.querySelector('.activityItems').setAttribute('data-useractivity', 'false'); - view.querySelector('.sectionTitle').innerHTML = globalize.translate('Alerts'); - } - - view.addEventListener('viewshow', function () { - if (!activityLog) { - activityLog = new ActivityLog({ - serverId: ApiClient.serverId(), - element: view.querySelector('.activityItems') - }); - } - }); - view.addEventListener('viewdestroy', function () { - if (activityLog) { - activityLog.destroy(); - } - - activityLog = null; - }); + if (toBoolean(params.useractivity, true)) { + view.querySelector('.activityItems').setAttribute('data-useractivity', 'true'); + view.querySelector('.sectionTitle').innerHTML = globalize.translate('HeaderActivity'); + } else { + view.querySelector('.activityItems').setAttribute('data-useractivity', 'false'); + view.querySelector('.sectionTitle').innerHTML = globalize.translate('Alerts'); } -/* eslint-enable indent */ + view.addEventListener('viewshow', function () { + if (!activityLog) { + activityLog = new ActivityLog({ + serverId: ApiClient.serverId(), + element: view.querySelector('.activityItems') + }); + } + }); + view.addEventListener('viewdestroy', function () { + if (activityLog) { + activityLog.destroy(); + } + + activityLog = null; + }); +} + diff --git a/src/controllers/dashboard/streaming.js b/src/controllers/dashboard/streaming.js index 8832e4bff4..ba9d767517 100644 --- a/src/controllers/dashboard/streaming.js +++ b/src/controllers/dashboard/streaming.js @@ -4,46 +4,43 @@ import loading from '../../components/loading/loading'; import globalize from '../../scripts/globalize'; import Dashboard from '../../utils/dashboard'; -/* eslint-disable indent */ +function loadPage(page, config) { + $('#txtRemoteClientBitrateLimit', page).val(config.RemoteClientBitrateLimit / 1e6 || ''); + loading.hide(); +} - function loadPage(page, config) { - $('#txtRemoteClientBitrateLimit', page).val(config.RemoteClientBitrateLimit / 1e6 || ''); - loading.hide(); - } - - function onSubmit() { - loading.show(); - const form = this; - ApiClient.getServerConfiguration().then(function (config) { - config.RemoteClientBitrateLimit = parseInt(1e6 * parseFloat($('#txtRemoteClientBitrateLimit', form).val() || '0'), 10); - ApiClient.updateServerConfiguration(config).then(Dashboard.processServerConfigurationUpdateResult); - }); - - return false; - } - - function getTabs() { - return [{ - href: '#/encodingsettings.html', - name: globalize.translate('Transcoding') - }, { - href: '#/playbackconfiguration.html', - name: globalize.translate('ButtonResume') - }, { - href: '#/streamingsettings.html', - name: globalize.translate('TabStreaming') - }]; - } - - $(document).on('pageinit', '#streamingSettingsPage', function () { - $('.streamingSettingsForm').off('submit', onSubmit).on('submit', onSubmit); - }).on('pageshow', '#streamingSettingsPage', function () { - loading.show(); - libraryMenu.setTabs('playback', 2, getTabs); - const page = this; - ApiClient.getServerConfiguration().then(function (config) { - loadPage(page, config); - }); +function onSubmit() { + loading.show(); + const form = this; + ApiClient.getServerConfiguration().then(function (config) { + config.RemoteClientBitrateLimit = parseInt(1e6 * parseFloat($('#txtRemoteClientBitrateLimit', form).val() || '0'), 10); + ApiClient.updateServerConfiguration(config).then(Dashboard.processServerConfigurationUpdateResult); }); -/* eslint-enable indent */ + return false; +} + +function getTabs() { + return [{ + href: '#/encodingsettings.html', + name: globalize.translate('Transcoding') + }, { + href: '#/playbackconfiguration.html', + name: globalize.translate('ButtonResume') + }, { + href: '#/streamingsettings.html', + name: globalize.translate('TabStreaming') + }]; +} + +$(document).on('pageinit', '#streamingSettingsPage', function () { + $('.streamingSettingsForm').off('submit', onSubmit).on('submit', onSubmit); +}).on('pageshow', '#streamingSettingsPage', function () { + loading.show(); + libraryMenu.setTabs('playback', 2, getTabs); + const page = this; + ApiClient.getServerConfiguration().then(function (config) { + loadPage(page, config); + }); +}); + diff --git a/src/controllers/favorites.js b/src/controllers/favorites.js index 88114385c9..97ce3eaf86 100644 --- a/src/controllers/favorites.js +++ b/src/controllers/favorites.js @@ -9,265 +9,263 @@ import '../elements/emby-itemscontainer/emby-itemscontainer'; import '../elements/emby-scroller/emby-scroller'; import ServerConnections from '../components/ServerConnections'; -/* eslint-disable indent */ +function enableScrollX() { + return true; +} - function enableScrollX() { - return true; - } +function getThumbShape() { + return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; +} - function getThumbShape() { - return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; - } +function getPosterShape() { + return enableScrollX() ? 'overflowPortrait' : 'portrait'; +} - function getPosterShape() { - return enableScrollX() ? 'overflowPortrait' : 'portrait'; - } +function getSquareShape() { + return enableScrollX() ? 'overflowSquare' : 'square'; +} - function getSquareShape() { - return enableScrollX() ? 'overflowSquare' : 'square'; - } +function getSections() { + return [{ + name: 'Movies', + types: 'Movie', + shape: getPosterShape(), + showTitle: true, + showYear: true, + overlayPlayButton: true, + overlayText: false, + centerText: true + }, { + name: 'Shows', + types: 'Series', + shape: getPosterShape(), + showTitle: true, + showYear: true, + overlayPlayButton: true, + overlayText: false, + centerText: true + }, { + name: 'Episodes', + types: 'Episode', + shape: getThumbShape(), + preferThumb: false, + showTitle: true, + showParentTitle: true, + overlayPlayButton: true, + overlayText: false, + centerText: true + }, { + name: 'Videos', + types: 'Video', + shape: getThumbShape(), + preferThumb: true, + showTitle: true, + overlayPlayButton: true, + overlayText: false, + centerText: true + }, { + name: 'Collections', + types: 'BoxSet', + shape: getPosterShape(), + showTitle: true, + overlayPlayButton: true, + overlayText: false, + centerText: true + }, { + name: 'Playlists', + types: 'Playlist', + shape: getSquareShape(), + preferThumb: false, + showTitle: true, + overlayText: false, + showParentTitle: false, + centerText: true, + overlayPlayButton: true, + coverImage: true + }, { + name: 'People', + types: 'Person', + shape: getPosterShape(), + preferThumb: false, + showTitle: true, + overlayText: false, + showParentTitle: false, + centerText: true, + overlayPlayButton: true, + coverImage: true + }, { + name: 'Artists', + types: 'MusicArtist', + shape: getSquareShape(), + preferThumb: false, + showTitle: true, + overlayText: false, + showParentTitle: false, + centerText: true, + overlayPlayButton: true, + coverImage: true + }, { + name: 'Albums', + types: 'MusicAlbum', + shape: getSquareShape(), + preferThumb: false, + showTitle: true, + overlayText: false, + showParentTitle: true, + centerText: true, + overlayPlayButton: true, + coverImage: true + }, { + name: 'Songs', + types: 'Audio', + shape: getSquareShape(), + preferThumb: false, + showTitle: true, + overlayText: false, + showParentTitle: true, + centerText: true, + overlayMoreButton: true, + action: 'instantmix', + coverImage: true + }, { + name: 'Books', + types: 'Book', + shape: getPosterShape(), + showTitle: true, + showYear: true, + overlayPlayButton: true, + overlayText: false, + centerText: true + }]; +} - function getSections() { - return [{ - name: 'Movies', - types: 'Movie', - shape: getPosterShape(), - showTitle: true, - showYear: true, - overlayPlayButton: true, - overlayText: false, - centerText: true - }, { - name: 'Shows', - types: 'Series', - shape: getPosterShape(), - showTitle: true, - showYear: true, - overlayPlayButton: true, - overlayText: false, - centerText: true - }, { - name: 'Episodes', - types: 'Episode', - shape: getThumbShape(), - preferThumb: false, - showTitle: true, - showParentTitle: true, - overlayPlayButton: true, - overlayText: false, - centerText: true - }, { - name: 'Videos', - types: 'Video', - shape: getThumbShape(), - preferThumb: true, - showTitle: true, - overlayPlayButton: true, - overlayText: false, - centerText: true - }, { - name: 'Collections', - types: 'BoxSet', - shape: getPosterShape(), - showTitle: true, - overlayPlayButton: true, - overlayText: false, - centerText: true - }, { - name: 'Playlists', - types: 'Playlist', - shape: getSquareShape(), - preferThumb: false, - showTitle: true, - overlayText: false, - showParentTitle: false, - centerText: true, - overlayPlayButton: true, - coverImage: true - }, { - name: 'People', - types: 'Person', - shape: getPosterShape(), - preferThumb: false, - showTitle: true, - overlayText: false, - showParentTitle: false, - centerText: true, - overlayPlayButton: true, - coverImage: true - }, { - name: 'Artists', - types: 'MusicArtist', - shape: getSquareShape(), - preferThumb: false, - showTitle: true, - overlayText: false, - showParentTitle: false, - centerText: true, - overlayPlayButton: true, - coverImage: true - }, { - name: 'Albums', - types: 'MusicAlbum', - shape: getSquareShape(), - preferThumb: false, - showTitle: true, - overlayText: false, - showParentTitle: true, - centerText: true, - overlayPlayButton: true, - coverImage: true - }, { - name: 'Songs', - types: 'Audio', - shape: getSquareShape(), - preferThumb: false, - showTitle: true, - overlayText: false, - showParentTitle: true, - centerText: true, - overlayMoreButton: true, - action: 'instantmix', - coverImage: true - }, { - name: 'Books', - types: 'Book', - shape: getPosterShape(), - showTitle: true, - showYear: true, - overlayPlayButton: true, - overlayText: false, - centerText: true - }]; - } - - function getFetchDataFn(section) { - return function () { - const apiClient = this.apiClient; - const options = { - SortBy: 'SeriesName,SortName', - SortOrder: 'Ascending', - Filters: 'IsFavorite', - Recursive: true, - Fields: 'PrimaryImageAspectRatio,BasicSyncInfo', - CollapseBoxSetItems: false, - ExcludeLocationTypes: 'Virtual', - EnableTotalRecordCount: false - }; - options.Limit = 20; - const userId = apiClient.getCurrentUserId(); - - if (section.types === 'MusicArtist') { - return apiClient.getArtists(userId, options); - } - - if (section.types === 'Person') { - return apiClient.getPeople(userId, options); - } - - options.IncludeItemTypes = section.types; - return apiClient.getItems(userId, options); +function getFetchDataFn(section) { + return function () { + const apiClient = this.apiClient; + const options = { + SortBy: 'SeriesName,SortName', + SortOrder: 'Ascending', + Filters: 'IsFavorite', + Recursive: true, + Fields: 'PrimaryImageAspectRatio,BasicSyncInfo', + CollapseBoxSetItems: false, + ExcludeLocationTypes: 'Virtual', + EnableTotalRecordCount: false }; - } + options.Limit = 20; + const userId = apiClient.getCurrentUserId(); - function getRouteUrl(section, serverId) { - return appRouter.getRouteUrl('list', { - serverId: serverId, - itemTypes: section.types, - isFavorite: true + if (section.types === 'MusicArtist') { + return apiClient.getArtists(userId, options); + } + + if (section.types === 'Person') { + return apiClient.getPeople(userId, options); + } + + options.IncludeItemTypes = section.types; + return apiClient.getItems(userId, options); + }; +} + +function getRouteUrl(section, serverId) { + return appRouter.getRouteUrl('list', { + serverId: serverId, + itemTypes: section.types, + isFavorite: true + }); +} + +function getItemsHtmlFn(section) { + return function (items) { + let cardLayout = appHost.preferVisualCards && section.autoCardLayout && section.showTitle; + cardLayout = false; + const serverId = this.apiClient.serverId(); + const leadingButtons = layoutManager.tv ? [{ + name: globalize.translate('All'), + id: 'more', + icon: 'favorite', + routeUrl: getRouteUrl(section, serverId) + }] : null; + let lines = 0; + + if (section.showTitle) { + lines++; + } + + if (section.showYear) { + lines++; + } + + if (section.showParentTitle) { + lines++; + } + + return cardBuilder.getCardsHtml({ + items: items, + preferThumb: section.preferThumb, + shape: section.shape, + centerText: section.centerText && !cardLayout, + overlayText: section.overlayText !== false, + showTitle: section.showTitle, + showYear: section.showYear, + showParentTitle: section.showParentTitle, + scalable: true, + coverImage: section.coverImage, + overlayPlayButton: section.overlayPlayButton, + overlayMoreButton: section.overlayMoreButton && !cardLayout, + action: section.action, + allowBottomPadding: !enableScrollX(), + cardLayout: cardLayout, + leadingButtons: leadingButtons, + lines: lines }); - } + }; +} - function getItemsHtmlFn(section) { - return function (items) { - let cardLayout = appHost.preferVisualCards && section.autoCardLayout && section.showTitle; - cardLayout = false; - const serverId = this.apiClient.serverId(); - const leadingButtons = layoutManager.tv ? [{ - name: globalize.translate('All'), - id: 'more', - icon: 'favorite', - routeUrl: getRouteUrl(section, serverId) - }] : null; - let lines = 0; +function createSections(instance, elem, apiClient) { + const sections = getSections(); + let html = ''; - if (section.showTitle) { - lines++; - } + for (const section of sections) { + let sectionClass = 'verticalSection'; - if (section.showYear) { - lines++; - } - - if (section.showParentTitle) { - lines++; - } - - return cardBuilder.getCardsHtml({ - items: items, - preferThumb: section.preferThumb, - shape: section.shape, - centerText: section.centerText && !cardLayout, - overlayText: section.overlayText !== false, - showTitle: section.showTitle, - showYear: section.showYear, - showParentTitle: section.showParentTitle, - scalable: true, - coverImage: section.coverImage, - overlayPlayButton: section.overlayPlayButton, - overlayMoreButton: section.overlayMoreButton && !cardLayout, - action: section.action, - allowBottomPadding: !enableScrollX(), - cardLayout: cardLayout, - leadingButtons: leadingButtons, - lines: lines - }); - }; - } - - function createSections(instance, elem, apiClient) { - const sections = getSections(); - let html = ''; - - for (const section of sections) { - let sectionClass = 'verticalSection'; - - if (!section.showTitle) { - sectionClass += ' verticalSection-extrabottompadding'; - } - - html += '
'; - html += '
'; - - if (layoutManager.tv) { - html += '

' + globalize.translate(section.name) + '

'; - } else { - html += ''; - html += '

'; - html += globalize.translate(section.name); - html += '

'; - html += ''; - html += '
'; - } - - html += '
'; - html += '
'; - html += '
'; + if (!section.showTitle) { + sectionClass += ' verticalSection-extrabottompadding'; } - elem.innerHTML = html; - window.CustomElements.upgradeSubtree(elem); + html += '
'; + html += '
'; - const elems = elem.querySelectorAll('.itemsContainer'); - - for (let i = 0, length = elems.length; i < length; i++) { - const itemsContainer = elems[i]; - itemsContainer.fetchData = getFetchDataFn(sections[i]).bind(instance); - itemsContainer.getItemsHtml = getItemsHtmlFn(sections[i]).bind(instance); - itemsContainer.parentContainer = dom.parentWithClass(itemsContainer, 'verticalSection'); + if (layoutManager.tv) { + html += '

' + globalize.translate(section.name) + '

'; + } else { + html += ''; + html += '

'; + html += globalize.translate(section.name); + html += '

'; + html += ''; + html += '
'; } + + html += '
'; + html += '
'; + html += '
'; } + elem.innerHTML = html; + window.CustomElements.upgradeSubtree(elem); + + const elems = elem.querySelectorAll('.itemsContainer'); + + for (let i = 0, length = elems.length; i < length; i++) { + const itemsContainer = elems[i]; + itemsContainer.fetchData = getFetchDataFn(sections[i]).bind(instance); + itemsContainer.getItemsHtml = getItemsHtmlFn(sections[i]).bind(instance); + itemsContainer.parentContainer = dom.parentWithClass(itemsContainer, 'verticalSection'); + } +} + class FavoritesTab { constructor(view, params) { this.view = view; @@ -318,4 +316,3 @@ class FavoritesTab { export default FavoritesTab; -/* eslint-enable indent */ diff --git a/src/controllers/list.js b/src/controllers/list.js index cf57fa2ee0..62ef3c12cb 100644 --- a/src/controllers/list.js +++ b/src/controllers/list.js @@ -13,417 +13,415 @@ import '../elements/emby-scroller/emby-scroller'; import ServerConnections from '../components/ServerConnections'; import LibraryMenu from '../scripts/libraryMenu'; -/* eslint-disable indent */ +function getInitialLiveTvQuery(instance, params, startIndex = 0, limit = 300) { + const query = { + UserId: ServerConnections.getApiClient(params.serverId).getCurrentUserId(), + StartIndex: startIndex, + Fields: 'ChannelInfo,PrimaryImageAspectRatio', + Limit: limit + }; - function getInitialLiveTvQuery(instance, params, startIndex = 0, limit = 300) { - const query = { - UserId: ServerConnections.getApiClient(params.serverId).getCurrentUserId(), - StartIndex: startIndex, - Fields: 'ChannelInfo,PrimaryImageAspectRatio', - Limit: limit - }; - - if (params.type === 'Recordings') { - query.IsInProgress = false; - } else { - query.HasAired = false; - } - - if (params.genreId) { - query.GenreIds = params.genreId; - } - - if (params.IsMovie === 'true') { - query.IsMovie = true; - } else if (params.IsMovie === 'false') { - query.IsMovie = false; - } - - if (params.IsSeries === 'true') { - query.IsSeries = true; - } else if (params.IsSeries === 'false') { - query.IsSeries = false; - } - - if (params.IsNews === 'true') { - query.IsNews = true; - } else if (params.IsNews === 'false') { - query.IsNews = false; - } - - if (params.IsSports === 'true') { - query.IsSports = true; - } else if (params.IsSports === 'false') { - query.IsSports = false; - } - - if (params.IsKids === 'true') { - query.IsKids = true; - } else if (params.IsKids === 'false') { - query.IsKids = false; - } - - if (params.IsAiring === 'true') { - query.IsAiring = true; - } else if (params.IsAiring === 'false') { - query.IsAiring = false; - } - - return modifyQueryWithFilters(instance, query); + if (params.type === 'Recordings') { + query.IsInProgress = false; + } else { + query.HasAired = false; } - function modifyQueryWithFilters(instance, query) { - const sortValues = instance.getSortValues(); + if (params.genreId) { + query.GenreIds = params.genreId; + } - if (!query.SortBy) { - query.SortBy = sortValues.sortBy; - query.SortOrder = sortValues.sortOrder; + if (params.IsMovie === 'true') { + query.IsMovie = true; + } else if (params.IsMovie === 'false') { + query.IsMovie = false; + } + + if (params.IsSeries === 'true') { + query.IsSeries = true; + } else if (params.IsSeries === 'false') { + query.IsSeries = false; + } + + if (params.IsNews === 'true') { + query.IsNews = true; + } else if (params.IsNews === 'false') { + query.IsNews = false; + } + + if (params.IsSports === 'true') { + query.IsSports = true; + } else if (params.IsSports === 'false') { + query.IsSports = false; + } + + if (params.IsKids === 'true') { + query.IsKids = true; + } else if (params.IsKids === 'false') { + query.IsKids = false; + } + + if (params.IsAiring === 'true') { + query.IsAiring = true; + } else if (params.IsAiring === 'false') { + query.IsAiring = false; + } + + return modifyQueryWithFilters(instance, query); +} + +function modifyQueryWithFilters(instance, query) { + const sortValues = instance.getSortValues(); + + if (!query.SortBy) { + query.SortBy = sortValues.sortBy; + query.SortOrder = sortValues.sortOrder; + } + + query.Fields = query.Fields ? query.Fields + ',PrimaryImageAspectRatio' : 'PrimaryImageAspectRatio'; + query.ImageTypeLimit = 1; + let hasFilters; + const queryFilters = []; + const filters = instance.getFilters(); + + if (filters.IsPlayed) { + queryFilters.push('IsPlayed'); + hasFilters = true; + } + + if (filters.IsUnplayed) { + queryFilters.push('IsUnplayed'); + hasFilters = true; + } + + if (filters.IsFavorite) { + queryFilters.push('IsFavorite'); + hasFilters = true; + } + + if (filters.IsResumable) { + queryFilters.push('IsResumable'); + hasFilters = true; + } + + if (filters.VideoTypes) { + hasFilters = true; + query.VideoTypes = filters.VideoTypes; + } + + if (filters.GenreIds) { + hasFilters = true; + query.GenreIds = filters.GenreIds; + } + + if (filters.Is4K) { + query.Is4K = true; + hasFilters = true; + } + + if (filters.IsHD) { + query.IsHD = true; + hasFilters = true; + } + + if (filters.IsSD) { + query.IsHD = false; + hasFilters = true; + } + + if (filters.Is3D) { + query.Is3D = true; + hasFilters = true; + } + + if (filters.HasSubtitles) { + query.HasSubtitles = true; + hasFilters = true; + } + + if (filters.HasTrailer) { + query.HasTrailer = true; + hasFilters = true; + } + + if (filters.HasSpecialFeature) { + query.HasSpecialFeature = true; + hasFilters = true; + } + + if (filters.HasThemeSong) { + query.HasThemeSong = true; + hasFilters = true; + } + + if (filters.HasThemeVideo) { + query.HasThemeVideo = true; + hasFilters = true; + } + + query.Filters = queryFilters.length ? queryFilters.join(',') : null; + instance.setFilterStatus(hasFilters); + + if (instance.alphaPicker) { + const newValue = instance.alphaPicker.value(); + if (newValue === '#') { + query.NameLessThan = 'A'; + delete query.NameStartsWith; + } else { + query.NameStartsWith = newValue; + delete query.NameLessThan; } + } - query.Fields = query.Fields ? query.Fields + ',PrimaryImageAspectRatio' : 'PrimaryImageAspectRatio'; - query.ImageTypeLimit = 1; - let hasFilters; - const queryFilters = []; - const filters = instance.getFilters(); + return query; +} - if (filters.IsPlayed) { - queryFilters.push('IsPlayed'); - hasFilters = true; - } +function setSortButtonIcon(btnSortIcon, icon) { + btnSortIcon.classList.remove('arrow_downward'); + btnSortIcon.classList.remove('arrow_upward'); + btnSortIcon.classList.add(icon); +} - if (filters.IsUnplayed) { - queryFilters.push('IsUnplayed'); - hasFilters = true; - } +function updateSortText(instance) { + const btnSortText = instance.btnSortText; - if (filters.IsFavorite) { - queryFilters.push('IsFavorite'); - hasFilters = true; - } + if (btnSortText) { + const options = instance.getSortMenuOptions(); + const values = instance.getSortValues(); + const sortBy = values.sortBy; - if (filters.IsResumable) { - queryFilters.push('IsResumable'); - hasFilters = true; - } - - if (filters.VideoTypes) { - hasFilters = true; - query.VideoTypes = filters.VideoTypes; - } - - if (filters.GenreIds) { - hasFilters = true; - query.GenreIds = filters.GenreIds; - } - - if (filters.Is4K) { - query.Is4K = true; - hasFilters = true; - } - - if (filters.IsHD) { - query.IsHD = true; - hasFilters = true; - } - - if (filters.IsSD) { - query.IsHD = false; - hasFilters = true; - } - - if (filters.Is3D) { - query.Is3D = true; - hasFilters = true; - } - - if (filters.HasSubtitles) { - query.HasSubtitles = true; - hasFilters = true; - } - - if (filters.HasTrailer) { - query.HasTrailer = true; - hasFilters = true; - } - - if (filters.HasSpecialFeature) { - query.HasSpecialFeature = true; - hasFilters = true; - } - - if (filters.HasThemeSong) { - query.HasThemeSong = true; - hasFilters = true; - } - - if (filters.HasThemeVideo) { - query.HasThemeVideo = true; - hasFilters = true; - } - - query.Filters = queryFilters.length ? queryFilters.join(',') : null; - instance.setFilterStatus(hasFilters); - - if (instance.alphaPicker) { - const newValue = instance.alphaPicker.value(); - if (newValue === '#') { - query.NameLessThan = 'A'; - delete query.NameStartsWith; - } else { - query.NameStartsWith = newValue; - delete query.NameLessThan; + for (const option of options) { + if (sortBy === option.value) { + btnSortText.innerHTML = globalize.translate('SortByValue', option.name); + break; } } - return query; + const btnSortIcon = instance.btnSortIcon; + + if (btnSortIcon) { + setSortButtonIcon(btnSortIcon, values.sortOrder === 'Descending' ? 'arrow_downward' : 'arrow_upward'); + } } +} - function setSortButtonIcon(btnSortIcon, icon) { - btnSortIcon.classList.remove('arrow_downward'); - btnSortIcon.classList.remove('arrow_upward'); - btnSortIcon.classList.add(icon); +function updateItemsContainerForViewType(instance) { + if (instance.getViewSettings().imageType === 'list') { + instance.itemsContainer.classList.remove('vertical-wrap'); + instance.itemsContainer.classList.add('vertical-list'); + } else { + instance.itemsContainer.classList.add('vertical-wrap'); + instance.itemsContainer.classList.remove('vertical-list'); } +} - function updateSortText(instance) { - const btnSortText = instance.btnSortText; +function updateAlphaPickerState(instance) { + if (instance.alphaPicker) { + const alphaPicker = instance.alphaPickerElement; - if (btnSortText) { - const options = instance.getSortMenuOptions(); + if (alphaPicker) { const values = instance.getSortValues(); - const sortBy = values.sortBy; - for (const option of options) { - if (sortBy === option.value) { - btnSortText.innerHTML = globalize.translate('SortByValue', option.name); - break; - } - } - - const btnSortIcon = instance.btnSortIcon; - - if (btnSortIcon) { - setSortButtonIcon(btnSortIcon, values.sortOrder === 'Descending' ? 'arrow_downward' : 'arrow_upward'); + if (values.sortBy.indexOf('SortName') !== -1) { + alphaPicker.classList.remove('hide'); + instance.itemsContainer.parentNode.classList.add('padded-right-withalphapicker'); + } else { + alphaPicker.classList.add('hide'); + instance.itemsContainer.parentNode.classList.remove('padded-right-withalphapicker'); } } } +} - function updateItemsContainerForViewType(instance) { - if (instance.getViewSettings().imageType === 'list') { - instance.itemsContainer.classList.remove('vertical-wrap'); - instance.itemsContainer.classList.add('vertical-list'); - } else { - instance.itemsContainer.classList.add('vertical-wrap'); - instance.itemsContainer.classList.remove('vertical-list'); - } +function getItems(instance, params, item, sortBy, startIndex, limit) { + const apiClient = ServerConnections.getApiClient(params.serverId); + + instance.queryRecursive = false; + if (params.type === 'Recordings') { + return apiClient.getLiveTvRecordings(getInitialLiveTvQuery(instance, params, startIndex, limit)); } - function updateAlphaPickerState(instance) { - if (instance.alphaPicker) { - const alphaPicker = instance.alphaPickerElement; - - if (alphaPicker) { - const values = instance.getSortValues(); - - if (values.sortBy.indexOf('SortName') !== -1) { - alphaPicker.classList.remove('hide'); - instance.itemsContainer.parentNode.classList.add('padded-right-withalphapicker'); - } else { - alphaPicker.classList.add('hide'); - instance.itemsContainer.parentNode.classList.remove('padded-right-withalphapicker'); - } - } + if (params.type === 'Programs') { + if (params.IsAiring === 'true') { + return apiClient.getLiveTvRecommendedPrograms(getInitialLiveTvQuery(instance, params, startIndex, limit)); } + + return apiClient.getLiveTvPrograms(getInitialLiveTvQuery(instance, params, startIndex, limit)); } - function getItems(instance, params, item, sortBy, startIndex, limit) { - const apiClient = ServerConnections.getApiClient(params.serverId); + if (params.type === 'nextup') { + return apiClient.getNextUpEpisodes(modifyQueryWithFilters(instance, { + Limit: limit, + Fields: 'PrimaryImageAspectRatio,DateCreated,BasicSyncInfo,MediaSourceCount', + UserId: apiClient.getCurrentUserId(), + ImageTypeLimit: 1, + EnableImageTypes: 'Primary,Backdrop,Thumb', + EnableTotalRecordCount: false, + SortBy: sortBy, + EnableRewatching: userSettings.enableRewatchingInNextUp() + })); + } - instance.queryRecursive = false; - if (params.type === 'Recordings') { - return apiClient.getLiveTvRecordings(getInitialLiveTvQuery(instance, params, startIndex, limit)); + if (!item) { + instance.queryRecursive = true; + let method = 'getItems'; + + if (params.type === 'MusicArtist') { + method = 'getArtists'; + } else if (params.type === 'Person') { + method = 'getPeople'; } - if (params.type === 'Programs') { - if (params.IsAiring === 'true') { - return apiClient.getLiveTvRecommendedPrograms(getInitialLiveTvQuery(instance, params, startIndex, limit)); - } - - return apiClient.getLiveTvPrograms(getInitialLiveTvQuery(instance, params, startIndex, limit)); - } - - if (params.type === 'nextup') { - return apiClient.getNextUpEpisodes(modifyQueryWithFilters(instance, { - Limit: limit, - Fields: 'PrimaryImageAspectRatio,DateCreated,BasicSyncInfo,MediaSourceCount', - UserId: apiClient.getCurrentUserId(), - ImageTypeLimit: 1, - EnableImageTypes: 'Primary,Backdrop,Thumb', - EnableTotalRecordCount: false, - SortBy: sortBy, - EnableRewatching: userSettings.enableRewatchingInNextUp() - })); - } - - if (!item) { - instance.queryRecursive = true; - let method = 'getItems'; - - if (params.type === 'MusicArtist') { - method = 'getArtists'; - } else if (params.type === 'Person') { - method = 'getPeople'; - } - - return apiClient[method](apiClient.getCurrentUserId(), modifyQueryWithFilters(instance, { - StartIndex: startIndex, - Limit: limit, - Fields: 'PrimaryImageAspectRatio,SortName', - ImageTypeLimit: 1, - IncludeItemTypes: params.type === 'MusicArtist' || params.type === 'Person' ? null : params.type, - Recursive: true, - IsFavorite: params.IsFavorite === 'true' || null, - ArtistIds: params.artistId || null, - SortBy: sortBy - })); - } - - if (item.Type === 'Genre' || item.Type === 'MusicGenre' || item.Type === 'Studio' || item.Type === 'Person') { - instance.queryRecursive = true; - const query = { - StartIndex: startIndex, - Limit: limit, - Fields: 'PrimaryImageAspectRatio,SortName', - Recursive: true, - parentId: params.parentId, - SortBy: sortBy - }; - - if (item.Type === 'Studio') { - query.StudioIds = item.Id; - } else if (item.Type === 'Genre' || item.Type === 'MusicGenre') { - query.GenreIds = item.Id; - } else if (item.Type === 'Person') { - query.PersonIds = item.Id; - } - - if (item.Type === 'MusicGenre') { - query.IncludeItemTypes = 'MusicAlbum'; - } else if (item.CollectionType === 'movies') { - query.IncludeItemTypes = 'Movie'; - } else if (item.CollectionType === 'tvshows') { - query.IncludeItemTypes = 'Series'; - } else if (item.Type === 'Genre') { - query.IncludeItemTypes = 'Movie,Series,Video'; - } else if (item.Type === 'Person') { - query.IncludeItemTypes = params.type; - } - - return apiClient.getItems(apiClient.getCurrentUserId(), modifyQueryWithFilters(instance, query)); - } - - return apiClient.getItems(apiClient.getCurrentUserId(), modifyQueryWithFilters(instance, { + return apiClient[method](apiClient.getCurrentUserId(), modifyQueryWithFilters(instance, { StartIndex: startIndex, Limit: limit, - Fields: 'PrimaryImageAspectRatio,SortName,Path,SongCount,ChildCount,MediaSourceCount', + Fields: 'PrimaryImageAspectRatio,SortName', ImageTypeLimit: 1, - ParentId: item.Id, + IncludeItemTypes: params.type === 'MusicArtist' || params.type === 'Person' ? null : params.type, + Recursive: true, + IsFavorite: params.IsFavorite === 'true' || null, + ArtistIds: params.artistId || null, SortBy: sortBy })); } - function getItem(params) { - if (params.type === 'Recordings' || params.type === 'Programs' || params.type === 'nextup') { - return Promise.resolve(null); + if (item.Type === 'Genre' || item.Type === 'MusicGenre' || item.Type === 'Studio' || item.Type === 'Person') { + instance.queryRecursive = true; + const query = { + StartIndex: startIndex, + Limit: limit, + Fields: 'PrimaryImageAspectRatio,SortName', + Recursive: true, + parentId: params.parentId, + SortBy: sortBy + }; + + if (item.Type === 'Studio') { + query.StudioIds = item.Id; + } else if (item.Type === 'Genre' || item.Type === 'MusicGenre') { + query.GenreIds = item.Id; + } else if (item.Type === 'Person') { + query.PersonIds = item.Id; } - const apiClient = ServerConnections.getApiClient(params.serverId); - const itemId = params.genreId || params.musicGenreId || params.studioId || params.personId || params.parentId; - - if (itemId) { - return apiClient.getItem(apiClient.getCurrentUserId(), itemId); + if (item.Type === 'MusicGenre') { + query.IncludeItemTypes = 'MusicAlbum'; + } else if (item.CollectionType === 'movies') { + query.IncludeItemTypes = 'Movie'; + } else if (item.CollectionType === 'tvshows') { + query.IncludeItemTypes = 'Series'; + } else if (item.Type === 'Genre') { + query.IncludeItemTypes = 'Movie,Series,Video'; + } else if (item.Type === 'Person') { + query.IncludeItemTypes = params.type; } + return apiClient.getItems(apiClient.getCurrentUserId(), modifyQueryWithFilters(instance, query)); + } + + return apiClient.getItems(apiClient.getCurrentUserId(), modifyQueryWithFilters(instance, { + StartIndex: startIndex, + Limit: limit, + Fields: 'PrimaryImageAspectRatio,SortName,Path,SongCount,ChildCount,MediaSourceCount', + ImageTypeLimit: 1, + ParentId: item.Id, + SortBy: sortBy + })); +} + +function getItem(params) { + if (params.type === 'Recordings' || params.type === 'Programs' || params.type === 'nextup') { return Promise.resolve(null); } - function showViewSettingsMenu() { - const instance = this; + const apiClient = ServerConnections.getApiClient(params.serverId); + const itemId = params.genreId || params.musicGenreId || params.studioId || params.personId || params.parentId; - import('../components/viewSettings/viewSettings').then(({ default: ViewSettings }) => { - new ViewSettings().show({ - settingsKey: instance.getSettingsKey(), - settings: instance.getViewSettings(), - visibleSettings: instance.getVisibleViewSettings() - }).then(function () { - updateItemsContainerForViewType(instance); - instance.itemsContainer.refreshItems(); - }); - }); + if (itemId) { + return apiClient.getItem(apiClient.getCurrentUserId(), itemId); } - function showFilterMenu() { - const instance = this; + return Promise.resolve(null); +} - import('../components/filtermenu/filtermenu').then(({ default: FilterMenu }) => { - new FilterMenu().show({ - settingsKey: instance.getSettingsKey(), - settings: instance.getFilters(), - visibleSettings: instance.getVisibleFilters(), - onChange: instance.itemsContainer.refreshItems.bind(instance.itemsContainer), - parentId: instance.params.parentId, - itemTypes: instance.getItemTypes(), - serverId: instance.params.serverId, - filterMenuOptions: instance.getFilterMenuOptions() - }).then(function () { - instance.itemsContainer.refreshItems(); - }); +function showViewSettingsMenu() { + const instance = this; + + import('../components/viewSettings/viewSettings').then(({ default: ViewSettings }) => { + new ViewSettings().show({ + settingsKey: instance.getSettingsKey(), + settings: instance.getViewSettings(), + visibleSettings: instance.getVisibleViewSettings() + }).then(function () { + updateItemsContainerForViewType(instance); + instance.itemsContainer.refreshItems(); }); - } + }); +} - function showSortMenu() { - const instance = this; +function showFilterMenu() { + const instance = this; - import('../components/sortmenu/sortmenu').then(({ default: SortMenu }) => { - new SortMenu().show({ - settingsKey: instance.getSettingsKey(), - settings: instance.getSortValues(), - onChange: instance.itemsContainer.refreshItems.bind(instance.itemsContainer), - serverId: instance.params.serverId, - sortOptions: instance.getSortMenuOptions() - }).then(function () { - updateSortText(instance); - updateAlphaPickerState(instance); - instance.itemsContainer.refreshItems(); - }); + import('../components/filtermenu/filtermenu').then(({ default: FilterMenu }) => { + new FilterMenu().show({ + settingsKey: instance.getSettingsKey(), + settings: instance.getFilters(), + visibleSettings: instance.getVisibleFilters(), + onChange: instance.itemsContainer.refreshItems.bind(instance.itemsContainer), + parentId: instance.params.parentId, + itemTypes: instance.getItemTypes(), + serverId: instance.params.serverId, + filterMenuOptions: instance.getFilterMenuOptions() + }).then(function () { + instance.itemsContainer.refreshItems(); }); - } + }); +} - function onNewItemClick() { - const instance = this; +function showSortMenu() { + const instance = this; - import('../components/playlisteditor/playlisteditor').then(({ default: playlistEditor }) => { - new playlistEditor({ - items: [], - serverId: instance.params.serverId - }); + import('../components/sortmenu/sortmenu').then(({ default: SortMenu }) => { + new SortMenu().show({ + settingsKey: instance.getSettingsKey(), + settings: instance.getSortValues(), + onChange: instance.itemsContainer.refreshItems.bind(instance.itemsContainer), + serverId: instance.params.serverId, + sortOptions: instance.getSortMenuOptions() + }).then(function () { + updateSortText(instance); + updateAlphaPickerState(instance); + instance.itemsContainer.refreshItems(); }); - } + }); +} - function hideOrShowAll(elems, hide) { - for (const elem of elems) { - if (hide) { - elem.classList.add('hide'); - } else { - elem.classList.remove('hide'); - } +function onNewItemClick() { + const instance = this; + + import('../components/playlisteditor/playlisteditor').then(({ default: playlistEditor }) => { + new playlistEditor({ + items: [], + serverId: instance.params.serverId + }); + }); +} + +function hideOrShowAll(elems, hide) { + for (const elem of elems) { + if (hide) { + elem.classList.add('hide'); + } else { + elem.classList.remove('hide'); } } +} - function bindAll(elems, eventName, fn) { - for (const elem of elems) { - elem.addEventListener(eventName, fn); - } +function bindAll(elems, eventName, fn) { + for (const elem of elems) { + elem.addEventListener(eventName, fn); } +} class ItemsView { constructor(view, params) { @@ -1295,4 +1293,3 @@ class ItemsView { export default ItemsView; -/* eslint-enable indent */ diff --git a/src/controllers/music/musicalbums.js b/src/controllers/music/musicalbums.js index d1ad28b992..04ea17a983 100644 --- a/src/controllers/music/musicalbums.js +++ b/src/controllers/music/musicalbums.js @@ -11,294 +11,291 @@ import Events from '../../utils/events.ts'; import '../../elements/emby-itemscontainer/emby-itemscontainer'; -/* eslint-disable indent */ - - export default function (view, params, tabContent) { - function playAll() { - ApiClient.getItem(ApiClient.getCurrentUserId(), params.topParentId).then(function (item) { - playbackManager.play({ - items: [item] - }); +export default function (view, params, tabContent) { + function playAll() { + ApiClient.getItem(ApiClient.getCurrentUserId(), params.topParentId).then(function (item) { + playbackManager.play({ + items: [item] }); - } - - function shuffle() { - ApiClient.getItem(ApiClient.getCurrentUserId(), params.topParentId).then(function (item) { - getQuery(); - playbackManager.shuffle(item); - }); - } - - function getPageData() { - const key = getSavedQueryKey(); - - if (!pageData) { - pageData = { - query: { - SortBy: 'SortName', - SortOrder: 'Ascending', - IncludeItemTypes: 'MusicAlbum', - Recursive: true, - Fields: 'PrimaryImageAspectRatio,SortName,BasicSyncInfo', - ImageTypeLimit: 1, - EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', - StartIndex: 0 - }, - view: libraryBrowser.getSavedView(key) || 'Poster' - }; - - if (userSettings.libraryPageSize() > 0) { - pageData.query['Limit'] = userSettings.libraryPageSize(); - } - - pageData.query.ParentId = params.topParentId; - libraryBrowser.loadSavedQueryValues(key, pageData.query); - } - - return pageData; - } - - function getQuery() { - return getPageData().query; - } - - function getSavedQueryKey() { - if (!savedQueryKey) { - savedQueryKey = libraryBrowser.getSavedQueryKey('musicalbums'); - } - - return savedQueryKey; - } - - const onViewStyleChange = () => { - const viewStyle = this.getCurrentViewStyle(); - const itemsContainer = tabContent.querySelector('.itemsContainer'); - - if (viewStyle == 'List') { - itemsContainer.classList.add('vertical-list'); - itemsContainer.classList.remove('vertical-wrap'); - } else { - itemsContainer.classList.remove('vertical-list'); - itemsContainer.classList.add('vertical-wrap'); - } - - itemsContainer.innerHTML = ''; - }; - - const reloadItems = () => { - loading.show(); - isLoading = true; - const query = getQuery(); - ApiClient.getItems(ApiClient.getCurrentUserId(), query).then((result) => { - function onNextPageClick() { - if (isLoading) { - return; - } - - if (userSettings.libraryPageSize() > 0) { - query.StartIndex += query.Limit; - } - reloadItems(); - } - - function onPreviousPageClick() { - if (isLoading) { - return; - } - - if (userSettings.libraryPageSize() > 0) { - query.StartIndex = Math.max(0, query.StartIndex - query.Limit); - } - reloadItems(); - } - - window.scrollTo(0, 0); - this.alphaPicker?.updateControls(query); - let html; - const pagingHtml = libraryBrowser.getQueryPagingHtml({ - startIndex: query.StartIndex, - limit: query.Limit, - totalRecordCount: result.TotalRecordCount, - showLimit: false, - updatePageSizeSetting: false, - addLayoutButton: false, - sortButton: false, - filterButton: false - }); - const viewStyle = this.getCurrentViewStyle(); - if (viewStyle == 'List') { - html = listView.getListViewHtml({ - items: result.Items, - context: 'music', - sortBy: query.SortBy, - addToListButton: true - }); - } else if (viewStyle == 'PosterCard') { - html = cardBuilder.getCardsHtml({ - items: result.Items, - shape: 'square', - context: 'music', - showTitle: true, - coverImage: true, - showParentTitle: true, - lazy: true, - cardLayout: true - }); - } else { - html = cardBuilder.getCardsHtml({ - items: result.Items, - shape: 'square', - context: 'music', - showTitle: true, - showParentTitle: true, - lazy: true, - centerText: true, - overlayPlayButton: true - }); - } - - let elems = tabContent.querySelectorAll('.paging'); - - for (const elem of elems) { - elem.innerHTML = pagingHtml; - } - - elems = tabContent.querySelectorAll('.btnNextPage'); - for (const elem of elems) { - elem.addEventListener('click', onNextPageClick); - } - - elems = tabContent.querySelectorAll('.btnPreviousPage'); - for (const elem of elems) { - elem.addEventListener('click', onPreviousPageClick); - } - - const itemsContainer = tabContent.querySelector('.itemsContainer'); - itemsContainer.innerHTML = html; - imageLoader.lazyChildren(itemsContainer); - libraryBrowser.saveQueryValues(getSavedQueryKey(), query); - loading.hide(); - isLoading = false; - - import('../../components/autoFocuser').then(({ default: autoFocuser }) => { - autoFocuser.autoFocus(tabContent); - }); - }); - }; - - let savedQueryKey; - let pageData; - let isLoading = false; - - this.showFilterMenu = function () { - import('../../components/filterdialog/filterdialog').then(({ default: filterDialogFactory }) => { - const filterDialog = new filterDialogFactory({ - query: getQuery(), - mode: 'albums', - serverId: ApiClient.serverId() - }); - Events.on(filterDialog, 'filterchange', function () { - getQuery().StartIndex = 0; - reloadItems(); - }); - - filterDialog.show(); - }); - }; - - this.getCurrentViewStyle = function () { - return getPageData().view; - }; - - const initPage = (tabElement) => { - const alphaPickerElement = tabElement.querySelector('.alphaPicker'); - const itemsContainer = tabElement.querySelector('.itemsContainer'); - - alphaPickerElement.addEventListener('alphavaluechanged', function (e) { - const newValue = e.detail.value; - const query = getQuery(); - if (newValue === '#') { - query.NameLessThan = 'A'; - delete query.NameStartsWith; - } else { - query.NameStartsWith = newValue; - delete query.NameLessThan; - } - query.StartIndex = 0; - reloadItems(); - }); - - this.alphaPicker = new AlphaPicker({ - element: alphaPickerElement, - valueChangeEvent: 'click' - }); - - tabElement.querySelector('.alphaPicker').classList.add('alphabetPicker-right'); - alphaPickerElement.classList.add('alphaPicker-fixed-right'); - itemsContainer.classList.add('padded-right-withalphapicker'); - - tabElement.querySelector('.btnFilter').addEventListener('click', () => { - this.showFilterMenu(); - }); - - tabElement.querySelector('.btnSort').addEventListener('click', (e) => { - libraryBrowser.showSortMenu({ - items: [{ - name: globalize.translate('Name'), - id: 'SortName' - }, { - name: globalize.translate('AlbumArtist'), - id: 'AlbumArtist,SortName' - }, { - name: globalize.translate('OptionCommunityRating'), - id: 'CommunityRating,SortName' - }, { - name: globalize.translate('OptionCriticRating'), - id: 'CriticRating,SortName' - }, { - name: globalize.translate('OptionDateAdded'), - id: 'DateCreated,SortName' - }, { - name: globalize.translate('OptionReleaseDate'), - id: 'ProductionYear,PremiereDate,SortName' - }, { - name: globalize.translate('OptionRandom'), - id: 'Random,SortName' - }], - callback: function () { - getQuery().StartIndex = 0; - reloadItems(); - }, - query: getQuery(), - button: e.target - }); - }); - - const btnSelectView = tabElement.querySelector('.btnSelectView'); - btnSelectView.addEventListener('click', (e) => { - libraryBrowser.showLayoutMenu(e.target, this.getCurrentViewStyle(), 'List,Poster,PosterCard'.split(',')); - }); - - btnSelectView.addEventListener('layoutchange', function (e) { - const viewStyle = e.detail.viewStyle; - getPageData().view = viewStyle; - libraryBrowser.saveViewSetting(getSavedQueryKey(), viewStyle); - getQuery().StartIndex = 0; - onViewStyleChange(); - reloadItems(); - }); - - tabElement.querySelector('.btnPlayAll').addEventListener('click', playAll); - tabElement.querySelector('.btnShuffle').addEventListener('click', shuffle); - }; - - initPage(tabContent); - onViewStyleChange(); - - this.renderTab = () => { - reloadItems(); - this.alphaPicker?.updateControls(getQuery()); - }; + }); } -/* eslint-enable indent */ + function shuffle() { + ApiClient.getItem(ApiClient.getCurrentUserId(), params.topParentId).then(function (item) { + getQuery(); + playbackManager.shuffle(item); + }); + } + + function getPageData() { + const key = getSavedQueryKey(); + + if (!pageData) { + pageData = { + query: { + SortBy: 'SortName', + SortOrder: 'Ascending', + IncludeItemTypes: 'MusicAlbum', + Recursive: true, + Fields: 'PrimaryImageAspectRatio,SortName,BasicSyncInfo', + ImageTypeLimit: 1, + EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', + StartIndex: 0 + }, + view: libraryBrowser.getSavedView(key) || 'Poster' + }; + + if (userSettings.libraryPageSize() > 0) { + pageData.query['Limit'] = userSettings.libraryPageSize(); + } + + pageData.query.ParentId = params.topParentId; + libraryBrowser.loadSavedQueryValues(key, pageData.query); + } + + return pageData; + } + + function getQuery() { + return getPageData().query; + } + + function getSavedQueryKey() { + if (!savedQueryKey) { + savedQueryKey = libraryBrowser.getSavedQueryKey('musicalbums'); + } + + return savedQueryKey; + } + + const onViewStyleChange = () => { + const viewStyle = this.getCurrentViewStyle(); + const itemsContainer = tabContent.querySelector('.itemsContainer'); + + if (viewStyle == 'List') { + itemsContainer.classList.add('vertical-list'); + itemsContainer.classList.remove('vertical-wrap'); + } else { + itemsContainer.classList.remove('vertical-list'); + itemsContainer.classList.add('vertical-wrap'); + } + + itemsContainer.innerHTML = ''; + }; + + const reloadItems = () => { + loading.show(); + isLoading = true; + const query = getQuery(); + ApiClient.getItems(ApiClient.getCurrentUserId(), query).then((result) => { + function onNextPageClick() { + if (isLoading) { + return; + } + + if (userSettings.libraryPageSize() > 0) { + query.StartIndex += query.Limit; + } + reloadItems(); + } + + function onPreviousPageClick() { + if (isLoading) { + return; + } + + if (userSettings.libraryPageSize() > 0) { + query.StartIndex = Math.max(0, query.StartIndex - query.Limit); + } + reloadItems(); + } + + window.scrollTo(0, 0); + this.alphaPicker?.updateControls(query); + let html; + const pagingHtml = libraryBrowser.getQueryPagingHtml({ + startIndex: query.StartIndex, + limit: query.Limit, + totalRecordCount: result.TotalRecordCount, + showLimit: false, + updatePageSizeSetting: false, + addLayoutButton: false, + sortButton: false, + filterButton: false + }); + const viewStyle = this.getCurrentViewStyle(); + if (viewStyle == 'List') { + html = listView.getListViewHtml({ + items: result.Items, + context: 'music', + sortBy: query.SortBy, + addToListButton: true + }); + } else if (viewStyle == 'PosterCard') { + html = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'square', + context: 'music', + showTitle: true, + coverImage: true, + showParentTitle: true, + lazy: true, + cardLayout: true + }); + } else { + html = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'square', + context: 'music', + showTitle: true, + showParentTitle: true, + lazy: true, + centerText: true, + overlayPlayButton: true + }); + } + + let elems = tabContent.querySelectorAll('.paging'); + + for (const elem of elems) { + elem.innerHTML = pagingHtml; + } + + elems = tabContent.querySelectorAll('.btnNextPage'); + for (const elem of elems) { + elem.addEventListener('click', onNextPageClick); + } + + elems = tabContent.querySelectorAll('.btnPreviousPage'); + for (const elem of elems) { + elem.addEventListener('click', onPreviousPageClick); + } + + const itemsContainer = tabContent.querySelector('.itemsContainer'); + itemsContainer.innerHTML = html; + imageLoader.lazyChildren(itemsContainer); + libraryBrowser.saveQueryValues(getSavedQueryKey(), query); + loading.hide(); + isLoading = false; + + import('../../components/autoFocuser').then(({ default: autoFocuser }) => { + autoFocuser.autoFocus(tabContent); + }); + }); + }; + + let savedQueryKey; + let pageData; + let isLoading = false; + + this.showFilterMenu = function () { + import('../../components/filterdialog/filterdialog').then(({ default: filterDialogFactory }) => { + const filterDialog = new filterDialogFactory({ + query: getQuery(), + mode: 'albums', + serverId: ApiClient.serverId() + }); + Events.on(filterDialog, 'filterchange', function () { + getQuery().StartIndex = 0; + reloadItems(); + }); + + filterDialog.show(); + }); + }; + + this.getCurrentViewStyle = function () { + return getPageData().view; + }; + + const initPage = (tabElement) => { + const alphaPickerElement = tabElement.querySelector('.alphaPicker'); + const itemsContainer = tabElement.querySelector('.itemsContainer'); + + alphaPickerElement.addEventListener('alphavaluechanged', function (e) { + const newValue = e.detail.value; + const query = getQuery(); + if (newValue === '#') { + query.NameLessThan = 'A'; + delete query.NameStartsWith; + } else { + query.NameStartsWith = newValue; + delete query.NameLessThan; + } + query.StartIndex = 0; + reloadItems(); + }); + + this.alphaPicker = new AlphaPicker({ + element: alphaPickerElement, + valueChangeEvent: 'click' + }); + + tabElement.querySelector('.alphaPicker').classList.add('alphabetPicker-right'); + alphaPickerElement.classList.add('alphaPicker-fixed-right'); + itemsContainer.classList.add('padded-right-withalphapicker'); + + tabElement.querySelector('.btnFilter').addEventListener('click', () => { + this.showFilterMenu(); + }); + + tabElement.querySelector('.btnSort').addEventListener('click', (e) => { + libraryBrowser.showSortMenu({ + items: [{ + name: globalize.translate('Name'), + id: 'SortName' + }, { + name: globalize.translate('AlbumArtist'), + id: 'AlbumArtist,SortName' + }, { + name: globalize.translate('OptionCommunityRating'), + id: 'CommunityRating,SortName' + }, { + name: globalize.translate('OptionCriticRating'), + id: 'CriticRating,SortName' + }, { + name: globalize.translate('OptionDateAdded'), + id: 'DateCreated,SortName' + }, { + name: globalize.translate('OptionReleaseDate'), + id: 'ProductionYear,PremiereDate,SortName' + }, { + name: globalize.translate('OptionRandom'), + id: 'Random,SortName' + }], + callback: function () { + getQuery().StartIndex = 0; + reloadItems(); + }, + query: getQuery(), + button: e.target + }); + }); + + const btnSelectView = tabElement.querySelector('.btnSelectView'); + btnSelectView.addEventListener('click', (e) => { + libraryBrowser.showLayoutMenu(e.target, this.getCurrentViewStyle(), 'List,Poster,PosterCard'.split(',')); + }); + + btnSelectView.addEventListener('layoutchange', function (e) { + const viewStyle = e.detail.viewStyle; + getPageData().view = viewStyle; + libraryBrowser.saveViewSetting(getSavedQueryKey(), viewStyle); + getQuery().StartIndex = 0; + onViewStyleChange(); + reloadItems(); + }); + + tabElement.querySelector('.btnPlayAll').addEventListener('click', playAll); + tabElement.querySelector('.btnShuffle').addEventListener('click', shuffle); + }; + + initPage(tabContent); + onViewStyleChange(); + + this.renderTab = () => { + reloadItems(); + this.alphaPicker?.updateControls(getQuery()); + }; +} + diff --git a/src/controllers/music/musicartists.js b/src/controllers/music/musicartists.js index 040df1af73..084f092db3 100644 --- a/src/controllers/music/musicartists.js +++ b/src/controllers/music/musicartists.js @@ -9,237 +9,234 @@ import Events from '../../utils/events.ts'; import '../../elements/emby-itemscontainer/emby-itemscontainer'; -/* eslint-disable indent */ +export default function (view, params, tabContent) { + function getPageData(context) { + const key = getSavedQueryKey(context); + let pageData = data[key]; - export default function (view, params, tabContent) { - function getPageData(context) { - const key = getSavedQueryKey(context); - let pageData = data[key]; + if (!pageData) { + const queryValues = { + SortBy: 'SortName', + SortOrder: 'Ascending', + Recursive: true, + Fields: 'PrimaryImageAspectRatio,SortName,BasicSyncInfo', + StartIndex: 0, + ImageTypeLimit: 1, + EnableImageTypes: 'Primary,Backdrop,Banner,Thumb' + }; - if (!pageData) { - const queryValues = { - SortBy: 'SortName', - SortOrder: 'Ascending', - Recursive: true, - Fields: 'PrimaryImageAspectRatio,SortName,BasicSyncInfo', - StartIndex: 0, - ImageTypeLimit: 1, - EnableImageTypes: 'Primary,Backdrop,Banner,Thumb' - }; - - if (userSettings.libraryPageSize() > 0) { - queryValues['Limit'] = userSettings.libraryPageSize(); - } - - pageData = data[key] = { - query: queryValues, - view: libraryBrowser.getSavedView(key) || 'Poster' - }; - pageData.query.ParentId = params.topParentId; - libraryBrowser.loadSavedQueryValues(key, pageData.query); + if (userSettings.libraryPageSize() > 0) { + queryValues['Limit'] = userSettings.libraryPageSize(); } - return pageData; + pageData = data[key] = { + query: queryValues, + view: libraryBrowser.getSavedView(key) || 'Poster' + }; + pageData.query.ParentId = params.topParentId; + libraryBrowser.loadSavedQueryValues(key, pageData.query); } - function getQuery(context) { - return getPageData(context).query; - } - - const getSavedQueryKey = (context) => { - if (!context.savedQueryKey) { - context.savedQueryKey = libraryBrowser.getSavedQueryKey(this.mode); - } - - return context.savedQueryKey; - }; - - const onViewStyleChange = () => { - const viewStyle = this.getCurrentViewStyle(); - const itemsContainer = tabContent.querySelector('.itemsContainer'); - - if (viewStyle == 'List') { - itemsContainer.classList.add('vertical-list'); - itemsContainer.classList.remove('vertical-wrap'); - } else { - itemsContainer.classList.remove('vertical-list'); - itemsContainer.classList.add('vertical-wrap'); - } - - itemsContainer.innerHTML = ''; - }; - - const reloadItems = (page) => { - loading.show(); - isLoading = true; - const query = getQuery(page); - const promise = this.mode == 'albumartists' ? - ApiClient.getAlbumArtists(ApiClient.getCurrentUserId(), query) : - ApiClient.getArtists(ApiClient.getCurrentUserId(), query); - promise.then((result) => { - function onNextPageClick() { - if (isLoading) { - return; - } - - if (userSettings.libraryPageSize() > 0) { - query.StartIndex += query.Limit; - } - reloadItems(tabContent); - } - - function onPreviousPageClick() { - if (isLoading) { - return; - } - - if (userSettings.libraryPageSize() > 0) { - query.StartIndex = Math.max(0, query.StartIndex - query.Limit); - } - reloadItems(tabContent); - } - - window.scrollTo(0, 0); - this.alphaPicker?.updateControls(query); - let html; - const pagingHtml = libraryBrowser.getQueryPagingHtml({ - startIndex: query.StartIndex, - limit: query.Limit, - totalRecordCount: result.TotalRecordCount, - showLimit: false, - updatePageSizeSetting: false, - addLayoutButton: false, - sortButton: false, - filterButton: false - }); - const viewStyle = this.getCurrentViewStyle(); - if (viewStyle == 'List') { - html = listView.getListViewHtml({ - items: result.Items, - sortBy: query.SortBy - }); - } else if (viewStyle == 'PosterCard') { - html = cardBuilder.getCardsHtml({ - items: result.Items, - shape: 'square', - context: 'music', - showTitle: true, - coverImage: true, - cardLayout: true - }); - } else { - html = cardBuilder.getCardsHtml({ - items: result.Items, - shape: 'square', - context: 'music', - showTitle: true, - coverImage: true, - lazy: true, - centerText: true, - overlayPlayButton: true - }); - } - let elems = tabContent.querySelectorAll('.paging'); - - for (let i = 0, length = elems.length; i < length; i++) { - elems[i].innerHTML = pagingHtml; - } - - elems = tabContent.querySelectorAll('.btnNextPage'); - for (let i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener('click', onNextPageClick); - } - - elems = tabContent.querySelectorAll('.btnPreviousPage'); - for (let i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener('click', onPreviousPageClick); - } - - const itemsContainer = tabContent.querySelector('.itemsContainer'); - itemsContainer.innerHTML = html; - imageLoader.lazyChildren(itemsContainer); - libraryBrowser.saveQueryValues(getSavedQueryKey(page), query); - loading.hide(); - isLoading = false; - - import('../../components/autoFocuser').then(({ default: autoFocuser }) => { - autoFocuser.autoFocus(tabContent); - }); - }); - }; - - const data = {}; - let isLoading = false; - - this.showFilterMenu = function () { - import('../../components/filterdialog/filterdialog').then(({ default: filterDialogFactory }) => { - const filterDialog = new filterDialogFactory({ - query: getQuery(tabContent), - mode: this.mode, - serverId: ApiClient.serverId() - }); - Events.on(filterDialog, 'filterchange', function () { - getQuery(tabContent).StartIndex = 0; - reloadItems(tabContent); - }); - filterDialog.show(); - }); - }; - - this.getCurrentViewStyle = function () { - return getPageData(tabContent).view; - }; - - const initPage = (tabElement) => { - const alphaPickerElement = tabElement.querySelector('.alphaPicker'); - const itemsContainer = tabElement.querySelector('.itemsContainer'); - - alphaPickerElement.addEventListener('alphavaluechanged', function (e) { - const newValue = e.detail.value; - const query = getQuery(tabElement); - if (newValue === '#') { - query.NameLessThan = 'A'; - delete query.NameStartsWith; - } else { - query.NameStartsWith = newValue; - delete query.NameLessThan; - } - query.StartIndex = 0; - reloadItems(tabElement); - }); - this.alphaPicker = new AlphaPicker({ - element: alphaPickerElement, - valueChangeEvent: 'click' - }); - - tabElement.querySelector('.alphaPicker').classList.add('alphabetPicker-right'); - alphaPickerElement.classList.add('alphaPicker-fixed-right'); - itemsContainer.classList.add('padded-right-withalphapicker'); - - tabElement.querySelector('.btnFilter').addEventListener('click', () => { - this.showFilterMenu(); - }); - const btnSelectView = tabElement.querySelector('.btnSelectView'); - btnSelectView.addEventListener('click', (e) => { - libraryBrowser.showLayoutMenu(e.target, this.getCurrentViewStyle(), 'List,Poster,PosterCard'.split(',')); - }); - btnSelectView.addEventListener('layoutchange', function (e) { - const viewStyle = e.detail.viewStyle; - getPageData(tabElement).view = viewStyle; - libraryBrowser.saveViewSetting(getSavedQueryKey(tabElement), viewStyle); - getQuery(tabElement).StartIndex = 0; - onViewStyleChange(); - reloadItems(tabElement); - }); - }; - - initPage(tabContent); - onViewStyleChange(); - - this.renderTab = () => { - reloadItems(tabContent); - this.alphaPicker?.updateControls(getQuery(tabContent)); - }; + return pageData; } -/* eslint-enable indent */ + function getQuery(context) { + return getPageData(context).query; + } + + const getSavedQueryKey = (context) => { + if (!context.savedQueryKey) { + context.savedQueryKey = libraryBrowser.getSavedQueryKey(this.mode); + } + + return context.savedQueryKey; + }; + + const onViewStyleChange = () => { + const viewStyle = this.getCurrentViewStyle(); + const itemsContainer = tabContent.querySelector('.itemsContainer'); + + if (viewStyle == 'List') { + itemsContainer.classList.add('vertical-list'); + itemsContainer.classList.remove('vertical-wrap'); + } else { + itemsContainer.classList.remove('vertical-list'); + itemsContainer.classList.add('vertical-wrap'); + } + + itemsContainer.innerHTML = ''; + }; + + const reloadItems = (page) => { + loading.show(); + isLoading = true; + const query = getQuery(page); + const promise = this.mode == 'albumartists' ? + ApiClient.getAlbumArtists(ApiClient.getCurrentUserId(), query) : + ApiClient.getArtists(ApiClient.getCurrentUserId(), query); + promise.then((result) => { + function onNextPageClick() { + if (isLoading) { + return; + } + + if (userSettings.libraryPageSize() > 0) { + query.StartIndex += query.Limit; + } + reloadItems(tabContent); + } + + function onPreviousPageClick() { + if (isLoading) { + return; + } + + if (userSettings.libraryPageSize() > 0) { + query.StartIndex = Math.max(0, query.StartIndex - query.Limit); + } + reloadItems(tabContent); + } + + window.scrollTo(0, 0); + this.alphaPicker?.updateControls(query); + let html; + const pagingHtml = libraryBrowser.getQueryPagingHtml({ + startIndex: query.StartIndex, + limit: query.Limit, + totalRecordCount: result.TotalRecordCount, + showLimit: false, + updatePageSizeSetting: false, + addLayoutButton: false, + sortButton: false, + filterButton: false + }); + const viewStyle = this.getCurrentViewStyle(); + if (viewStyle == 'List') { + html = listView.getListViewHtml({ + items: result.Items, + sortBy: query.SortBy + }); + } else if (viewStyle == 'PosterCard') { + html = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'square', + context: 'music', + showTitle: true, + coverImage: true, + cardLayout: true + }); + } else { + html = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'square', + context: 'music', + showTitle: true, + coverImage: true, + lazy: true, + centerText: true, + overlayPlayButton: true + }); + } + let elems = tabContent.querySelectorAll('.paging'); + + for (let i = 0, length = elems.length; i < length; i++) { + elems[i].innerHTML = pagingHtml; + } + + elems = tabContent.querySelectorAll('.btnNextPage'); + for (let i = 0, length = elems.length; i < length; i++) { + elems[i].addEventListener('click', onNextPageClick); + } + + elems = tabContent.querySelectorAll('.btnPreviousPage'); + for (let i = 0, length = elems.length; i < length; i++) { + elems[i].addEventListener('click', onPreviousPageClick); + } + + const itemsContainer = tabContent.querySelector('.itemsContainer'); + itemsContainer.innerHTML = html; + imageLoader.lazyChildren(itemsContainer); + libraryBrowser.saveQueryValues(getSavedQueryKey(page), query); + loading.hide(); + isLoading = false; + + import('../../components/autoFocuser').then(({ default: autoFocuser }) => { + autoFocuser.autoFocus(tabContent); + }); + }); + }; + + const data = {}; + let isLoading = false; + + this.showFilterMenu = function () { + import('../../components/filterdialog/filterdialog').then(({ default: filterDialogFactory }) => { + const filterDialog = new filterDialogFactory({ + query: getQuery(tabContent), + mode: this.mode, + serverId: ApiClient.serverId() + }); + Events.on(filterDialog, 'filterchange', function () { + getQuery(tabContent).StartIndex = 0; + reloadItems(tabContent); + }); + filterDialog.show(); + }); + }; + + this.getCurrentViewStyle = function () { + return getPageData(tabContent).view; + }; + + const initPage = (tabElement) => { + const alphaPickerElement = tabElement.querySelector('.alphaPicker'); + const itemsContainer = tabElement.querySelector('.itemsContainer'); + + alphaPickerElement.addEventListener('alphavaluechanged', function (e) { + const newValue = e.detail.value; + const query = getQuery(tabElement); + if (newValue === '#') { + query.NameLessThan = 'A'; + delete query.NameStartsWith; + } else { + query.NameStartsWith = newValue; + delete query.NameLessThan; + } + query.StartIndex = 0; + reloadItems(tabElement); + }); + this.alphaPicker = new AlphaPicker({ + element: alphaPickerElement, + valueChangeEvent: 'click' + }); + + tabElement.querySelector('.alphaPicker').classList.add('alphabetPicker-right'); + alphaPickerElement.classList.add('alphaPicker-fixed-right'); + itemsContainer.classList.add('padded-right-withalphapicker'); + + tabElement.querySelector('.btnFilter').addEventListener('click', () => { + this.showFilterMenu(); + }); + const btnSelectView = tabElement.querySelector('.btnSelectView'); + btnSelectView.addEventListener('click', (e) => { + libraryBrowser.showLayoutMenu(e.target, this.getCurrentViewStyle(), 'List,Poster,PosterCard'.split(',')); + }); + btnSelectView.addEventListener('layoutchange', function (e) { + const viewStyle = e.detail.viewStyle; + getPageData(tabElement).view = viewStyle; + libraryBrowser.saveViewSetting(getSavedQueryKey(tabElement), viewStyle); + getQuery(tabElement).StartIndex = 0; + onViewStyleChange(); + reloadItems(tabElement); + }); + }; + + initPage(tabContent); + onViewStyleChange(); + + this.renderTab = () => { + reloadItems(tabContent); + this.alphaPicker?.updateControls(getQuery(tabContent)); + }; +} + diff --git a/src/controllers/music/musicgenres.js b/src/controllers/music/musicgenres.js index de64af44de..9c5bc9dcb2 100644 --- a/src/controllers/music/musicgenres.js +++ b/src/controllers/music/musicgenres.js @@ -3,132 +3,129 @@ import cardBuilder from '../../components/cardbuilder/cardBuilder'; import imageLoader from '../../components/images/imageLoader'; import loading from '../../components/loading/loading'; -/* eslint-disable indent */ +export default function (view, params, tabContent) { + function getPageData() { + const key = getSavedQueryKey(); + let pageData = data[key]; - export default function (view, params, tabContent) { - function getPageData() { - const key = getSavedQueryKey(); - let pageData = data[key]; - - if (!pageData) { - pageData = data[key] = { - query: { - SortBy: 'SortName', - SortOrder: 'Ascending', - Recursive: true, - Fields: 'PrimaryImageAspectRatio,ItemCounts', - StartIndex: 0 - }, - view: libraryBrowser.getSavedView(key) || 'Poster' - }; - pageData.query.ParentId = params.topParentId; - libraryBrowser.loadSavedQueryValues(key, pageData.query); - } - - return pageData; + if (!pageData) { + pageData = data[key] = { + query: { + SortBy: 'SortName', + SortOrder: 'Ascending', + Recursive: true, + Fields: 'PrimaryImageAspectRatio,ItemCounts', + StartIndex: 0 + }, + view: libraryBrowser.getSavedView(key) || 'Poster' + }; + pageData.query.ParentId = params.topParentId; + libraryBrowser.loadSavedQueryValues(key, pageData.query); } - function getQuery() { - return getPageData().query; - } - - function getSavedQueryKey() { - return libraryBrowser.getSavedQueryKey('genres'); - } - - function getPromise() { - loading.show(); - const query = getQuery(); - return ApiClient.getGenres(ApiClient.getCurrentUserId(), query); - } - - const reloadItems = (context, promise) => { - const query = getQuery(); - promise.then((result) => { - let html = ''; - const viewStyle = this.getCurrentViewStyle(); - - if (viewStyle == 'Thumb') { - html = cardBuilder.getCardsHtml({ - items: result.Items, - shape: 'backdrop', - preferThumb: true, - context: 'music', - centerText: true, - overlayMoreButton: true, - showTitle: true - }); - } else if (viewStyle == 'ThumbCard') { - html = cardBuilder.getCardsHtml({ - items: result.Items, - shape: 'backdrop', - preferThumb: true, - context: 'music', - cardLayout: true, - showTitle: true - }); - } else if (viewStyle == 'PosterCard') { - html = cardBuilder.getCardsHtml({ - items: result.Items, - shape: 'auto', - context: 'music', - cardLayout: true, - showTitle: true - }); - } else if (viewStyle == 'Poster') { - html = cardBuilder.getCardsHtml({ - items: result.Items, - shape: 'auto', - context: 'music', - centerText: true, - overlayMoreButton: true, - showTitle: true - }); - } - - const elem = context.querySelector('#items'); - elem.innerHTML = html; - imageLoader.lazyChildren(elem); - libraryBrowser.saveQueryValues(getSavedQueryKey(), query); - loading.hide(); - - import('../../components/autoFocuser').then(({ default: autoFocuser }) => { - autoFocuser.autoFocus(context); - }); - }); - }; - - function fullyReload() { - this.preRender(); - this.renderTab(); - } - - const data = {}; - - this.getViewStyles = function () { - return 'Poster,PosterCard,Thumb,ThumbCard'.split(','); - }; - - this.getCurrentViewStyle = function () { - return getPageData().view; - }; - - this.setCurrentViewStyle = function (viewStyle) { - getPageData().view = viewStyle; - libraryBrowser.saveViewSetting(getSavedQueryKey(), viewStyle); - fullyReload(); - }; - - this.enableViewSelection = true; - let promise; - - this.preRender = function () { - promise = getPromise(); - }; - - this.renderTab = function () { - reloadItems(tabContent, promise); - }; + return pageData; } -/* eslint-enable indent */ + function getQuery() { + return getPageData().query; + } + + function getSavedQueryKey() { + return libraryBrowser.getSavedQueryKey('genres'); + } + + function getPromise() { + loading.show(); + const query = getQuery(); + return ApiClient.getGenres(ApiClient.getCurrentUserId(), query); + } + + const reloadItems = (context, promise) => { + const query = getQuery(); + promise.then((result) => { + let html = ''; + const viewStyle = this.getCurrentViewStyle(); + + if (viewStyle == 'Thumb') { + html = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'backdrop', + preferThumb: true, + context: 'music', + centerText: true, + overlayMoreButton: true, + showTitle: true + }); + } else if (viewStyle == 'ThumbCard') { + html = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'backdrop', + preferThumb: true, + context: 'music', + cardLayout: true, + showTitle: true + }); + } else if (viewStyle == 'PosterCard') { + html = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'auto', + context: 'music', + cardLayout: true, + showTitle: true + }); + } else if (viewStyle == 'Poster') { + html = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'auto', + context: 'music', + centerText: true, + overlayMoreButton: true, + showTitle: true + }); + } + + const elem = context.querySelector('#items'); + elem.innerHTML = html; + imageLoader.lazyChildren(elem); + libraryBrowser.saveQueryValues(getSavedQueryKey(), query); + loading.hide(); + + import('../../components/autoFocuser').then(({ default: autoFocuser }) => { + autoFocuser.autoFocus(context); + }); + }); + }; + + function fullyReload() { + this.preRender(); + this.renderTab(); + } + + const data = {}; + + this.getViewStyles = function () { + return 'Poster,PosterCard,Thumb,ThumbCard'.split(','); + }; + + this.getCurrentViewStyle = function () { + return getPageData().view; + }; + + this.setCurrentViewStyle = function (viewStyle) { + getPageData().view = viewStyle; + libraryBrowser.saveViewSetting(getSavedQueryKey(), viewStyle); + fullyReload(); + }; + + this.enableViewSelection = true; + let promise; + + this.preRender = function () { + promise = getPromise(); + }; + + this.renderTab = function () { + reloadItems(tabContent, promise); + }; +} + diff --git a/src/controllers/music/musicplaylists.js b/src/controllers/music/musicplaylists.js index 8f562f1aaa..ab3cf8f1d2 100644 --- a/src/controllers/music/musicplaylists.js +++ b/src/controllers/music/musicplaylists.js @@ -3,87 +3,84 @@ import cardBuilder from '../../components/cardbuilder/cardBuilder'; import imageLoader from '../../components/images/imageLoader'; import loading from '../../components/loading/loading'; -/* eslint-disable indent */ +export default function (view, params, tabContent) { + function getPageData() { + const key = getSavedQueryKey(); + let pageData = data[key]; - export default function (view, params, tabContent) { - function getPageData() { - const key = getSavedQueryKey(); - let pageData = data[key]; - - if (!pageData) { - pageData = data[key] = { - query: { - SortBy: 'SortName', - SortOrder: 'Ascending', - IncludeItemTypes: 'Playlist', - Recursive: true, - Fields: 'PrimaryImageAspectRatio,SortName,CanDelete', - StartIndex: 0 - }, - view: libraryBrowser.getSavedView(key) || 'Poster' - }; - pageData.query.ParentId = params.topParentId; - libraryBrowser.loadSavedQueryValues(key, pageData.query); - } - - return pageData; + if (!pageData) { + pageData = data[key] = { + query: { + SortBy: 'SortName', + SortOrder: 'Ascending', + IncludeItemTypes: 'Playlist', + Recursive: true, + Fields: 'PrimaryImageAspectRatio,SortName,CanDelete', + StartIndex: 0 + }, + view: libraryBrowser.getSavedView(key) || 'Poster' + }; + pageData.query.ParentId = params.topParentId; + libraryBrowser.loadSavedQueryValues(key, pageData.query); } - function getQuery() { - return getPageData().query; - } - - function getSavedQueryKey() { - return libraryBrowser.getSavedQueryKey('genres'); - } - - function getPromise() { - loading.show(); - const query = getQuery(); - return ApiClient.getItems(ApiClient.getCurrentUserId(), query); - } - - function reloadItems(context, promise) { - const query = getQuery(); - promise.then(function (result) { - let html = ''; - html = cardBuilder.getCardsHtml({ - items: result.Items, - shape: 'square', - showTitle: true, - coverImage: true, - centerText: true, - overlayPlayButton: true, - allowBottomPadding: true, - cardLayout: false - }); - const elem = context.querySelector('#items'); - elem.innerHTML = html; - imageLoader.lazyChildren(elem); - libraryBrowser.saveQueryValues(getSavedQueryKey(), query); - loading.hide(); - - import('../../components/autoFocuser').then(({ default: autoFocuser }) => { - autoFocuser.autoFocus(context); - }); - }); - } - - const data = {}; - - this.getCurrentViewStyle = function () { - return getPageData().view; - }; - - let promise; - - this.preRender = function () { - promise = getPromise(); - }; - - this.renderTab = function () { - reloadItems(tabContent, promise); - }; + return pageData; } -/* eslint-enable indent */ + function getQuery() { + return getPageData().query; + } + + function getSavedQueryKey() { + return libraryBrowser.getSavedQueryKey('genres'); + } + + function getPromise() { + loading.show(); + const query = getQuery(); + return ApiClient.getItems(ApiClient.getCurrentUserId(), query); + } + + function reloadItems(context, promise) { + const query = getQuery(); + promise.then(function (result) { + let html = ''; + html = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'square', + showTitle: true, + coverImage: true, + centerText: true, + overlayPlayButton: true, + allowBottomPadding: true, + cardLayout: false + }); + const elem = context.querySelector('#items'); + elem.innerHTML = html; + imageLoader.lazyChildren(elem); + libraryBrowser.saveQueryValues(getSavedQueryKey(), query); + loading.hide(); + + import('../../components/autoFocuser').then(({ default: autoFocuser }) => { + autoFocuser.autoFocus(context); + }); + }); + } + + const data = {}; + + this.getCurrentViewStyle = function () { + return getPageData().view; + }; + + let promise; + + this.preRender = function () { + promise = getPromise(); + }; + + this.renderTab = function () { + reloadItems(tabContent, promise); + }; +} + diff --git a/src/controllers/music/musicrecommended.js b/src/controllers/music/musicrecommended.js index e2e15d2ab2..07946897c7 100644 --- a/src/controllers/music/musicrecommended.js +++ b/src/controllers/music/musicrecommended.js @@ -16,386 +16,383 @@ import '../../elements/emby-button/emby-button'; import '../../styles/flexstyles.scss'; import Dashboard from '../../utils/dashboard'; -/* eslint-disable indent */ +function itemsPerRow() { + const screenWidth = dom.getWindowSize().innerWidth; - function itemsPerRow() { - const screenWidth = dom.getWindowSize().innerWidth; - - if (screenWidth >= 1920) { - return 9; - } - - if (screenWidth >= 1200) { - return 12; - } - - if (screenWidth >= 1000) { - return 10; - } - - return 8; + if (screenWidth >= 1920) { + return 9; } - function enableScrollX() { - return !layoutManager.desktop; + if (screenWidth >= 1200) { + return 12; } - function getSquareShape() { - return enableScrollX() ? 'overflowSquare' : 'square'; + if (screenWidth >= 1000) { + return 10; } - function loadLatest(page, parentId) { + return 8; +} + +function enableScrollX() { + return !layoutManager.desktop; +} + +function getSquareShape() { + return enableScrollX() ? 'overflowSquare' : 'square'; +} + +function loadLatest(page, parentId) { + loading.show(); + const userId = ApiClient.getCurrentUserId(); + const options = { + IncludeItemTypes: 'Audio', + Limit: enableScrollX() ? 3 * itemsPerRow() : 2 * itemsPerRow(), + Fields: 'PrimaryImageAspectRatio,BasicSyncInfo', + ParentId: parentId, + ImageTypeLimit: 1, + EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', + EnableTotalRecordCount: false + }; + ApiClient.getJSON(ApiClient.getUrl('Users/' + userId + '/Items/Latest', options)).then(function (items) { + const elem = page.querySelector('#recentlyAddedSongs'); + elem.innerHTML = cardBuilder.getCardsHtml({ + items: items, + showUnplayedIndicator: false, + showLatestItemsPopup: false, + shape: getSquareShape(), + showTitle: true, + showParentTitle: true, + lazy: true, + centerText: true, + overlayPlayButton: true, + allowBottomPadding: !enableScrollX(), + cardLayout: false, + coverImage: true + }); + imageLoader.lazyChildren(elem); + loading.hide(); + + import('../../components/autoFocuser').then(({ default: autoFocuser }) => { + autoFocuser.autoFocus(page); + }); + }); +} + +function loadRecentlyPlayed(page, parentId) { + const options = { + SortBy: 'DatePlayed', + SortOrder: 'Descending', + IncludeItemTypes: 'Audio', + Limit: itemsPerRow(), + Recursive: true, + Fields: 'PrimaryImageAspectRatio,AudioInfo', + Filters: 'IsPlayed', + ParentId: parentId, + ImageTypeLimit: 1, + EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', + EnableTotalRecordCount: false + }; + ApiClient.getItems(ApiClient.getCurrentUserId(), options).then(function (result) { + const elem = page.querySelector('#recentlyPlayed'); + + if (result.Items.length) { + elem.classList.remove('hide'); + } else { + elem.classList.add('hide'); + } + + const itemsContainer = elem.querySelector('.itemsContainer'); + itemsContainer.innerHTML = cardBuilder.getCardsHtml({ + items: result.Items, + showUnplayedIndicator: false, + shape: getSquareShape(), + showTitle: true, + showParentTitle: true, + action: 'instantmix', + lazy: true, + centerText: true, + overlayMoreButton: true, + allowBottomPadding: !enableScrollX(), + cardLayout: false, + coverImage: true + }); + imageLoader.lazyChildren(itemsContainer); + }); +} + +function loadFrequentlyPlayed(page, parentId) { + const options = { + SortBy: 'PlayCount', + SortOrder: 'Descending', + IncludeItemTypes: 'Audio', + Limit: itemsPerRow(), + Recursive: true, + Fields: 'PrimaryImageAspectRatio,AudioInfo', + Filters: 'IsPlayed', + ParentId: parentId, + ImageTypeLimit: 1, + EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', + EnableTotalRecordCount: false + }; + ApiClient.getItems(ApiClient.getCurrentUserId(), options).then(function (result) { + const elem = page.querySelector('#topPlayed'); + + if (result.Items.length) { + elem.classList.remove('hide'); + } else { + elem.classList.add('hide'); + } + + const itemsContainer = elem.querySelector('.itemsContainer'); + itemsContainer.innerHTML = cardBuilder.getCardsHtml({ + items: result.Items, + showUnplayedIndicator: false, + shape: getSquareShape(), + showTitle: true, + showParentTitle: true, + action: 'instantmix', + lazy: true, + centerText: true, + overlayMoreButton: true, + allowBottomPadding: !enableScrollX(), + cardLayout: false, + coverImage: true + }); + imageLoader.lazyChildren(itemsContainer); + }); +} + +function loadSuggestionsTab(page, tabContent, parentId) { + console.debug('loadSuggestionsTab'); + loadLatest(tabContent, parentId); + loadRecentlyPlayed(tabContent, parentId); + loadFrequentlyPlayed(tabContent, parentId); + + import('../../components/favoriteitems').then(({ default: favoriteItems }) => { + favoriteItems.render(tabContent, ApiClient.getCurrentUserId(), parentId, ['favoriteArtists', 'favoriteAlbums', 'favoriteSongs']); + }); +} + +function getTabs() { + return [{ + name: globalize.translate('Albums') + }, { + name: globalize.translate('Suggestions') + }, { + name: globalize.translate('HeaderAlbumArtists') + }, { + name: globalize.translate('Artists') + }, { + name: globalize.translate('Playlists') + }, { + name: globalize.translate('Songs') + }, { + name: globalize.translate('Genres') + }]; +} + +function getDefaultTabIndex(folderId) { + switch (userSettings.get('landing-' + folderId)) { + case 'suggestions': + return 1; + + case 'albumartists': + return 2; + + case 'artists': + return 3; + + case 'playlists': + return 4; + + case 'songs': + return 5; + + case 'genres': + return 6; + + default: + return 0; + } +} + +export default function (view, params) { + function reload() { loading.show(); - const userId = ApiClient.getCurrentUserId(); - const options = { - IncludeItemTypes: 'Audio', - Limit: enableScrollX() ? 3 * itemsPerRow() : 2 * itemsPerRow(), - Fields: 'PrimaryImageAspectRatio,BasicSyncInfo', - ParentId: parentId, - ImageTypeLimit: 1, - EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', - EnableTotalRecordCount: false - }; - ApiClient.getJSON(ApiClient.getUrl('Users/' + userId + '/Items/Latest', options)).then(function (items) { - const elem = page.querySelector('#recentlyAddedSongs'); - elem.innerHTML = cardBuilder.getCardsHtml({ - items: items, - showUnplayedIndicator: false, - showLatestItemsPopup: false, - shape: getSquareShape(), - showTitle: true, - showParentTitle: true, - lazy: true, - centerText: true, - overlayPlayButton: true, - allowBottomPadding: !enableScrollX(), - cardLayout: false, - coverImage: true - }); - imageLoader.lazyChildren(elem); - loading.hide(); - - import('../../components/autoFocuser').then(({ default: autoFocuser }) => { - autoFocuser.autoFocus(page); - }); - }); + const tabContent = view.querySelector(".pageTabContent[data-index='" + suggestionsTabIndex + "']"); + loadSuggestionsTab(view, tabContent, params.topParentId); } - function loadRecentlyPlayed(page, parentId) { - const options = { - SortBy: 'DatePlayed', - SortOrder: 'Descending', - IncludeItemTypes: 'Audio', - Limit: itemsPerRow(), - Recursive: true, - Fields: 'PrimaryImageAspectRatio,AudioInfo', - Filters: 'IsPlayed', - ParentId: parentId, - ImageTypeLimit: 1, - EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', - EnableTotalRecordCount: false - }; - ApiClient.getItems(ApiClient.getCurrentUserId(), options).then(function (result) { - const elem = page.querySelector('#recentlyPlayed'); + function setScrollClasses(elem, scrollX) { + if (scrollX) { + elem.classList.add('hiddenScrollX'); - if (result.Items.length) { - elem.classList.remove('hide'); - } else { - elem.classList.add('hide'); + if (layoutManager.tv) { + elem.classList.add('smoothScrollX'); } - const itemsContainer = elem.querySelector('.itemsContainer'); - itemsContainer.innerHTML = cardBuilder.getCardsHtml({ - items: result.Items, - showUnplayedIndicator: false, - shape: getSquareShape(), - showTitle: true, - showParentTitle: true, - action: 'instantmix', - lazy: true, - centerText: true, - overlayMoreButton: true, - allowBottomPadding: !enableScrollX(), - cardLayout: false, - coverImage: true - }); - imageLoader.lazyChildren(itemsContainer); - }); - } - - function loadFrequentlyPlayed(page, parentId) { - const options = { - SortBy: 'PlayCount', - SortOrder: 'Descending', - IncludeItemTypes: 'Audio', - Limit: itemsPerRow(), - Recursive: true, - Fields: 'PrimaryImageAspectRatio,AudioInfo', - Filters: 'IsPlayed', - ParentId: parentId, - ImageTypeLimit: 1, - EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', - EnableTotalRecordCount: false - }; - ApiClient.getItems(ApiClient.getCurrentUserId(), options).then(function (result) { - const elem = page.querySelector('#topPlayed'); - - if (result.Items.length) { - elem.classList.remove('hide'); - } else { - elem.classList.add('hide'); - } - - const itemsContainer = elem.querySelector('.itemsContainer'); - itemsContainer.innerHTML = cardBuilder.getCardsHtml({ - items: result.Items, - showUnplayedIndicator: false, - shape: getSquareShape(), - showTitle: true, - showParentTitle: true, - action: 'instantmix', - lazy: true, - centerText: true, - overlayMoreButton: true, - allowBottomPadding: !enableScrollX(), - cardLayout: false, - coverImage: true - }); - imageLoader.lazyChildren(itemsContainer); - }); - } - - function loadSuggestionsTab(page, tabContent, parentId) { - console.debug('loadSuggestionsTab'); - loadLatest(tabContent, parentId); - loadRecentlyPlayed(tabContent, parentId); - loadFrequentlyPlayed(tabContent, parentId); - - import('../../components/favoriteitems').then(({ default: favoriteItems }) => { - favoriteItems.render(tabContent, ApiClient.getCurrentUserId(), parentId, ['favoriteArtists', 'favoriteAlbums', 'favoriteSongs']); - }); - } - - function getTabs() { - return [{ - name: globalize.translate('Albums') - }, { - name: globalize.translate('Suggestions') - }, { - name: globalize.translate('HeaderAlbumArtists') - }, { - name: globalize.translate('Artists') - }, { - name: globalize.translate('Playlists') - }, { - name: globalize.translate('Songs') - }, { - name: globalize.translate('Genres') - }]; - } - - function getDefaultTabIndex(folderId) { - switch (userSettings.get('landing-' + folderId)) { - case 'suggestions': - return 1; - - case 'albumartists': - return 2; - - case 'artists': - return 3; - - case 'playlists': - return 4; - - case 'songs': - return 5; - - case 'genres': - return 6; - - default: - return 0; + elem.classList.add('scrollX'); + elem.classList.remove('vertical-wrap'); + } else { + elem.classList.remove('hiddenScrollX'); + elem.classList.remove('smoothScrollX'); + elem.classList.remove('scrollX'); + elem.classList.add('vertical-wrap'); } } - export default function (view, params) { - function reload() { - loading.show(); - const tabContent = view.querySelector(".pageTabContent[data-index='" + suggestionsTabIndex + "']"); - loadSuggestionsTab(view, tabContent, params.topParentId); + function onBeforeTabChange(e) { + preLoadTab(view, parseInt(e.detail.selectedTabIndex, 10)); + } + + function onTabChange(e) { + loadTab(view, parseInt(e.detail.selectedTabIndex, 10)); + } + + function getTabContainers() { + return view.querySelectorAll('.pageTabContent'); + } + + function initTabs() { + mainTabsManager.setTabs(view, currentTabIndex, getTabs, getTabContainers, onBeforeTabChange, onTabChange); + } + + const getTabController = (page, index, callback) => { + let depends; + + switch (index) { + case 0: + depends = 'musicalbums'; + break; + + case 1: + depends = 'musicrecommended'; + break; + + case 2: + case 3: + depends = 'musicartists'; + break; + + case 4: + depends = 'musicplaylists'; + break; + + case 5: + depends = 'songs'; + break; + + case 6: + depends = 'musicgenres'; + break; } - function setScrollClasses(elem, scrollX) { - if (scrollX) { - elem.classList.add('hiddenScrollX'); + import(`../music/${depends}`).then(({ default: controllerFactory }) => { + let tabContent; - if (layoutManager.tv) { - elem.classList.add('smoothScrollX'); - } - - elem.classList.add('scrollX'); - elem.classList.remove('vertical-wrap'); - } else { - elem.classList.remove('hiddenScrollX'); - elem.classList.remove('smoothScrollX'); - elem.classList.remove('scrollX'); - elem.classList.add('vertical-wrap'); - } - } - - function onBeforeTabChange(e) { - preLoadTab(view, parseInt(e.detail.selectedTabIndex, 10)); - } - - function onTabChange(e) { - loadTab(view, parseInt(e.detail.selectedTabIndex, 10)); - } - - function getTabContainers() { - return view.querySelectorAll('.pageTabContent'); - } - - function initTabs() { - mainTabsManager.setTabs(view, currentTabIndex, getTabs, getTabContainers, onBeforeTabChange, onTabChange); - } - - const getTabController = (page, index, callback) => { - let depends; - - switch (index) { - case 0: - depends = 'musicalbums'; - break; - - case 1: - depends = 'musicrecommended'; - break; - - case 2: - case 3: - depends = 'musicartists'; - break; - - case 4: - depends = 'musicplaylists'; - break; - - case 5: - depends = 'songs'; - break; - - case 6: - depends = 'musicgenres'; - break; + if (index == 1) { + tabContent = view.querySelector(".pageTabContent[data-index='" + index + "']"); + this.tabContent = tabContent; } - import(`../music/${depends}`).then(({ default: controllerFactory }) => { - let tabContent; + let controller = tabControllers[index]; - if (index == 1) { - tabContent = view.querySelector(".pageTabContent[data-index='" + index + "']"); - this.tabContent = tabContent; - } + if (!controller) { + tabContent = view.querySelector(".pageTabContent[data-index='" + index + "']"); - let controller = tabControllers[index]; - - if (!controller) { - tabContent = view.querySelector(".pageTabContent[data-index='" + index + "']"); - - if (index === 1) { - controller = this; - } else { - controller = new controllerFactory(view, params, tabContent); - } - - if (index == 2) { - controller.mode = 'albumartists'; - } else if (index == 3) { - controller.mode = 'artists'; - } - - tabControllers[index] = controller; - if (controller.initTab) { - controller.initTab(); - } - } - - callback(controller); - }); - }; - - function preLoadTab(page, index) { - getTabController(page, index, function (controller) { - if (renderedTabs.indexOf(index) == -1 && controller.preRender) { - controller.preRender(); - } - }); - } - - function loadTab(page, index) { - currentTabIndex = index; - getTabController(page, index, function (controller) { - if (renderedTabs.indexOf(index) == -1) { - renderedTabs.push(index); - controller.renderTab(); - } - }); - } - - function onInputCommand(e) { - if (e.detail.command === 'search') { - e.preventDefault(); - Dashboard.navigate('search.html?collectionType=music&parentId=' + params.topParentId); - } - } - - let currentTabIndex = parseInt(params.tab || getDefaultTabIndex(params.topParentId), 10); - const suggestionsTabIndex = 1; - - this.initTab = function () { - const tabContent = view.querySelector(".pageTabContent[data-index='" + suggestionsTabIndex + "']"); - const containers = tabContent.querySelectorAll('.itemsContainer'); - - for (let i = 0, length = containers.length; i < length; i++) { - setScrollClasses(containers[i], browser.mobile); - } - }; - - this.renderTab = function () { - reload(); - }; - - const tabControllers = []; - const renderedTabs = []; - view.addEventListener('viewshow', function () { - initTabs(); - if (!view.getAttribute('data-title')) { - const parentId = params.topParentId; - - if (parentId) { - ApiClient.getItem(ApiClient.getCurrentUserId(), parentId).then(function (item) { - view.setAttribute('data-title', item.Name); - libraryMenu.setTitle(item.Name); - }); + if (index === 1) { + controller = this; } else { - view.setAttribute('data-title', globalize.translate('TabMusic')); - libraryMenu.setTitle(globalize.translate('TabMusic')); + controller = new controllerFactory(view, params, tabContent); + } + + if (index == 2) { + controller.mode = 'albumartists'; + } else if (index == 3) { + controller.mode = 'artists'; + } + + tabControllers[index] = controller; + if (controller.initTab) { + controller.initTab(); } } - inputManager.on(window, onInputCommand); + callback(controller); }); - view.addEventListener('viewbeforehide', function () { - inputManager.off(window, onInputCommand); - }); - view.addEventListener('viewdestroy', function () { - tabControllers.forEach(function (t) { - if (t.destroy) { - t.destroy(); - } - }); + }; + + function preLoadTab(page, index) { + getTabController(page, index, function (controller) { + if (renderedTabs.indexOf(index) == -1 && controller.preRender) { + controller.preRender(); + } }); } -/* eslint-enable indent */ + function loadTab(page, index) { + currentTabIndex = index; + getTabController(page, index, function (controller) { + if (renderedTabs.indexOf(index) == -1) { + renderedTabs.push(index); + controller.renderTab(); + } + }); + } + + function onInputCommand(e) { + if (e.detail.command === 'search') { + e.preventDefault(); + Dashboard.navigate('search.html?collectionType=music&parentId=' + params.topParentId); + } + } + + let currentTabIndex = parseInt(params.tab || getDefaultTabIndex(params.topParentId), 10); + const suggestionsTabIndex = 1; + + this.initTab = function () { + const tabContent = view.querySelector(".pageTabContent[data-index='" + suggestionsTabIndex + "']"); + const containers = tabContent.querySelectorAll('.itemsContainer'); + + for (let i = 0, length = containers.length; i < length; i++) { + setScrollClasses(containers[i], browser.mobile); + } + }; + + this.renderTab = function () { + reload(); + }; + + const tabControllers = []; + const renderedTabs = []; + view.addEventListener('viewshow', function () { + initTabs(); + if (!view.getAttribute('data-title')) { + const parentId = params.topParentId; + + if (parentId) { + ApiClient.getItem(ApiClient.getCurrentUserId(), parentId).then(function (item) { + view.setAttribute('data-title', item.Name); + libraryMenu.setTitle(item.Name); + }); + } else { + view.setAttribute('data-title', globalize.translate('TabMusic')); + libraryMenu.setTitle(globalize.translate('TabMusic')); + } + } + + inputManager.on(window, onInputCommand); + }); + view.addEventListener('viewbeforehide', function () { + inputManager.off(window, onInputCommand); + }); + view.addEventListener('viewdestroy', function () { + tabControllers.forEach(function (t) { + if (t.destroy) { + t.destroy(); + } + }); + }); +} + diff --git a/src/controllers/playback/video/index.js b/src/controllers/playback/video/index.js index a245581fe5..745170f5fc 100644 --- a/src/controllers/playback/video/index.js +++ b/src/controllers/playback/video/index.js @@ -27,1055 +27,1054 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components import { pluginManager } from '../../../components/pluginManager'; import { PluginType } from '../../../types/plugin.ts'; -/* eslint-disable indent */ - const TICKS_PER_MINUTE = 600000000; - const TICKS_PER_SECOND = 10000000; +const TICKS_PER_MINUTE = 600000000; +const TICKS_PER_SECOND = 10000000; - function getOpenedDialog() { - return document.querySelector('.dialogContainer .dialog.opened'); +function getOpenedDialog() { + return document.querySelector('.dialogContainer .dialog.opened'); +} + +export default function (view) { + function getDisplayItem(item) { + if (item.Type === 'TvChannel') { + const apiClient = ServerConnections.getApiClient(item.ServerId); + return apiClient.getItem(apiClient.getCurrentUserId(), item.Id).then(function (refreshedItem) { + return { + originalItem: refreshedItem, + displayItem: refreshedItem.CurrentProgram + }; + }); + } + + return Promise.resolve({ + originalItem: item + }); } - export default function (view) { - function getDisplayItem(item) { - if (item.Type === 'TvChannel') { - const apiClient = ServerConnections.getApiClient(item.ServerId); - return apiClient.getItem(apiClient.getCurrentUserId(), item.Id).then(function (refreshedItem) { - return { - originalItem: refreshedItem, - displayItem: refreshedItem.CurrentProgram - }; - }); + function updateRecordingButton(item) { + if (!item || item.Type !== 'Program') { + if (recordingButtonManager) { + recordingButtonManager.destroy(); + recordingButtonManager = null; } - return Promise.resolve({ - originalItem: item - }); + view.querySelector('.btnRecord').classList.add('hide'); + return; } - function updateRecordingButton(item) { - if (!item || item.Type !== 'Program') { - if (recordingButtonManager) { - recordingButtonManager.destroy(); - recordingButtonManager = null; - } + ServerConnections.getApiClient(item.ServerId).getCurrentUser().then(function (user) { + if (user.Policy.EnableLiveTvManagement) { + import('../../../components/recordingcreator/recordingbutton').then(({ default: RecordingButton }) => { + if (recordingButtonManager) { + recordingButtonManager.refreshItem(item); + return; + } - view.querySelector('.btnRecord').classList.add('hide'); - return; - } - - ServerConnections.getApiClient(item.ServerId).getCurrentUser().then(function (user) { - if (user.Policy.EnableLiveTvManagement) { - import('../../../components/recordingcreator/recordingbutton').then(({ default: RecordingButton }) => { - if (recordingButtonManager) { - recordingButtonManager.refreshItem(item); - return; - } - - recordingButtonManager = new RecordingButton({ - item: item, - button: view.querySelector('.btnRecord') - }); - view.querySelector('.btnRecord').classList.remove('hide'); + recordingButtonManager = new RecordingButton({ + item: item, + button: view.querySelector('.btnRecord') }); - } - }); - } - - function updateDisplayItem(itemInfo) { - const item = itemInfo.originalItem; - currentItem = item; - const displayItem = itemInfo.displayItem || item; - updateRecordingButton(displayItem); - let parentName = displayItem.SeriesName || displayItem.Album; - - if (displayItem.EpisodeTitle || displayItem.IsSeries) { - parentName = displayItem.Name; - } - - setTitle(displayItem, parentName); - ratingsText.innerHTML = mediaInfo.getPrimaryMediaInfoHtml(displayItem, { - officialRating: false, - criticRating: true, - starRating: true, - endsAt: false, - year: false, - programIndicator: false, - runtime: false, - subtitles: false, - originalAirDate: false, - episodeTitle: false - }); - - const secondaryMediaInfo = view.querySelector('.osdSecondaryMediaInfo'); - const secondaryMediaInfoHtml = mediaInfo.getSecondaryMediaInfoHtml(displayItem, { - startDate: false, - programTime: false - }); - secondaryMediaInfo.innerHTML = secondaryMediaInfoHtml; - - if (secondaryMediaInfoHtml) { - secondaryMediaInfo.classList.remove('hide'); - } else { - secondaryMediaInfo.classList.add('hide'); - } - - if (enableProgressByTimeOfDay) { - setDisplayTime(startTimeText, displayItem.StartDate); - setDisplayTime(endTimeText, displayItem.EndDate); - startTimeText.classList.remove('hide'); - endTimeText.classList.remove('hide'); - programStartDateMs = displayItem.StartDate ? datetime.parseISO8601Date(displayItem.StartDate).getTime() : 0; - programEndDateMs = displayItem.EndDate ? datetime.parseISO8601Date(displayItem.EndDate).getTime() : 0; - } else { - startTimeText.classList.add('hide'); - endTimeText.classList.add('hide'); - startTimeText.innerHTML = ''; - endTimeText.innerHTML = ''; - programStartDateMs = 0; - programEndDateMs = 0; - } - } - - function getDisplayTimeWithoutAmPm(date, showSeconds) { - if (showSeconds) { - return datetime.toLocaleTimeString(date, { - hour: 'numeric', - minute: '2-digit', - second: '2-digit' - }).toLowerCase().replace('am', '').replace('pm', '').trim(); - } - - return datetime.getDisplayTime(date).toLowerCase().replace('am', '').replace('pm', '').trim(); - } - - function setDisplayTime(elem, date) { - let html; - - if (date) { - date = datetime.parseISO8601Date(date); - html = getDisplayTimeWithoutAmPm(date); - } - - elem.innerHTML = html || ''; - } - - function shouldEnableProgressByTimeOfDay(item) { - return !(item.Type !== 'TvChannel' || !item.CurrentProgram); - } - - function updateNowPlayingInfo(player, state) { - const item = state.NowPlayingItem; - - currentItem = item; - if (!item) { - updateRecordingButton(null); - LibraryMenu.setTitle(''); - nowPlayingVolumeSlider.disabled = true; - nowPlayingPositionSlider.disabled = true; - btnFastForward.disabled = true; - btnRewind.disabled = true; - view.querySelector('.btnSubtitles').classList.add('hide'); - view.querySelector('.btnAudio').classList.add('hide'); - view.querySelector('.osdTitle').innerHTML = ''; - view.querySelector('.osdMediaInfo').innerHTML = ''; - return; - } - - enableProgressByTimeOfDay = shouldEnableProgressByTimeOfDay(item); - getDisplayItem(item).then(updateDisplayItem); - nowPlayingVolumeSlider.disabled = false; - nowPlayingPositionSlider.disabled = false; - btnFastForward.disabled = false; - btnRewind.disabled = false; - - if (playbackManager.subtitleTracks(player).length) { - view.querySelector('.btnSubtitles').classList.remove('hide'); - toggleSubtitleSync(); - } else { - view.querySelector('.btnSubtitles').classList.add('hide'); - toggleSubtitleSync('forceToHide'); - } - - if (playbackManager.audioTracks(player).length > 1) { - view.querySelector('.btnAudio').classList.remove('hide'); - } else { - view.querySelector('.btnAudio').classList.add('hide'); - } - - if (currentItem.Chapters?.length > 1) { - view.querySelector('.btnPreviousChapter').classList.remove('hide'); - view.querySelector('.btnNextChapter').classList.remove('hide'); - } else { - view.querySelector('.btnPreviousChapter').classList.add('hide'); - view.querySelector('.btnNextChapter').classList.add('hide'); - } - } - - function setTitle(item, parentName) { - let itemName = itemHelper.getDisplayName(item, { - includeParentInfo: item.Type !== 'Program', - includeIndexNumber: item.Type !== 'Program' - }); - - if (itemName && parentName) { - itemName = `${parentName} - ${itemName}`; - } - - if (!itemName) { - itemName = parentName || ''; - } - - // Display the item with its premiere date if it has one - let title = itemName; - if (item.PremiereDate) { - try { - const year = datetime.toLocaleString(datetime.parseISO8601Date(item.PremiereDate).getFullYear(), { useGrouping: false }); - title += ` (${year})`; - } catch (e) { - console.error(e); - } - } - - LibraryMenu.setTitle(title); - - const documentTitle = parentName || (item ? item.Name : null); - - if (documentTitle) { - document.title = documentTitle; - } - } - - let mouseIsDown = false; - - function showOsd() { - slideDownToShow(headerElement); - showMainOsdControls(); - resetIdle(); - } - - function hideOsd() { - slideUpToHide(headerElement); - hideMainOsdControls(); - mouseManager.hideCursor(); - } - - function toggleOsd() { - if (currentVisibleMenu === 'osd') { - hideOsd(); - } else if (!currentVisibleMenu) { - showOsd(); - } - } - - function startOsdHideTimer() { - stopOsdHideTimer(); - osdHideTimeout = setTimeout(hideOsd, 3e3); - } - - function stopOsdHideTimer() { - if (osdHideTimeout) { - clearTimeout(osdHideTimeout); - osdHideTimeout = null; - } - } - - function slideDownToShow(elem) { - elem.classList.remove('osdHeader-hidden'); - } - - function slideUpToHide(elem) { - elem.classList.add('osdHeader-hidden'); - } - - function clearHideAnimationEventListeners(elem) { - dom.removeEventListener(elem, transitionEndEventName, onHideAnimationComplete, { - once: true - }); - } - - function onHideAnimationComplete(e) { - const elem = e.target; - if (elem != osdBottomElement) - return; - elem.classList.add('hide'); - dom.removeEventListener(elem, transitionEndEventName, onHideAnimationComplete, { - once: true - }); - } - - function showMainOsdControls() { - if (!currentVisibleMenu) { - const elem = osdBottomElement; - currentVisibleMenu = 'osd'; - clearHideAnimationEventListeners(elem); - elem.classList.remove('hide'); - elem.classList.remove('videoOsdBottom-hidden'); - - if (!layoutManager.mobile) { - setTimeout(function () { - focusManager.focus(elem.querySelector('.btnPause')); - }, 50); - } - toggleSubtitleSync(); - } - } - - function hideMainOsdControls() { - if (currentVisibleMenu === 'osd') { - const elem = osdBottomElement; - clearHideAnimationEventListeners(elem); - elem.classList.add('videoOsdBottom-hidden'); - - dom.addEventListener(elem, transitionEndEventName, onHideAnimationComplete, { - once: true + view.querySelector('.btnRecord').classList.remove('hide'); }); - currentVisibleMenu = null; - toggleSubtitleSync('hide'); + } + }); + } - // Firefox does not blur by itself - if (document.activeElement) { - document.activeElement.blur(); - } + function updateDisplayItem(itemInfo) { + const item = itemInfo.originalItem; + currentItem = item; + const displayItem = itemInfo.displayItem || item; + updateRecordingButton(displayItem); + let parentName = displayItem.SeriesName || displayItem.Album; + + if (displayItem.EpisodeTitle || displayItem.IsSeries) { + parentName = displayItem.Name; + } + + setTitle(displayItem, parentName); + ratingsText.innerHTML = mediaInfo.getPrimaryMediaInfoHtml(displayItem, { + officialRating: false, + criticRating: true, + starRating: true, + endsAt: false, + year: false, + programIndicator: false, + runtime: false, + subtitles: false, + originalAirDate: false, + episodeTitle: false + }); + + const secondaryMediaInfo = view.querySelector('.osdSecondaryMediaInfo'); + const secondaryMediaInfoHtml = mediaInfo.getSecondaryMediaInfoHtml(displayItem, { + startDate: false, + programTime: false + }); + secondaryMediaInfo.innerHTML = secondaryMediaInfoHtml; + + if (secondaryMediaInfoHtml) { + secondaryMediaInfo.classList.remove('hide'); + } else { + secondaryMediaInfo.classList.add('hide'); + } + + if (enableProgressByTimeOfDay) { + setDisplayTime(startTimeText, displayItem.StartDate); + setDisplayTime(endTimeText, displayItem.EndDate); + startTimeText.classList.remove('hide'); + endTimeText.classList.remove('hide'); + programStartDateMs = displayItem.StartDate ? datetime.parseISO8601Date(displayItem.StartDate).getTime() : 0; + programEndDateMs = displayItem.EndDate ? datetime.parseISO8601Date(displayItem.EndDate).getTime() : 0; + } else { + startTimeText.classList.add('hide'); + endTimeText.classList.add('hide'); + startTimeText.innerHTML = ''; + endTimeText.innerHTML = ''; + programStartDateMs = 0; + programEndDateMs = 0; + } + } + + function getDisplayTimeWithoutAmPm(date, showSeconds) { + if (showSeconds) { + return datetime.toLocaleTimeString(date, { + hour: 'numeric', + minute: '2-digit', + second: '2-digit' + }).toLowerCase().replace('am', '').replace('pm', '').trim(); + } + + return datetime.getDisplayTime(date).toLowerCase().replace('am', '').replace('pm', '').trim(); + } + + function setDisplayTime(elem, date) { + let html; + + if (date) { + date = datetime.parseISO8601Date(date); + html = getDisplayTimeWithoutAmPm(date); + } + + elem.innerHTML = html || ''; + } + + function shouldEnableProgressByTimeOfDay(item) { + return !(item.Type !== 'TvChannel' || !item.CurrentProgram); + } + + function updateNowPlayingInfo(player, state) { + const item = state.NowPlayingItem; + + currentItem = item; + if (!item) { + updateRecordingButton(null); + LibraryMenu.setTitle(''); + nowPlayingVolumeSlider.disabled = true; + nowPlayingPositionSlider.disabled = true; + btnFastForward.disabled = true; + btnRewind.disabled = true; + view.querySelector('.btnSubtitles').classList.add('hide'); + view.querySelector('.btnAudio').classList.add('hide'); + view.querySelector('.osdTitle').innerHTML = ''; + view.querySelector('.osdMediaInfo').innerHTML = ''; + return; + } + + enableProgressByTimeOfDay = shouldEnableProgressByTimeOfDay(item); + getDisplayItem(item).then(updateDisplayItem); + nowPlayingVolumeSlider.disabled = false; + nowPlayingPositionSlider.disabled = false; + btnFastForward.disabled = false; + btnRewind.disabled = false; + + if (playbackManager.subtitleTracks(player).length) { + view.querySelector('.btnSubtitles').classList.remove('hide'); + toggleSubtitleSync(); + } else { + view.querySelector('.btnSubtitles').classList.add('hide'); + toggleSubtitleSync('forceToHide'); + } + + if (playbackManager.audioTracks(player).length > 1) { + view.querySelector('.btnAudio').classList.remove('hide'); + } else { + view.querySelector('.btnAudio').classList.add('hide'); + } + + if (currentItem.Chapters?.length > 1) { + view.querySelector('.btnPreviousChapter').classList.remove('hide'); + view.querySelector('.btnNextChapter').classList.remove('hide'); + } else { + view.querySelector('.btnPreviousChapter').classList.add('hide'); + view.querySelector('.btnNextChapter').classList.add('hide'); + } + } + + function setTitle(item, parentName) { + let itemName = itemHelper.getDisplayName(item, { + includeParentInfo: item.Type !== 'Program', + includeIndexNumber: item.Type !== 'Program' + }); + + if (itemName && parentName) { + itemName = `${parentName} - ${itemName}`; + } + + if (!itemName) { + itemName = parentName || ''; + } + + // Display the item with its premiere date if it has one + let title = itemName; + if (item.PremiereDate) { + try { + const year = datetime.toLocaleString(datetime.parseISO8601Date(item.PremiereDate).getFullYear(), { useGrouping: false }); + title += ` (${year})`; + } catch (e) { + console.error(e); } } - // TODO: Move all idle-related code to `inputManager` or `idleManager` or `idleHelper` (per dialog thing) and listen event from there. + LibraryMenu.setTitle(title); - function resetIdle() { - // Restart hide timer if OSD is currently visible and there is no opened dialog - if (currentVisibleMenu && !mouseIsDown && !getOpenedDialog()) { - startOsdHideTimer(); - } else { - stopOsdHideTimer(); + const documentTitle = parentName || (item ? item.Name : null); + + if (documentTitle) { + document.title = documentTitle; + } + } + + let mouseIsDown = false; + + function showOsd() { + slideDownToShow(headerElement); + showMainOsdControls(); + resetIdle(); + } + + function hideOsd() { + slideUpToHide(headerElement); + hideMainOsdControls(); + mouseManager.hideCursor(); + } + + function toggleOsd() { + if (currentVisibleMenu === 'osd') { + hideOsd(); + } else if (!currentVisibleMenu) { + showOsd(); + } + } + + function startOsdHideTimer() { + stopOsdHideTimer(); + osdHideTimeout = setTimeout(hideOsd, 3e3); + } + + function stopOsdHideTimer() { + if (osdHideTimeout) { + clearTimeout(osdHideTimeout); + osdHideTimeout = null; + } + } + + function slideDownToShow(elem) { + elem.classList.remove('osdHeader-hidden'); + } + + function slideUpToHide(elem) { + elem.classList.add('osdHeader-hidden'); + } + + function clearHideAnimationEventListeners(elem) { + dom.removeEventListener(elem, transitionEndEventName, onHideAnimationComplete, { + once: true + }); + } + + function onHideAnimationComplete(e) { + const elem = e.target; + if (elem != osdBottomElement) + return; + elem.classList.add('hide'); + dom.removeEventListener(elem, transitionEndEventName, onHideAnimationComplete, { + once: true + }); + } + + function showMainOsdControls() { + if (!currentVisibleMenu) { + const elem = osdBottomElement; + currentVisibleMenu = 'osd'; + clearHideAnimationEventListeners(elem); + elem.classList.remove('hide'); + elem.classList.remove('videoOsdBottom-hidden'); + + if (!layoutManager.mobile) { + setTimeout(function () { + focusManager.focus(elem.querySelector('.btnPause')); + }, 50); + } + toggleSubtitleSync(); + } + } + + function hideMainOsdControls() { + if (currentVisibleMenu === 'osd') { + const elem = osdBottomElement; + clearHideAnimationEventListeners(elem); + elem.classList.add('videoOsdBottom-hidden'); + + dom.addEventListener(elem, transitionEndEventName, onHideAnimationComplete, { + once: true + }); + currentVisibleMenu = null; + toggleSubtitleSync('hide'); + + // Firefox does not blur by itself + if (document.activeElement) { + document.activeElement.blur(); } } + } - function onPointerMove(e) { - if ((e.pointerType || (layoutManager.mobile ? 'touch' : 'mouse')) === 'mouse') { - const eventX = e.screenX || e.clientX || 0; - const eventY = e.screenY || e.clientY || 0; - const obj = lastPointerMoveData; + // TODO: Move all idle-related code to `inputManager` or `idleManager` or `idleHelper` (per dialog thing) and listen event from there. - if (!obj) { - lastPointerMoveData = { - x: eventX, - y: eventY - }; - return; - } - - if (Math.abs(eventX - obj.x) < 10 && Math.abs(eventY - obj.y) < 10) { - return; - } - - obj.x = eventX; - obj.y = eventY; - showOsd(); - } + function resetIdle() { + // Restart hide timer if OSD is currently visible and there is no opened dialog + if (currentVisibleMenu && !mouseIsDown && !getOpenedDialog()) { + startOsdHideTimer(); + } else { + stopOsdHideTimer(); } + } - function onInputCommand(e) { - const player = currentPlayer; + function onPointerMove(e) { + if ((e.pointerType || (layoutManager.mobile ? 'touch' : 'mouse')) === 'mouse') { + const eventX = e.screenX || e.clientX || 0; + const eventY = e.screenY || e.clientY || 0; + const obj = lastPointerMoveData; - switch (e.detail.command) { - case 'left': - if (currentVisibleMenu === 'osd') { - showOsd(); - } else { - if (!currentVisibleMenu) { - e.preventDefault(); - playbackManager.rewind(player); - } - } - - break; - - case 'right': - if (currentVisibleMenu === 'osd') { - showOsd(); - } else if (!currentVisibleMenu) { - e.preventDefault(); - playbackManager.fastForward(player); - } - - break; - - case 'pageup': - playbackManager.nextChapter(player); - break; - - case 'pagedown': - playbackManager.previousChapter(player); - break; - - case 'up': - case 'down': - case 'select': - case 'menu': - case 'info': - case 'play': - case 'playpause': - case 'pause': - case 'fastforward': - case 'rewind': - case 'next': - case 'previous': - showOsd(); - break; - - case 'record': - onRecordingCommand(); - showOsd(); - break; - - case 'togglestats': - toggleStats(); - break; - - case 'back': - // Ignore command when some dialog is opened - if (currentVisibleMenu === 'osd' && !getOpenedDialog()) { - hideOsd(); - e.preventDefault(); - } - break; - } - } - - function onRecordingCommand() { - const btnRecord = view.querySelector('.btnRecord'); - - if (!btnRecord.classList.contains('hide')) { - btnRecord.click(); - } - } - - function onFullscreenChanged() { - if (currentPlayer.forcedFullscreen && !playbackManager.isFullscreen(currentPlayer)) { - appRouter.back(); + if (!obj) { + lastPointerMoveData = { + x: eventX, + y: eventY + }; return; } - updateFullscreenIcon(); - } - - function updateFullscreenIcon() { - const button = view.querySelector('.btnFullscreen'); - const icon = button.querySelector('.material-icons'); - - icon.classList.remove('fullscreen_exit', 'fullscreen'); - - if (playbackManager.isFullscreen(currentPlayer)) { - button.setAttribute('title', globalize.translate('ExitFullscreen') + ' (f)'); - icon.classList.add('fullscreen_exit'); - } else { - button.setAttribute('title', globalize.translate('Fullscreen') + ' (f)'); - icon.classList.add('fullscreen'); + if (Math.abs(eventX - obj.x) < 10 && Math.abs(eventY - obj.y) < 10) { + return; } + + obj.x = eventX; + obj.y = eventY; + showOsd(); } + } - function onPlayerChange() { - bindToPlayer(playbackManager.getCurrentPlayer()); - } + function onInputCommand(e) { + const player = currentPlayer; - function onStateChanged(event, state) { - const player = this; - - if (state.NowPlayingItem) { - isEnabled = true; - updatePlayerStateInternal(event, player, state); - updatePlaylist(); - enableStopOnBack(true); - updatePlaybackRate(player); - } - } - - function onPlayPauseStateChanged() { - if (isEnabled) { - updatePlayPauseState(this.paused()); - } - } - - function onVolumeChanged() { - if (isEnabled) { - const player = this; - updatePlayerVolumeState(player, player.isMuted(), player.getVolume()); - } - } - - function onPlaybackStart(e, state) { - console.debug('nowplaying event: ' + e.type); - const player = this; - onStateChanged.call(player, e, state); - resetUpNextDialog(); - } - - function resetUpNextDialog() { - comingUpNextDisplayed = false; - const dlg = currentUpNextDialog; - - if (dlg) { - dlg.destroy(); - currentUpNextDialog = null; - } - } - - function onPlaybackStopped(e, state) { - currentRuntimeTicks = null; - resetUpNextDialog(); - console.debug('nowplaying event: ' + e.type); - - if (state.NextMediaType !== 'Video') { - view.removeEventListener('viewbeforehide', onViewHideStopPlayback); - appRouter.back(); - } - } - - function onMediaStreamsChanged() { - const player = this; - const state = playbackManager.getPlayerState(player); - onStateChanged.call(player, { - type: 'init' - }, state); - } - - function onBeginFetch() { - document.querySelector('.osdMediaStatus').classList.remove('hide'); - } - - function onEndFetch() { - document.querySelector('.osdMediaStatus').classList.add('hide'); - } - - function bindToPlayer(player) { - if (player !== currentPlayer) { - releaseCurrentPlayer(); - currentPlayer = player; - if (!player) return; - } - const state = playbackManager.getPlayerState(player); - onStateChanged.call(player, { - type: 'init' - }, state); - Events.on(player, 'playbackstart', onPlaybackStart); - Events.on(player, 'playbackstop', onPlaybackStopped); - Events.on(player, 'volumechange', onVolumeChanged); - Events.on(player, 'pause', onPlayPauseStateChanged); - Events.on(player, 'unpause', onPlayPauseStateChanged); - Events.on(player, 'timeupdate', onTimeUpdate); - Events.on(player, 'fullscreenchange', onFullscreenChanged); - Events.on(player, 'mediastreamschange', onMediaStreamsChanged); - Events.on(player, 'beginFetch', onBeginFetch); - Events.on(player, 'endFetch', onEndFetch); - resetUpNextDialog(); - - if (player.isFetching) { - onBeginFetch(); - } - } - - function releaseCurrentPlayer() { - destroyStats(); - destroySubtitleSync(); - resetUpNextDialog(); - const player = currentPlayer; - - if (player) { - Events.off(player, 'playbackstart', onPlaybackStart); - Events.off(player, 'playbackstop', onPlaybackStopped); - Events.off(player, 'volumechange', onVolumeChanged); - Events.off(player, 'pause', onPlayPauseStateChanged); - Events.off(player, 'unpause', onPlayPauseStateChanged); - Events.off(player, 'timeupdate', onTimeUpdate); - Events.off(player, 'fullscreenchange', onFullscreenChanged); - Events.off(player, 'mediastreamschange', onMediaStreamsChanged); - currentPlayer = null; - } - } - - function onTimeUpdate() { - // Test for 'currentItem' is required for Firefox since its player spams 'timeupdate' events even being at breakpoint - if (isEnabled && currentItem) { - const now = new Date().getTime(); - - if (now - lastUpdateTime >= 700) { - lastUpdateTime = now; - const player = this; - currentRuntimeTicks = playbackManager.duration(player); - const currentTime = playbackManager.currentTime(player) * 10000; - updateTimeDisplay(currentTime, currentRuntimeTicks, playbackManager.playbackStartTime(player), playbackManager.getPlaybackRate(player), playbackManager.getBufferedRanges(player)); - const item = currentItem; - refreshProgramInfoIfNeeded(player, item); - showComingUpNextIfNeeded(player, item, currentTime, currentRuntimeTicks); - } - } - } - - function showComingUpNextIfNeeded(player, currentItem, currentTimeTicks, runtimeTicks) { - if (runtimeTicks && currentTimeTicks && !comingUpNextDisplayed && !currentVisibleMenu && currentItem.Type === 'Episode' && userSettings.enableNextVideoInfoOverlay()) { - let showAtSecondsLeft = 30; - if (runtimeTicks >= 50 * TICKS_PER_MINUTE) { - showAtSecondsLeft = 40; - } else if (runtimeTicks >= 40 * TICKS_PER_MINUTE) { - showAtSecondsLeft = 35; - } - const showAtTicks = runtimeTicks - showAtSecondsLeft * TICKS_PER_SECOND; - const timeRemainingTicks = runtimeTicks - currentTimeTicks; - - if (currentTimeTicks >= showAtTicks && runtimeTicks >= (10 * TICKS_PER_MINUTE) && timeRemainingTicks >= (20 * TICKS_PER_SECOND)) { - showComingUpNext(player); - } - } - } - - function onUpNextHidden() { - if (currentVisibleMenu === 'upnext') { - currentVisibleMenu = null; - } - } - - function showComingUpNext(player) { - import('../../../components/upnextdialog/upnextdialog').then(({ default: UpNextDialog }) => { - if (!(currentVisibleMenu || currentUpNextDialog)) { - currentVisibleMenu = 'upnext'; - comingUpNextDisplayed = true; - playbackManager.nextItem(player).then(function (nextItem) { - currentUpNextDialog = new UpNextDialog({ - parent: view.querySelector('.upNextContainer'), - player: player, - nextItem: nextItem - }); - Events.on(currentUpNextDialog, 'hide', onUpNextHidden); - }, onUpNextHidden); - } - }); - } - - function refreshProgramInfoIfNeeded(player, item) { - if (item.Type === 'TvChannel') { - const program = item.CurrentProgram; - - if (program && program.EndDate) { - try { - const endDate = datetime.parseISO8601Date(program.EndDate); - - if (new Date().getTime() >= endDate.getTime()) { - console.debug('program info needs to be refreshed'); - const state = playbackManager.getPlayerState(player); - onStateChanged.call(player, { - type: 'init' - }, state); - } - } catch (e) { - console.error('error parsing date: ' + program.EndDate); + switch (e.detail.command) { + case 'left': + if (currentVisibleMenu === 'osd') { + showOsd(); + } else { + if (!currentVisibleMenu) { + e.preventDefault(); + playbackManager.rewind(player); } } - } + + break; + + case 'right': + if (currentVisibleMenu === 'osd') { + showOsd(); + } else if (!currentVisibleMenu) { + e.preventDefault(); + playbackManager.fastForward(player); + } + + break; + + case 'pageup': + playbackManager.nextChapter(player); + break; + + case 'pagedown': + playbackManager.previousChapter(player); + break; + + case 'up': + case 'down': + case 'select': + case 'menu': + case 'info': + case 'play': + case 'playpause': + case 'pause': + case 'fastforward': + case 'rewind': + case 'next': + case 'previous': + showOsd(); + break; + + case 'record': + onRecordingCommand(); + showOsd(); + break; + + case 'togglestats': + toggleStats(); + break; + + case 'back': + // Ignore command when some dialog is opened + if (currentVisibleMenu === 'osd' && !getOpenedDialog()) { + hideOsd(); + e.preventDefault(); + } + break; + } + } + + function onRecordingCommand() { + const btnRecord = view.querySelector('.btnRecord'); + + if (!btnRecord.classList.contains('hide')) { + btnRecord.click(); + } + } + + function onFullscreenChanged() { + if (currentPlayer.forcedFullscreen && !playbackManager.isFullscreen(currentPlayer)) { + appRouter.back(); + return; } - function updatePlayPauseState(isPaused) { - const btnPlayPause = view.querySelector('.btnPause'); - const btnPlayPauseIcon = btnPlayPause.querySelector('.material-icons'); + updateFullscreenIcon(); + } - btnPlayPauseIcon.classList.remove('play_arrow', 'pause'); + function updateFullscreenIcon() { + const button = view.querySelector('.btnFullscreen'); + const icon = button.querySelector('.material-icons'); - let icon; - let title; + icon.classList.remove('fullscreen_exit', 'fullscreen'); - if (isPaused) { - icon = 'play_arrow'; - title = globalize.translate('Play'); - } else { - icon = 'pause'; - title = globalize.translate('ButtonPause'); + if (playbackManager.isFullscreen(currentPlayer)) { + button.setAttribute('title', globalize.translate('ExitFullscreen') + ' (f)'); + icon.classList.add('fullscreen_exit'); + } else { + button.setAttribute('title', globalize.translate('Fullscreen') + ' (f)'); + icon.classList.add('fullscreen'); + } + } + + function onPlayerChange() { + bindToPlayer(playbackManager.getCurrentPlayer()); + } + + function onStateChanged(event, state) { + const player = this; + + if (state.NowPlayingItem) { + isEnabled = true; + updatePlayerStateInternal(event, player, state); + updatePlaylist(); + enableStopOnBack(true); + updatePlaybackRate(player); + } + } + + function onPlayPauseStateChanged() { + if (isEnabled) { + updatePlayPauseState(this.paused()); + } + } + + function onVolumeChanged() { + if (isEnabled) { + const player = this; + updatePlayerVolumeState(player, player.isMuted(), player.getVolume()); + } + } + + function onPlaybackStart(e, state) { + console.debug('nowplaying event: ' + e.type); + const player = this; + onStateChanged.call(player, e, state); + resetUpNextDialog(); + } + + function resetUpNextDialog() { + comingUpNextDisplayed = false; + const dlg = currentUpNextDialog; + + if (dlg) { + dlg.destroy(); + currentUpNextDialog = null; + } + } + + function onPlaybackStopped(e, state) { + currentRuntimeTicks = null; + resetUpNextDialog(); + console.debug('nowplaying event: ' + e.type); + + if (state.NextMediaType !== 'Video') { + view.removeEventListener('viewbeforehide', onViewHideStopPlayback); + appRouter.back(); + } + } + + function onMediaStreamsChanged() { + const player = this; + const state = playbackManager.getPlayerState(player); + onStateChanged.call(player, { + type: 'init' + }, state); + } + + function onBeginFetch() { + document.querySelector('.osdMediaStatus').classList.remove('hide'); + } + + function onEndFetch() { + document.querySelector('.osdMediaStatus').classList.add('hide'); + } + + function bindToPlayer(player) { + if (player !== currentPlayer) { + releaseCurrentPlayer(); + currentPlayer = player; + if (!player) return; + } + const state = playbackManager.getPlayerState(player); + onStateChanged.call(player, { + type: 'init' + }, state); + Events.on(player, 'playbackstart', onPlaybackStart); + Events.on(player, 'playbackstop', onPlaybackStopped); + Events.on(player, 'volumechange', onVolumeChanged); + Events.on(player, 'pause', onPlayPauseStateChanged); + Events.on(player, 'unpause', onPlayPauseStateChanged); + Events.on(player, 'timeupdate', onTimeUpdate); + Events.on(player, 'fullscreenchange', onFullscreenChanged); + Events.on(player, 'mediastreamschange', onMediaStreamsChanged); + Events.on(player, 'beginFetch', onBeginFetch); + Events.on(player, 'endFetch', onEndFetch); + resetUpNextDialog(); + + if (player.isFetching) { + onBeginFetch(); + } + } + + function releaseCurrentPlayer() { + destroyStats(); + destroySubtitleSync(); + resetUpNextDialog(); + const player = currentPlayer; + + if (player) { + Events.off(player, 'playbackstart', onPlaybackStart); + Events.off(player, 'playbackstop', onPlaybackStopped); + Events.off(player, 'volumechange', onVolumeChanged); + Events.off(player, 'pause', onPlayPauseStateChanged); + Events.off(player, 'unpause', onPlayPauseStateChanged); + Events.off(player, 'timeupdate', onTimeUpdate); + Events.off(player, 'fullscreenchange', onFullscreenChanged); + Events.off(player, 'mediastreamschange', onMediaStreamsChanged); + currentPlayer = null; + } + } + + function onTimeUpdate() { + // Test for 'currentItem' is required for Firefox since its player spams 'timeupdate' events even being at breakpoint + if (isEnabled && currentItem) { + const now = new Date().getTime(); + + if (now - lastUpdateTime >= 700) { + lastUpdateTime = now; + const player = this; + currentRuntimeTicks = playbackManager.duration(player); + const currentTime = playbackManager.currentTime(player) * 10000; + updateTimeDisplay(currentTime, currentRuntimeTicks, playbackManager.playbackStartTime(player), playbackManager.getPlaybackRate(player), playbackManager.getBufferedRanges(player)); + const item = currentItem; + refreshProgramInfoIfNeeded(player, item); + showComingUpNextIfNeeded(player, item, currentTime, currentRuntimeTicks); } + } + } - btnPlayPauseIcon.classList.add(icon); - dom.setElementTitle(btnPlayPause, title + ' (k)', title); + function showComingUpNextIfNeeded(player, currentItem, currentTimeTicks, runtimeTicks) { + if (runtimeTicks && currentTimeTicks && !comingUpNextDisplayed && !currentVisibleMenu && currentItem.Type === 'Episode' && userSettings.enableNextVideoInfoOverlay()) { + let showAtSecondsLeft = 30; + if (runtimeTicks >= 50 * TICKS_PER_MINUTE) { + showAtSecondsLeft = 40; + } else if (runtimeTicks >= 40 * TICKS_PER_MINUTE) { + showAtSecondsLeft = 35; + } + const showAtTicks = runtimeTicks - showAtSecondsLeft * TICKS_PER_SECOND; + const timeRemainingTicks = runtimeTicks - currentTimeTicks; + + if (currentTimeTicks >= showAtTicks && runtimeTicks >= (10 * TICKS_PER_MINUTE) && timeRemainingTicks >= (20 * TICKS_PER_SECOND)) { + showComingUpNext(player); + } + } + } + + function onUpNextHidden() { + if (currentVisibleMenu === 'upnext') { + currentVisibleMenu = null; + } + } + + function showComingUpNext(player) { + import('../../../components/upnextdialog/upnextdialog').then(({ default: UpNextDialog }) => { + if (!(currentVisibleMenu || currentUpNextDialog)) { + currentVisibleMenu = 'upnext'; + comingUpNextDisplayed = true; + playbackManager.nextItem(player).then(function (nextItem) { + currentUpNextDialog = new UpNextDialog({ + parent: view.querySelector('.upNextContainer'), + player: player, + nextItem: nextItem + }); + Events.on(currentUpNextDialog, 'hide', onUpNextHidden); + }, onUpNextHidden); + } + }); + } + + function refreshProgramInfoIfNeeded(player, item) { + if (item.Type === 'TvChannel') { + const program = item.CurrentProgram; + + if (program && program.EndDate) { + try { + const endDate = datetime.parseISO8601Date(program.EndDate); + + if (new Date().getTime() >= endDate.getTime()) { + console.debug('program info needs to be refreshed'); + const state = playbackManager.getPlayerState(player); + onStateChanged.call(player, { + type: 'init' + }, state); + } + } catch (e) { + console.error('error parsing date: ' + program.EndDate); + } + } + } + } + + function updatePlayPauseState(isPaused) { + const btnPlayPause = view.querySelector('.btnPause'); + const btnPlayPauseIcon = btnPlayPause.querySelector('.material-icons'); + + btnPlayPauseIcon.classList.remove('play_arrow', 'pause'); + + let icon; + let title; + + if (isPaused) { + icon = 'play_arrow'; + title = globalize.translate('Play'); + } else { + icon = 'pause'; + title = globalize.translate('ButtonPause'); } - function updatePlayerStateInternal(event, player, state) { - const playState = state.PlayState || {}; - updatePlayPauseState(playState.IsPaused); - const supportedCommands = playbackManager.getSupportedCommands(player); - currentPlayerSupportedCommands = supportedCommands; - updatePlayerVolumeState(player, playState.IsMuted, playState.VolumeLevel); + btnPlayPauseIcon.classList.add(icon); + dom.setElementTitle(btnPlayPause, title + ' (k)', title); + } + function updatePlayerStateInternal(event, player, state) { + const playState = state.PlayState || {}; + updatePlayPauseState(playState.IsPaused); + const supportedCommands = playbackManager.getSupportedCommands(player); + currentPlayerSupportedCommands = supportedCommands; + updatePlayerVolumeState(player, playState.IsMuted, playState.VolumeLevel); + + if (nowPlayingPositionSlider && !nowPlayingPositionSlider.dragging) { + nowPlayingPositionSlider.disabled = !playState.CanSeek; + } + + btnFastForward.disabled = !playState.CanSeek; + btnRewind.disabled = !playState.CanSeek; + const nowPlayingItem = state.NowPlayingItem || {}; + playbackStartTimeTicks = playState.PlaybackStartTimeTicks; + updateTimeDisplay(playState.PositionTicks, nowPlayingItem.RunTimeTicks, playState.PlaybackStartTimeTicks, playState.PlaybackRate, playState.BufferedRanges || []); + updateNowPlayingInfo(player, state); + + const isProgressClear = state.MediaSource && state.MediaSource.RunTimeTicks == null; + nowPlayingPositionSlider.setIsClear(isProgressClear); + + if (nowPlayingItem.RunTimeTicks) { + nowPlayingPositionSlider.setKeyboardSteps(userSettings.skipBackLength() * 1000000 / nowPlayingItem.RunTimeTicks, + userSettings.skipForwardLength() * 1000000 / nowPlayingItem.RunTimeTicks); + } + + if (supportedCommands.indexOf('ToggleFullscreen') === -1 || player.isLocalPlayer && layoutManager.tv && playbackManager.isFullscreen(player)) { + view.querySelector('.btnFullscreen').classList.add('hide'); + } else { + view.querySelector('.btnFullscreen').classList.remove('hide'); + } + + if (supportedCommands.indexOf('PictureInPicture') === -1) { + view.querySelector('.btnPip').classList.add('hide'); + } else { + view.querySelector('.btnPip').classList.remove('hide'); + } + + if (supportedCommands.indexOf('AirPlay') === -1) { + view.querySelector('.btnAirPlay').classList.add('hide'); + } else { + view.querySelector('.btnAirPlay').classList.remove('hide'); + } + + onFullscreenChanged(); + } + + function getDisplayPercentByTimeOfDay(programStartDateMs, programRuntimeMs, currentTimeMs) { + return (currentTimeMs - programStartDateMs) / programRuntimeMs * 100; + } + + function updateTimeDisplay(positionTicks, runtimeTicks, playbackStartTimeTicks, playbackRate, bufferedRanges) { + if (enableProgressByTimeOfDay) { if (nowPlayingPositionSlider && !nowPlayingPositionSlider.dragging) { - nowPlayingPositionSlider.disabled = !playState.CanSeek; - } + if (programStartDateMs && programEndDateMs) { + const currentTimeMs = (playbackStartTimeTicks + (positionTicks || 0)) / 1e4; + const programRuntimeMs = programEndDateMs - programStartDateMs; - btnFastForward.disabled = !playState.CanSeek; - btnRewind.disabled = !playState.CanSeek; - const nowPlayingItem = state.NowPlayingItem || {}; - playbackStartTimeTicks = playState.PlaybackStartTimeTicks; - updateTimeDisplay(playState.PositionTicks, nowPlayingItem.RunTimeTicks, playState.PlaybackStartTimeTicks, playState.PlaybackRate, playState.BufferedRanges || []); - updateNowPlayingInfo(player, state); + nowPlayingPositionSlider.value = getDisplayPercentByTimeOfDay(programStartDateMs, programRuntimeMs, currentTimeMs); - const isProgressClear = state.MediaSource && state.MediaSource.RunTimeTicks == null; - nowPlayingPositionSlider.setIsClear(isProgressClear); - - if (nowPlayingItem.RunTimeTicks) { - nowPlayingPositionSlider.setKeyboardSteps(userSettings.skipBackLength() * 1000000 / nowPlayingItem.RunTimeTicks, - userSettings.skipForwardLength() * 1000000 / nowPlayingItem.RunTimeTicks); - } - - if (supportedCommands.indexOf('ToggleFullscreen') === -1 || player.isLocalPlayer && layoutManager.tv && playbackManager.isFullscreen(player)) { - view.querySelector('.btnFullscreen').classList.add('hide'); - } else { - view.querySelector('.btnFullscreen').classList.remove('hide'); - } - - if (supportedCommands.indexOf('PictureInPicture') === -1) { - view.querySelector('.btnPip').classList.add('hide'); - } else { - view.querySelector('.btnPip').classList.remove('hide'); - } - - if (supportedCommands.indexOf('AirPlay') === -1) { - view.querySelector('.btnAirPlay').classList.add('hide'); - } else { - view.querySelector('.btnAirPlay').classList.remove('hide'); - } - - onFullscreenChanged(); - } - - function getDisplayPercentByTimeOfDay(programStartDateMs, programRuntimeMs, currentTimeMs) { - return (currentTimeMs - programStartDateMs) / programRuntimeMs * 100; - } - - function updateTimeDisplay(positionTicks, runtimeTicks, playbackStartTimeTicks, playbackRate, bufferedRanges) { - if (enableProgressByTimeOfDay) { - if (nowPlayingPositionSlider && !nowPlayingPositionSlider.dragging) { - if (programStartDateMs && programEndDateMs) { - const currentTimeMs = (playbackStartTimeTicks + (positionTicks || 0)) / 1e4; - const programRuntimeMs = programEndDateMs - programStartDateMs; - - nowPlayingPositionSlider.value = getDisplayPercentByTimeOfDay(programStartDateMs, programRuntimeMs, currentTimeMs); - - if (bufferedRanges.length) { - const rangeStart = getDisplayPercentByTimeOfDay(programStartDateMs, programRuntimeMs, (playbackStartTimeTicks + (bufferedRanges[0].start || 0)) / 1e4); - const rangeEnd = getDisplayPercentByTimeOfDay(programStartDateMs, programRuntimeMs, (playbackStartTimeTicks + (bufferedRanges[0].end || 0)) / 1e4); - nowPlayingPositionSlider.setBufferedRanges([{ - start: rangeStart, - end: rangeEnd - }]); - } else { - nowPlayingPositionSlider.setBufferedRanges([]); - } + if (bufferedRanges.length) { + const rangeStart = getDisplayPercentByTimeOfDay(programStartDateMs, programRuntimeMs, (playbackStartTimeTicks + (bufferedRanges[0].start || 0)) / 1e4); + const rangeEnd = getDisplayPercentByTimeOfDay(programStartDateMs, programRuntimeMs, (playbackStartTimeTicks + (bufferedRanges[0].end || 0)) / 1e4); + nowPlayingPositionSlider.setBufferedRanges([{ + start: rangeStart, + end: rangeEnd + }]); } else { - nowPlayingPositionSlider.value = 0; nowPlayingPositionSlider.setBufferedRanges([]); } + } else { + nowPlayingPositionSlider.value = 0; + nowPlayingPositionSlider.setBufferedRanges([]); + } + } + + nowPlayingPositionText.innerHTML = ''; + nowPlayingDurationText.innerHTML = ''; + } else { + if (nowPlayingPositionSlider && !nowPlayingPositionSlider.dragging) { + if (runtimeTicks) { + let pct = positionTicks / runtimeTicks; + pct *= 100; + nowPlayingPositionSlider.value = pct; + } else { + nowPlayingPositionSlider.value = 0; } - nowPlayingPositionText.innerHTML = ''; - nowPlayingDurationText.innerHTML = ''; + if (runtimeTicks && positionTicks != null && currentRuntimeTicks && !enableProgressByTimeOfDay && currentItem.RunTimeTicks && currentItem.Type !== 'Recording' && playbackRate !== null) { + endsAtText.innerHTML = '    ' + mediaInfo.getEndsAtFromPosition(runtimeTicks, positionTicks, playbackRate, true); + } else { + endsAtText.innerHTML = ''; + } + } + + if (nowPlayingPositionSlider) { + nowPlayingPositionSlider.setBufferedRanges(bufferedRanges, runtimeTicks, positionTicks); + } + + if (positionTicks >= 0) { + updateTimeText(nowPlayingPositionText, positionTicks); + nowPlayingPositionText.classList.remove('hide'); } else { - if (nowPlayingPositionSlider && !nowPlayingPositionSlider.dragging) { - if (runtimeTicks) { - let pct = positionTicks / runtimeTicks; - pct *= 100; - nowPlayingPositionSlider.value = pct; - } else { - nowPlayingPositionSlider.value = 0; - } + nowPlayingPositionText.classList.add('hide'); + } - if (runtimeTicks && positionTicks != null && currentRuntimeTicks && !enableProgressByTimeOfDay && currentItem.RunTimeTicks && currentItem.Type !== 'Recording' && playbackRate !== null) { - endsAtText.innerHTML = '    ' + mediaInfo.getEndsAtFromPosition(runtimeTicks, positionTicks, playbackRate, true); - } else { - endsAtText.innerHTML = ''; - } - } - - if (nowPlayingPositionSlider) { - nowPlayingPositionSlider.setBufferedRanges(bufferedRanges, runtimeTicks, positionTicks); - } - - if (positionTicks >= 0) { - updateTimeText(nowPlayingPositionText, positionTicks); - nowPlayingPositionText.classList.remove('hide'); + if (userSettings.enableVideoRemainingTime()) { + const leftTicks = runtimeTicks - positionTicks; + if (leftTicks >= 0) { + updateTimeText(nowPlayingDurationText, leftTicks); + nowPlayingDurationText.innerHTML = '-' + nowPlayingDurationText.innerHTML; + nowPlayingDurationText.classList.remove('hide'); } else { nowPlayingPositionText.classList.add('hide'); } - - if (userSettings.enableVideoRemainingTime()) { - const leftTicks = runtimeTicks - positionTicks; - if (leftTicks >= 0) { - updateTimeText(nowPlayingDurationText, leftTicks); - nowPlayingDurationText.innerHTML = '-' + nowPlayingDurationText.innerHTML; - nowPlayingDurationText.classList.remove('hide'); - } else { - nowPlayingPositionText.classList.add('hide'); - } - } else { - updateTimeText(nowPlayingDurationText, runtimeTicks); - nowPlayingDurationText.classList.remove('hide'); - } - } - } - - function updatePlayerVolumeState(player, isMuted, volumeLevel) { - const supportedCommands = currentPlayerSupportedCommands; - let showMuteButton = true; - let showVolumeSlider = true; - - if (supportedCommands.indexOf('Mute') === -1) { - showMuteButton = false; - } - - if (supportedCommands.indexOf('SetVolume') === -1) { - showVolumeSlider = false; - } - - if (player.isLocalPlayer && appHost.supports('physicalvolumecontrol')) { - showMuteButton = false; - showVolumeSlider = false; - } - - const buttonMute = view.querySelector('.buttonMute'); - const buttonMuteIcon = buttonMute.querySelector('.material-icons'); - - buttonMuteIcon.classList.remove('volume_off', 'volume_up'); - - if (isMuted) { - buttonMute.setAttribute('title', globalize.translate('Unmute') + ' (m)'); - buttonMuteIcon.classList.add('volume_off'); } else { - buttonMute.setAttribute('title', globalize.translate('Mute') + ' (m)'); - buttonMuteIcon.classList.add('volume_up'); + updateTimeText(nowPlayingDurationText, runtimeTicks); + nowPlayingDurationText.classList.remove('hide'); } + } + } - if (showMuteButton) { - buttonMute.classList.remove('hide'); + function updatePlayerVolumeState(player, isMuted, volumeLevel) { + const supportedCommands = currentPlayerSupportedCommands; + let showMuteButton = true; + let showVolumeSlider = true; + + if (supportedCommands.indexOf('Mute') === -1) { + showMuteButton = false; + } + + if (supportedCommands.indexOf('SetVolume') === -1) { + showVolumeSlider = false; + } + + if (player.isLocalPlayer && appHost.supports('physicalvolumecontrol')) { + showMuteButton = false; + showVolumeSlider = false; + } + + const buttonMute = view.querySelector('.buttonMute'); + const buttonMuteIcon = buttonMute.querySelector('.material-icons'); + + buttonMuteIcon.classList.remove('volume_off', 'volume_up'); + + if (isMuted) { + buttonMute.setAttribute('title', globalize.translate('Unmute') + ' (m)'); + buttonMuteIcon.classList.add('volume_off'); + } else { + buttonMute.setAttribute('title', globalize.translate('Mute') + ' (m)'); + buttonMuteIcon.classList.add('volume_up'); + } + + if (showMuteButton) { + buttonMute.classList.remove('hide'); + } else { + buttonMute.classList.add('hide'); + } + + if (nowPlayingVolumeSlider) { + if (showVolumeSlider) { + nowPlayingVolumeSliderContainer.classList.remove('hide'); } else { - buttonMute.classList.add('hide'); + nowPlayingVolumeSliderContainer.classList.add('hide'); } - if (nowPlayingVolumeSlider) { - if (showVolumeSlider) { - nowPlayingVolumeSliderContainer.classList.remove('hide'); - } else { - nowPlayingVolumeSliderContainer.classList.add('hide'); - } - - if (!nowPlayingVolumeSlider.dragging) { - nowPlayingVolumeSlider.value = volumeLevel || 0; - } + if (!nowPlayingVolumeSlider.dragging) { + nowPlayingVolumeSlider.value = volumeLevel || 0; } } + } - function updatePlaylist() { - const btnPreviousTrack = view.querySelector('.btnPreviousTrack'); - const btnNextTrack = view.querySelector('.btnNextTrack'); - btnPreviousTrack.classList.remove('hide'); - btnNextTrack.classList.remove('hide'); - btnNextTrack.disabled = false; - btnPreviousTrack.disabled = false; + function updatePlaylist() { + const btnPreviousTrack = view.querySelector('.btnPreviousTrack'); + const btnNextTrack = view.querySelector('.btnNextTrack'); + btnPreviousTrack.classList.remove('hide'); + btnNextTrack.classList.remove('hide'); + btnNextTrack.disabled = false; + btnPreviousTrack.disabled = false; + } + + function updateTimeText(elem, ticks, divider) { + if (ticks == null) { + elem.innerHTML = ''; + return; } - function updateTimeText(elem, ticks, divider) { - if (ticks == null) { - elem.innerHTML = ''; - return; - } + let html = datetime.getDisplayRunningTime(ticks); - let html = datetime.getDisplayRunningTime(ticks); - - if (divider) { - html = ' / ' + html; - } - - elem.innerHTML = html; + if (divider) { + html = ' / ' + html; } - function nowPlayingDurationTextClick() { - userSettings.enableVideoRemainingTime(!userSettings.enableVideoRemainingTime()); - // immediately update the text, without waiting for the next tick update or if the player is paused - const state = playbackManager.getPlayerState(currentPlayer); - const playState = state.PlayState; - const nowPlayingItem = state.NowPlayingItem; - updateTimeDisplay(playState.PositionTicks, nowPlayingItem.RunTimeTicks, playState.PlaybackStartTimeTicks, playState.PlaybackRate, playState.BufferedRanges || []); - } + elem.innerHTML = html; + } - function onSettingsButtonClick() { - const btn = this; + function nowPlayingDurationTextClick() { + userSettings.enableVideoRemainingTime(!userSettings.enableVideoRemainingTime()); + // immediately update the text, without waiting for the next tick update or if the player is paused + const state = playbackManager.getPlayerState(currentPlayer); + const playState = state.PlayState; + const nowPlayingItem = state.NowPlayingItem; + updateTimeDisplay(playState.PositionTicks, nowPlayingItem.RunTimeTicks, playState.PlaybackStartTimeTicks, playState.PlaybackRate, playState.BufferedRanges || []); + } - import('../../../components/playback/playersettingsmenu').then((playerSettingsMenu) => { - const player = currentPlayer; + function onSettingsButtonClick() { + const btn = this; - if (player) { - const state = playbackManager.getPlayerState(player); + import('../../../components/playback/playersettingsmenu').then((playerSettingsMenu) => { + const player = currentPlayer; - // show subtitle offset feature only if player and media support it - const showSubOffset = playbackManager.supportSubtitleOffset(player) + if (player) { + const state = playbackManager.getPlayerState(player); + + // show subtitle offset feature only if player and media support it + const showSubOffset = playbackManager.supportSubtitleOffset(player) && playbackManager.canHandleOffsetOnCurrentSubtitle(player); - playerSettingsMenu.show({ - mediaType: 'Video', - player: player, - positionTo: btn, - quality: state.MediaSource?.SupportsTranscoding, - stats: true, - suboffset: showSubOffset, - onOption: onSettingsOption - }).finally(() => { - resetIdle(); - }); - - setTimeout(resetIdle, 0); - } - }); - } - - function onSettingsOption(selectedOption) { - if (selectedOption === 'stats') { - toggleStats(); - } else if (selectedOption === 'suboffset') { - const player = currentPlayer; - if (player) { - playbackManager.enableShowingSubtitleOffset(player); - toggleSubtitleSync(); - } - } - } - - function toggleStats() { - import('../../../components/playerstats/playerstats').then(({ default: PlayerStats }) => { - const player = currentPlayer; - - if (player) { - if (statsOverlay) { - statsOverlay.toggle(); - } else { - statsOverlay = new PlayerStats({ - player: player - }); - } - } - }); - } - - function destroyStats() { - if (statsOverlay) { - statsOverlay.destroy(); - statsOverlay = null; - } - } - - function showAudioTrackSelection() { - const player = currentPlayer; - const audioTracks = playbackManager.audioTracks(player); - const currentIndex = playbackManager.getAudioStreamIndex(player); - const menuItems = audioTracks.map(function (stream) { - const opt = { - name: stream.DisplayTitle, - id: stream.Index - }; - - if (stream.Index === currentIndex) { - opt.selected = true; - } - - return opt; - }); - const positionTo = this; - - import('../../../components/actionSheet/actionSheet').then(({ default: actionsheet }) => { - actionsheet.show({ - items: menuItems, - title: globalize.translate('Audio'), - positionTo: positionTo - }).then(function (id) { - const index = parseInt(id, 10); - - if (index !== currentIndex) { - playbackManager.setAudioStreamIndex(index, player); - } + playerSettingsMenu.show({ + mediaType: 'Video', + player: player, + positionTo: btn, + quality: state.MediaSource?.SupportsTranscoding, + stats: true, + suboffset: showSubOffset, + onOption: onSettingsOption }).finally(() => { resetIdle(); }); setTimeout(resetIdle, 0); - }); - } + } + }); + } - function showSecondarySubtitlesMenu(actionsheet, positionTo) { + function onSettingsOption(selectedOption) { + if (selectedOption === 'stats') { + toggleStats(); + } else if (selectedOption === 'suboffset') { const player = currentPlayer; - if (!playbackManager.playerHasSecondarySubtitleSupport(player)) return; - let currentIndex = playbackManager.getSecondarySubtitleStreamIndex(player); - const streams = playbackManager.secondarySubtitleTracks(player); + if (player) { + playbackManager.enableShowingSubtitleOffset(player); + toggleSubtitleSync(); + } + } + } - if (currentIndex == null) { - currentIndex = -1; + function toggleStats() { + import('../../../components/playerstats/playerstats').then(({ default: PlayerStats }) => { + const player = currentPlayer; + + if (player) { + if (statsOverlay) { + statsOverlay.toggle(); + } else { + statsOverlay = new PlayerStats({ + player: player + }); + } + } + }); + } + + function destroyStats() { + if (statsOverlay) { + statsOverlay.destroy(); + statsOverlay = null; + } + } + + function showAudioTrackSelection() { + const player = currentPlayer; + const audioTracks = playbackManager.audioTracks(player); + const currentIndex = playbackManager.getAudioStreamIndex(player); + const menuItems = audioTracks.map(function (stream) { + const opt = { + name: stream.DisplayTitle, + id: stream.Index + }; + + if (stream.Index === currentIndex) { + opt.selected = true; } - streams.unshift({ - Index: -1, - DisplayTitle: globalize.translate('Off') - }); - - const menuItems = streams.map(function (stream) { - const opt = { - name: stream.DisplayTitle, - id: stream.Index - }; - - if (stream.Index === currentIndex) { - opt.selected = true; - } - - return opt; - }); + return opt; + }); + const positionTo = this; + import('../../../components/actionSheet/actionSheet').then(({ default: actionsheet }) => { actionsheet.show({ - title: globalize.translate('SecondarySubtitles'), items: menuItems, - positionTo + title: globalize.translate('Audio'), + positionTo: positionTo }).then(function (id) { - if (id) { - const index = parseInt(id, 10); - if (index !== currentIndex) { - playbackManager.setSecondarySubtitleStreamIndex(index, player); - } + const index = parseInt(id, 10); + + if (index !== currentIndex) { + playbackManager.setAudioStreamIndex(index, player); } - }) - .finally(() => { + }).finally(() => { resetIdle(); }); setTimeout(resetIdle, 0); + }); + } + + function showSecondarySubtitlesMenu(actionsheet, positionTo) { + const player = currentPlayer; + if (!playbackManager.playerHasSecondarySubtitleSupport(player)) return; + let currentIndex = playbackManager.getSecondarySubtitleStreamIndex(player); + const streams = playbackManager.secondarySubtitleTracks(player); + + if (currentIndex == null) { + currentIndex = -1; } - function showSubtitleTrackSelection() { - const player = currentPlayer; - const streams = playbackManager.subtitleTracks(player); - const secondaryStreams = playbackManager.secondarySubtitleTracks(player); - let currentIndex = playbackManager.getSubtitleStreamIndex(player); + streams.unshift({ + Index: -1, + DisplayTitle: globalize.translate('Off') + }); - if (currentIndex == null) { - currentIndex = -1; + const menuItems = streams.map(function (stream) { + const opt = { + name: stream.DisplayTitle, + id: stream.Index + }; + + if (stream.Index === currentIndex) { + opt.selected = true; } - streams.unshift({ - Index: -1, - DisplayTitle: globalize.translate('Off') - }); - const menuItems = streams.map(function (stream) { - const opt = { - name: stream.DisplayTitle, - id: stream.Index - }; + return opt; + }); - if (stream.Index === currentIndex) { - opt.selected = true; + actionsheet.show({ + title: globalize.translate('SecondarySubtitles'), + items: menuItems, + positionTo + }).then(function (id) { + if (id) { + const index = parseInt(id, 10); + if (index !== currentIndex) { + playbackManager.setSecondarySubtitleStreamIndex(index, player); } - - return opt; + } + }) + .finally(() => { + resetIdle(); }); - /** + setTimeout(resetIdle, 0); + } + + function showSubtitleTrackSelection() { + const player = currentPlayer; + const streams = playbackManager.subtitleTracks(player); + const secondaryStreams = playbackManager.secondarySubtitleTracks(player); + let currentIndex = playbackManager.getSubtitleStreamIndex(player); + + if (currentIndex == null) { + currentIndex = -1; + } + + streams.unshift({ + Index: -1, + DisplayTitle: globalize.translate('Off') + }); + const menuItems = streams.map(function (stream) { + const opt = { + name: stream.DisplayTitle, + id: stream.Index + }; + + if (stream.Index === currentIndex) { + opt.selected = true; + } + + return opt; + }); + + /** * Only show option if: * - player has support * - has more than 1 subtitle track @@ -1083,755 +1082,754 @@ import { PluginType } from '../../../types/plugin.ts'; * - primary subtitle is not off * - primary subtitle has support */ - const currentTrackCanAddSecondarySubtitle = playbackManager.playerHasSecondarySubtitleSupport(player) + const currentTrackCanAddSecondarySubtitle = playbackManager.playerHasSecondarySubtitleSupport(player) && streams.length > 1 && secondaryStreams.length > 0 && currentIndex !== -1 && playbackManager.trackHasSecondarySubtitleSupport(playbackManager.getSubtitleStream(player, currentIndex), player); - if (currentTrackCanAddSecondarySubtitle) { - const secondarySubtitleMenuItem = { - name: globalize.translate('SecondarySubtitles'), - id: 'secondarysubtitle' - }; - menuItems.unshift(secondarySubtitleMenuItem); - } + if (currentTrackCanAddSecondarySubtitle) { + const secondarySubtitleMenuItem = { + name: globalize.translate('SecondarySubtitles'), + id: 'secondarysubtitle' + }; + menuItems.unshift(secondarySubtitleMenuItem); + } - const positionTo = this; + const positionTo = this; - import('../../../components/actionSheet/actionSheet').then(({ default: actionsheet }) => { - actionsheet.show({ - title: globalize.translate('Subtitles'), - items: menuItems, - positionTo: positionTo - }).then(function (id) { - if (id === 'secondarysubtitle') { - try { - showSecondarySubtitlesMenu(actionsheet, positionTo); - } catch (e) { - console.error(e); - } - } else { - const index = parseInt(id, 10); - - if (index !== currentIndex) { - playbackManager.setSubtitleStreamIndex(index, player); - } + import('../../../components/actionSheet/actionSheet').then(({ default: actionsheet }) => { + actionsheet.show({ + title: globalize.translate('Subtitles'), + items: menuItems, + positionTo: positionTo + }).then(function (id) { + if (id === 'secondarysubtitle') { + try { + showSecondarySubtitlesMenu(actionsheet, positionTo); + } catch (e) { + console.error(e); } + } else { + const index = parseInt(id, 10); - toggleSubtitleSync(); - }).finally(() => { - resetIdle(); - }); + if (index !== currentIndex) { + playbackManager.setSubtitleStreamIndex(index, player); + } + } - setTimeout(resetIdle, 0); + toggleSubtitleSync(); + }).finally(() => { + resetIdle(); }); - } - function toggleSubtitleSync(action) { - const player = currentPlayer; - if (subtitleSyncOverlay) { - subtitleSyncOverlay.toggle(action); - } else if (player) { - subtitleSyncOverlay = new SubtitleSync(player); - } - } + setTimeout(resetIdle, 0); + }); + } - function destroySubtitleSync() { - if (subtitleSyncOverlay) { - subtitleSyncOverlay.destroy(); - subtitleSyncOverlay = null; - } + function toggleSubtitleSync(action) { + const player = currentPlayer; + if (subtitleSyncOverlay) { + subtitleSyncOverlay.toggle(action); + } else if (player) { + subtitleSyncOverlay = new SubtitleSync(player); } + } - /** + function destroySubtitleSync() { + if (subtitleSyncOverlay) { + subtitleSyncOverlay.destroy(); + subtitleSyncOverlay = null; + } + } + + /** * Clicked element. * To skip 'click' handling on Firefox/Edge. */ - let clickedElement; + let clickedElement; - function onClickCapture(e) { - // Firefox/Edge emits `click` even if `preventDefault` was used on `keydown` - // Ignore 'click' if another element was originally clicked - if (!e.target.contains(clickedElement)) { + function onClickCapture(e) { + // Firefox/Edge emits `click` even if `preventDefault` was used on `keydown` + // Ignore 'click' if another element was originally clicked + if (!e.target.contains(clickedElement)) { + e.preventDefault(); + e.stopPropagation(); + return false; + } + } + + function onKeyDown(e) { + clickedElement = e.target; + + const key = keyboardnavigation.getKeyName(e); + const isKeyModified = e.ctrlKey || e.altKey || e.metaKey; + + if (e.keyCode === 32) { + if (e.target.tagName !== 'BUTTON' || !layoutManager.tv) { + playbackManager.playPause(currentPlayer); e.preventDefault(); e.stopPropagation(); - return false; + // Trick Firefox with a null element to skip next click + clickedElement = null; } + showOsd(); + return; } - function onKeyDown(e) { - clickedElement = e.target; + if (layoutManager.tv && keyboardnavigation.isNavigationKey(key)) { + showOsd(); + return; + } - const key = keyboardnavigation.getKeyName(e); - const isKeyModified = e.ctrlKey || e.altKey || e.metaKey; - - if (e.keyCode === 32) { - if (e.target.tagName !== 'BUTTON' || !layoutManager.tv) { - playbackManager.playPause(currentPlayer); - e.preventDefault(); + switch (key) { + case 'Enter': + showOsd(); + break; + case 'Escape': + case 'Back': + // Ignore key when some dialog is opened + if (currentVisibleMenu === 'osd' && !getOpenedDialog()) { + hideOsd(); e.stopPropagation(); - // Trick Firefox with a null element to skip next click - clickedElement = null; } + break; + case 'k': + playbackManager.playPause(currentPlayer); showOsd(); - return; - } - - if (layoutManager.tv && keyboardnavigation.isNavigationKey(key)) { + break; + case 'ArrowUp': + case 'Up': + playbackManager.volumeUp(currentPlayer); + break; + case 'ArrowDown': + case 'Down': + playbackManager.volumeDown(currentPlayer); + break; + case 'l': + case 'ArrowRight': + case 'Right': + playbackManager.fastForward(currentPlayer); showOsd(); - return; - } - - switch (key) { - case 'Enter': + break; + case 'j': + case 'ArrowLeft': + case 'Left': + playbackManager.rewind(currentPlayer); + showOsd(); + break; + case 'f': + if (!e.ctrlKey && !e.metaKey) { + playbackManager.toggleFullscreen(currentPlayer); showOsd(); - break; - case 'Escape': - case 'Back': - // Ignore key when some dialog is opened - if (currentVisibleMenu === 'osd' && !getOpenedDialog()) { - hideOsd(); - e.stopPropagation(); - } - break; - case 'k': - playbackManager.playPause(currentPlayer); - showOsd(); - break; - case 'ArrowUp': - case 'Up': - playbackManager.volumeUp(currentPlayer); - break; - case 'ArrowDown': - case 'Down': - playbackManager.volumeDown(currentPlayer); - break; - case 'l': - case 'ArrowRight': - case 'Right': - playbackManager.fastForward(currentPlayer); - showOsd(); - break; - case 'j': - case 'ArrowLeft': - case 'Left': + } + break; + case 'm': + playbackManager.toggleMute(currentPlayer); + showOsd(); + break; + case 'p': + case 'P': + if (e.shiftKey) { + playbackManager.previousTrack(currentPlayer); + } + break; + case 'n': + case 'N': + if (e.shiftKey) { + playbackManager.nextTrack(currentPlayer); + } + break; + case 'NavigationLeft': + case 'GamepadDPadLeft': + case 'GamepadLeftThumbstickLeft': + // Ignores gamepad events that are always triggered, even when not focused. + if (document.hasFocus()) { /* eslint-disable-line compat/compat */ playbackManager.rewind(currentPlayer); showOsd(); - break; - case 'f': - if (!e.ctrlKey && !e.metaKey) { - playbackManager.toggleFullscreen(currentPlayer); - showOsd(); - } - break; - case 'm': - playbackManager.toggleMute(currentPlayer); + } + break; + case 'NavigationRight': + case 'GamepadDPadRight': + case 'GamepadLeftThumbstickRight': + // Ignores gamepad events that are always triggered, even when not focused. + if (document.hasFocus()) { /* eslint-disable-line compat/compat */ + playbackManager.fastForward(currentPlayer); showOsd(); - break; - case 'p': - case 'P': - if (e.shiftKey) { - playbackManager.previousTrack(currentPlayer); - } - break; - case 'n': - case 'N': - if (e.shiftKey) { - playbackManager.nextTrack(currentPlayer); - } - break; - case 'NavigationLeft': - case 'GamepadDPadLeft': - case 'GamepadLeftThumbstickLeft': - // Ignores gamepad events that are always triggered, even when not focused. - if (document.hasFocus()) { /* eslint-disable-line compat/compat */ - playbackManager.rewind(currentPlayer); - showOsd(); - } - break; - case 'NavigationRight': - case 'GamepadDPadRight': - case 'GamepadLeftThumbstickRight': - // Ignores gamepad events that are always triggered, even when not focused. - if (document.hasFocus()) { /* eslint-disable-line compat/compat */ - playbackManager.fastForward(currentPlayer); - showOsd(); - } - break; - case 'Home': - playbackManager.seekPercent(0, currentPlayer); - break; - case 'End': - playbackManager.seekPercent(100, currentPlayer); - break; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': { - if (!isKeyModified) { - const percent = parseInt(key, 10) * 10; - playbackManager.seekPercent(percent, currentPlayer); - } - break; } - case '>': - playbackManager.increasePlaybackRate(currentPlayer); - break; - case '<': - playbackManager.decreasePlaybackRate(currentPlayer); - break; - case 'PageUp': - playbackManager.nextChapter(currentPlayer); - break; - case 'PageDown': - playbackManager.previousChapter(currentPlayer); - break; + break; + case 'Home': + playbackManager.seekPercent(0, currentPlayer); + break; + case 'End': + playbackManager.seekPercent(100, currentPlayer); + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + if (!isKeyModified) { + const percent = parseInt(key, 10) * 10; + playbackManager.seekPercent(percent, currentPlayer); + } + break; + } + case '>': + playbackManager.increasePlaybackRate(currentPlayer); + break; + case '<': + playbackManager.decreasePlaybackRate(currentPlayer); + break; + case 'PageUp': + playbackManager.nextChapter(currentPlayer); + break; + case 'PageDown': + playbackManager.previousChapter(currentPlayer); + break; + } + } + + function onKeyDownCapture() { + resetIdle(); + } + + function onWheel(e) { + if (e.deltaY < 0) { + playbackManager.volumeUp(currentPlayer); + } + if (e.deltaY > 0) { + playbackManager.volumeDown(currentPlayer); + } + } + + function onWindowMouseDown(e) { + clickedElement = e.target; + mouseIsDown = true; + resetIdle(); + } + + function onWindowMouseUp() { + mouseIsDown = false; + resetIdle(); + } + + function onWindowDragEnd() { + // mousedown -> dragstart -> dragend !!! no mouseup :( + mouseIsDown = false; + resetIdle(); + } + + 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 getChapterBubbleHtml(apiClient, item, chapters, positionTicks) { + let chapter; + let index = -1; + + for (let i = 0, length = chapters.length; i < length; i++) { + const currentChapter = chapters[i]; + + if (positionTicks >= currentChapter.StartPositionTicks) { + chapter = currentChapter; + index = i; } } - function onKeyDownCapture() { - resetIdle(); - } - - function onWheel(e) { - if (e.deltaY < 0) { - playbackManager.volumeUp(currentPlayer); - } - if (e.deltaY > 0) { - playbackManager.volumeDown(currentPlayer); - } - } - - function onWindowMouseDown(e) { - clickedElement = e.target; - mouseIsDown = true; - resetIdle(); - } - - function onWindowMouseUp() { - mouseIsDown = false; - resetIdle(); - } - - function onWindowDragEnd() { - // mousedown -> dragstart -> dragend !!! no mouseup :( - mouseIsDown = false; - resetIdle(); - } - - function getImgUrl(item, chapter, index, maxWidth, apiClient) { - if (chapter.ImageTag) { - return apiClient.getScaledImageUrl(item.Id, { - maxWidth: maxWidth, - tag: chapter.ImageTag, - type: 'Chapter', - index: index - }); - } - + if (!chapter) { return null; } - function getChapterBubbleHtml(apiClient, item, chapters, positionTicks) { - let chapter; - let index = -1; + const src = getImgUrl(item, chapter, index, 400, apiClient); - for (let i = 0, length = chapters.length; i < length; i++) { - const currentChapter = chapters[i]; - - if (positionTicks >= currentChapter.StartPositionTicks) { - chapter = currentChapter; - index = i; - } - } - - if (!chapter) { - return null; - } - - const src = getImgUrl(item, chapter, index, 400, apiClient); - - if (src) { - let html = '
'; - html += ''; - html += '
'; - html += '
'; - html += escapeHtml(chapter.Name); - html += '
'; - html += '

'; - html += datetime.getDisplayRunningTime(positionTicks); - html += '

'; - html += '
'; - return html + '
'; - } - - return null; + if (src) { + let html = '
'; + html += ''; + html += '
'; + html += '
'; + html += escapeHtml(chapter.Name); + html += '
'; + html += '

'; + html += datetime.getDisplayRunningTime(positionTicks); + html += '

'; + html += '
'; + return html + '
'; } - let playPauseClickTimeout; - function onViewHideStopPlayback() { - if (playbackManager.isPlayingVideo()) { - shell.disableFullscreen(); + return null; + } - clearTimeout(playPauseClickTimeout); - const player = currentPlayer; - view.removeEventListener('viewbeforehide', onViewHideStopPlayback); - releaseCurrentPlayer(); - playbackManager.stop(player); - } - } + let playPauseClickTimeout; + function onViewHideStopPlayback() { + if (playbackManager.isPlayingVideo()) { + shell.disableFullscreen(); - function enableStopOnBack(enabled) { + clearTimeout(playPauseClickTimeout); + const player = currentPlayer; view.removeEventListener('viewbeforehide', onViewHideStopPlayback); - - if (enabled && playbackManager.isPlayingVideo(currentPlayer)) { - view.addEventListener('viewbeforehide', onViewHideStopPlayback); - } + releaseCurrentPlayer(); + playbackManager.stop(player); } + } - function updatePlaybackRate(player) { - // Restore playback speed control, if it exists in the session. - const playbackRateSpeed = sessionStorage.getItem('playbackRateSpeed'); - if (playbackRateSpeed !== null) { - player.setPlaybackRate(playbackRateSpeed); - } + function enableStopOnBack(enabled) { + view.removeEventListener('viewbeforehide', onViewHideStopPlayback); + + if (enabled && playbackManager.isPlayingVideo(currentPlayer)) { + view.addEventListener('viewbeforehide', onViewHideStopPlayback); } + } - shell.enableFullscreen(); - - let currentPlayer; - let comingUpNextDisplayed; - let currentUpNextDialog; - let isEnabled; - let currentItem; - let recordingButtonManager; - let enableProgressByTimeOfDay; - let currentVisibleMenu; - let statsOverlay; - let osdHideTimeout; - let lastPointerMoveData; - const self = this; - let currentPlayerSupportedCommands = []; - let currentRuntimeTicks = 0; - let lastUpdateTime = 0; - let programStartDateMs = 0; - let programEndDateMs = 0; - let playbackStartTimeTicks = 0; - let subtitleSyncOverlay; - const nowPlayingVolumeSlider = view.querySelector('.osdVolumeSlider'); - const nowPlayingVolumeSliderContainer = view.querySelector('.osdVolumeSliderContainer'); - const nowPlayingPositionSlider = view.querySelector('.osdPositionSlider'); - const nowPlayingPositionText = view.querySelector('.osdPositionText'); - const nowPlayingDurationText = view.querySelector('.osdDurationText'); - const startTimeText = view.querySelector('.startTimeText'); - const endTimeText = view.querySelector('.endTimeText'); - const endsAtText = view.querySelector('.endsAtText'); - const ratingsText = view.querySelector('.osdRatingsText'); - const btnRewind = view.querySelector('.btnRewind'); - const btnFastForward = view.querySelector('.btnFastForward'); - const transitionEndEventName = dom.whichTransitionEvent(); - const headerElement = document.querySelector('.skinHeader'); - const osdBottomElement = document.querySelector('.videoOsdBottom-maincontrols'); - - nowPlayingPositionSlider.enableKeyboardDragging(); - nowPlayingVolumeSlider.enableKeyboardDragging(); - - if (layoutManager.tv) { - nowPlayingPositionSlider.classList.add('focusable'); + function updatePlaybackRate(player) { + // Restore playback speed control, if it exists in the session. + const playbackRateSpeed = sessionStorage.getItem('playbackRateSpeed'); + if (playbackRateSpeed !== null) { + player.setPlaybackRate(playbackRateSpeed); } + } - nowPlayingDurationText.addEventListener('click', nowPlayingDurationTextClick); + shell.enableFullscreen(); - view.addEventListener('viewbeforeshow', function () { - headerElement.classList.add('osdHeader'); - setBackdropTransparency(TRANSPARENCY_LEVEL.Full); - }); - view.addEventListener('viewshow', function () { - try { - Events.on(playbackManager, 'playerchange', onPlayerChange); - bindToPlayer(playbackManager.getCurrentPlayer()); - /* eslint-disable-next-line compat/compat */ - dom.addEventListener(document, window.PointerEvent ? 'pointermove' : 'mousemove', onPointerMove, { - passive: true - }); - showOsd(); - inputManager.on(window, onInputCommand); - document.addEventListener('keydown', onKeyDown); - dom.addEventListener(document, 'keydown', onKeyDownCapture, { - capture: true, - passive: true - }); - document.addEventListener('wheel', onWheel); - /* eslint-disable-next-line compat/compat */ - dom.addEventListener(window, window.PointerEvent ? 'pointerdown' : 'mousedown', onWindowMouseDown, { - capture: true, - passive: true - }); - /* eslint-disable-next-line compat/compat */ - dom.addEventListener(window, window.PointerEvent ? 'pointerup' : 'mouseup', onWindowMouseUp, { - capture: true, - passive: true - }); - dom.addEventListener(window, 'touchstart', onWindowMouseDown, { - capture: true, - passive: true - }); - ['touchend', 'touchcancel'].forEach((event) => { - dom.addEventListener(window, event, onWindowMouseUp, { - capture: true, - passive: true - }); - }); - dom.addEventListener(window, 'dragend', onWindowDragEnd, { - capture: true, - passive: true - }); - if (browser.firefox || browser.edge) { - dom.addEventListener(document, 'click', onClickCapture, { capture: true }); - } - } catch (e) { - appRouter.goHome(); - } - }); - view.addEventListener('viewbeforehide', function () { - if (statsOverlay) { - statsOverlay.enabled(false); - } + let currentPlayer; + let comingUpNextDisplayed; + let currentUpNextDialog; + let isEnabled; + let currentItem; + let recordingButtonManager; + let enableProgressByTimeOfDay; + let currentVisibleMenu; + let statsOverlay; + let osdHideTimeout; + let lastPointerMoveData; + const self = this; + let currentPlayerSupportedCommands = []; + let currentRuntimeTicks = 0; + let lastUpdateTime = 0; + let programStartDateMs = 0; + let programEndDateMs = 0; + let playbackStartTimeTicks = 0; + let subtitleSyncOverlay; + const nowPlayingVolumeSlider = view.querySelector('.osdVolumeSlider'); + const nowPlayingVolumeSliderContainer = view.querySelector('.osdVolumeSliderContainer'); + const nowPlayingPositionSlider = view.querySelector('.osdPositionSlider'); + const nowPlayingPositionText = view.querySelector('.osdPositionText'); + const nowPlayingDurationText = view.querySelector('.osdDurationText'); + const startTimeText = view.querySelector('.startTimeText'); + const endTimeText = view.querySelector('.endTimeText'); + const endsAtText = view.querySelector('.endsAtText'); + const ratingsText = view.querySelector('.osdRatingsText'); + const btnRewind = view.querySelector('.btnRewind'); + const btnFastForward = view.querySelector('.btnFastForward'); + const transitionEndEventName = dom.whichTransitionEvent(); + const headerElement = document.querySelector('.skinHeader'); + const osdBottomElement = document.querySelector('.videoOsdBottom-maincontrols'); - document.removeEventListener('keydown', onKeyDown); - dom.removeEventListener(document, 'keydown', onKeyDownCapture, { + nowPlayingPositionSlider.enableKeyboardDragging(); + nowPlayingVolumeSlider.enableKeyboardDragging(); + + if (layoutManager.tv) { + nowPlayingPositionSlider.classList.add('focusable'); + } + + nowPlayingDurationText.addEventListener('click', nowPlayingDurationTextClick); + + view.addEventListener('viewbeforeshow', function () { + headerElement.classList.add('osdHeader'); + setBackdropTransparency(TRANSPARENCY_LEVEL.Full); + }); + view.addEventListener('viewshow', function () { + try { + Events.on(playbackManager, 'playerchange', onPlayerChange); + bindToPlayer(playbackManager.getCurrentPlayer()); + /* eslint-disable-next-line compat/compat */ + dom.addEventListener(document, window.PointerEvent ? 'pointermove' : 'mousemove', onPointerMove, { + passive: true + }); + showOsd(); + inputManager.on(window, onInputCommand); + document.addEventListener('keydown', onKeyDown); + dom.addEventListener(document, 'keydown', onKeyDownCapture, { capture: true, passive: true }); - document.removeEventListener('wheel', onWheel); + document.addEventListener('wheel', onWheel); /* eslint-disable-next-line compat/compat */ - dom.removeEventListener(window, window.PointerEvent ? 'pointerdown' : 'mousedown', onWindowMouseDown, { + dom.addEventListener(window, window.PointerEvent ? 'pointerdown' : 'mousedown', onWindowMouseDown, { capture: true, passive: true }); /* eslint-disable-next-line compat/compat */ - dom.removeEventListener(window, window.PointerEvent ? 'pointerup' : 'mouseup', onWindowMouseUp, { + dom.addEventListener(window, window.PointerEvent ? 'pointerup' : 'mouseup', onWindowMouseUp, { capture: true, passive: true }); - dom.removeEventListener(window, 'touchstart', onWindowMouseDown, { + dom.addEventListener(window, 'touchstart', onWindowMouseDown, { capture: true, passive: true }); ['touchend', 'touchcancel'].forEach((event) => { - dom.removeEventListener(window, event, onWindowMouseUp, { + dom.addEventListener(window, event, onWindowMouseUp, { capture: true, passive: true }); }); - dom.removeEventListener(window, 'dragend', onWindowDragEnd, { + dom.addEventListener(window, 'dragend', onWindowDragEnd, { capture: true, passive: true }); if (browser.firefox || browser.edge) { - dom.removeEventListener(document, 'click', onClickCapture, { capture: true }); - } - stopOsdHideTimer(); - headerElement.classList.remove('osdHeader'); - headerElement.classList.remove('osdHeader-hidden'); - /* eslint-disable-next-line compat/compat */ - dom.removeEventListener(document, window.PointerEvent ? 'pointermove' : 'mousemove', onPointerMove, { - passive: true - }); - inputManager.off(window, onInputCommand); - Events.off(playbackManager, 'playerchange', onPlayerChange); - releaseCurrentPlayer(); - }); - view.querySelector('.btnFullscreen').addEventListener('click', function () { - playbackManager.toggleFullscreen(currentPlayer); - }); - view.querySelector('.btnPip').addEventListener('click', function () { - playbackManager.togglePictureInPicture(currentPlayer); - }); - view.querySelector('.btnAirPlay').addEventListener('click', function () { - playbackManager.toggleAirPlay(currentPlayer); - }); - view.querySelector('.btnVideoOsdSettings').addEventListener('click', onSettingsButtonClick); - view.addEventListener('viewhide', function () { - headerElement.classList.remove('hide'); - }); - view.addEventListener('viewdestroy', function () { - if (self.touchHelper) { - self.touchHelper.destroy(); - self.touchHelper = null; + dom.addEventListener(document, 'click', onClickCapture, { capture: true }); } + } catch (e) { + appRouter.goHome(); + } + }); + view.addEventListener('viewbeforehide', function () { + if (statsOverlay) { + statsOverlay.enabled(false); + } - if (recordingButtonManager) { - recordingButtonManager.destroy(); - recordingButtonManager = null; - } - - destroyStats(); - destroySubtitleSync(); - }); - let lastPointerDown = 0; - /* eslint-disable-next-line compat/compat */ - dom.addEventListener(view, window.PointerEvent ? 'pointerdown' : 'click', function (e) { - if (dom.parentWithClass(e.target, ['videoOsdBottom', 'upNextContainer'])) { - showOsd(); - return; - } - - const pointerType = e.pointerType || (layoutManager.mobile ? 'touch' : 'mouse'); - const now = new Date().getTime(); - - switch (pointerType) { - case 'touch': - if (now - lastPointerDown > 300) { - lastPointerDown = now; - toggleOsd(); - } - - break; - - case 'mouse': - if (!e.button) { - if (playPauseClickTimeout) { - clearTimeout(playPauseClickTimeout); - playPauseClickTimeout = 0; - } else { - playPauseClickTimeout = setTimeout(function() { - playbackManager.playPause(currentPlayer); - showOsd(); - playPauseClickTimeout = 0; - }, 300); - } - } - - break; - - default: - playbackManager.playPause(currentPlayer); - showOsd(); - } - }, { + document.removeEventListener('keydown', onKeyDown); + dom.removeEventListener(document, 'keydown', onKeyDownCapture, { + capture: true, passive: true }); - - dom.addEventListener(view, 'dblclick', (e) => { - if (e.target !== view) return; - playbackManager.toggleFullscreen(currentPlayer); + document.removeEventListener('wheel', onWheel); + /* eslint-disable-next-line compat/compat */ + dom.removeEventListener(window, window.PointerEvent ? 'pointerdown' : 'mousedown', onWindowMouseDown, { + capture: true, + passive: true }); - - view.querySelector('.buttonMute').addEventListener('click', function () { - playbackManager.toggleMute(currentPlayer); + /* eslint-disable-next-line compat/compat */ + dom.removeEventListener(window, window.PointerEvent ? 'pointerup' : 'mouseup', onWindowMouseUp, { + capture: true, + passive: true }); - - nowPlayingVolumeSlider.addEventListener('input', (e) => { - playbackManager.setVolume(e.target.value, currentPlayer); + dom.removeEventListener(window, 'touchstart', onWindowMouseDown, { + capture: true, + passive: true }); - - nowPlayingPositionSlider.addEventListener('change', function () { - const player = currentPlayer; - - if (player) { - const newPercent = parseFloat(this.value); - - if (enableProgressByTimeOfDay) { - let seekAirTimeTicks = newPercent / 100 * (programEndDateMs - programStartDateMs) * 1e4; - seekAirTimeTicks += 1e4 * programStartDateMs; - seekAirTimeTicks -= playbackStartTimeTicks; - playbackManager.seek(seekAirTimeTicks, player); - } else { - playbackManager.seekPercent(newPercent, player); - } - } + ['touchend', 'touchcancel'].forEach((event) => { + dom.removeEventListener(window, event, onWindowMouseUp, { + capture: true, + passive: true + }); }); + dom.removeEventListener(window, 'dragend', onWindowDragEnd, { + capture: true, + passive: true + }); + if (browser.firefox || browser.edge) { + dom.removeEventListener(document, 'click', onClickCapture, { capture: true }); + } + stopOsdHideTimer(); + headerElement.classList.remove('osdHeader'); + headerElement.classList.remove('osdHeader-hidden'); + /* eslint-disable-next-line compat/compat */ + dom.removeEventListener(document, window.PointerEvent ? 'pointermove' : 'mousemove', onPointerMove, { + passive: true + }); + inputManager.off(window, onInputCommand); + Events.off(playbackManager, 'playerchange', onPlayerChange); + releaseCurrentPlayer(); + }); + view.querySelector('.btnFullscreen').addEventListener('click', function () { + playbackManager.toggleFullscreen(currentPlayer); + }); + view.querySelector('.btnPip').addEventListener('click', function () { + playbackManager.togglePictureInPicture(currentPlayer); + }); + view.querySelector('.btnAirPlay').addEventListener('click', function () { + playbackManager.toggleAirPlay(currentPlayer); + }); + view.querySelector('.btnVideoOsdSettings').addEventListener('click', onSettingsButtonClick); + view.addEventListener('viewhide', function () { + headerElement.classList.remove('hide'); + }); + view.addEventListener('viewdestroy', function () { + if (self.touchHelper) { + self.touchHelper.destroy(); + self.touchHelper = null; + } - nowPlayingPositionSlider.getBubbleHtml = function (value) { + if (recordingButtonManager) { + recordingButtonManager.destroy(); + recordingButtonManager = null; + } + + destroyStats(); + destroySubtitleSync(); + }); + let lastPointerDown = 0; + /* eslint-disable-next-line compat/compat */ + dom.addEventListener(view, window.PointerEvent ? 'pointerdown' : 'click', function (e) { + if (dom.parentWithClass(e.target, ['videoOsdBottom', 'upNextContainer'])) { showOsd(); + return; + } + + const pointerType = e.pointerType || (layoutManager.mobile ? 'touch' : 'mouse'); + const now = new Date().getTime(); + + switch (pointerType) { + case 'touch': + if (now - lastPointerDown > 300) { + lastPointerDown = now; + toggleOsd(); + } + + break; + + case 'mouse': + if (!e.button) { + if (playPauseClickTimeout) { + clearTimeout(playPauseClickTimeout); + playPauseClickTimeout = 0; + } else { + playPauseClickTimeout = setTimeout(function() { + playbackManager.playPause(currentPlayer); + showOsd(); + playPauseClickTimeout = 0; + }, 300); + } + } + + break; + + default: + playbackManager.playPause(currentPlayer); + showOsd(); + } + }, { + passive: true + }); + + dom.addEventListener(view, 'dblclick', (e) => { + if (e.target !== view) return; + playbackManager.toggleFullscreen(currentPlayer); + }); + + view.querySelector('.buttonMute').addEventListener('click', function () { + playbackManager.toggleMute(currentPlayer); + }); + + nowPlayingVolumeSlider.addEventListener('input', (e) => { + playbackManager.setVolume(e.target.value, currentPlayer); + }); + + nowPlayingPositionSlider.addEventListener('change', function () { + const player = currentPlayer; + + if (player) { + const newPercent = parseFloat(this.value); + if (enableProgressByTimeOfDay) { - if (programStartDateMs && programEndDateMs) { - let ms = programEndDateMs - programStartDateMs; - ms /= 100; - ms *= value; - ms += programStartDateMs; - return '

' + getDisplayTimeWithoutAmPm(new Date(parseInt(ms, 10)), true) + '

'; - } + let seekAirTimeTicks = newPercent / 100 * (programEndDateMs - programStartDateMs) * 1e4; + seekAirTimeTicks += 1e4 * programStartDateMs; + seekAirTimeTicks -= playbackStartTimeTicks; + playbackManager.seek(seekAirTimeTicks, player); + } else { + playbackManager.seekPercent(newPercent, player); + } + } + }); - return '--:--'; + nowPlayingPositionSlider.getBubbleHtml = function (value) { + showOsd(); + if (enableProgressByTimeOfDay) { + if (programStartDateMs && programEndDateMs) { + let ms = programEndDateMs - programStartDateMs; + ms /= 100; + ms *= value; + ms += programStartDateMs; + return '

' + getDisplayTimeWithoutAmPm(new Date(parseInt(ms, 10)), true) + '

'; } - if (!currentRuntimeTicks) { - return '--:--'; + return '--:--'; + } + + if (!currentRuntimeTicks) { + return '--:--'; + } + + let ticks = currentRuntimeTicks; + ticks /= 100; + ticks *= value; + const item = currentItem; + + if (item && item.Chapters && item.Chapters.length && item.Chapters[0].ImageTag) { + const html = getChapterBubbleHtml(ServerConnections.getApiClient(item.ServerId), item, item.Chapters, ticks); + + if (html) { + return html; } + } - let ticks = currentRuntimeTicks; - ticks /= 100; - ticks *= value; - const item = currentItem; + return '

' + datetime.getDisplayRunningTime(ticks) + '

'; + }; - if (item && item.Chapters && item.Chapters.length && item.Chapters[0].ImageTag) { - const html = getChapterBubbleHtml(ServerConnections.getApiClient(item.ServerId), item, item.Chapters, ticks); + nowPlayingPositionSlider.getMarkerInfo = function () { + const markers = []; - if (html) { - return html; - } - } + const item = currentItem; - return '

' + datetime.getDisplayRunningTime(ticks) + '

'; - }; - - nowPlayingPositionSlider.getMarkerInfo = function () { - const markers = []; - - const item = currentItem; - - // use markers based on chapters - if (item?.Chapters?.length) { - item.Chapters.forEach(currentChapter => { - markers.push({ - className: 'chapterMarker', - name: currentChapter.Name, - progress: currentChapter.StartPositionTicks / item.RunTimeTicks - }); + // use markers based on chapters + if (item?.Chapters?.length) { + item.Chapters.forEach(currentChapter => { + markers.push({ + className: 'chapterMarker', + name: currentChapter.Name, + progress: currentChapter.StartPositionTicks / item.RunTimeTicks }); - } - - return markers; - }; - - view.querySelector('.btnPreviousTrack').addEventListener('click', function () { - playbackManager.previousTrack(currentPlayer); - }); - view.querySelector('.btnPreviousChapter').addEventListener('click', function () { - playbackManager.previousChapter(currentPlayer); - }); - view.querySelector('.btnPause').addEventListener('click', function () { - playbackManager.playPause(currentPlayer); - }); - view.querySelector('.btnNextChapter').addEventListener('click', function () { - playbackManager.nextChapter(currentPlayer); - }); - view.querySelector('.btnNextTrack').addEventListener('click', function () { - playbackManager.nextTrack(currentPlayer); - }); - btnRewind.addEventListener('click', function () { - playbackManager.rewind(currentPlayer); - }); - btnFastForward.addEventListener('click', function () { - playbackManager.fastForward(currentPlayer); - }); - view.querySelector('.btnAudio').addEventListener('click', showAudioTrackSelection); - view.querySelector('.btnSubtitles').addEventListener('click', showSubtitleTrackSelection); - - // Register to SyncPlay playback events and show big animated icon - const showIcon = (action) => { - let primary_icon_name = ''; - let secondary_icon_name = ''; - let animation_class = 'oneShotPulse'; - let iconVisibilityTime = 1500; - const syncPlayIcon = view.querySelector('#syncPlayIcon'); - - switch (action) { - case 'schedule-play': - primary_icon_name = 'sync spin'; - secondary_icon_name = 'play_arrow centered'; - animation_class = 'infinitePulse'; - iconVisibilityTime = -1; - hideOsd(); - break; - case 'unpause': - primary_icon_name = 'play_circle_outline'; - break; - case 'pause': - primary_icon_name = 'pause_circle_outline'; - showOsd(); - break; - case 'seek': - primary_icon_name = 'update'; - animation_class = 'infinitePulse'; - iconVisibilityTime = -1; - break; - case 'buffering': - primary_icon_name = 'schedule'; - animation_class = 'infinitePulse'; - iconVisibilityTime = -1; - break; - case 'wait-pause': - primary_icon_name = 'schedule'; - secondary_icon_name = 'pause shifted'; - animation_class = 'infinitePulse'; - iconVisibilityTime = -1; - break; - case 'wait-unpause': - primary_icon_name = 'schedule'; - secondary_icon_name = 'play_arrow shifted'; - animation_class = 'infinitePulse'; - iconVisibilityTime = -1; - break; - default: { - syncPlayIcon.style.visibility = 'hidden'; - return; - } - } - - syncPlayIcon.setAttribute('class', 'syncPlayIconCircle ' + animation_class); - - const primaryIcon = syncPlayIcon.querySelector('.primary-icon'); - primaryIcon.setAttribute('class', 'primary-icon material-icons ' + primary_icon_name); - - const secondaryIcon = syncPlayIcon.querySelector('.secondary-icon'); - secondaryIcon.setAttribute('class', 'secondary-icon material-icons ' + secondary_icon_name); - - const clone = syncPlayIcon.cloneNode(true); - clone.style.visibility = 'visible'; - syncPlayIcon.parentNode.replaceChild(clone, syncPlayIcon); - - if (iconVisibilityTime < 0) { - return; - } - - setTimeout(() => { - clone.style.visibility = 'hidden'; - }, iconVisibilityTime); - }; - - const SyncPlay = pluginManager.firstOfType(PluginType.SyncPlay)?.instance; - if (SyncPlay) { - Events.on(SyncPlay.Manager, 'enabled', (_event, enabled) => { - if (!enabled) { - const syncPlayIcon = view.querySelector('#syncPlayIcon'); - syncPlayIcon.style.visibility = 'hidden'; - } - }); - - Events.on(SyncPlay.Manager, 'notify-osd', (_event, action) => { - showIcon(action); - }); - - Events.on(SyncPlay.Manager, 'group-state-update', (_event, state, reason) => { - if (state === 'Playing' && reason === 'Unpause') { - showIcon('schedule-play'); - } else if (state === 'Playing' && reason === 'Ready') { - showIcon('schedule-play'); - } else if (state === 'Paused' && reason === 'Pause') { - showIcon('pause'); - } else if (state === 'Paused' && reason === 'Ready') { - showIcon('clear'); - } else if (state === 'Waiting' && reason === 'Seek') { - showIcon('seek'); - } else if (state === 'Waiting' && reason === 'Buffer') { - showIcon('buffering'); - } else if (state === 'Waiting' && reason === 'Pause') { - showIcon('wait-pause'); - } else if (state === 'Waiting' && reason === 'Unpause') { - showIcon('wait-unpause'); - } }); } - } -/* eslint-enable indent */ + return markers; + }; + + view.querySelector('.btnPreviousTrack').addEventListener('click', function () { + playbackManager.previousTrack(currentPlayer); + }); + view.querySelector('.btnPreviousChapter').addEventListener('click', function () { + playbackManager.previousChapter(currentPlayer); + }); + view.querySelector('.btnPause').addEventListener('click', function () { + playbackManager.playPause(currentPlayer); + }); + view.querySelector('.btnNextChapter').addEventListener('click', function () { + playbackManager.nextChapter(currentPlayer); + }); + view.querySelector('.btnNextTrack').addEventListener('click', function () { + playbackManager.nextTrack(currentPlayer); + }); + btnRewind.addEventListener('click', function () { + playbackManager.rewind(currentPlayer); + }); + btnFastForward.addEventListener('click', function () { + playbackManager.fastForward(currentPlayer); + }); + view.querySelector('.btnAudio').addEventListener('click', showAudioTrackSelection); + view.querySelector('.btnSubtitles').addEventListener('click', showSubtitleTrackSelection); + + // Register to SyncPlay playback events and show big animated icon + const showIcon = (action) => { + let primary_icon_name = ''; + let secondary_icon_name = ''; + let animation_class = 'oneShotPulse'; + let iconVisibilityTime = 1500; + const syncPlayIcon = view.querySelector('#syncPlayIcon'); + + switch (action) { + case 'schedule-play': + primary_icon_name = 'sync spin'; + secondary_icon_name = 'play_arrow centered'; + animation_class = 'infinitePulse'; + iconVisibilityTime = -1; + hideOsd(); + break; + case 'unpause': + primary_icon_name = 'play_circle_outline'; + break; + case 'pause': + primary_icon_name = 'pause_circle_outline'; + showOsd(); + break; + case 'seek': + primary_icon_name = 'update'; + animation_class = 'infinitePulse'; + iconVisibilityTime = -1; + break; + case 'buffering': + primary_icon_name = 'schedule'; + animation_class = 'infinitePulse'; + iconVisibilityTime = -1; + break; + case 'wait-pause': + primary_icon_name = 'schedule'; + secondary_icon_name = 'pause shifted'; + animation_class = 'infinitePulse'; + iconVisibilityTime = -1; + break; + case 'wait-unpause': + primary_icon_name = 'schedule'; + secondary_icon_name = 'play_arrow shifted'; + animation_class = 'infinitePulse'; + iconVisibilityTime = -1; + break; + default: { + syncPlayIcon.style.visibility = 'hidden'; + return; + } + } + + syncPlayIcon.setAttribute('class', 'syncPlayIconCircle ' + animation_class); + + const primaryIcon = syncPlayIcon.querySelector('.primary-icon'); + primaryIcon.setAttribute('class', 'primary-icon material-icons ' + primary_icon_name); + + const secondaryIcon = syncPlayIcon.querySelector('.secondary-icon'); + secondaryIcon.setAttribute('class', 'secondary-icon material-icons ' + secondary_icon_name); + + const clone = syncPlayIcon.cloneNode(true); + clone.style.visibility = 'visible'; + syncPlayIcon.parentNode.replaceChild(clone, syncPlayIcon); + + if (iconVisibilityTime < 0) { + return; + } + + setTimeout(() => { + clone.style.visibility = 'hidden'; + }, iconVisibilityTime); + }; + + const SyncPlay = pluginManager.firstOfType(PluginType.SyncPlay)?.instance; + if (SyncPlay) { + Events.on(SyncPlay.Manager, 'enabled', (_event, enabled) => { + if (!enabled) { + const syncPlayIcon = view.querySelector('#syncPlayIcon'); + syncPlayIcon.style.visibility = 'hidden'; + } + }); + + Events.on(SyncPlay.Manager, 'notify-osd', (_event, action) => { + showIcon(action); + }); + + Events.on(SyncPlay.Manager, 'group-state-update', (_event, state, reason) => { + if (state === 'Playing' && reason === 'Unpause') { + showIcon('schedule-play'); + } else if (state === 'Playing' && reason === 'Ready') { + showIcon('schedule-play'); + } else if (state === 'Paused' && reason === 'Pause') { + showIcon('pause'); + } else if (state === 'Paused' && reason === 'Ready') { + showIcon('clear'); + } else if (state === 'Waiting' && reason === 'Seek') { + showIcon('seek'); + } else if (state === 'Waiting' && reason === 'Buffer') { + showIcon('buffering'); + } else if (state === 'Waiting' && reason === 'Pause') { + showIcon('wait-pause'); + } else if (state === 'Waiting' && reason === 'Unpause') { + showIcon('wait-unpause'); + } + }); + } +} + diff --git a/src/controllers/session/addServer/index.js b/src/controllers/session/addServer/index.js index 64deaa2fcf..548e6a388d 100644 --- a/src/controllers/session/addServer/index.js +++ b/src/controllers/session/addServer/index.js @@ -6,69 +6,66 @@ import Dashboard from '../../../utils/dashboard'; import ServerConnections from '../../../components/ServerConnections'; import { ConnectionState } from '../../../utils/jellyfin-apiclient/ConnectionState.ts'; -/* eslint-disable indent */ - - function handleConnectionResult(page, result) { - loading.hide(); - switch (result.State) { - case ConnectionState.SignedIn: { - const apiClient = result.ApiClient; - Dashboard.onServerChanged(apiClient.getCurrentUserId(), apiClient.accessToken(), apiClient); - Dashboard.navigate('home.html'); - break; - } - case ConnectionState.ServerSignIn: - Dashboard.navigate('login.html?serverid=' + result.Servers[0].Id, false, 'none'); - break; - case ConnectionState.ServerSelection: - Dashboard.navigate('selectserver.html', false, 'none'); - break; - case ConnectionState.ServerUpdateNeeded: - Dashboard.alert({ - message: globalize.translate('ServerUpdateNeeded', 'https://github.com/jellyfin/jellyfin') - }); - break; - case ConnectionState.Unavailable: - Dashboard.alert({ - message: globalize.translate('MessageUnableToConnectToServer'), - title: globalize.translate('HeaderConnectionFailure') - }); +function handleConnectionResult(page, result) { + loading.hide(); + switch (result.State) { + case ConnectionState.SignedIn: { + const apiClient = result.ApiClient; + Dashboard.onServerChanged(apiClient.getCurrentUserId(), apiClient.accessToken(), apiClient); + Dashboard.navigate('home.html'); + break; } + case ConnectionState.ServerSignIn: + Dashboard.navigate('login.html?serverid=' + result.Servers[0].Id, false, 'none'); + break; + case ConnectionState.ServerSelection: + Dashboard.navigate('selectserver.html', false, 'none'); + break; + case ConnectionState.ServerUpdateNeeded: + Dashboard.alert({ + message: globalize.translate('ServerUpdateNeeded', 'https://github.com/jellyfin/jellyfin') + }); + break; + case ConnectionState.Unavailable: + Dashboard.alert({ + message: globalize.translate('MessageUnableToConnectToServer'), + title: globalize.translate('HeaderConnectionFailure') + }); + } +} + +function submitServer(page) { + loading.show(); + const host = page.querySelector('#txtServerHost').value; + ServerConnections.connectToAddress(host, { + enableAutoLogin: appSettings.enableAutoLogin() + }).then(function(result) { + handleConnectionResult(page, result); + }, function() { + handleConnectionResult(page, { + State: ConnectionState.Unavailable + }); + }); +} + +export default function(view) { + view.querySelector('.addServerForm').addEventListener('submit', onServerSubmit); + view.querySelector('.btnCancel').addEventListener('click', goBack); + + import('../../../components/autoFocuser').then(({ default: autoFocuser }) => { + autoFocuser.autoFocus(view); + }); + + function onServerSubmit(e) { + submitServer(view); + e.preventDefault(); + return false; } - function submitServer(page) { - loading.show(); - const host = page.querySelector('#txtServerHost').value; - ServerConnections.connectToAddress(host, { - enableAutoLogin: appSettings.enableAutoLogin() - }).then(function(result) { - handleConnectionResult(page, result); - }, function() { - handleConnectionResult(page, { - State: ConnectionState.Unavailable - }); + function goBack() { + import('../../../components/appRouter').then(({ appRouter }) => { + appRouter.back(); }); } +} - export default function(view) { - view.querySelector('.addServerForm').addEventListener('submit', onServerSubmit); - view.querySelector('.btnCancel').addEventListener('click', goBack); - - import('../../../components/autoFocuser').then(({ default: autoFocuser }) => { - autoFocuser.autoFocus(view); - }); - - function onServerSubmit(e) { - submitServer(view); - e.preventDefault(); - return false; - } - - function goBack() { - import('../../../components/appRouter').then(({ appRouter }) => { - appRouter.back(); - }); - } - } - -/* eslint-enable indent */ diff --git a/src/controllers/session/forgotPassword/index.js b/src/controllers/session/forgotPassword/index.js index 87a0498bf0..fb9a7c73d7 100644 --- a/src/controllers/session/forgotPassword/index.js +++ b/src/controllers/session/forgotPassword/index.js @@ -1,59 +1,56 @@ import globalize from '../../../scripts/globalize'; import Dashboard from '../../../utils/dashboard'; -/* eslint-disable indent */ - - function processForgotPasswordResult(result) { - if (result.Action == 'ContactAdmin') { - Dashboard.alert({ - message: globalize.translate('MessageContactAdminToResetPassword'), - title: globalize.translate('ButtonForgotPassword') - }); - return; - } - - if (result.Action == 'InNetworkRequired') { - Dashboard.alert({ - message: globalize.translate('MessageForgotPasswordInNetworkRequired'), - title: globalize.translate('ButtonForgotPassword') - }); - return; - } - - if (result.Action == 'PinCode') { - let msg = globalize.translate('MessageForgotPasswordFileCreated'); - msg += '
'; - msg += '
'; - msg += 'Enter PIN here to finish Password Reset
'; - msg += '
'; - msg += result.PinFile; - msg += '
'; - Dashboard.alert({ - message: msg, - title: globalize.translate('ButtonForgotPassword'), - callback: function () { - Dashboard.navigate('forgotpasswordpin.html'); - } - }); - } +function processForgotPasswordResult(result) { + if (result.Action == 'ContactAdmin') { + Dashboard.alert({ + message: globalize.translate('MessageContactAdminToResetPassword'), + title: globalize.translate('ButtonForgotPassword') + }); + return; } - export default function (view) { - function onSubmit(e) { - ApiClient.ajax({ - type: 'POST', - url: ApiClient.getUrl('Users/ForgotPassword'), - dataType: 'json', - contentType: 'application/json', - data: JSON.stringify({ - EnteredUsername: view.querySelector('#txtName').value - }) - }).then(processForgotPasswordResult); - e.preventDefault(); - return false; - } - - view.querySelector('form').addEventListener('submit', onSubmit); + if (result.Action == 'InNetworkRequired') { + Dashboard.alert({ + message: globalize.translate('MessageForgotPasswordInNetworkRequired'), + title: globalize.translate('ButtonForgotPassword') + }); + return; } -/* eslint-enable indent */ + if (result.Action == 'PinCode') { + let msg = globalize.translate('MessageForgotPasswordFileCreated'); + msg += '
'; + msg += '
'; + msg += 'Enter PIN here to finish Password Reset
'; + msg += '
'; + msg += result.PinFile; + msg += '
'; + Dashboard.alert({ + message: msg, + title: globalize.translate('ButtonForgotPassword'), + callback: function () { + Dashboard.navigate('forgotpasswordpin.html'); + } + }); + } +} + +export default function (view) { + function onSubmit(e) { + ApiClient.ajax({ + type: 'POST', + url: ApiClient.getUrl('Users/ForgotPassword'), + dataType: 'json', + contentType: 'application/json', + data: JSON.stringify({ + EnteredUsername: view.querySelector('#txtName').value + }) + }).then(processForgotPasswordResult); + e.preventDefault(); + return false; + } + + view.querySelector('form').addEventListener('submit', onSubmit); +} + diff --git a/src/controllers/session/login/index.js b/src/controllers/session/login/index.js index f0c3639b83..0f2d18580c 100644 --- a/src/controllers/session/login/index.js +++ b/src/controllers/session/login/index.js @@ -18,292 +18,289 @@ import baseAlert from '../../../components/alert'; import cardBuilder from '../../../components/cardbuilder/cardBuilder'; import './login.scss'; -/* eslint-disable indent */ +const enableFocusTransform = !browser.slow && !browser.edge; - const enableFocusTransform = !browser.slow && !browser.edge; +function authenticateUserByName(page, apiClient, username, password) { + loading.show(); + apiClient.authenticateUserByName(username, password).then(function (result) { + const user = result.User; + loading.hide(); - function authenticateUserByName(page, apiClient, username, password) { - loading.show(); - apiClient.authenticateUserByName(username, password).then(function (result) { - const user = result.User; - loading.hide(); + onLoginSuccessful(user.Id, result.AccessToken, apiClient); + }, function (response) { + page.querySelector('#txtManualPassword').value = ''; + loading.hide(); - onLoginSuccessful(user.Id, result.AccessToken, apiClient); - }, function (response) { - page.querySelector('#txtManualPassword').value = ''; - loading.hide(); - - const UnauthorizedOrForbidden = [401, 403]; - if (UnauthorizedOrForbidden.includes(response.status)) { - const messageKey = response.status === 401 ? 'MessageInvalidUser' : 'MessageUnauthorizedUser'; - toast(globalize.translate(messageKey)); - } else { - Dashboard.alert({ - message: globalize.translate('MessageUnableToConnectToServer'), - title: globalize.translate('HeaderConnectionFailure') - }); - } - }); - } - - function authenticateQuickConnect(apiClient) { - const url = apiClient.getUrl('/QuickConnect/Initiate'); - apiClient.ajax({ type: 'POST', url }, true).then(res => res.json()).then(function (json) { - if (!json.Secret || !json.Code) { - console.error('Malformed quick connect response', json); - return false; - } - - baseAlert({ - dialogOptions: { - id: 'quickConnectAlert' - }, - title: globalize.translate('QuickConnect'), - text: globalize.translate('QuickConnectAuthorizeCode', json.Code) - }); - - const connectUrl = apiClient.getUrl('/QuickConnect/Connect?Secret=' + json.Secret); - - const interval = setInterval(function() { - apiClient.getJSON(connectUrl).then(async function(data) { - if (!data.Authenticated) { - return; - } - - clearInterval(interval); - - // Close the QuickConnect dialog - const dlg = document.getElementById('quickConnectAlert'); - if (dlg) { - dialogHelper.close(dlg); - } - - const result = await apiClient.quickConnect(data.Secret); - onLoginSuccessful(result.User.Id, result.AccessToken, apiClient); - }, function (e) { - clearInterval(interval); - - // Close the QuickConnect dialog - const dlg = document.getElementById('quickConnectAlert'); - if (dlg) { - dialogHelper.close(dlg); - } - - Dashboard.alert({ - message: globalize.translate('QuickConnectDeactivated'), - title: globalize.translate('HeaderError') - }); - - console.error('Unable to login with quick connect', e); - }); - }, 5000, connectUrl); - - return true; - }, function(e) { + const UnauthorizedOrForbidden = [401, 403]; + if (UnauthorizedOrForbidden.includes(response.status)) { + const messageKey = response.status === 401 ? 'MessageInvalidUser' : 'MessageUnauthorizedUser'; + toast(globalize.translate(messageKey)); + } else { Dashboard.alert({ - message: globalize.translate('QuickConnectNotActive'), - title: globalize.translate('HeaderError') + message: globalize.translate('MessageUnableToConnectToServer'), + title: globalize.translate('HeaderConnectionFailure') + }); + } + }); +} + +function authenticateQuickConnect(apiClient) { + const url = apiClient.getUrl('/QuickConnect/Initiate'); + apiClient.ajax({ type: 'POST', url }, true).then(res => res.json()).then(function (json) { + if (!json.Secret || !json.Code) { + console.error('Malformed quick connect response', json); + return false; + } + + baseAlert({ + dialogOptions: { + id: 'quickConnectAlert' + }, + title: globalize.translate('QuickConnect'), + text: globalize.translate('QuickConnectAuthorizeCode', json.Code) + }); + + const connectUrl = apiClient.getUrl('/QuickConnect/Connect?Secret=' + json.Secret); + + const interval = setInterval(function() { + apiClient.getJSON(connectUrl).then(async function(data) { + if (!data.Authenticated) { + return; + } + + clearInterval(interval); + + // Close the QuickConnect dialog + const dlg = document.getElementById('quickConnectAlert'); + if (dlg) { + dialogHelper.close(dlg); + } + + const result = await apiClient.quickConnect(data.Secret); + onLoginSuccessful(result.User.Id, result.AccessToken, apiClient); + }, function (e) { + clearInterval(interval); + + // Close the QuickConnect dialog + const dlg = document.getElementById('quickConnectAlert'); + if (dlg) { + dialogHelper.close(dlg); + } + + Dashboard.alert({ + message: globalize.translate('QuickConnectDeactivated'), + title: globalize.translate('HeaderError') + }); + + console.error('Unable to login with quick connect', e); + }); + }, 5000, connectUrl); + + return true; + }, function(e) { + Dashboard.alert({ + message: globalize.translate('QuickConnectNotActive'), + title: globalize.translate('HeaderError') + }); + + console.error('Quick connect error: ', e); + return false; + }); +} + +function onLoginSuccessful(id, accessToken, apiClient) { + Dashboard.onServerChanged(id, accessToken, apiClient); + Dashboard.navigate('home.html'); +} + +function showManualForm(context, showCancel, focusPassword) { + context.querySelector('.chkRememberLogin').checked = appSettings.enableAutoLogin(); + context.querySelector('.manualLoginForm').classList.remove('hide'); + context.querySelector('.visualLoginForm').classList.add('hide'); + context.querySelector('.btnManual').classList.add('hide'); + + if (focusPassword) { + context.querySelector('#txtManualPassword').focus(); + } else { + context.querySelector('#txtManualName').focus(); + } + + if (showCancel) { + context.querySelector('.btnCancel').classList.remove('hide'); + } else { + context.querySelector('.btnCancel').classList.add('hide'); + } +} + +function loadUserList(context, apiClient, users) { + let html = ''; + + for (let i = 0; i < users.length; i++) { + const user = users[i]; + + // TODO move card creation code to Card component + let cssClass = 'card squareCard scalableCard squareCard-scalable'; + + if (layoutManager.tv) { + cssClass += ' show-focus'; + + if (enableFocusTransform) { + cssClass += ' show-animation'; + } + } + + const cardBoxCssClass = 'cardBox cardBox-bottompadded'; + html += ''; + } + + context.querySelector('#divUsers').innerHTML = html; +} + +export default function (view, params) { + function getApiClient() { + const serverId = params.serverid; + + if (serverId) { + return ServerConnections.getOrCreateApiClient(serverId); + } + + return ApiClient; + } + + function showVisualForm() { + view.querySelector('.visualLoginForm').classList.remove('hide'); + view.querySelector('.manualLoginForm').classList.add('hide'); + view.querySelector('.btnManual').classList.remove('hide'); + + import('../../../components/autoFocuser').then(({ default: autoFocuser }) => { + autoFocuser.autoFocus(view); }); } - function onLoginSuccessful(id, accessToken, apiClient) { - Dashboard.onServerChanged(id, accessToken, apiClient); - Dashboard.navigate('home.html'); - } + view.querySelector('#divUsers').addEventListener('click', function (e) { + const card = dom.parentWithClass(e.target, 'card'); + const cardContent = card ? card.querySelector('.cardContent') : null; - function showManualForm(context, showCancel, focusPassword) { - context.querySelector('.chkRememberLogin').checked = appSettings.enableAutoLogin(); - context.querySelector('.manualLoginForm').classList.remove('hide'); - context.querySelector('.visualLoginForm').classList.add('hide'); - context.querySelector('.btnManual').classList.add('hide'); + if (cardContent) { + const context = view; + const id = cardContent.getAttribute('data-userid'); + const name = cardContent.getAttribute('data-username'); + const haspw = cardContent.getAttribute('data-haspw'); - if (focusPassword) { - context.querySelector('#txtManualPassword').focus(); - } else { - context.querySelector('#txtManualName').focus(); - } - - if (showCancel) { - context.querySelector('.btnCancel').classList.remove('hide'); - } else { - context.querySelector('.btnCancel').classList.add('hide'); - } - } - - function loadUserList(context, apiClient, users) { - let html = ''; - - for (let i = 0; i < users.length; i++) { - const user = users[i]; - - // TODO move card creation code to Card component - let cssClass = 'card squareCard scalableCard squareCard-scalable'; - - if (layoutManager.tv) { - cssClass += ' show-focus'; - - if (enableFocusTransform) { - cssClass += ' show-animation'; - } - } - - const cardBoxCssClass = 'cardBox cardBox-bottompadded'; - html += ''; + view.addEventListener('viewshow', function () { + loading.show(); + libraryMenu.setTransparentMenu(true); + + if (!appHost.supports('multiserver')) { + view.querySelector('.btnSelectServer').classList.add('hide'); } - context.querySelector('#divUsers').innerHTML = html; - } + const apiClient = getApiClient(); - export default function (view, params) { - function getApiClient() { - const serverId = params.serverid; - - if (serverId) { - return ServerConnections.getOrCreateApiClient(serverId); - } - - return ApiClient; - } - - function showVisualForm() { - view.querySelector('.visualLoginForm').classList.remove('hide'); - view.querySelector('.manualLoginForm').classList.add('hide'); - view.querySelector('.btnManual').classList.remove('hide'); - - import('../../../components/autoFocuser').then(({ default: autoFocuser }) => { - autoFocuser.autoFocus(view); + apiClient.getQuickConnect('Enabled') + .then(enabled => { + if (enabled === true) { + view.querySelector('.btnQuick').classList.remove('hide'); + } + }) + .catch(() => { + console.debug('Failed to get QuickConnect status'); }); - } - view.querySelector('#divUsers').addEventListener('click', function (e) { - const card = dom.parentWithClass(e.target, 'card'); - const cardContent = card ? card.querySelector('.cardContent') : null; + apiClient.getPublicUsers().then(function (users) { + if (users.length) { + showVisualForm(); + loadUserList(view, apiClient, users); + } else { + view.querySelector('#txtManualName').value = ''; + showManualForm(view, false, false); + } + }).catch().then(function () { + loading.hide(); + }); + apiClient.getJSON(apiClient.getUrl('Branding/Configuration')).then(function (options) { + const loginDisclaimer = view.querySelector('.loginDisclaimer'); - if (cardContent) { - const context = view; - const id = cardContent.getAttribute('data-userid'); - const name = cardContent.getAttribute('data-username'); - const haspw = cardContent.getAttribute('data-haspw'); + loginDisclaimer.innerHTML = DOMPurify.sanitize(marked(options.LoginDisclaimer || '')); - if (id === 'manual') { - context.querySelector('#txtManualName').value = ''; - showManualForm(context, true); - } else if (haspw == 'false') { - authenticateUserByName(context, getApiClient(), name, ''); - } else { - context.querySelector('#txtManualName').value = name; - context.querySelector('#txtManualPassword').value = ''; - showManualForm(context, true, true); + for (const elem of loginDisclaimer.querySelectorAll('a')) { + elem.rel = 'noopener noreferrer'; + elem.target = '_blank'; + elem.classList.add('button-link'); + elem.setAttribute('is', 'emby-linkbutton'); + + if (layoutManager.tv) { + // Disable links navigation on TV + elem.tabIndex = -1; } } }); - view.querySelector('.manualLoginForm').addEventListener('submit', function (e) { - appSettings.enableAutoLogin(view.querySelector('.chkRememberLogin').checked); - const apiClient = getApiClient(); - authenticateUserByName(view, apiClient, view.querySelector('#txtManualName').value, view.querySelector('#txtManualPassword').value); - e.preventDefault(); - return false; - }); - view.querySelector('.btnForgotPassword').addEventListener('click', function () { - Dashboard.navigate('forgotpassword.html'); - }); - view.querySelector('.btnCancel').addEventListener('click', showVisualForm); - view.querySelector('.btnQuick').addEventListener('click', function () { - const apiClient = getApiClient(); - authenticateQuickConnect(apiClient); - return false; - }); - view.querySelector('.btnManual').addEventListener('click', function () { - view.querySelector('#txtManualName').value = ''; - showManualForm(view, true); - }); - view.querySelector('.btnSelectServer').addEventListener('click', function () { - Dashboard.selectServer(); - }); + }); + view.addEventListener('viewhide', function () { + libraryMenu.setTransparentMenu(false); + }); +} - view.addEventListener('viewshow', function () { - loading.show(); - libraryMenu.setTransparentMenu(true); - - if (!appHost.supports('multiserver')) { - view.querySelector('.btnSelectServer').classList.add('hide'); - } - - const apiClient = getApiClient(); - - apiClient.getQuickConnect('Enabled') - .then(enabled => { - if (enabled === true) { - view.querySelector('.btnQuick').classList.remove('hide'); - } - }) - .catch(() => { - console.debug('Failed to get QuickConnect status'); - }); - - apiClient.getPublicUsers().then(function (users) { - if (users.length) { - showVisualForm(); - loadUserList(view, apiClient, users); - } else { - view.querySelector('#txtManualName').value = ''; - showManualForm(view, false, false); - } - }).catch().then(function () { - loading.hide(); - }); - apiClient.getJSON(apiClient.getUrl('Branding/Configuration')).then(function (options) { - const loginDisclaimer = view.querySelector('.loginDisclaimer'); - - loginDisclaimer.innerHTML = DOMPurify.sanitize(marked(options.LoginDisclaimer || '')); - - for (const elem of loginDisclaimer.querySelectorAll('a')) { - elem.rel = 'noopener noreferrer'; - elem.target = '_blank'; - elem.classList.add('button-link'); - elem.setAttribute('is', 'emby-linkbutton'); - - if (layoutManager.tv) { - // Disable links navigation on TV - elem.tabIndex = -1; - } - } - }); - }); - view.addEventListener('viewhide', function () { - libraryMenu.setTransparentMenu(false); - }); - } - -/* eslint-enable indent */ diff --git a/src/controllers/session/resetPassword/index.js b/src/controllers/session/resetPassword/index.js index ef311ff88c..10a721ec61 100644 --- a/src/controllers/session/resetPassword/index.js +++ b/src/controllers/session/resetPassword/index.js @@ -1,46 +1,43 @@ import globalize from '../../../scripts/globalize'; import Dashboard from '../../../utils/dashboard'; -/* eslint-disable indent */ - - function processForgotPasswordResult(result) { - if (result.Success) { - let msg = globalize.translate('MessagePasswordResetForUsers'); - msg += '
'; - msg += '
'; - msg += result.UsersReset.join('
'); - Dashboard.alert({ - message: msg, - title: globalize.translate('HeaderPasswordReset'), - callback: function () { - window.location.href = 'index.html'; - } - }); - return; - } - +function processForgotPasswordResult(result) { + if (result.Success) { + let msg = globalize.translate('MessagePasswordResetForUsers'); + msg += '
'; + msg += '
'; + msg += result.UsersReset.join('
'); Dashboard.alert({ - message: globalize.translate('MessageInvalidForgotPasswordPin'), - title: globalize.translate('HeaderPasswordReset') + message: msg, + title: globalize.translate('HeaderPasswordReset'), + callback: function () { + window.location.href = 'index.html'; + } }); + return; } - export default function (view) { - function onSubmit(e) { - ApiClient.ajax({ - type: 'POST', - url: ApiClient.getUrl('Users/ForgotPassword/Pin'), - dataType: 'json', - data: JSON.stringify({ - Pin: view.querySelector('#txtPin').value - }), - contentType: 'application/json' - }).then(processForgotPasswordResult); - e.preventDefault(); - return false; - } + Dashboard.alert({ + message: globalize.translate('MessageInvalidForgotPasswordPin'), + title: globalize.translate('HeaderPasswordReset') + }); +} - view.querySelector('form').addEventListener('submit', onSubmit); +export default function (view) { + function onSubmit(e) { + ApiClient.ajax({ + type: 'POST', + url: ApiClient.getUrl('Users/ForgotPassword/Pin'), + dataType: 'json', + data: JSON.stringify({ + Pin: view.querySelector('#txtPin').value + }), + contentType: 'application/json' + }).then(processForgotPasswordResult); + e.preventDefault(); + return false; } -/* eslint-enable indent */ + view.querySelector('form').addEventListener('submit', onSubmit); +} + diff --git a/src/controllers/session/selectServer/index.js b/src/controllers/session/selectServer/index.js index b45cb71060..8d3edbf3cb 100644 --- a/src/controllers/session/selectServer/index.js +++ b/src/controllers/session/selectServer/index.js @@ -21,196 +21,193 @@ import alert from '../../../components/alert'; import cardBuilder from '../../../components/cardbuilder/cardBuilder'; import { ConnectionState } from '../../../utils/jellyfin-apiclient/ConnectionState.ts'; -/* eslint-disable indent */ +const enableFocusTransform = !browser.slow && !browser.edge; - const enableFocusTransform = !browser.slow && !browser.edge; +function renderSelectServerItems(view, servers) { + const items = servers.map(function (server) { + return { + name: server.Name, + icon: 'storage', + cardType: '', + id: server.Id, + server: server + }; + }); + let html = items.map(function (item) { + // TODO move card creation code to Card component + const cardImageContainer = ''; + let cssClass = 'card overflowSquareCard loginSquareCard scalableCard overflowSquareCard-scalable'; - function renderSelectServerItems(view, servers) { - const items = servers.map(function (server) { - return { - name: server.Name, - icon: 'storage', - cardType: '', - id: server.Id, - server: server - }; - }); - let html = items.map(function (item) { - // TODO move card creation code to Card component - const cardImageContainer = ''; - let cssClass = 'card overflowSquareCard loginSquareCard scalableCard overflowSquareCard-scalable'; + if (layoutManager.tv) { + cssClass += ' show-focus'; - if (layoutManager.tv) { - cssClass += ' show-focus'; - - if (enableFocusTransform) { - cssClass += ' show-animation'; - } - } - - const cardBoxCssClass = 'cardBox'; - - const innerOpening = '
'; - let cardContainer = ''; - cardContainer += '
'; - return cardContainer; - }).join(''); - const itemsContainer = view.querySelector('.servers'); - - if (!items.length) { - html = '

' + globalize.translate('MessageNoServersAvailable') + '

'; - } - - itemsContainer.innerHTML = html; - loading.hide(); - } - - function updatePageStyle(view, params) { - if (params.showuser == '1') { - view.classList.add('libraryPage'); - view.classList.remove('standalonePage'); - view.classList.add('noSecondaryNavPage'); - } else { - view.classList.add('standalonePage'); - view.classList.remove('libraryPage'); - view.classList.remove('noSecondaryNavPage'); - } - } - - function alertText(text) { - alertTextWithOptions({ - text: text - }); - } - - function alertTextWithOptions(options) { - alert(options); - } - - function showServerConnectionFailure() { - alertText(globalize.translate('MessageUnableToConnectToServer')); - } - - export default function (view, params) { - function connectToServer(server) { - loading.show(); - ServerConnections.connectToServer(server, { - enableAutoLogin: appSettings.enableAutoLogin() - }).then(function (result) { - loading.hide(); - const apiClient = result.ApiClient; - - switch (result.State) { - case ConnectionState.SignedIn: - Dashboard.onServerChanged(apiClient.getCurrentUserId(), apiClient.accessToken(), apiClient); - Dashboard.navigate('home.html'); - break; - - case ConnectionState.ServerSignIn: - Dashboard.onServerChanged(null, null, apiClient); - Dashboard.navigate('login.html?serverid=' + result.Servers[0].Id); - break; - - case ConnectionState.ServerUpdateNeeded: - alertTextWithOptions({ - text: globalize.translate('core#ServerUpdateNeeded', 'https://github.com/jellyfin/jellyfin'), - html: globalize.translate('core#ServerUpdateNeeded', 'https://github.com/jellyfin/jellyfin') - }); - break; - - default: - showServerConnectionFailure(); - } - }); - } - - function deleteServer(server) { - loading.show(); - ServerConnections.deleteServer(server.Id).then(function () { - loading.hide(); - loadServers(); - }); - } - - function onServerClick(server) { - const menuItems = []; - menuItems.push({ - name: globalize.translate('Connect'), - id: 'connect' - }); - menuItems.push({ - name: globalize.translate('Delete'), - id: 'delete' - }); - actionSheet.show({ - items: menuItems, - title: server.Name - }).then(function (id) { - switch (id) { - case 'connect': - connectToServer(server); - break; - - case 'delete': - deleteServer(server); - } - }); - } - - function onServersRetrieved(result) { - servers = result; - renderSelectServerItems(view, result); - - if (layoutManager.tv) { - focusManager.autoFocus(view); + if (enableFocusTransform) { + cssClass += ' show-animation'; } } - function loadServers() { - loading.show(); - ServerConnections.getAvailableServers().then(onServersRetrieved); - } + const cardBoxCssClass = 'cardBox'; - let servers; - updatePageStyle(view, params); - view.addEventListener('viewshow', function (e) { - const isRestored = e.detail.isRestored; - libraryMenu.setTitle(null); - libraryMenu.setTransparentMenu(true); + const innerOpening = '
'; + let cardContainer = ''; + cardContainer += '
'; + return cardContainer; + }).join(''); + const itemsContainer = view.querySelector('.servers'); - if (!isRestored) { - loadServers(); - } - }); - view.querySelector('.servers').addEventListener('click', function (e) { - const card = dom.parentWithClass(e.target, 'card'); + if (!items.length) { + html = '

' + globalize.translate('MessageNoServersAvailable') + '

'; + } - if (card) { - const url = card.getAttribute('data-url'); + itemsContainer.innerHTML = html; + loading.hide(); +} - if (url) { - appRouter.show(url); - } else { - const id = card.getAttribute('data-id'); - onServerClick(servers.filter(function (s) { - return s.Id === id; - })[0]); - } +function updatePageStyle(view, params) { + if (params.showuser == '1') { + view.classList.add('libraryPage'); + view.classList.remove('standalonePage'); + view.classList.add('noSecondaryNavPage'); + } else { + view.classList.add('standalonePage'); + view.classList.remove('libraryPage'); + view.classList.remove('noSecondaryNavPage'); + } +} + +function alertText(text) { + alertTextWithOptions({ + text: text + }); +} + +function alertTextWithOptions(options) { + alert(options); +} + +function showServerConnectionFailure() { + alertText(globalize.translate('MessageUnableToConnectToServer')); +} + +export default function (view, params) { + function connectToServer(server) { + loading.show(); + ServerConnections.connectToServer(server, { + enableAutoLogin: appSettings.enableAutoLogin() + }).then(function (result) { + loading.hide(); + const apiClient = result.ApiClient; + + switch (result.State) { + case ConnectionState.SignedIn: + Dashboard.onServerChanged(apiClient.getCurrentUserId(), apiClient.accessToken(), apiClient); + Dashboard.navigate('home.html'); + break; + + case ConnectionState.ServerSignIn: + Dashboard.onServerChanged(null, null, apiClient); + Dashboard.navigate('login.html?serverid=' + result.Servers[0].Id); + break; + + case ConnectionState.ServerUpdateNeeded: + alertTextWithOptions({ + text: globalize.translate('core#ServerUpdateNeeded', 'https://github.com/jellyfin/jellyfin'), + html: globalize.translate('core#ServerUpdateNeeded', 'https://github.com/jellyfin/jellyfin') + }); + break; + + default: + showServerConnectionFailure(); } }); } -/* eslint-enable indent */ + function deleteServer(server) { + loading.show(); + ServerConnections.deleteServer(server.Id).then(function () { + loading.hide(); + loadServers(); + }); + } + + function onServerClick(server) { + const menuItems = []; + menuItems.push({ + name: globalize.translate('Connect'), + id: 'connect' + }); + menuItems.push({ + name: globalize.translate('Delete'), + id: 'delete' + }); + actionSheet.show({ + items: menuItems, + title: server.Name + }).then(function (id) { + switch (id) { + case 'connect': + connectToServer(server); + break; + + case 'delete': + deleteServer(server); + } + }); + } + + function onServersRetrieved(result) { + servers = result; + renderSelectServerItems(view, result); + + if (layoutManager.tv) { + focusManager.autoFocus(view); + } + } + + function loadServers() { + loading.show(); + ServerConnections.getAvailableServers().then(onServersRetrieved); + } + + let servers; + updatePageStyle(view, params); + view.addEventListener('viewshow', function (e) { + const isRestored = e.detail.isRestored; + libraryMenu.setTitle(null); + libraryMenu.setTransparentMenu(true); + + if (!isRestored) { + loadServers(); + } + }); + view.querySelector('.servers').addEventListener('click', function (e) { + const card = dom.parentWithClass(e.target, 'card'); + + if (card) { + const url = card.getAttribute('data-url'); + + if (url) { + appRouter.show(url); + } else { + const id = card.getAttribute('data-id'); + onServerClick(servers.filter(function (s) { + return s.Id === id; + })[0]); + } + } + }); +} + diff --git a/src/controllers/shows/episodes.js b/src/controllers/shows/episodes.js index 2cbe4117f6..38b7d077c3 100644 --- a/src/controllers/shows/episodes.js +++ b/src/controllers/shows/episodes.js @@ -10,243 +10,240 @@ import Events from '../../utils/events.ts'; import '../../elements/emby-itemscontainer/emby-itemscontainer'; -/* eslint-disable indent */ +export default function (view, params, tabContent) { + function getPageData(context) { + const key = getSavedQueryKey(context); + let pageData = data[key]; - export default function (view, params, tabContent) { - function getPageData(context) { - const key = getSavedQueryKey(context); - let pageData = data[key]; + if (!pageData) { + pageData = data[key] = { + query: { + SortBy: 'SeriesSortName,SortName', + SortOrder: 'Ascending', + IncludeItemTypes: 'Episode', + Recursive: true, + Fields: 'PrimaryImageAspectRatio,MediaSourceCount', + IsMissing: false, + ImageTypeLimit: 1, + EnableImageTypes: 'Primary,Backdrop,Thumb', + StartIndex: 0 + }, + view: libraryBrowser.getSavedView(key) || 'Poster' + }; - if (!pageData) { - pageData = data[key] = { - query: { - SortBy: 'SeriesSortName,SortName', - SortOrder: 'Ascending', - IncludeItemTypes: 'Episode', - Recursive: true, - Fields: 'PrimaryImageAspectRatio,MediaSourceCount', - IsMissing: false, - ImageTypeLimit: 1, - EnableImageTypes: 'Primary,Backdrop,Thumb', - StartIndex: 0 - }, - view: libraryBrowser.getSavedView(key) || 'Poster' - }; - - if (userSettings.libraryPageSize() > 0) { - pageData.query['Limit'] = userSettings.libraryPageSize(); - } - - pageData.query.ParentId = params.topParentId; - libraryBrowser.loadSavedQueryValues(key, pageData.query); + if (userSettings.libraryPageSize() > 0) { + pageData.query['Limit'] = userSettings.libraryPageSize(); } - return pageData; + pageData.query.ParentId = params.topParentId; + libraryBrowser.loadSavedQueryValues(key, pageData.query); } - function getQuery(context) { - return getPageData(context).query; - } - - function getSavedQueryKey(context) { - if (!context.savedQueryKey) { - context.savedQueryKey = libraryBrowser.getSavedQueryKey('episodes'); - } - - return context.savedQueryKey; - } - - function onViewStyleChange() { - const viewStyle = self.getCurrentViewStyle(); - const itemsContainer = tabContent.querySelector('.itemsContainer'); - - if (viewStyle == 'List') { - itemsContainer.classList.add('vertical-list'); - itemsContainer.classList.remove('vertical-wrap'); - } else { - itemsContainer.classList.remove('vertical-list'); - itemsContainer.classList.add('vertical-wrap'); - } - - itemsContainer.innerHTML = ''; - } - - function reloadItems(page) { - loading.show(); - isLoading = true; - const query = getQuery(page); - ApiClient.getItems(Dashboard.getCurrentUserId(), query).then(function (result) { - function onNextPageClick() { - if (isLoading) { - return; - } - - if (userSettings.libraryPageSize() > 0) { - query.StartIndex += query.Limit; - } - reloadItems(tabContent); - } - - function onPreviousPageClick() { - if (isLoading) { - return; - } - - if (userSettings.libraryPageSize() > 0) { - query.StartIndex = Math.max(0, query.StartIndex - query.Limit); - } - reloadItems(tabContent); - } - - window.scrollTo(0, 0); - let html; - const pagingHtml = libraryBrowser.getQueryPagingHtml({ - startIndex: query.StartIndex, - limit: query.Limit, - totalRecordCount: result.TotalRecordCount, - showLimit: false, - updatePageSizeSetting: false, - addLayoutButton: false, - sortButton: false, - filterButton: false - }); - const viewStyle = self.getCurrentViewStyle(); - const itemsContainer = tabContent.querySelector('.itemsContainer'); - if (viewStyle == 'List') { - html = listView.getListViewHtml({ - items: result.Items, - sortBy: query.SortBy, - showParentTitle: true - }); - } else if (viewStyle == 'PosterCard') { - html = cardBuilder.getCardsHtml({ - items: result.Items, - shape: 'backdrop', - showTitle: true, - showParentTitle: true, - scalable: true, - cardLayout: true - }); - } else { - html = cardBuilder.getCardsHtml({ - items: result.Items, - shape: 'backdrop', - showTitle: true, - showParentTitle: true, - overlayText: false, - centerText: true, - scalable: true, - overlayPlayButton: true - }); - } - let elems; - - elems = tabContent.querySelectorAll('.paging'); - for (let i = 0, length = elems.length; i < length; i++) { - elems[i].innerHTML = pagingHtml; - } - - elems = tabContent.querySelectorAll('.btnNextPage'); - for (let i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener('click', onNextPageClick); - } - - elems = tabContent.querySelectorAll('.btnPreviousPage'); - for (let i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener('click', onPreviousPageClick); - } - - itemsContainer.innerHTML = html; - imageLoader.lazyChildren(itemsContainer); - libraryBrowser.saveQueryValues(getSavedQueryKey(page), query); - loading.hide(); - isLoading = false; - - import('../../components/autoFocuser').then(({ default: autoFocuser }) => { - autoFocuser.autoFocus(page); - }); - }); - } - - const self = this; - const data = {}; - let isLoading = false; - - self.showFilterMenu = function () { - import('../../components/filterdialog/filterdialog').then(({ default: filterDialogFactory }) => { - const filterDialog = new filterDialogFactory({ - query: getQuery(tabContent), - mode: 'episodes', - serverId: ApiClient.serverId() - }); - Events.on(filterDialog, 'filterchange', function () { - reloadItems(tabContent); - }); - filterDialog.show(); - }); - }; - - self.getCurrentViewStyle = function () { - return getPageData(tabContent).view; - }; - - function initPage(tabElement) { - tabElement.querySelector('.btnFilter').addEventListener('click', function () { - self.showFilterMenu(); - }); - tabElement.querySelector('.btnSort').addEventListener('click', function (e) { - libraryBrowser.showSortMenu({ - items: [{ - name: globalize.translate('Name'), - id: 'SeriesSortName,SortName' - }, { - name: globalize.translate('OptionTvdbRating'), - id: 'CommunityRating,SeriesSortName,SortName' - }, { - name: globalize.translate('OptionDateAdded'), - id: 'DateCreated,SeriesSortName,SortName' - }, { - name: globalize.translate('OptionPremiereDate'), - id: 'PremiereDate,SeriesSortName,SortName' - }, { - name: globalize.translate('OptionDatePlayed'), - id: 'DatePlayed,SeriesSortName,SortName' - }, { - name: globalize.translate('OptionParentalRating'), - id: 'OfficialRating,SeriesSortName,SortName' - }, { - name: globalize.translate('OptionPlayCount'), - id: 'PlayCount,SeriesSortName,SortName' - }, { - name: globalize.translate('Runtime'), - id: 'Runtime,SeriesSortName,SortName' - }], - callback: function () { - reloadItems(tabElement); - }, - query: getQuery(tabElement), - button: e.target - }); - }); - const btnSelectView = tabElement.querySelector('.btnSelectView'); - btnSelectView.addEventListener('click', function (e) { - libraryBrowser.showLayoutMenu(e.target, self.getCurrentViewStyle(), 'List,Poster,PosterCard'.split(',')); - }); - btnSelectView.addEventListener('layoutchange', function (e) { - const viewStyle = e.detail.viewStyle; - getPageData(tabElement).view = viewStyle; - libraryBrowser.saveViewSetting(getSavedQueryKey(tabElement), viewStyle); - onViewStyleChange(); - reloadItems(tabElement); - }); - } - - initPage(tabContent); - onViewStyleChange(); - - self.renderTab = function () { - reloadItems(tabContent); - }; + return pageData; } -/* eslint-enable indent */ + function getQuery(context) { + return getPageData(context).query; + } + + function getSavedQueryKey(context) { + if (!context.savedQueryKey) { + context.savedQueryKey = libraryBrowser.getSavedQueryKey('episodes'); + } + + return context.savedQueryKey; + } + + function onViewStyleChange() { + const viewStyle = self.getCurrentViewStyle(); + const itemsContainer = tabContent.querySelector('.itemsContainer'); + + if (viewStyle == 'List') { + itemsContainer.classList.add('vertical-list'); + itemsContainer.classList.remove('vertical-wrap'); + } else { + itemsContainer.classList.remove('vertical-list'); + itemsContainer.classList.add('vertical-wrap'); + } + + itemsContainer.innerHTML = ''; + } + + function reloadItems(page) { + loading.show(); + isLoading = true; + const query = getQuery(page); + ApiClient.getItems(Dashboard.getCurrentUserId(), query).then(function (result) { + function onNextPageClick() { + if (isLoading) { + return; + } + + if (userSettings.libraryPageSize() > 0) { + query.StartIndex += query.Limit; + } + reloadItems(tabContent); + } + + function onPreviousPageClick() { + if (isLoading) { + return; + } + + if (userSettings.libraryPageSize() > 0) { + query.StartIndex = Math.max(0, query.StartIndex - query.Limit); + } + reloadItems(tabContent); + } + + window.scrollTo(0, 0); + let html; + const pagingHtml = libraryBrowser.getQueryPagingHtml({ + startIndex: query.StartIndex, + limit: query.Limit, + totalRecordCount: result.TotalRecordCount, + showLimit: false, + updatePageSizeSetting: false, + addLayoutButton: false, + sortButton: false, + filterButton: false + }); + const viewStyle = self.getCurrentViewStyle(); + const itemsContainer = tabContent.querySelector('.itemsContainer'); + if (viewStyle == 'List') { + html = listView.getListViewHtml({ + items: result.Items, + sortBy: query.SortBy, + showParentTitle: true + }); + } else if (viewStyle == 'PosterCard') { + html = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'backdrop', + showTitle: true, + showParentTitle: true, + scalable: true, + cardLayout: true + }); + } else { + html = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'backdrop', + showTitle: true, + showParentTitle: true, + overlayText: false, + centerText: true, + scalable: true, + overlayPlayButton: true + }); + } + let elems; + + elems = tabContent.querySelectorAll('.paging'); + for (let i = 0, length = elems.length; i < length; i++) { + elems[i].innerHTML = pagingHtml; + } + + elems = tabContent.querySelectorAll('.btnNextPage'); + for (let i = 0, length = elems.length; i < length; i++) { + elems[i].addEventListener('click', onNextPageClick); + } + + elems = tabContent.querySelectorAll('.btnPreviousPage'); + for (let i = 0, length = elems.length; i < length; i++) { + elems[i].addEventListener('click', onPreviousPageClick); + } + + itemsContainer.innerHTML = html; + imageLoader.lazyChildren(itemsContainer); + libraryBrowser.saveQueryValues(getSavedQueryKey(page), query); + loading.hide(); + isLoading = false; + + import('../../components/autoFocuser').then(({ default: autoFocuser }) => { + autoFocuser.autoFocus(page); + }); + }); + } + + const self = this; + const data = {}; + let isLoading = false; + + self.showFilterMenu = function () { + import('../../components/filterdialog/filterdialog').then(({ default: filterDialogFactory }) => { + const filterDialog = new filterDialogFactory({ + query: getQuery(tabContent), + mode: 'episodes', + serverId: ApiClient.serverId() + }); + Events.on(filterDialog, 'filterchange', function () { + reloadItems(tabContent); + }); + filterDialog.show(); + }); + }; + + self.getCurrentViewStyle = function () { + return getPageData(tabContent).view; + }; + + function initPage(tabElement) { + tabElement.querySelector('.btnFilter').addEventListener('click', function () { + self.showFilterMenu(); + }); + tabElement.querySelector('.btnSort').addEventListener('click', function (e) { + libraryBrowser.showSortMenu({ + items: [{ + name: globalize.translate('Name'), + id: 'SeriesSortName,SortName' + }, { + name: globalize.translate('OptionTvdbRating'), + id: 'CommunityRating,SeriesSortName,SortName' + }, { + name: globalize.translate('OptionDateAdded'), + id: 'DateCreated,SeriesSortName,SortName' + }, { + name: globalize.translate('OptionPremiereDate'), + id: 'PremiereDate,SeriesSortName,SortName' + }, { + name: globalize.translate('OptionDatePlayed'), + id: 'DatePlayed,SeriesSortName,SortName' + }, { + name: globalize.translate('OptionParentalRating'), + id: 'OfficialRating,SeriesSortName,SortName' + }, { + name: globalize.translate('OptionPlayCount'), + id: 'PlayCount,SeriesSortName,SortName' + }, { + name: globalize.translate('Runtime'), + id: 'Runtime,SeriesSortName,SortName' + }], + callback: function () { + reloadItems(tabElement); + }, + query: getQuery(tabElement), + button: e.target + }); + }); + const btnSelectView = tabElement.querySelector('.btnSelectView'); + btnSelectView.addEventListener('click', function (e) { + libraryBrowser.showLayoutMenu(e.target, self.getCurrentViewStyle(), 'List,Poster,PosterCard'.split(',')); + }); + btnSelectView.addEventListener('layoutchange', function (e) { + const viewStyle = e.detail.viewStyle; + getPageData(tabElement).view = viewStyle; + libraryBrowser.saveViewSetting(getSavedQueryKey(tabElement), viewStyle); + onViewStyleChange(); + reloadItems(tabElement); + }); + } + + initPage(tabContent); + onViewStyleChange(); + + self.renderTab = function () { + reloadItems(tabContent); + }; +} + diff --git a/src/controllers/shows/tvgenres.js b/src/controllers/shows/tvgenres.js index 7db974744b..bc27ad4759 100644 --- a/src/controllers/shows/tvgenres.js +++ b/src/controllers/shows/tvgenres.js @@ -8,213 +8,210 @@ import globalize from '../../scripts/globalize'; import { appRouter } from '../../components/appRouter'; import '../../elements/emby-button/emby-button'; -/* eslint-disable indent */ +export default function (view, params, tabContent) { + function getPageData() { + const key = getSavedQueryKey(); + let pageData = data[key]; - export default function (view, params, tabContent) { - function getPageData() { - const key = getSavedQueryKey(); - let pageData = data[key]; - - if (!pageData) { - pageData = data[key] = { - query: { - SortBy: 'SortName', - SortOrder: 'Ascending', - IncludeItemTypes: 'Series', - Recursive: true, - EnableTotalRecordCount: false - }, - view: 'Poster' - }; - pageData.query.ParentId = params.topParentId; - libraryBrowser.loadSavedQueryValues(key, pageData.query); - } - - return pageData; - } - - function getQuery() { - return getPageData().query; - } - - function getSavedQueryKey() { - return libraryBrowser.getSavedQueryKey('seriesgenres'); - } - - function getPromise() { - loading.show(); - const query = getQuery(); - return ApiClient.getGenres(ApiClient.getCurrentUserId(), query); - } - - function enableScrollX() { - return !layoutManager.desktop; - } - - function getThumbShape() { - return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; - } - - function getPortraitShape() { - return enableScrollX() ? 'overflowPortrait' : 'portrait'; - } - - function fillItemsContainer(entry) { - const elem = entry.target; - const id = elem.getAttribute('data-id'); - const viewStyle = self.getCurrentViewStyle(); - let limit = viewStyle == 'Thumb' || viewStyle == 'ThumbCard' ? 5 : 9; - - if (enableScrollX()) { - limit = 10; - } - - const enableImageTypes = viewStyle == 'Thumb' || viewStyle == 'ThumbCard' ? 'Primary,Backdrop,Thumb' : 'Primary'; - const query = { - SortBy: 'Random', - SortOrder: 'Ascending', - IncludeItemTypes: 'Series', - Recursive: true, - Fields: 'PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo', - ImageTypeLimit: 1, - EnableImageTypes: enableImageTypes, - Limit: limit, - GenreIds: id, - EnableTotalRecordCount: false, - ParentId: params.topParentId + if (!pageData) { + pageData = data[key] = { + query: { + SortBy: 'SortName', + SortOrder: 'Ascending', + IncludeItemTypes: 'Series', + Recursive: true, + EnableTotalRecordCount: false + }, + view: 'Poster' }; - ApiClient.getItems(ApiClient.getCurrentUserId(), query).then(function (result) { - if (viewStyle == 'Thumb') { - cardBuilder.buildCards(result.Items, { - itemsContainer: elem, - shape: getThumbShape(), - preferThumb: true, - showTitle: true, - scalable: true, - centerText: true, - overlayMoreButton: true, - allowBottomPadding: false - }); - } else if (viewStyle == 'ThumbCard') { - cardBuilder.buildCards(result.Items, { - itemsContainer: elem, - shape: getThumbShape(), - preferThumb: true, - showTitle: true, - scalable: true, - centerText: false, - cardLayout: true, - showYear: true - }); - } else if (viewStyle == 'PosterCard') { - cardBuilder.buildCards(result.Items, { - itemsContainer: elem, - shape: getPortraitShape(), - showTitle: true, - scalable: true, - centerText: false, - cardLayout: true, - showYear: true - }); - } else if (viewStyle == 'Poster') { - cardBuilder.buildCards(result.Items, { - itemsContainer: elem, - shape: getPortraitShape(), - scalable: true, - showTitle: true, - centerText: true, - showYear: true, - overlayMoreButton: true, - allowBottomPadding: false - }); - } - if (result.Items.length >= query.Limit) { - tabContent.querySelector('.btnMoreFromGenre' + id + ' .material-icons').classList.remove('hide'); - } - }); + pageData.query.ParentId = params.topParentId; + libraryBrowser.loadSavedQueryValues(key, pageData.query); } - function reloadItems(context, promise) { - const query = getQuery(); - promise.then(function (result) { - const elem = context.querySelector('#items'); - let html = ''; - const items = result.Items; - - for (const item of items) { - html += '
'; - html += ''; - if (enableScrollX()) { - let scrollXClass = 'scrollX hiddenScrollX'; - if (layoutManager.tv) { - scrollXClass += 'smoothScrollX padded-top-focusscale padded-bottom-focusscale'; - } - html += '
'; - } else { - html += '
'; - } - html += '
'; - html += '
'; - } - - if (!result.Items.length) { - html = ''; - - html += '
'; - html += '

' + globalize.translate('MessageNothingHere') + '

'; - html += '

' + globalize.translate('MessageNoGenresAvailable') + '

'; - html += '
'; - } - - elem.innerHTML = html; - lazyLoader.lazyChildren(elem, fillItemsContainer); - libraryBrowser.saveQueryValues(getSavedQueryKey(), query); - loading.hide(); - }); - } - - function fullyReload() { - self.preRender(); - self.renderTab(); - } - - const self = this; - const data = {}; - - self.getViewStyles = function () { - return 'Poster,PosterCard,Thumb,ThumbCard'.split(','); - }; - - self.getCurrentViewStyle = function () { - return getPageData().view; - }; - - self.setCurrentViewStyle = function (viewStyle) { - getPageData().view = viewStyle; - libraryBrowser.saveViewSetting(getSavedQueryKey(), viewStyle); - fullyReload(); - }; - - self.enableViewSelection = true; - let promise; - - self.preRender = function () { - promise = getPromise(); - }; - - self.renderTab = function () { - reloadItems(tabContent, promise); - }; + return pageData; } -/* eslint-enable indent */ + function getQuery() { + return getPageData().query; + } + + function getSavedQueryKey() { + return libraryBrowser.getSavedQueryKey('seriesgenres'); + } + + function getPromise() { + loading.show(); + const query = getQuery(); + return ApiClient.getGenres(ApiClient.getCurrentUserId(), query); + } + + function enableScrollX() { + return !layoutManager.desktop; + } + + function getThumbShape() { + return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; + } + + function getPortraitShape() { + return enableScrollX() ? 'overflowPortrait' : 'portrait'; + } + + function fillItemsContainer(entry) { + const elem = entry.target; + const id = elem.getAttribute('data-id'); + const viewStyle = self.getCurrentViewStyle(); + let limit = viewStyle == 'Thumb' || viewStyle == 'ThumbCard' ? 5 : 9; + + if (enableScrollX()) { + limit = 10; + } + + const enableImageTypes = viewStyle == 'Thumb' || viewStyle == 'ThumbCard' ? 'Primary,Backdrop,Thumb' : 'Primary'; + const query = { + SortBy: 'Random', + SortOrder: 'Ascending', + IncludeItemTypes: 'Series', + Recursive: true, + Fields: 'PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo', + ImageTypeLimit: 1, + EnableImageTypes: enableImageTypes, + Limit: limit, + GenreIds: id, + EnableTotalRecordCount: false, + ParentId: params.topParentId + }; + ApiClient.getItems(ApiClient.getCurrentUserId(), query).then(function (result) { + if (viewStyle == 'Thumb') { + cardBuilder.buildCards(result.Items, { + itemsContainer: elem, + shape: getThumbShape(), + preferThumb: true, + showTitle: true, + scalable: true, + centerText: true, + overlayMoreButton: true, + allowBottomPadding: false + }); + } else if (viewStyle == 'ThumbCard') { + cardBuilder.buildCards(result.Items, { + itemsContainer: elem, + shape: getThumbShape(), + preferThumb: true, + showTitle: true, + scalable: true, + centerText: false, + cardLayout: true, + showYear: true + }); + } else if (viewStyle == 'PosterCard') { + cardBuilder.buildCards(result.Items, { + itemsContainer: elem, + shape: getPortraitShape(), + showTitle: true, + scalable: true, + centerText: false, + cardLayout: true, + showYear: true + }); + } else if (viewStyle == 'Poster') { + cardBuilder.buildCards(result.Items, { + itemsContainer: elem, + shape: getPortraitShape(), + scalable: true, + showTitle: true, + centerText: true, + showYear: true, + overlayMoreButton: true, + allowBottomPadding: false + }); + } + if (result.Items.length >= query.Limit) { + tabContent.querySelector('.btnMoreFromGenre' + id + ' .material-icons').classList.remove('hide'); + } + }); + } + + function reloadItems(context, promise) { + const query = getQuery(); + promise.then(function (result) { + const elem = context.querySelector('#items'); + let html = ''; + const items = result.Items; + + for (const item of items) { + html += '
'; + html += ''; + if (enableScrollX()) { + let scrollXClass = 'scrollX hiddenScrollX'; + if (layoutManager.tv) { + scrollXClass += 'smoothScrollX padded-top-focusscale padded-bottom-focusscale'; + } + html += '
'; + } else { + html += '
'; + } + html += '
'; + html += '
'; + } + + if (!result.Items.length) { + html = ''; + + html += '
'; + html += '

' + globalize.translate('MessageNothingHere') + '

'; + html += '

' + globalize.translate('MessageNoGenresAvailable') + '

'; + html += '
'; + } + + elem.innerHTML = html; + lazyLoader.lazyChildren(elem, fillItemsContainer); + libraryBrowser.saveQueryValues(getSavedQueryKey(), query); + loading.hide(); + }); + } + + function fullyReload() { + self.preRender(); + self.renderTab(); + } + + const self = this; + const data = {}; + + self.getViewStyles = function () { + return 'Poster,PosterCard,Thumb,ThumbCard'.split(','); + }; + + self.getCurrentViewStyle = function () { + return getPageData().view; + }; + + self.setCurrentViewStyle = function (viewStyle) { + getPageData().view = viewStyle; + libraryBrowser.saveViewSetting(getSavedQueryKey(), viewStyle); + fullyReload(); + }; + + self.enableViewSelection = true; + let promise; + + self.preRender = function () { + promise = getPromise(); + }; + + self.renderTab = function () { + reloadItems(tabContent, promise); + }; +} + diff --git a/src/controllers/shows/tvrecommended.js b/src/controllers/shows/tvrecommended.js index 0066cf48a2..944a75260c 100644 --- a/src/controllers/shows/tvrecommended.js +++ b/src/controllers/shows/tvrecommended.js @@ -16,377 +16,374 @@ import Dashboard from '../../utils/dashboard'; import Events from '../../utils/events.ts'; import autoFocuser from '../../components/autoFocuser'; -/* eslint-disable indent */ +function getTabs() { + return [{ + name: globalize.translate('Shows') + }, { + name: globalize.translate('Suggestions') + }, { + name: globalize.translate('TabUpcoming') + }, { + name: globalize.translate('Genres') + }, { + name: globalize.translate('TabNetworks') + }, { + name: globalize.translate('Episodes') + }]; +} - function getTabs() { - return [{ - name: globalize.translate('Shows') - }, { - name: globalize.translate('Suggestions') - }, { - name: globalize.translate('TabUpcoming') - }, { - name: globalize.translate('Genres') - }, { - name: globalize.translate('TabNetworks') - }, { - name: globalize.translate('Episodes') - }]; +function getDefaultTabIndex(folderId) { + switch (userSettings.get('landing-' + folderId)) { + case 'suggestions': + return 1; + + case 'upcoming': + return 2; + + case 'genres': + return 3; + + case 'networks': + return 4; + + case 'episodes': + return 5; + + default: + return 0; } +} - function getDefaultTabIndex(folderId) { - switch (userSettings.get('landing-' + folderId)) { - case 'suggestions': - return 1; +function setScrollClasses(elem, scrollX) { + if (scrollX) { + elem.classList.add('hiddenScrollX'); - case 'upcoming': - return 2; - - case 'genres': - return 3; - - case 'networks': - return 4; - - case 'episodes': - return 5; - - default: - return 0; + if (layoutManager.tv) { + elem.classList.add('smoothScrollX'); } + + elem.classList.add('scrollX'); + elem.classList.remove('vertical-wrap'); + } else { + elem.classList.remove('hiddenScrollX'); + elem.classList.remove('smoothScrollX'); + elem.classList.remove('scrollX'); + elem.classList.add('vertical-wrap'); } +} - function setScrollClasses(elem, scrollX) { - if (scrollX) { - elem.classList.add('hiddenScrollX'); +function initSuggestedTab(page, tabContent) { + const containers = tabContent.querySelectorAll('.itemsContainer'); - if (layoutManager.tv) { - elem.classList.add('smoothScrollX'); - } + for (let i = 0, length = containers.length; i < length; i++) { + setScrollClasses(containers[i], enableScrollX()); + } +} - elem.classList.add('scrollX'); - elem.classList.remove('vertical-wrap'); +function loadSuggestionsTab(view, params, tabContent) { + const parentId = params.topParentId; + const userId = ApiClient.getCurrentUserId(); + console.debug('loadSuggestionsTab'); + loadResume(tabContent, userId, parentId); + loadLatest(tabContent, userId, parentId); + loadNextUp(tabContent, userId, parentId); +} + +function loadResume(view, userId, parentId) { + const screenWidth = dom.getWindowSize().innerWidth; + const options = { + SortBy: 'DatePlayed', + SortOrder: 'Descending', + IncludeItemTypes: 'Episode', + Filters: 'IsResumable', + Limit: screenWidth >= 1600 ? 5 : 3, + Recursive: true, + Fields: 'PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo', + CollapseBoxSetItems: false, + ParentId: parentId, + ImageTypeLimit: 1, + EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', + EnableTotalRecordCount: false + }; + ApiClient.getItems(userId, options).then(function (result) { + if (result.Items.length) { + view.querySelector('#resumableSection').classList.remove('hide'); } else { - elem.classList.remove('hiddenScrollX'); - elem.classList.remove('smoothScrollX'); - elem.classList.remove('scrollX'); - elem.classList.add('vertical-wrap'); + view.querySelector('#resumableSection').classList.add('hide'); } - } - function initSuggestedTab(page, tabContent) { - const containers = tabContent.querySelectorAll('.itemsContainer'); + const allowBottomPadding = !enableScrollX(); + const container = view.querySelector('#resumableItems'); + cardBuilder.buildCards(result.Items, { + itemsContainer: container, + preferThumb: true, + inheritThumb: !userSettings.useEpisodeImagesInNextUpAndResume(), + shape: getThumbShape(), + scalable: true, + overlayPlayButton: true, + allowBottomPadding: allowBottomPadding, + cardLayout: false, + showTitle: true, + showYear: true, + centerText: true + }); + loading.hide(); - for (let i = 0, length = containers.length; i < length; i++) { - setScrollClasses(containers[i], enableScrollX()); + autoFocuser.autoFocus(view); + }); +} + +function loadLatest(view, userId, parentId) { + const options = { + userId: userId, + IncludeItemTypes: 'Episode', + Limit: 30, + Fields: 'PrimaryImageAspectRatio,BasicSyncInfo', + ParentId: parentId, + ImageTypeLimit: 1, + EnableImageTypes: 'Primary,Backdrop,Thumb' + }; + ApiClient.getLatestItems(options).then(function (items) { + const section = view.querySelector('#latestItemsSection'); + const allowBottomPadding = !enableScrollX(); + const container = section.querySelector('#latestEpisodesItems'); + cardBuilder.buildCards(items, { + parentContainer: section, + itemsContainer: container, + items: items, + shape: 'backdrop', + preferThumb: true, + showTitle: true, + showSeriesYear: true, + showParentTitle: true, + overlayText: false, + cardLayout: false, + allowBottomPadding: allowBottomPadding, + showUnplayedIndicator: false, + showChildCountIndicator: true, + centerText: true, + lazy: true, + overlayPlayButton: true, + lines: 2 + }); + loading.hide(); + + autoFocuser.autoFocus(view); + }); +} + +function loadNextUp(view, userId, parentId) { + const query = { + userId: userId, + Limit: 24, + Fields: 'PrimaryImageAspectRatio,DateCreated,BasicSyncInfo,MediaSourceCount', + ParentId: parentId, + ImageTypeLimit: 1, + EnableImageTypes: 'Primary,Backdrop,Thumb', + EnableTotalRecordCount: false + }; + query.ParentId = libraryMenu.getTopParentId(); + ApiClient.getNextUpEpisodes(query).then(function (result) { + if (result.Items.length) { + view.querySelector('.noNextUpItems').classList.add('hide'); + } else { + view.querySelector('.noNextUpItems').classList.remove('hide'); } + + const section = view.querySelector('#nextUpItemsSection'); + const container = section.querySelector('#nextUpItems'); + cardBuilder.buildCards(result.Items, { + parentContainer: section, + itemsContainer: container, + preferThumb: true, + inheritThumb: !userSettings.useEpisodeImagesInNextUpAndResume(), + shape: 'backdrop', + scalable: true, + showTitle: true, + showParentTitle: true, + overlayText: false, + centerText: true, + overlayPlayButton: true, + cardLayout: false + }); + loading.hide(); + + autoFocuser.autoFocus(view); + }); +} + +function enableScrollX() { + return !layoutManager.desktop; +} + +function getThumbShape() { + return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; +} + +export default function (view, params) { + function onBeforeTabChange(e) { + preLoadTab(view, parseInt(e.detail.selectedTabIndex, 10)); } - function loadSuggestionsTab(view, params, tabContent) { - const parentId = params.topParentId; - const userId = ApiClient.getCurrentUserId(); - console.debug('loadSuggestionsTab'); - loadResume(tabContent, userId, parentId); - loadLatest(tabContent, userId, parentId); - loadNextUp(tabContent, userId, parentId); + function onTabChange(e) { + const newIndex = parseInt(e.detail.selectedTabIndex, 10); + loadTab(view, newIndex); } - function loadResume(view, userId, parentId) { - const screenWidth = dom.getWindowSize().innerWidth; - const options = { - SortBy: 'DatePlayed', - SortOrder: 'Descending', - IncludeItemTypes: 'Episode', - Filters: 'IsResumable', - Limit: screenWidth >= 1600 ? 5 : 3, - Recursive: true, - Fields: 'PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo', - CollapseBoxSetItems: false, - ParentId: parentId, - ImageTypeLimit: 1, - EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', - EnableTotalRecordCount: false - }; - ApiClient.getItems(userId, options).then(function (result) { - if (result.Items.length) { - view.querySelector('#resumableSection').classList.remove('hide'); - } else { - view.querySelector('#resumableSection').classList.add('hide'); + function getTabContainers() { + return view.querySelectorAll('.pageTabContent'); + } + + function initTabs() { + mainTabsManager.setTabs(view, currentTabIndex, getTabs, getTabContainers, onBeforeTabChange, onTabChange); + } + + function getTabController(page, index, callback) { + let depends; + + switch (index) { + case 0: + depends = 'tvshows'; + break; + + case 1: + depends = 'tvrecommended'; + break; + + case 2: + depends = 'tvupcoming'; + break; + + case 3: + depends = 'tvgenres'; + break; + + case 4: + depends = 'tvstudios'; + break; + + case 5: + depends = 'episodes'; + break; + } + + import(`../shows/${depends}`).then(({ default: controllerFactory }) => { + let tabContent; + + if (index === 1) { + tabContent = view.querySelector(".pageTabContent[data-index='" + index + "']"); + self.tabContent = tabContent; } - const allowBottomPadding = !enableScrollX(); - const container = view.querySelector('#resumableItems'); - cardBuilder.buildCards(result.Items, { - itemsContainer: container, - preferThumb: true, - inheritThumb: !userSettings.useEpisodeImagesInNextUpAndResume(), - shape: getThumbShape(), - scalable: true, - overlayPlayButton: true, - allowBottomPadding: allowBottomPadding, - cardLayout: false, - showTitle: true, - showYear: true, - centerText: true - }); - loading.hide(); + let controller = tabControllers[index]; - autoFocuser.autoFocus(view); - }); - } - - function loadLatest(view, userId, parentId) { - const options = { - userId: userId, - IncludeItemTypes: 'Episode', - Limit: 30, - Fields: 'PrimaryImageAspectRatio,BasicSyncInfo', - ParentId: parentId, - ImageTypeLimit: 1, - EnableImageTypes: 'Primary,Backdrop,Thumb' - }; - ApiClient.getLatestItems(options).then(function (items) { - const section = view.querySelector('#latestItemsSection'); - const allowBottomPadding = !enableScrollX(); - const container = section.querySelector('#latestEpisodesItems'); - cardBuilder.buildCards(items, { - parentContainer: section, - itemsContainer: container, - items: items, - shape: 'backdrop', - preferThumb: true, - showTitle: true, - showSeriesYear: true, - showParentTitle: true, - overlayText: false, - cardLayout: false, - allowBottomPadding: allowBottomPadding, - showUnplayedIndicator: false, - showChildCountIndicator: true, - centerText: true, - lazy: true, - overlayPlayButton: true, - lines: 2 - }); - loading.hide(); - - autoFocuser.autoFocus(view); - }); - } - - function loadNextUp(view, userId, parentId) { - const query = { - userId: userId, - Limit: 24, - Fields: 'PrimaryImageAspectRatio,DateCreated,BasicSyncInfo,MediaSourceCount', - ParentId: parentId, - ImageTypeLimit: 1, - EnableImageTypes: 'Primary,Backdrop,Thumb', - EnableTotalRecordCount: false - }; - query.ParentId = libraryMenu.getTopParentId(); - ApiClient.getNextUpEpisodes(query).then(function (result) { - if (result.Items.length) { - view.querySelector('.noNextUpItems').classList.add('hide'); - } else { - view.querySelector('.noNextUpItems').classList.remove('hide'); - } - - const section = view.querySelector('#nextUpItemsSection'); - const container = section.querySelector('#nextUpItems'); - cardBuilder.buildCards(result.Items, { - parentContainer: section, - itemsContainer: container, - preferThumb: true, - inheritThumb: !userSettings.useEpisodeImagesInNextUpAndResume(), - shape: 'backdrop', - scalable: true, - showTitle: true, - showParentTitle: true, - overlayText: false, - centerText: true, - overlayPlayButton: true, - cardLayout: false - }); - loading.hide(); - - autoFocuser.autoFocus(view); - }); - } - - function enableScrollX() { - return !layoutManager.desktop; - } - - function getThumbShape() { - return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; - } - - export default function (view, params) { - function onBeforeTabChange(e) { - preLoadTab(view, parseInt(e.detail.selectedTabIndex, 10)); - } - - function onTabChange(e) { - const newIndex = parseInt(e.detail.selectedTabIndex, 10); - loadTab(view, newIndex); - } - - function getTabContainers() { - return view.querySelectorAll('.pageTabContent'); - } - - function initTabs() { - mainTabsManager.setTabs(view, currentTabIndex, getTabs, getTabContainers, onBeforeTabChange, onTabChange); - } - - function getTabController(page, index, callback) { - let depends; - - switch (index) { - case 0: - depends = 'tvshows'; - break; - - case 1: - depends = 'tvrecommended'; - break; - - case 2: - depends = 'tvupcoming'; - break; - - case 3: - depends = 'tvgenres'; - break; - - case 4: - depends = 'tvstudios'; - break; - - case 5: - depends = 'episodes'; - break; - } - - import(`../shows/${depends}`).then(({ default: controllerFactory }) => { - let tabContent; + if (!controller) { + tabContent = view.querySelector(".pageTabContent[data-index='" + index + "']"); if (index === 1) { - tabContent = view.querySelector(".pageTabContent[data-index='" + index + "']"); - self.tabContent = tabContent; - } - - let controller = tabControllers[index]; - - if (!controller) { - tabContent = view.querySelector(".pageTabContent[data-index='" + index + "']"); - - if (index === 1) { - controller = self; - } else { - controller = new controllerFactory(view, params, tabContent); - } - - tabControllers[index] = controller; - - if (controller.initTab) { - controller.initTab(); - } - } - - callback(controller); - }); - } - - function preLoadTab(page, index) { - getTabController(page, index, function (controller) { - if (renderedTabs.indexOf(index) == -1 && controller.preRender) { - controller.preRender(); - } - }); - } - - function loadTab(page, index) { - currentTabIndex = index; - getTabController(page, index, function (controller) { - if (renderedTabs.indexOf(index) == -1) { - renderedTabs.push(index); - controller.renderTab(); - } - }); - } - - function onPlaybackStop(e, state) { - if (state.NowPlayingItem && state.NowPlayingItem.MediaType == 'Video') { - renderedTabs = []; - mainTabsManager.getTabsElement().triggerTabChange(); - } - } - - function onWebSocketMessage(e, data) { - const msg = data; - - if (msg.MessageType === 'UserDataChanged' && msg.Data.UserId == ApiClient.getCurrentUserId()) { - renderedTabs = []; - } - } - - function onInputCommand(e) { - if (e.detail.command === 'search') { - e.preventDefault(); - Dashboard.navigate('search.html?collectionType=tv&parentId=' + params.topParentId); - } - } - - const self = this; - let currentTabIndex = parseInt(params.tab || getDefaultTabIndex(params.topParentId), 10); - const suggestionsTabIndex = 1; - - self.initTab = function () { - const tabContent = view.querySelector(".pageTabContent[data-index='" + suggestionsTabIndex + "']"); - initSuggestedTab(view, tabContent); - }; - - self.renderTab = function () { - const tabContent = view.querySelector(".pageTabContent[data-index='" + suggestionsTabIndex + "']"); - loadSuggestionsTab(view, params, tabContent); - }; - - const tabControllers = []; - let renderedTabs = []; - view.addEventListener('viewshow', function () { - initTabs(); - if (!view.getAttribute('data-title')) { - const parentId = params.topParentId; - - if (parentId) { - ApiClient.getItem(ApiClient.getCurrentUserId(), parentId).then(function (item) { - view.setAttribute('data-title', item.Name); - libraryMenu.setTitle(item.Name); - }); + controller = self; } else { - view.setAttribute('data-title', globalize.translate('Shows')); - libraryMenu.setTitle(globalize.translate('Shows')); + controller = new controllerFactory(view, params, tabContent); + } + + tabControllers[index] = controller; + + if (controller.initTab) { + controller.initTab(); } } - Events.on(playbackManager, 'playbackstop', onPlaybackStop); - Events.on(ApiClient, 'message', onWebSocketMessage); - inputManager.on(window, onInputCommand); - }); - view.addEventListener('viewbeforehide', function () { - inputManager.off(window, onInputCommand); - Events.off(playbackManager, 'playbackstop', onPlaybackStop); - Events.off(ApiClient, 'message', onWebSocketMessage); - }); - view.addEventListener('viewdestroy', function () { - tabControllers.forEach(function (t) { - if (t.destroy) { - t.destroy(); - } - }); + callback(controller); }); } -/* eslint-enable indent */ + function preLoadTab(page, index) { + getTabController(page, index, function (controller) { + if (renderedTabs.indexOf(index) == -1 && controller.preRender) { + controller.preRender(); + } + }); + } + + function loadTab(page, index) { + currentTabIndex = index; + getTabController(page, index, function (controller) { + if (renderedTabs.indexOf(index) == -1) { + renderedTabs.push(index); + controller.renderTab(); + } + }); + } + + function onPlaybackStop(e, state) { + if (state.NowPlayingItem && state.NowPlayingItem.MediaType == 'Video') { + renderedTabs = []; + mainTabsManager.getTabsElement().triggerTabChange(); + } + } + + function onWebSocketMessage(e, data) { + const msg = data; + + if (msg.MessageType === 'UserDataChanged' && msg.Data.UserId == ApiClient.getCurrentUserId()) { + renderedTabs = []; + } + } + + function onInputCommand(e) { + if (e.detail.command === 'search') { + e.preventDefault(); + Dashboard.navigate('search.html?collectionType=tv&parentId=' + params.topParentId); + } + } + + const self = this; + let currentTabIndex = parseInt(params.tab || getDefaultTabIndex(params.topParentId), 10); + const suggestionsTabIndex = 1; + + self.initTab = function () { + const tabContent = view.querySelector(".pageTabContent[data-index='" + suggestionsTabIndex + "']"); + initSuggestedTab(view, tabContent); + }; + + self.renderTab = function () { + const tabContent = view.querySelector(".pageTabContent[data-index='" + suggestionsTabIndex + "']"); + loadSuggestionsTab(view, params, tabContent); + }; + + const tabControllers = []; + let renderedTabs = []; + view.addEventListener('viewshow', function () { + initTabs(); + if (!view.getAttribute('data-title')) { + const parentId = params.topParentId; + + if (parentId) { + ApiClient.getItem(ApiClient.getCurrentUserId(), parentId).then(function (item) { + view.setAttribute('data-title', item.Name); + libraryMenu.setTitle(item.Name); + }); + } else { + view.setAttribute('data-title', globalize.translate('Shows')); + libraryMenu.setTitle(globalize.translate('Shows')); + } + } + + Events.on(playbackManager, 'playbackstop', onPlaybackStop); + Events.on(ApiClient, 'message', onWebSocketMessage); + inputManager.on(window, onInputCommand); + }); + view.addEventListener('viewbeforehide', function () { + inputManager.off(window, onInputCommand); + Events.off(playbackManager, 'playbackstop', onPlaybackStop); + Events.off(ApiClient, 'message', onWebSocketMessage); + }); + view.addEventListener('viewdestroy', function () { + tabControllers.forEach(function (t) { + if (t.destroy) { + t.destroy(); + } + }); + }); +} + diff --git a/src/controllers/shows/tvshows.js b/src/controllers/shows/tvshows.js index 296746613e..d378cd34d5 100644 --- a/src/controllers/shows/tvshows.js +++ b/src/controllers/shows/tvshows.js @@ -10,300 +10,297 @@ import Events from '../../utils/events.ts'; import '../../elements/emby-itemscontainer/emby-itemscontainer'; -/* eslint-disable indent */ +export default function (view, params, tabContent) { + function getPageData(context) { + const key = getSavedQueryKey(context); + let pageData = data[key]; - export default function (view, params, tabContent) { - function getPageData(context) { - const key = getSavedQueryKey(context); - let pageData = data[key]; + if (!pageData) { + pageData = data[key] = { + query: { + SortBy: 'SortName', + SortOrder: 'Ascending', + IncludeItemTypes: 'Series', + Recursive: true, + Fields: 'PrimaryImageAspectRatio,BasicSyncInfo', + ImageTypeLimit: 1, + EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', + StartIndex: 0 + }, + view: libraryBrowser.getSavedView(key) || 'Poster' + }; - if (!pageData) { - pageData = data[key] = { - query: { - SortBy: 'SortName', - SortOrder: 'Ascending', - IncludeItemTypes: 'Series', - Recursive: true, - Fields: 'PrimaryImageAspectRatio,BasicSyncInfo', - ImageTypeLimit: 1, - EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', - StartIndex: 0 - }, - view: libraryBrowser.getSavedView(key) || 'Poster' - }; - - if (userSettings.libraryPageSize() > 0) { - pageData.query['Limit'] = userSettings.libraryPageSize(); - } - - pageData.query.ParentId = params.topParentId; - libraryBrowser.loadSavedQueryValues(key, pageData.query); + if (userSettings.libraryPageSize() > 0) { + pageData.query['Limit'] = userSettings.libraryPageSize(); } - return pageData; + pageData.query.ParentId = params.topParentId; + libraryBrowser.loadSavedQueryValues(key, pageData.query); } - function getQuery(context) { - return getPageData(context).query; - } - - function getSavedQueryKey(context) { - if (!context.savedQueryKey) { - context.savedQueryKey = libraryBrowser.getSavedQueryKey('series'); - } - - return context.savedQueryKey; - } - - const onViewStyleChange = () => { - const viewStyle = this.getCurrentViewStyle(); - const itemsContainer = tabContent.querySelector('.itemsContainer'); - - if (viewStyle == 'List') { - itemsContainer.classList.add('vertical-list'); - itemsContainer.classList.remove('vertical-wrap'); - } else { - itemsContainer.classList.remove('vertical-list'); - itemsContainer.classList.add('vertical-wrap'); - } - - itemsContainer.innerHTML = ''; - }; - - const reloadItems = (page) => { - loading.show(); - isLoading = true; - const query = getQuery(page); - ApiClient.getItems(ApiClient.getCurrentUserId(), query).then((result) => { - function onNextPageClick() { - if (isLoading) { - return; - } - - if (userSettings.libraryPageSize() > 0) { - query.StartIndex += query.Limit; - } - reloadItems(tabContent); - } - - function onPreviousPageClick() { - if (isLoading) { - return; - } - - if (userSettings.libraryPageSize() > 0) { - query.StartIndex = Math.max(0, query.StartIndex - query.Limit); - } - reloadItems(tabContent); - } - - window.scrollTo(0, 0); - this.alphaPicker?.updateControls(query); - let html; - const pagingHtml = libraryBrowser.getQueryPagingHtml({ - startIndex: query.StartIndex, - limit: query.Limit, - totalRecordCount: result.TotalRecordCount, - showLimit: false, - updatePageSizeSetting: false, - addLayoutButton: false, - sortButton: false, - filterButton: false - }); - const viewStyle = this.getCurrentViewStyle(); - if (viewStyle == 'Thumb') { - html = cardBuilder.getCardsHtml({ - items: result.Items, - shape: 'backdrop', - preferThumb: true, - context: 'tvshows', - overlayMoreButton: true, - showTitle: true, - centerText: true - }); - } else if (viewStyle == 'ThumbCard') { - html = cardBuilder.getCardsHtml({ - items: result.Items, - shape: 'backdrop', - preferThumb: true, - context: 'tvshows', - cardLayout: true, - showTitle: true, - showYear: true, - centerText: true - }); - } else if (viewStyle == 'Banner') { - html = cardBuilder.getCardsHtml({ - items: result.Items, - shape: 'banner', - preferBanner: true, - context: 'tvshows' - }); - } else if (viewStyle == 'List') { - html = listView.getListViewHtml({ - items: result.Items, - context: 'tvshows', - sortBy: query.SortBy - }); - } else if (viewStyle == 'PosterCard') { - html = cardBuilder.getCardsHtml({ - items: result.Items, - shape: 'portrait', - context: 'tvshows', - showTitle: true, - showYear: true, - centerText: true, - cardLayout: true - }); - } else { - html = cardBuilder.getCardsHtml({ - items: result.Items, - shape: 'portrait', - context: 'tvshows', - centerText: true, - lazy: true, - overlayMoreButton: true, - showTitle: true, - showYear: true - }); - } - - let elems = tabContent.querySelectorAll('.paging'); - - for (const elem of elems) { - elem.innerHTML = pagingHtml; - } - - elems = tabContent.querySelectorAll('.btnNextPage'); - for (const elem of elems) { - elem.addEventListener('click', onNextPageClick); - } - - elems = tabContent.querySelectorAll('.btnPreviousPage'); - for (const elem of elems) { - elem.addEventListener('click', onPreviousPageClick); - } - - const itemsContainer = tabContent.querySelector('.itemsContainer'); - itemsContainer.innerHTML = html; - imageLoader.lazyChildren(itemsContainer); - libraryBrowser.saveQueryValues(getSavedQueryKey(page), query); - loading.hide(); - isLoading = false; - - import('../../components/autoFocuser').then(({ default: autoFocuser }) => { - autoFocuser.autoFocus(page); - }); - }); - }; - - const data = {}; - let isLoading = false; - - this.showFilterMenu = function () { - import('../../components/filterdialog/filterdialog').then(({ default: filterDialogFactory }) => { - const filterDialog = new filterDialogFactory({ - query: getQuery(tabContent), - mode: 'series', - serverId: ApiClient.serverId() - }); - Events.on(filterDialog, 'filterchange', function () { - getQuery(tabContent).StartIndex = 0; - reloadItems(tabContent); - }); - filterDialog.show(); - }); - }; - - this.getCurrentViewStyle = function () { - return getPageData(tabContent).view; - }; - - const initPage = (tabElement) => { - const alphaPickerElement = tabElement.querySelector('.alphaPicker'); - const itemsContainer = tabElement.querySelector('.itemsContainer'); - - alphaPickerElement.addEventListener('alphavaluechanged', function (e) { - const newValue = e.detail.value; - const query = getQuery(tabElement); - if (newValue === '#') { - query.NameLessThan = 'A'; - delete query.NameStartsWith; - } else { - query.NameStartsWith = newValue; - delete query.NameLessThan; - } - query.StartIndex = 0; - reloadItems(tabElement); - }); - this.alphaPicker = new AlphaPicker({ - element: alphaPickerElement, - valueChangeEvent: 'click' - }); - - tabElement.querySelector('.alphaPicker').classList.add('alphabetPicker-right'); - alphaPickerElement.classList.add('alphaPicker-fixed-right'); - itemsContainer.classList.add('padded-right-withalphapicker'); - - tabElement.querySelector('.btnFilter').addEventListener('click', () => { - this.showFilterMenu(); - }); - tabElement.querySelector('.btnSort').addEventListener('click', function (e) { - libraryBrowser.showSortMenu({ - items: [{ - name: globalize.translate('Name'), - id: 'SortName' - }, { - name: globalize.translate('OptionRandom'), - id: 'Random' - }, { - name: globalize.translate('OptionImdbRating'), - id: 'CommunityRating,SortName' - }, { - name: globalize.translate('OptionDateShowAdded'), - id: 'DateCreated,SortName' - }, { - name: globalize.translate('OptionDateEpisodeAdded'), - id: 'DateLastContentAdded,SortName' - }, { - name: globalize.translate('OptionDatePlayed'), - id: 'SeriesDatePlayed,SortName' - }, { - name: globalize.translate('OptionParentalRating'), - id: 'OfficialRating,SortName' - }, { - name: globalize.translate('OptionReleaseDate'), - id: 'PremiereDate,SortName' - }], - callback: function () { - getQuery(tabElement).StartIndex = 0; - reloadItems(tabElement); - }, - query: getQuery(tabElement), - button: e.target - }); - }); - const btnSelectView = tabElement.querySelector('.btnSelectView'); - btnSelectView.addEventListener('click', (e) => { - libraryBrowser.showLayoutMenu(e.target, this.getCurrentViewStyle(), 'Banner,List,Poster,PosterCard,Thumb,ThumbCard'.split(',')); - }); - btnSelectView.addEventListener('layoutchange', function (e) { - const viewStyle = e.detail.viewStyle; - getPageData(tabElement).view = viewStyle; - libraryBrowser.saveViewSetting(getSavedQueryKey(tabElement), viewStyle); - getQuery(tabElement).StartIndex = 0; - onViewStyleChange(); - reloadItems(tabElement); - }); - }; - - initPage(tabContent); - onViewStyleChange(); - - this.renderTab = () => { - reloadItems(tabContent); - this.alphaPicker?.updateControls(getQuery(tabContent)); - }; + return pageData; } -/* eslint-enable indent */ + function getQuery(context) { + return getPageData(context).query; + } + + function getSavedQueryKey(context) { + if (!context.savedQueryKey) { + context.savedQueryKey = libraryBrowser.getSavedQueryKey('series'); + } + + return context.savedQueryKey; + } + + const onViewStyleChange = () => { + const viewStyle = this.getCurrentViewStyle(); + const itemsContainer = tabContent.querySelector('.itemsContainer'); + + if (viewStyle == 'List') { + itemsContainer.classList.add('vertical-list'); + itemsContainer.classList.remove('vertical-wrap'); + } else { + itemsContainer.classList.remove('vertical-list'); + itemsContainer.classList.add('vertical-wrap'); + } + + itemsContainer.innerHTML = ''; + }; + + const reloadItems = (page) => { + loading.show(); + isLoading = true; + const query = getQuery(page); + ApiClient.getItems(ApiClient.getCurrentUserId(), query).then((result) => { + function onNextPageClick() { + if (isLoading) { + return; + } + + if (userSettings.libraryPageSize() > 0) { + query.StartIndex += query.Limit; + } + reloadItems(tabContent); + } + + function onPreviousPageClick() { + if (isLoading) { + return; + } + + if (userSettings.libraryPageSize() > 0) { + query.StartIndex = Math.max(0, query.StartIndex - query.Limit); + } + reloadItems(tabContent); + } + + window.scrollTo(0, 0); + this.alphaPicker?.updateControls(query); + let html; + const pagingHtml = libraryBrowser.getQueryPagingHtml({ + startIndex: query.StartIndex, + limit: query.Limit, + totalRecordCount: result.TotalRecordCount, + showLimit: false, + updatePageSizeSetting: false, + addLayoutButton: false, + sortButton: false, + filterButton: false + }); + const viewStyle = this.getCurrentViewStyle(); + if (viewStyle == 'Thumb') { + html = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'backdrop', + preferThumb: true, + context: 'tvshows', + overlayMoreButton: true, + showTitle: true, + centerText: true + }); + } else if (viewStyle == 'ThumbCard') { + html = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'backdrop', + preferThumb: true, + context: 'tvshows', + cardLayout: true, + showTitle: true, + showYear: true, + centerText: true + }); + } else if (viewStyle == 'Banner') { + html = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'banner', + preferBanner: true, + context: 'tvshows' + }); + } else if (viewStyle == 'List') { + html = listView.getListViewHtml({ + items: result.Items, + context: 'tvshows', + sortBy: query.SortBy + }); + } else if (viewStyle == 'PosterCard') { + html = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'portrait', + context: 'tvshows', + showTitle: true, + showYear: true, + centerText: true, + cardLayout: true + }); + } else { + html = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'portrait', + context: 'tvshows', + centerText: true, + lazy: true, + overlayMoreButton: true, + showTitle: true, + showYear: true + }); + } + + let elems = tabContent.querySelectorAll('.paging'); + + for (const elem of elems) { + elem.innerHTML = pagingHtml; + } + + elems = tabContent.querySelectorAll('.btnNextPage'); + for (const elem of elems) { + elem.addEventListener('click', onNextPageClick); + } + + elems = tabContent.querySelectorAll('.btnPreviousPage'); + for (const elem of elems) { + elem.addEventListener('click', onPreviousPageClick); + } + + const itemsContainer = tabContent.querySelector('.itemsContainer'); + itemsContainer.innerHTML = html; + imageLoader.lazyChildren(itemsContainer); + libraryBrowser.saveQueryValues(getSavedQueryKey(page), query); + loading.hide(); + isLoading = false; + + import('../../components/autoFocuser').then(({ default: autoFocuser }) => { + autoFocuser.autoFocus(page); + }); + }); + }; + + const data = {}; + let isLoading = false; + + this.showFilterMenu = function () { + import('../../components/filterdialog/filterdialog').then(({ default: filterDialogFactory }) => { + const filterDialog = new filterDialogFactory({ + query: getQuery(tabContent), + mode: 'series', + serverId: ApiClient.serverId() + }); + Events.on(filterDialog, 'filterchange', function () { + getQuery(tabContent).StartIndex = 0; + reloadItems(tabContent); + }); + filterDialog.show(); + }); + }; + + this.getCurrentViewStyle = function () { + return getPageData(tabContent).view; + }; + + const initPage = (tabElement) => { + const alphaPickerElement = tabElement.querySelector('.alphaPicker'); + const itemsContainer = tabElement.querySelector('.itemsContainer'); + + alphaPickerElement.addEventListener('alphavaluechanged', function (e) { + const newValue = e.detail.value; + const query = getQuery(tabElement); + if (newValue === '#') { + query.NameLessThan = 'A'; + delete query.NameStartsWith; + } else { + query.NameStartsWith = newValue; + delete query.NameLessThan; + } + query.StartIndex = 0; + reloadItems(tabElement); + }); + this.alphaPicker = new AlphaPicker({ + element: alphaPickerElement, + valueChangeEvent: 'click' + }); + + tabElement.querySelector('.alphaPicker').classList.add('alphabetPicker-right'); + alphaPickerElement.classList.add('alphaPicker-fixed-right'); + itemsContainer.classList.add('padded-right-withalphapicker'); + + tabElement.querySelector('.btnFilter').addEventListener('click', () => { + this.showFilterMenu(); + }); + tabElement.querySelector('.btnSort').addEventListener('click', function (e) { + libraryBrowser.showSortMenu({ + items: [{ + name: globalize.translate('Name'), + id: 'SortName' + }, { + name: globalize.translate('OptionRandom'), + id: 'Random' + }, { + name: globalize.translate('OptionImdbRating'), + id: 'CommunityRating,SortName' + }, { + name: globalize.translate('OptionDateShowAdded'), + id: 'DateCreated,SortName' + }, { + name: globalize.translate('OptionDateEpisodeAdded'), + id: 'DateLastContentAdded,SortName' + }, { + name: globalize.translate('OptionDatePlayed'), + id: 'SeriesDatePlayed,SortName' + }, { + name: globalize.translate('OptionParentalRating'), + id: 'OfficialRating,SortName' + }, { + name: globalize.translate('OptionReleaseDate'), + id: 'PremiereDate,SortName' + }], + callback: function () { + getQuery(tabElement).StartIndex = 0; + reloadItems(tabElement); + }, + query: getQuery(tabElement), + button: e.target + }); + }); + const btnSelectView = tabElement.querySelector('.btnSelectView'); + btnSelectView.addEventListener('click', (e) => { + libraryBrowser.showLayoutMenu(e.target, this.getCurrentViewStyle(), 'Banner,List,Poster,PosterCard,Thumb,ThumbCard'.split(',')); + }); + btnSelectView.addEventListener('layoutchange', function (e) { + const viewStyle = e.detail.viewStyle; + getPageData(tabElement).view = viewStyle; + libraryBrowser.saveViewSetting(getSavedQueryKey(tabElement), viewStyle); + getQuery(tabElement).StartIndex = 0; + onViewStyleChange(); + reloadItems(tabElement); + }); + }; + + initPage(tabContent); + onViewStyleChange(); + + this.renderTab = () => { + reloadItems(tabContent); + this.alphaPicker?.updateControls(getQuery(tabContent)); + }; +} + diff --git a/src/controllers/shows/tvstudios.js b/src/controllers/shows/tvstudios.js index faa8bb90af..26ed743f73 100644 --- a/src/controllers/shows/tvstudios.js +++ b/src/controllers/shows/tvstudios.js @@ -2,73 +2,70 @@ import loading from '../../components/loading/loading'; import libraryBrowser from '../../scripts/libraryBrowser'; import cardBuilder from '../../components/cardbuilder/cardBuilder'; -/* eslint-disable indent */ +function getQuery(params) { + const key = getSavedQueryKey(); + let pageData = data[key]; - function getQuery(params) { - const key = getSavedQueryKey(); - let pageData = data[key]; - - if (!pageData) { - pageData = data[key] = { - query: { - SortBy: 'SortName', - SortOrder: 'Ascending', - IncludeItemTypes: 'Series', - Recursive: true, - Fields: 'DateCreated,PrimaryImageAspectRatio', - StartIndex: 0 - } - }; - pageData.query.ParentId = params.topParentId; - } - - return pageData.query; + if (!pageData) { + pageData = data[key] = { + query: { + SortBy: 'SortName', + SortOrder: 'Ascending', + IncludeItemTypes: 'Series', + Recursive: true, + Fields: 'DateCreated,PrimaryImageAspectRatio', + StartIndex: 0 + } + }; + pageData.query.ParentId = params.topParentId; } - function getSavedQueryKey() { - return libraryBrowser.getSavedQueryKey('studios'); - } + return pageData.query; +} - function getPromise(context, params) { - const query = getQuery(params); - loading.show(); - return ApiClient.getStudios(ApiClient.getCurrentUserId(), query); - } +function getSavedQueryKey() { + return libraryBrowser.getSavedQueryKey('studios'); +} - function reloadItems(context, params, promise) { - promise.then(function (result) { - const elem = context.querySelector('#items'); - cardBuilder.buildCards(result.Items, { - itemsContainer: elem, - shape: 'backdrop', - preferThumb: true, - showTitle: true, - scalable: true, - centerText: true, - overlayMoreButton: true, - context: 'tvshows' - }); - loading.hide(); +function getPromise(context, params) { + const query = getQuery(params); + loading.show(); + return ApiClient.getStudios(ApiClient.getCurrentUserId(), query); +} - import('../../components/autoFocuser').then(({ default: autoFocuser }) => { - autoFocuser.autoFocus(context); - }); +function reloadItems(context, params, promise) { + promise.then(function (result) { + const elem = context.querySelector('#items'); + cardBuilder.buildCards(result.Items, { + itemsContainer: elem, + shape: 'backdrop', + preferThumb: true, + showTitle: true, + scalable: true, + centerText: true, + overlayMoreButton: true, + context: 'tvshows' }); - } + loading.hide(); - const data = {}; + import('../../components/autoFocuser').then(({ default: autoFocuser }) => { + autoFocuser.autoFocus(context); + }); + }); +} - export default function (view, params, tabContent) { - let promise; - const self = this; +const data = {}; - self.preRender = function () { - promise = getPromise(view, params); - }; +export default function (view, params, tabContent) { + let promise; + const self = this; - self.renderTab = function () { - reloadItems(tabContent, params, promise); - }; - } + self.preRender = function () { + promise = getPromise(view, params); + }; + + self.renderTab = function () { + reloadItems(tabContent, params, promise); + }; +} -/* eslint-enable indent */ diff --git a/src/controllers/shows/tvupcoming.js b/src/controllers/shows/tvupcoming.js index fa6be1f356..f8b2f31eaa 100644 --- a/src/controllers/shows/tvupcoming.js +++ b/src/controllers/shows/tvupcoming.js @@ -7,138 +7,135 @@ import globalize from '../../scripts/globalize'; import '../../styles/scrollstyles.scss'; import '../../elements/emby-itemscontainer/emby-itemscontainer'; -/* eslint-disable indent */ +function getUpcomingPromise(context, params) { + loading.show(); + const query = { + Limit: 48, + Fields: 'AirTime', + UserId: ApiClient.getCurrentUserId(), + ImageTypeLimit: 1, + EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', + EnableTotalRecordCount: false + }; + query.ParentId = params.topParentId; + return ApiClient.getJSON(ApiClient.getUrl('Shows/Upcoming', query)); +} - function getUpcomingPromise(context, params) { - loading.show(); - const query = { - Limit: 48, - Fields: 'AirTime', - UserId: ApiClient.getCurrentUserId(), - ImageTypeLimit: 1, - EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', - EnableTotalRecordCount: false - }; - query.ParentId = params.topParentId; - return ApiClient.getJSON(ApiClient.getUrl('Shows/Upcoming', query)); - } +function loadUpcoming(context, params, promise) { + promise.then(function (result) { + const items = result.Items; - function loadUpcoming(context, params, promise) { - promise.then(function (result) { - const items = result.Items; + if (items.length) { + context.querySelector('.noItemsMessage').style.display = 'none'; + } else { + context.querySelector('.noItemsMessage').style.display = 'block'; + } - if (items.length) { - context.querySelector('.noItemsMessage').style.display = 'none'; - } else { - context.querySelector('.noItemsMessage').style.display = 'block'; + renderUpcoming(context.querySelector('#upcomingItems'), items); + loading.hide(); + }); +} + +function enableScrollX() { + return !layoutManager.desktop; +} + +function getThumbShape() { + return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; +} + +function renderUpcoming(elem, items) { + const groups = []; + let currentGroupName = ''; + let currentGroup = []; + + for (let i = 0, length = items.length; i < length; i++) { + const item = items[i]; + let dateText = ''; + + if (item.PremiereDate) { + try { + const premiereDate = datetime.parseISO8601Date(item.PremiereDate, true); + dateText = datetime.isRelativeDay(premiereDate, -1) ? globalize.translate('Yesterday') : datetime.toLocaleDateString(premiereDate, { + weekday: 'long', + month: 'short', + day: 'numeric' + }); + } catch (err) { + console.error('error parsing timestamp for upcoming tv shows'); + } + } + + if (dateText != currentGroupName) { + if (currentGroup.length) { + groups.push({ + name: currentGroupName, + items: currentGroup + }); } - renderUpcoming(context.querySelector('#upcomingItems'), items); - loading.hide(); + currentGroupName = dateText; + currentGroup = [item]; + } else { + currentGroup.push(item); + } + } + + let html = ''; + + for (let i = 0, length = groups.length; i < length; i++) { + const group = groups[i]; + html += '
'; + html += '

' + group.name + '

'; + let allowBottomPadding = true; + + if (enableScrollX()) { + allowBottomPadding = false; + let scrollXClass = 'scrollX hiddenScrollX'; + + if (layoutManager.tv) { + scrollXClass += ' smoothScrollX'; + } + + html += '
'; + } else { + html += '
'; + } + + html += cardBuilder.getCardsHtml({ + items: group.items, + showLocationTypeIndicator: false, + shape: getThumbShape(), + showTitle: true, + preferThumb: true, + lazy: true, + showDetailsMenu: true, + centerText: true, + showParentTitle: true, + overlayText: false, + allowBottomPadding: allowBottomPadding, + cardLayout: false, + overlayMoreButton: true, + missingIndicator: false }); + html += '
'; + html += '
'; } - function enableScrollX() { - return !layoutManager.desktop; - } + elem.innerHTML = html; + imageLoader.lazyChildren(elem); +} - function getThumbShape() { - return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; - } +export default function (view, params, tabContent) { + let upcomingPromise; + const self = this; - function renderUpcoming(elem, items) { - const groups = []; - let currentGroupName = ''; - let currentGroup = []; + self.preRender = function () { + upcomingPromise = getUpcomingPromise(view, params); + }; - for (let i = 0, length = items.length; i < length; i++) { - const item = items[i]; - let dateText = ''; + self.renderTab = function () { + loadUpcoming(tabContent, params, upcomingPromise); + }; +} - if (item.PremiereDate) { - try { - const premiereDate = datetime.parseISO8601Date(item.PremiereDate, true); - dateText = datetime.isRelativeDay(premiereDate, -1) ? globalize.translate('Yesterday') : datetime.toLocaleDateString(premiereDate, { - weekday: 'long', - month: 'short', - day: 'numeric' - }); - } catch (err) { - console.error('error parsing timestamp for upcoming tv shows'); - } - } - - if (dateText != currentGroupName) { - if (currentGroup.length) { - groups.push({ - name: currentGroupName, - items: currentGroup - }); - } - - currentGroupName = dateText; - currentGroup = [item]; - } else { - currentGroup.push(item); - } - } - - let html = ''; - - for (let i = 0, length = groups.length; i < length; i++) { - const group = groups[i]; - html += '
'; - html += '

' + group.name + '

'; - let allowBottomPadding = true; - - if (enableScrollX()) { - allowBottomPadding = false; - let scrollXClass = 'scrollX hiddenScrollX'; - - if (layoutManager.tv) { - scrollXClass += ' smoothScrollX'; - } - - html += '
'; - } else { - html += '
'; - } - - html += cardBuilder.getCardsHtml({ - items: group.items, - showLocationTypeIndicator: false, - shape: getThumbShape(), - showTitle: true, - preferThumb: true, - lazy: true, - showDetailsMenu: true, - centerText: true, - showParentTitle: true, - overlayText: false, - allowBottomPadding: allowBottomPadding, - cardLayout: false, - overlayMoreButton: true, - missingIndicator: false - }); - html += '
'; - html += '
'; - } - - elem.innerHTML = html; - imageLoader.lazyChildren(elem); - } - - export default function (view, params, tabContent) { - let upcomingPromise; - const self = this; - - self.preRender = function () { - upcomingPromise = getUpcomingPromise(view, params); - }; - - self.renderTab = function () { - loadUpcoming(tabContent, params, upcomingPromise); - }; - } - -/* eslint-enable indent */ diff --git a/src/controllers/user/display/index.js b/src/controllers/user/display/index.js index 5d34539c35..836c8c159a 100644 --- a/src/controllers/user/display/index.js +++ b/src/controllers/user/display/index.js @@ -2,39 +2,36 @@ import DisplaySettings from '../../../components/displaySettings/displaySettings import * as userSettings from '../../../scripts/settings/userSettings'; import autoFocuser from '../../../components/autoFocuser'; -/* eslint-disable indent */ +// Shortcuts +const UserSettings = userSettings.UserSettings; - // Shortcuts - const UserSettings = userSettings.UserSettings; +export default function (view, params) { + let settingsInstance; - export default function (view, params) { - let settingsInstance; + const userId = params.userId || ApiClient.getCurrentUserId(); + const currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new UserSettings(); - const userId = params.userId || ApiClient.getCurrentUserId(); - const currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new UserSettings(); + view.addEventListener('viewshow', function () { + if (settingsInstance) { + settingsInstance.loadData(); + } else { + settingsInstance = new DisplaySettings({ + serverId: ApiClient.serverId(), + userId: userId, + element: view.querySelector('.settingsContainer'), + userSettings: currentSettings, + enableSaveButton: true, + enableSaveConfirmation: true, + autoFocus: autoFocuser.isEnabled() + }); + } + }); - view.addEventListener('viewshow', function () { - if (settingsInstance) { - settingsInstance.loadData(); - } else { - settingsInstance = new DisplaySettings({ - serverId: ApiClient.serverId(), - userId: userId, - element: view.querySelector('.settingsContainer'), - userSettings: currentSettings, - enableSaveButton: true, - enableSaveConfirmation: true, - autoFocus: autoFocuser.isEnabled() - }); - } - }); + view.addEventListener('viewdestroy', function () { + if (settingsInstance) { + settingsInstance.destroy(); + settingsInstance = null; + } + }); +} - view.addEventListener('viewdestroy', function () { - if (settingsInstance) { - settingsInstance.destroy(); - settingsInstance = null; - } - }); - } - -/* eslint-enable indent */ diff --git a/src/controllers/user/home/index.js b/src/controllers/user/home/index.js index 45b0fd2f80..c2c0af99af 100644 --- a/src/controllers/user/home/index.js +++ b/src/controllers/user/home/index.js @@ -3,39 +3,36 @@ import * as userSettings from '../../../scripts/settings/userSettings'; import autoFocuser from '../../../components/autoFocuser'; import '../../../components/listview/listview.scss'; -/* eslint-disable indent */ +// Shortcuts +const UserSettings = userSettings.UserSettings; - // Shortcuts - const UserSettings = userSettings.UserSettings; +export default function (view, params) { + let homescreenSettingsInstance; - export default function (view, params) { - let homescreenSettingsInstance; + const userId = params.userId || ApiClient.getCurrentUserId(); + const currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new UserSettings(); - const userId = params.userId || ApiClient.getCurrentUserId(); - const currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new UserSettings(); + view.addEventListener('viewshow', function () { + if (homescreenSettingsInstance) { + homescreenSettingsInstance.loadData(); + } else { + homescreenSettingsInstance = new HomescreenSettings({ + serverId: ApiClient.serverId(), + userId: userId, + element: view.querySelector('.homeScreenSettingsContainer'), + userSettings: currentSettings, + enableSaveButton: true, + enableSaveConfirmation: true, + autoFocus: autoFocuser.isEnabled() + }); + } + }); - view.addEventListener('viewshow', function () { - if (homescreenSettingsInstance) { - homescreenSettingsInstance.loadData(); - } else { - homescreenSettingsInstance = new HomescreenSettings({ - serverId: ApiClient.serverId(), - userId: userId, - element: view.querySelector('.homeScreenSettingsContainer'), - userSettings: currentSettings, - enableSaveButton: true, - enableSaveConfirmation: true, - autoFocus: autoFocuser.isEnabled() - }); - } - }); + view.addEventListener('viewdestroy', function () { + if (homescreenSettingsInstance) { + homescreenSettingsInstance.destroy(); + homescreenSettingsInstance = null; + } + }); +} - view.addEventListener('viewdestroy', function () { - if (homescreenSettingsInstance) { - homescreenSettingsInstance.destroy(); - homescreenSettingsInstance = null; - } - }); - } - -/* eslint-enable indent */ diff --git a/src/controllers/user/playback/index.js b/src/controllers/user/playback/index.js index f0b4be6a25..efb24c76ee 100644 --- a/src/controllers/user/playback/index.js +++ b/src/controllers/user/playback/index.js @@ -4,39 +4,36 @@ import * as userSettings from '../../../scripts/settings/userSettings'; import autoFocuser from '../../../components/autoFocuser'; import '../../../components/listview/listview.scss'; -/* eslint-disable indent */ +// Shortcuts +const UserSettings = userSettings.UserSettings; - // Shortcuts - const UserSettings = userSettings.UserSettings; +export default function (view, params) { + let settingsInstance; - export default function (view, params) { - let settingsInstance; + const userId = params.userId || ApiClient.getCurrentUserId(); + const currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new UserSettings(); - const userId = params.userId || ApiClient.getCurrentUserId(); - const currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new UserSettings(); + view.addEventListener('viewshow', function () { + if (settingsInstance) { + settingsInstance.loadData(); + } else { + settingsInstance = new PlaybackSettings({ + serverId: ApiClient.serverId(), + userId: userId, + element: view.querySelector('.settingsContainer'), + userSettings: currentSettings, + enableSaveButton: true, + enableSaveConfirmation: true, + autoFocus: autoFocuser.isEnabled() + }); + } + }); - view.addEventListener('viewshow', function () { - if (settingsInstance) { - settingsInstance.loadData(); - } else { - settingsInstance = new PlaybackSettings({ - serverId: ApiClient.serverId(), - userId: userId, - element: view.querySelector('.settingsContainer'), - userSettings: currentSettings, - enableSaveButton: true, - enableSaveConfirmation: true, - autoFocus: autoFocuser.isEnabled() - }); - } - }); + view.addEventListener('viewdestroy', function () { + if (settingsInstance) { + settingsInstance.destroy(); + settingsInstance = null; + } + }); +} - view.addEventListener('viewdestroy', function () { - if (settingsInstance) { - settingsInstance.destroy(); - settingsInstance = null; - } - }); - } - -/* eslint-enable indent */ diff --git a/src/controllers/user/subtitles/index.js b/src/controllers/user/subtitles/index.js index 87676df65f..6eb3197184 100644 --- a/src/controllers/user/subtitles/index.js +++ b/src/controllers/user/subtitles/index.js @@ -2,39 +2,36 @@ import SubtitleSettings from '../../../components/subtitlesettings/subtitlesetti import * as userSettings from '../../../scripts/settings/userSettings'; import autoFocuser from '../../../components/autoFocuser'; -/* eslint-disable indent */ +// Shortcuts +const UserSettings = userSettings.UserSettings; - // Shortcuts - const UserSettings = userSettings.UserSettings; +export default function (view, params) { + let subtitleSettingsInstance; - export default function (view, params) { - let subtitleSettingsInstance; + const userId = params.userId || ApiClient.getCurrentUserId(); + const currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new UserSettings(); - const userId = params.userId || ApiClient.getCurrentUserId(); - const currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new UserSettings(); + view.addEventListener('viewshow', function () { + if (subtitleSettingsInstance) { + subtitleSettingsInstance.loadData(); + } else { + subtitleSettingsInstance = new SubtitleSettings({ + serverId: ApiClient.serverId(), + userId: userId, + element: view.querySelector('.settingsContainer'), + userSettings: currentSettings, + enableSaveButton: true, + enableSaveConfirmation: true, + autoFocus: autoFocuser.isEnabled() + }); + } + }); - view.addEventListener('viewshow', function () { - if (subtitleSettingsInstance) { - subtitleSettingsInstance.loadData(); - } else { - subtitleSettingsInstance = new SubtitleSettings({ - serverId: ApiClient.serverId(), - userId: userId, - element: view.querySelector('.settingsContainer'), - userSettings: currentSettings, - enableSaveButton: true, - enableSaveConfirmation: true, - autoFocus: autoFocuser.isEnabled() - }); - } - }); + view.addEventListener('viewdestroy', function () { + if (subtitleSettingsInstance) { + subtitleSettingsInstance.destroy(); + subtitleSettingsInstance = null; + } + }); +} - view.addEventListener('viewdestroy', function () { - if (subtitleSettingsInstance) { - subtitleSettingsInstance.destroy(); - subtitleSettingsInstance = null; - } - }); - } - -/* eslint-enable indent */ diff --git a/src/elements/emby-checkbox/emby-checkbox.js b/src/elements/emby-checkbox/emby-checkbox.js index 1923ef4e5c..2be4237d21 100644 --- a/src/elements/emby-checkbox/emby-checkbox.js +++ b/src/elements/emby-checkbox/emby-checkbox.js @@ -3,109 +3,106 @@ import dom from '../../scripts/dom'; import './emby-checkbox.scss'; import 'webcomponents.js/webcomponents-lite'; -/* eslint-disable indent */ +const EmbyCheckboxPrototype = Object.create(HTMLInputElement.prototype); - const EmbyCheckboxPrototype = Object.create(HTMLInputElement.prototype); +function onKeyDown(e) { + // Don't submit form on enter + // Real (non-emulator) Tizen does nothing on Space + if (e.keyCode === 13 || (e.keyCode === 32 && browser.tizen)) { + e.preventDefault(); - function onKeyDown(e) { - // Don't submit form on enter - // Real (non-emulator) Tizen does nothing on Space - if (e.keyCode === 13 || (e.keyCode === 32 && browser.tizen)) { - e.preventDefault(); + this.checked = !this.checked; - this.checked = !this.checked; + this.dispatchEvent(new CustomEvent('change', { + bubbles: true + })); - this.dispatchEvent(new CustomEvent('change', { - bubbles: true - })); + return false; + } +} - return false; - } +const enableRefreshHack = browser.tizen || browser.orsay || browser.operaTv || browser.web0s ? true : false; + +function forceRefresh(loading) { + const 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; } - const enableRefreshHack = browser.tizen || browser.orsay || browser.operaTv || browser.web0s ? true : false; + this.setAttribute('data-embycheckbox', 'true'); - function forceRefresh(loading) { - const elem = this.parentNode; + this.classList.add('emby-checkbox'); - elem.style.webkitAnimationName = 'repaintChrome'; - elem.style.webkitAnimationDelay = (loading === true ? '500ms' : ''); - elem.style.webkitAnimationDuration = '10ms'; - elem.style.webkitAnimationIterationCount = '1'; + const labelElement = this.parentNode; + labelElement.classList.add('emby-checkbox-label'); - setTimeout(function () { - elem.style.webkitAnimationName = ''; - }, (loading === true ? 520 : 20)); + const labelTextElement = labelElement.querySelector('span'); + + let outlineClass = 'checkboxOutline'; + + const customClass = this.getAttribute('data-outlineclass'); + if (customClass) { + outlineClass += ' ' + customClass; } - EmbyCheckboxPrototype.attachedCallback = function () { - if (this.getAttribute('data-embycheckbox') === 'true') { - return; - } + const checkedIcon = this.getAttribute('data-checkedicon') || 'check'; + const uncheckedIcon = this.getAttribute('data-uncheckedicon') || ''; + const checkHtml = ''; + const uncheckedHtml = ''; + labelElement.insertAdjacentHTML('beforeend', '' + checkHtml + uncheckedHtml + ''); - this.setAttribute('data-embycheckbox', 'true'); + labelTextElement.classList.add('checkboxLabel'); - this.classList.add('emby-checkbox'); + this.addEventListener('keydown', onKeyDown); - const labelElement = this.parentNode; - labelElement.classList.add('emby-checkbox-label'); - - const labelTextElement = labelElement.querySelector('span'); - - let outlineClass = 'checkboxOutline'; - - const customClass = this.getAttribute('data-outlineclass'); - if (customClass) { - outlineClass += ' ' + customClass; - } - - const checkedIcon = this.getAttribute('data-checkedicon') || 'check'; - const uncheckedIcon = this.getAttribute('data-uncheckedicon') || ''; - const checkHtml = ''; - const uncheckedHtml = ''; - labelElement.insertAdjacentHTML('beforeend', '' + checkHtml + uncheckedHtml + ''); - - 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, { + if (enableRefreshHack) { + forceRefresh.call(this, true); + dom.addEventListener(this, 'click', forceRefresh, { passive: true }); - dom.removeEventListener(this, 'blur', forceRefresh, { + dom.addEventListener(this, 'blur', forceRefresh, { passive: true }); - dom.removeEventListener(this, 'focus', forceRefresh, { + dom.addEventListener(this, 'focus', forceRefresh, { passive: true }); - dom.removeEventListener(this, 'change', forceRefresh, { + dom.addEventListener(this, 'change', forceRefresh, { passive: true }); - }; + } +}; - document.registerElement('emby-checkbox', { - prototype: EmbyCheckboxPrototype, - extends: 'input' +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' +}); -/* eslint-enable indent */ diff --git a/src/elements/emby-collapse/emby-collapse.js b/src/elements/emby-collapse/emby-collapse.js index bd9743c55b..bf281d1257 100644 --- a/src/elements/emby-collapse/emby-collapse.js +++ b/src/elements/emby-collapse/emby-collapse.js @@ -2,101 +2,98 @@ import './emby-collapse.scss'; import 'webcomponents.js/webcomponents-lite'; import '../emby-button/emby-button'; -/* eslint-disable indent */ +const EmbyButtonPrototype = Object.create(HTMLDivElement.prototype); - const EmbyButtonPrototype = Object.create(HTMLDivElement.prototype); +function slideDownToShow(button, elem) { + requestAnimationFrame(() => { + elem.classList.remove('hide'); + elem.classList.add('expanded'); + elem.style.height = 'auto'; + const height = elem.offsetHeight + 'px'; + elem.style.height = '0'; + // trigger reflow + // TODO: Find a better way to do this + const newHeight = elem.offsetHeight; /* eslint-disable-line no-unused-vars */ + elem.style.height = height; - function slideDownToShow(button, elem) { - requestAnimationFrame(() => { - elem.classList.remove('hide'); - elem.classList.add('expanded'); + setTimeout(function () { + if (elem.classList.contains('expanded')) { + elem.classList.remove('hide'); + } else { + elem.classList.add('hide'); + } elem.style.height = 'auto'; - const height = elem.offsetHeight + 'px'; - elem.style.height = '0'; - // trigger reflow - // TODO: Find a better way to do this - const newHeight = elem.offsetHeight; /* eslint-disable-line no-unused-vars */ - elem.style.height = height; + }, 300); - setTimeout(function () { - if (elem.classList.contains('expanded')) { - elem.classList.remove('hide'); - } else { - elem.classList.add('hide'); - } - elem.style.height = 'auto'; - }, 300); - - const icon = button.querySelector('.material-icons'); - icon.classList.add('emby-collapse-expandIconExpanded'); - }); - } - - function slideUpToHide(button, elem) { - requestAnimationFrame(() => { - elem.style.height = elem.offsetHeight + 'px'; - // trigger reflow - // TODO: Find a better way to do this - const newHeight = elem.offsetHeight; /* eslint-disable-line no-unused-vars */ - 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); - - const icon = button.querySelector('.material-icons'); - icon.classList.remove('emby-collapse-expandIconExpanded'); - }); - } - - function onButtonClick() { - const button = this; - const 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'); - - const collapseContent = this.querySelector('.collapseContent'); - if (collapseContent) { - collapseContent.classList.add('hide'); - } - - const title = this.getAttribute('title'); - - const html = ''; - - this.insertAdjacentHTML('afterbegin', html); - - const 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' + const icon = button.querySelector('.material-icons'); + icon.classList.add('emby-collapse-expandIconExpanded'); }); +} + +function slideUpToHide(button, elem) { + requestAnimationFrame(() => { + elem.style.height = elem.offsetHeight + 'px'; + // trigger reflow + // TODO: Find a better way to do this + const newHeight = elem.offsetHeight; /* eslint-disable-line no-unused-vars */ + 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); + + const icon = button.querySelector('.material-icons'); + icon.classList.remove('emby-collapse-expandIconExpanded'); + }); +} + +function onButtonClick() { + const button = this; + const 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'); + + const collapseContent = this.querySelector('.collapseContent'); + if (collapseContent) { + collapseContent.classList.add('hide'); + } + + const title = this.getAttribute('title'); + + const html = ''; + + this.insertAdjacentHTML('afterbegin', html); + + const 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' +}); -/* eslint-enable indent */ diff --git a/src/elements/emby-input/emby-input.js b/src/elements/emby-input/emby-input.js index f62bcbf345..1b7067cef8 100644 --- a/src/elements/emby-input/emby-input.js +++ b/src/elements/emby-input/emby-input.js @@ -3,119 +3,116 @@ import dom from '../../scripts/dom'; import './emby-input.scss'; import 'webcomponents.js/webcomponents-lite'; -/* eslint-disable indent */ +const EmbyInputPrototype = Object.create(HTMLInputElement.prototype); - const EmbyInputPrototype = Object.create(HTMLInputElement.prototype); +let inputId = 0; +let supportsFloatingLabel = false; - let inputId = 0; - let supportsFloatingLabel = false; +if (Object.getOwnPropertyDescriptor && Object.defineProperty) { + const descriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value'); - if (Object.getOwnPropertyDescriptor && Object.defineProperty) { - const descriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value'); + // descriptor returning null in webos + if (descriptor && descriptor.configurable) { + const baseSetMethod = descriptor.set; + descriptor.set = function (value) { + baseSetMethod.call(this, value); - // descriptor returning null in webos - if (descriptor && descriptor.configurable) { - const baseSetMethod = descriptor.set; - descriptor.set = function (value) { - baseSetMethod.call(this, value); + this.dispatchEvent(new CustomEvent('valueset', { + bubbles: false, + cancelable: false + })); + }; - this.dispatchEvent(new CustomEvent('valueset', { - bubbles: false, - cancelable: false - })); - }; + Object.defineProperty(HTMLInputElement.prototype, 'value', descriptor); + supportsFloatingLabel = true; + } +} - Object.defineProperty(HTMLInputElement.prototype, 'value', descriptor); - supportsFloatingLabel = true; - } +EmbyInputPrototype.createdCallback = function () { + if (!this.id) { + this.id = 'embyinput' + inputId; + inputId++; } - EmbyInputPrototype.createdCallback = function () { - if (!this.id) { - this.id = 'embyinput' + inputId; - inputId++; - } + if (this.classList.contains('emby-input')) { + return; + } - if (this.classList.contains('emby-input')) { - return; - } + this.classList.add('emby-input'); - this.classList.add('emby-input'); + const parentNode = this.parentNode; + const document = this.ownerDocument; + const label = document.createElement('label'); + label.innerText = this.getAttribute('label') || ''; + label.classList.add('inputLabel'); + label.classList.add('inputLabelUnfocused'); - const parentNode = this.parentNode; - const document = this.ownerDocument; - const label = document.createElement('label'); - label.innerText = this.getAttribute('label') || ''; - label.classList.add('inputLabel'); - label.classList.add('inputLabelUnfocused'); + label.htmlFor = this.id; + parentNode.insertBefore(label, this); + this.labelElement = label; - label.htmlFor = this.id; - parentNode.insertBefore(label, this); - this.labelElement = label; + dom.addEventListener(this, 'focus', function () { + onChange.call(this); - 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 - }); - - //Make sure the IME pops up if this is the first/default element on the page - if (browser.orsay && this === document.activeElement && document.attachIME) { + // For Samsung orsay devices + if (document.attachIME) { document.attachIME(this); } - }; - function onChange() { - const label = this.labelElement; - if (this.value) { - label.classList.remove('inputLabel-float'); - } else { - const 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.innerText = text; - }; - - document.registerElement('emby-input', { - prototype: EmbyInputPrototype, - extends: 'input' + label.classList.add('inputLabelFocused'); + label.classList.remove('inputLabelUnfocused'); + }, { + passive: true }); -/* eslint-enable indent */ + 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 + }); + + //Make sure the IME pops up if this is the first/default element on the page + if (browser.orsay && this === document.activeElement && document.attachIME) { + document.attachIME(this); + } +}; + +function onChange() { + const label = this.labelElement; + if (this.value) { + label.classList.remove('inputLabel-float'); + } else { + const 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.innerText = text; +}; + +document.registerElement('emby-input', { + prototype: EmbyInputPrototype, + extends: 'input' +}); + diff --git a/src/elements/emby-itemrefreshindicator/emby-itemrefreshindicator.js b/src/elements/emby-itemrefreshindicator/emby-itemrefreshindicator.js index 68206960fd..e2e1ec90ec 100644 --- a/src/elements/emby-itemrefreshindicator/emby-itemrefreshindicator.js +++ b/src/elements/emby-itemrefreshindicator/emby-itemrefreshindicator.js @@ -5,73 +5,70 @@ import Events from '../../utils/events.ts'; import 'webcomponents.js/webcomponents-lite'; -/* eslint-disable indent */ +function addNotificationEvent(instance, name, handler) { + const localHandler = handler.bind(instance); + Events.on(serverNotifications, name, localHandler); + instance[name] = localHandler; +} - function addNotificationEvent(instance, name, handler) { - const localHandler = handler.bind(instance); - Events.on(serverNotifications, name, localHandler); - instance[name] = localHandler; +function removeNotificationEvent(instance, name) { + const handler = instance[name]; + if (handler) { + Events.off(serverNotifications, name, handler); + instance[name] = null; + } +} + +function onRefreshProgress(e, apiClient, info) { + const indicator = this; + + if (!indicator.itemId) { + indicator.itemId = dom.parentWithAttribute(indicator, 'data-id').getAttribute('data-id'); } - function removeNotificationEvent(instance, name) { - const handler = instance[name]; - if (handler) { - Events.off(serverNotifications, name, handler); - instance[name] = null; + if (info.ItemId === indicator.itemId) { + const progress = parseFloat(info.Progress); + + if (progress && progress < 100) { + this.classList.remove('hide'); + } else { + this.classList.add('hide'); } + + this.setAttribute('data-progress', progress); + } +} + +const EmbyItemRefreshIndicatorPrototype = Object.create(EmbyProgressRing); + +EmbyItemRefreshIndicatorPrototype.createdCallback = function () { + // base method + if (EmbyProgressRing.createdCallback) { + EmbyProgressRing.createdCallback.call(this); } - function onRefreshProgress(e, apiClient, info) { - const indicator = this; + addNotificationEvent(this, 'RefreshProgress', onRefreshProgress); +}; - if (!indicator.itemId) { - indicator.itemId = dom.parentWithAttribute(indicator, 'data-id').getAttribute('data-id'); - } +EmbyItemRefreshIndicatorPrototype.attachedCallback = function () { + // base method + if (EmbyProgressRing.attachedCallback) { + EmbyProgressRing.attachedCallback.call(this); + } +}; - if (info.ItemId === indicator.itemId) { - const progress = parseFloat(info.Progress); - - if (progress && progress < 100) { - this.classList.remove('hide'); - } else { - this.classList.add('hide'); - } - - this.setAttribute('data-progress', progress); - } +EmbyItemRefreshIndicatorPrototype.detachedCallback = function () { + // base method + if (EmbyProgressRing.detachedCallback) { + EmbyProgressRing.detachedCallback.call(this); } - const EmbyItemRefreshIndicatorPrototype = Object.create(EmbyProgressRing); + removeNotificationEvent(this, 'RefreshProgress'); + this.itemId = null; +}; - EmbyItemRefreshIndicatorPrototype.createdCallback = function () { - // base method - if (EmbyProgressRing.createdCallback) { - EmbyProgressRing.createdCallback.call(this); - } +document.registerElement('emby-itemrefreshindicator', { + prototype: EmbyItemRefreshIndicatorPrototype, + extends: 'div' +}); - 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' - }); - -/* eslint-enable indent */ diff --git a/src/elements/emby-itemscontainer/emby-itemscontainer.js b/src/elements/emby-itemscontainer/emby-itemscontainer.js index 4c7769f32e..d34cc25438 100644 --- a/src/elements/emby-itemscontainer/emby-itemscontainer.js +++ b/src/elements/emby-itemscontainer/emby-itemscontainer.js @@ -13,471 +13,468 @@ import 'webcomponents.js/webcomponents-lite'; import ServerConnections from '../../components/ServerConnections'; import Sortable from 'sortablejs'; -/* eslint-disable indent */ +const ItemsContainerPrototype = Object.create(HTMLDivElement.prototype); - const ItemsContainerPrototype = Object.create(HTMLDivElement.prototype); +function onClick(e) { + const itemsContainer = this; + const multiSelect = itemsContainer.multiSelect; - function onClick(e) { - const itemsContainer = this; - const multiSelect = itemsContainer.multiSelect; - - if (multiSelect?.onContainerClick.call(itemsContainer, e) === false) { - return; - } - - itemShortcuts.onClick.call(itemsContainer, e); + if (multiSelect?.onContainerClick.call(itemsContainer, e) === false) { + return; } - function disableEvent(e) { + itemShortcuts.onClick.call(itemsContainer, e); +} + +function disableEvent(e) { + e.preventDefault(); + e.stopPropagation(); + return false; +} + +function onContextMenu(e) { + const target = e.target; + const card = dom.parentWithAttribute(target, 'data-id'); + + // check for serverId, it won't be present on selectserver + if (card && card.getAttribute('data-serverid')) { + inputManager.handleCommand('menu', { + sourceElement: card + }); + e.preventDefault(); e.stopPropagation(); return false; } +} - function onContextMenu(e) { - const target = e.target; - const card = dom.parentWithAttribute(target, 'data-id'); - - // check for serverId, it won't be present on selectserver - if (card && card.getAttribute('data-serverid')) { - inputManager.handleCommand('menu', { - sourceElement: card - }); - - e.preventDefault(); - e.stopPropagation(); - return false; - } - } - - function getShortcutOptions() { - return { - click: false - }; - } - - ItemsContainerPrototype.enableMultiSelect = function (enabled) { - const current = this.multiSelect; - - if (!enabled) { - if (current) { - current.destroy(); - this.multiSelect = null; - } - return; - } - - if (current) { - return; - } - - const self = this; - import('../../components/multiSelect/multiSelect').then(({ default: MultiSelect }) => { - self.multiSelect = new MultiSelect({ - container: self, - bindOnClick: false - }); - }); +function getShortcutOptions() { + return { + click: false }; +} - function onDrop(evt, itemsContainer) { - const el = evt.item; +ItemsContainerPrototype.enableMultiSelect = function (enabled) { + const current = this.multiSelect; - const newIndex = evt.newIndex; - const itemId = el.getAttribute('data-playlistitemid'); - const playlistId = el.getAttribute('data-playlistid'); - - if (!playlistId) { - const oldIndex = evt.oldIndex; - el.dispatchEvent(new CustomEvent('itemdrop', { - detail: { - oldIndex: oldIndex, - newIndex: newIndex, - playlistItemId: itemId - }, - bubbles: true, - cancelable: false - })); - return; + if (!enabled) { + if (current) { + current.destroy(); + this.multiSelect = null; } - - const serverId = el.getAttribute('data-serverid'); - const apiClient = ServerConnections.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(); - }); + return; } - ItemsContainerPrototype.enableDragReordering = function (enabled) { - const current = this.sortable; - if (!enabled) { - if (current) { - current.destroy(); - this.sortable = null; - } - return; - } + if (current) { + return; + } + const self = this; + import('../../components/multiSelect/multiSelect').then(({ default: MultiSelect }) => { + self.multiSelect = new MultiSelect({ + container: self, + bindOnClick: false + }); + }); +}; + +function onDrop(evt, itemsContainer) { + const el = evt.item; + + const newIndex = evt.newIndex; + const itemId = el.getAttribute('data-playlistitemid'); + const playlistId = el.getAttribute('data-playlistid'); + + if (!playlistId) { + const oldIndex = evt.oldIndex; + el.dispatchEvent(new CustomEvent('itemdrop', { + detail: { + oldIndex: oldIndex, + newIndex: newIndex, + playlistItemId: itemId + }, + bubbles: true, + cancelable: false + })); + return; + } + + const serverId = el.getAttribute('data-serverid'); + const apiClient = ServerConnections.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) { + const current = this.sortable; + if (!enabled) { if (current) { - return; + current.destroy(); + this.sortable = null; } + return; + } - const self = this; - self.sortable = new Sortable(self, { - draggable: '.listItem', - handle: '.listViewDragHandle', + if (current) { + return; + } - // dragging ended - onEnd: function (evt) { - return onDrop(evt, self); - } - }); - }; + const self = this; + self.sortable = new Sortable(self, { + draggable: '.listItem', + handle: '.listViewDragHandle', - function onUserDataChanged(e, apiClient, userData) { - const itemsContainer = this; + // dragging ended + onEnd: function (evt) { + return onDrop(evt, self); + } + }); +}; - import('../../components/cardbuilder/cardBuilder').then((cardBuilder) => { - cardBuilder.onUserDataChanged(userData, itemsContainer); - }); +function onUserDataChanged(e, apiClient, userData) { + const itemsContainer = this; - const eventsToMonitor = getEventsToMonitor(itemsContainer); + import('../../components/cardbuilder/cardBuilder').then((cardBuilder) => { + cardBuilder.onUserDataChanged(userData, itemsContainer); + }); - // TODO: Check user data change reason? - if (eventsToMonitor.indexOf('markfavorite') !== -1 + const eventsToMonitor = getEventsToMonitor(itemsContainer); + + // TODO: Check user data change reason? + if (eventsToMonitor.indexOf('markfavorite') !== -1 || eventsToMonitor.indexOf('markplayed') !== -1 - ) { - itemsContainer.notifyRefreshNeeded(); - } - } - - function getEventsToMonitor(itemsContainer) { - const monitor = itemsContainer.getAttribute('data-monitor'); - if (monitor) { - return monitor.split(','); - } - - return []; - } - - function onTimerCreated(e, apiClient, data) { - const itemsContainer = this; - - if (getEventsToMonitor(itemsContainer).indexOf('timers') !== -1) { - itemsContainer.notifyRefreshNeeded(); - return; - } - - const programId = data.ProgramId; - // This could be null, not supported by all tv providers - const newTimerId = data.Id; - - import('../../components/cardbuilder/cardBuilder').then((cardBuilder) => { - cardBuilder.onTimerCreated(programId, newTimerId, itemsContainer); - }); - } - - function onSeriesTimerCreated() { - const itemsContainer = this; - if (getEventsToMonitor(itemsContainer).indexOf('seriestimers') !== -1) { - itemsContainer.notifyRefreshNeeded(); - } - } - - function onTimerCancelled(e, apiClient, data) { - const itemsContainer = this; - if (getEventsToMonitor(itemsContainer).indexOf('timers') !== -1) { - itemsContainer.notifyRefreshNeeded(); - return; - } - - import('../../components/cardbuilder/cardBuilder').then((cardBuilder) => { - cardBuilder.onTimerCancelled(data.Id, itemsContainer); - }); - } - - function onSeriesTimerCancelled(e, apiClient, data) { - const itemsContainer = this; - if (getEventsToMonitor(itemsContainer).indexOf('seriestimers') !== -1) { - itemsContainer.notifyRefreshNeeded(); - return; - } - - import('../../components/cardbuilder/cardBuilder').then((cardBuilder) => { - cardBuilder.onSeriesTimerCancelled(data.Id, itemsContainer); - }); - } - - function onLibraryChanged(e, apiClient, data) { - const itemsContainer = this; - - const eventsToMonitor = getEventsToMonitor(itemsContainer); - if (eventsToMonitor.indexOf('seriestimers') !== -1 || eventsToMonitor.indexOf('timers') !== -1) { - // yes this is an assumption - return; - } - - const itemsAdded = data.ItemsAdded || []; - const itemsRemoved = data.ItemsRemoved || []; - if (!itemsAdded.length && !itemsRemoved.length) { - return; - } - - const parentId = itemsContainer.getAttribute('data-parentid'); - if (parentId) { - const foldersAddedTo = data.FoldersAddedTo || []; - const foldersRemovedFrom = data.FoldersRemovedFrom || []; - const collectionFolders = data.CollectionFolders || []; - - if (foldersAddedTo.indexOf(parentId) === -1 && foldersRemovedFrom.indexOf(parentId) === -1 && collectionFolders.indexOf(parentId) === -1) { - return; - } - } - + ) { itemsContainer.notifyRefreshNeeded(); } +} - function onPlaybackStopped(e, stopInfo) { - const itemsContainer = this; - const state = stopInfo.state; +function getEventsToMonitor(itemsContainer) { + const monitor = itemsContainer.getAttribute('data-monitor'); + if (monitor) { + return monitor.split(','); + } - const eventsToMonitor = getEventsToMonitor(itemsContainer); - if (state.NowPlayingItem && state.NowPlayingItem.MediaType === 'Video') { - if (eventsToMonitor.indexOf('videoplayback') !== -1) { - itemsContainer.notifyRefreshNeeded(true); - return; - } - } else if (state.NowPlayingItem?.MediaType === 'Audio' && eventsToMonitor.indexOf('audioplayback') !== -1) { + return []; +} + +function onTimerCreated(e, apiClient, data) { + const itemsContainer = this; + + if (getEventsToMonitor(itemsContainer).indexOf('timers') !== -1) { + itemsContainer.notifyRefreshNeeded(); + return; + } + + const programId = data.ProgramId; + // This could be null, not supported by all tv providers + const newTimerId = data.Id; + + import('../../components/cardbuilder/cardBuilder').then((cardBuilder) => { + cardBuilder.onTimerCreated(programId, newTimerId, itemsContainer); + }); +} + +function onSeriesTimerCreated() { + const itemsContainer = this; + if (getEventsToMonitor(itemsContainer).indexOf('seriestimers') !== -1) { + itemsContainer.notifyRefreshNeeded(); + } +} + +function onTimerCancelled(e, apiClient, data) { + const itemsContainer = this; + if (getEventsToMonitor(itemsContainer).indexOf('timers') !== -1) { + itemsContainer.notifyRefreshNeeded(); + return; + } + + import('../../components/cardbuilder/cardBuilder').then((cardBuilder) => { + cardBuilder.onTimerCancelled(data.Id, itemsContainer); + }); +} + +function onSeriesTimerCancelled(e, apiClient, data) { + const itemsContainer = this; + if (getEventsToMonitor(itemsContainer).indexOf('seriestimers') !== -1) { + itemsContainer.notifyRefreshNeeded(); + return; + } + + import('../../components/cardbuilder/cardBuilder').then((cardBuilder) => { + cardBuilder.onSeriesTimerCancelled(data.Id, itemsContainer); + }); +} + +function onLibraryChanged(e, apiClient, data) { + const itemsContainer = this; + + const eventsToMonitor = getEventsToMonitor(itemsContainer); + if (eventsToMonitor.indexOf('seriestimers') !== -1 || eventsToMonitor.indexOf('timers') !== -1) { + // yes this is an assumption + return; + } + + const itemsAdded = data.ItemsAdded || []; + const itemsRemoved = data.ItemsRemoved || []; + if (!itemsAdded.length && !itemsRemoved.length) { + return; + } + + const parentId = itemsContainer.getAttribute('data-parentid'); + if (parentId) { + const foldersAddedTo = data.FoldersAddedTo || []; + const foldersRemovedFrom = data.FoldersRemovedFrom || []; + const collectionFolders = data.CollectionFolders || []; + + if (foldersAddedTo.indexOf(parentId) === -1 && foldersRemovedFrom.indexOf(parentId) === -1 && collectionFolders.indexOf(parentId) === -1) { + return; + } + } + + itemsContainer.notifyRefreshNeeded(); +} + +function onPlaybackStopped(e, stopInfo) { + const itemsContainer = this; + const state = stopInfo.state; + + const eventsToMonitor = getEventsToMonitor(itemsContainer); + if (state.NowPlayingItem && state.NowPlayingItem.MediaType === 'Video') { + if (eventsToMonitor.indexOf('videoplayback') !== -1) { itemsContainer.notifyRefreshNeeded(true); return; } + } else if (state.NowPlayingItem?.MediaType === 'Audio' && eventsToMonitor.indexOf('audioplayback') !== -1) { + itemsContainer.notifyRefreshNeeded(true); + return; } +} - function addNotificationEvent(instance, name, handler, owner) { - const localHandler = handler.bind(instance); +function addNotificationEvent(instance, name, handler, owner) { + const localHandler = handler.bind(instance); + owner = owner || serverNotifications; + Events.on(owner, name, localHandler); + instance['event_' + name] = localHandler; +} + +function removeNotificationEvent(instance, name, owner) { + const handler = instance['event_' + name]; + if (handler) { owner = owner || serverNotifications; - Events.on(owner, name, localHandler); - instance['event_' + name] = localHandler; + Events.off(owner, name, handler); + instance['event_' + name] = null; } +} - function removeNotificationEvent(instance, name, owner) { - const 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); } } - ItemsContainerPrototype.createdCallback = function () { - this.classList.add('itemsContainer'); - }; + if (layoutManager.desktop || layoutManager.mobile && this.getAttribute('data-multiselect') !== 'false') { + this.enableMultiSelect(true); + } - ItemsContainerPrototype.attachedCallback = function () { - this.addEventListener('click', onClick); + if (layoutManager.tv) { + this.classList.add('itemsContainer-tv'); + } - if (browser.touch) { - this.addEventListener('contextmenu', disableEvent); + 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; + + const refreshIntervalEndTime = this.refreshIntervalEndTime; + if (refreshIntervalEndTime) { + const remainingMs = refreshIntervalEndTime - new Date().getTime(); + if (remainingMs > 0 && !this.needsRefresh) { + resetRefreshInterval(this, remainingMs); } else { - if (this.getAttribute('data-contextmenu') !== 'false') { - this.addEventListener('contextmenu', onContextMenu); - } + this.needsRefresh = true; + this.refreshIntervalEndTime = null; } + } - if (layoutManager.desktop || layoutManager.mobile && this.getAttribute('data-multiselect') !== 'false') { - this.enableMultiSelect(true); - } + if (this.needsRefresh || (options && options.refresh)) { + return this.refreshItems(); + } - 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; - - const refreshIntervalEndTime = this.refreshIntervalEndTime; - if (refreshIntervalEndTime) { - const 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(); - }; + } - 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; + } + + const 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; } + } +} - if (this.paused) { - this.needsRefresh = true; - return Promise.resolve(); - } +function resetRefreshInterval(itemsContainer, intervalMs) { + clearRefreshInterval(itemsContainer); - this.needsRefresh = false; + if (!intervalMs) { + intervalMs = parseInt(itemsContainer.getAttribute('data-refreshinterval') || '0', 10); + } - return this.fetchData().then(onDataFetched.bind(this)); - }; + if (intervalMs) { + itemsContainer.refreshInterval = setInterval(itemsContainer.notifyRefreshNeeded.bind(itemsContainer), intervalMs); + itemsContainer.refreshIntervalEndTime = new Date().getTime() + intervalMs; + } +} - ItemsContainerPrototype.notifyRefreshNeeded = function (isInForeground) { - if (this.paused) { - this.needsRefresh = true; - return; - } +function onDataFetched(result) { + const items = result.Items || result; - const timeout = this.refreshTimeout; - if (timeout) { - clearTimeout(timeout); - } - - if (isInForeground === true) { - this.refreshItems(); + const parentContainer = this.parentContainer; + if (parentContainer) { + if (items.length) { + parentContainer.classList.remove('hide'); } else { - this.refreshTimeout = setTimeout(this.refreshItems.bind(this), 10000); + parentContainer.classList.add('hide'); } - }; + } - function clearRefreshInterval(itemsContainer, isPausing) { - if (itemsContainer.refreshInterval) { - clearInterval(itemsContainer.refreshInterval); - itemsContainer.refreshInterval = null; + const activeElement = document.activeElement; + let focusId; + let hasActiveElement; - if (!isPausing) { - itemsContainer.refreshIntervalEndTime = null; + 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) { + const newElement = itemsContainer.querySelector('[data-id="' + focusId + '"]'); + if (newElement) { + try { + focusManager.focus(newElement); + return; + } catch (err) { + console.error(err); } } } - function resetRefreshInterval(itemsContainer, intervalMs) { - clearRefreshInterval(itemsContainer); + focusManager.autoFocus(itemsContainer); +} - if (!intervalMs) { - intervalMs = parseInt(itemsContainer.getAttribute('data-refreshinterval') || '0', 10); - } +document.registerElement('emby-itemscontainer', { + prototype: ItemsContainerPrototype, + extends: 'div' +}); - if (intervalMs) { - itemsContainer.refreshInterval = setInterval(itemsContainer.notifyRefreshNeeded.bind(itemsContainer), intervalMs); - itemsContainer.refreshIntervalEndTime = new Date().getTime() + intervalMs; - } - } - - function onDataFetched(result) { - const items = result.Items || result; - - const parentContainer = this.parentContainer; - if (parentContainer) { - if (items.length) { - parentContainer.classList.remove('hide'); - } else { - parentContainer.classList.add('hide'); - } - } - - const activeElement = document.activeElement; - let focusId; - let 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) { - const newElement = itemsContainer.querySelector('[data-id="' + focusId + '"]'); - if (newElement) { - try { - focusManager.focus(newElement); - return; - } catch (err) { - console.error(err); - } - } - } - - focusManager.autoFocus(itemsContainer); - } - - document.registerElement('emby-itemscontainer', { - prototype: ItemsContainerPrototype, - extends: 'div' - }); - -/* eslint-enable indent */ diff --git a/src/elements/emby-playstatebutton/emby-playstatebutton.js b/src/elements/emby-playstatebutton/emby-playstatebutton.js index 79ceb56dd6..6e7756d902 100644 --- a/src/elements/emby-playstatebutton/emby-playstatebutton.js +++ b/src/elements/emby-playstatebutton/emby-playstatebutton.js @@ -4,151 +4,148 @@ import Events from '../../utils/events.ts'; import EmbyButtonPrototype from '../../elements/emby-button/emby-button'; import ServerConnections from '../../components/ServerConnections'; -/* eslint-disable indent */ +function addNotificationEvent(instance, name, handler) { + const localHandler = handler.bind(instance); + Events.on(serverNotifications, name, localHandler); + instance[name] = localHandler; +} - function addNotificationEvent(instance, name, handler) { - const localHandler = handler.bind(instance); - Events.on(serverNotifications, name, localHandler); - instance[name] = localHandler; +function removeNotificationEvent(instance, name) { + const handler = instance[name]; + if (handler) { + Events.off(serverNotifications, name, handler); + instance[name] = null; + } +} + +function onClick() { + const button = this; + const id = button.getAttribute('data-id'); + const serverId = button.getAttribute('data-serverid'); + const apiClient = ServerConnections.getApiClient(serverId); + + if (!button.classList.contains('playstatebutton-played')) { + apiClient.markPlayed(apiClient.getCurrentUserId(), id, new Date()); + setState(button, true); + } else { + apiClient.markUnplayed(apiClient.getCurrentUserId(), id, new Date()); + setState(button, false); + } +} + +function onUserDataChanged(e, apiClient, userData) { + const button = this; + if (userData.ItemId === button.getAttribute('data-id')) { + setState(button, userData.Played); + } +} + +function setState(button, played, updateAttribute) { + let icon = button.iconElement; + if (!icon) { + button.iconElement = button.querySelector('.material-icons'); + icon = button.iconElement; } - function removeNotificationEvent(instance, name) { - const handler = instance[name]; - if (handler) { - Events.off(serverNotifications, name, handler); - instance[name] = null; + if (played) { + button.classList.add('playstatebutton-played'); + if (icon) { + icon.classList.add('playstatebutton-icon-played'); + icon.classList.remove('playstatebutton-icon-unplayed'); + } + } else { + button.classList.remove('playstatebutton-played'); + if (icon) { + icon.classList.remove('playstatebutton-icon-played'); + icon.classList.add('playstatebutton-icon-unplayed'); } } - function onClick() { - const button = this; - const id = button.getAttribute('data-id'); - const serverId = button.getAttribute('data-serverid'); - const apiClient = ServerConnections.getApiClient(serverId); - - if (!button.classList.contains('playstatebutton-played')) { - apiClient.markPlayed(apiClient.getCurrentUserId(), id, new Date()); - setState(button, true); - } else { - apiClient.markUnplayed(apiClient.getCurrentUserId(), id, new Date()); - setState(button, false); - } + if (updateAttribute !== false) { + button.setAttribute('data-played', played); } - function onUserDataChanged(e, apiClient, userData) { - const button = this; - if (userData.ItemId === button.getAttribute('data-id')) { - setState(button, userData.Played); - } + setTitle(button, button.getAttribute('data-type'), played); +} + +function setTitle(button, itemType, played) { + if (itemType !== 'AudioBook' && itemType !== 'AudioPodcast') { + button.title = played ? globalize.translate('Watched') : globalize.translate('MarkPlayed'); + } else { + button.title = played ? globalize.translate('Played') : globalize.translate('MarkPlayed'); } - function setState(button, played, updateAttribute) { - let icon = button.iconElement; - if (!icon) { - button.iconElement = button.querySelector('.material-icons'); - icon = button.iconElement; - } + const text = button.querySelector('.button-text'); + if (text) { + text.innerText = button.title; + } +} - if (played) { - button.classList.add('playstatebutton-played'); - if (icon) { - icon.classList.add('playstatebutton-icon-played'); - icon.classList.remove('playstatebutton-icon-unplayed'); - } - } else { - button.classList.remove('playstatebutton-played'); - if (icon) { - icon.classList.remove('playstatebutton-icon-played'); - icon.classList.add('playstatebutton-icon-unplayed'); - } - } +function clearEvents(button) { + button.removeEventListener('click', onClick); + removeNotificationEvent(button, 'UserDataChanged'); +} - if (updateAttribute !== false) { - button.setAttribute('data-played', played); - } +function bindEvents(button) { + clearEvents(button); - setTitle(button, button.getAttribute('data-type'), played); + button.addEventListener('click', onClick); + addNotificationEvent(button, 'UserDataChanged', onUserDataChanged); +} + +const EmbyPlaystateButtonPrototype = Object.create(EmbyButtonPrototype); + +EmbyPlaystateButtonPrototype.createdCallback = function () { + // base method + if (EmbyButtonPrototype.createdCallback) { + EmbyButtonPrototype.createdCallback.call(this); + } +}; + +EmbyPlaystateButtonPrototype.attachedCallback = function () { + // base method + if (EmbyButtonPrototype.attachedCallback) { + EmbyButtonPrototype.attachedCallback.call(this); } - function setTitle(button, itemType, played) { - if (itemType !== 'AudioBook' && itemType !== 'AudioPodcast') { - button.title = played ? globalize.translate('Watched') : globalize.translate('MarkPlayed'); - } else { - button.title = played ? globalize.translate('Played') : globalize.translate('MarkPlayed'); - } + const itemId = this.getAttribute('data-id'); + const serverId = this.getAttribute('data-serverid'); + if (itemId && serverId) { + setState(this, this.getAttribute('data-played') === 'true', false); + bindEvents(this); + } +}; - const text = button.querySelector('.button-text'); - if (text) { - text.innerText = button.title; - } +EmbyPlaystateButtonPrototype.detachedCallback = function () { + // base method + if (EmbyButtonPrototype.detachedCallback) { + EmbyButtonPrototype.detachedCallback.call(this); } - function clearEvents(button) { - button.removeEventListener('click', onClick); - removeNotificationEvent(button, 'UserDataChanged'); - } + clearEvents(this); + this.iconElement = null; +}; - function bindEvents(button) { - clearEvents(button); - - button.addEventListener('click', onClick); - addNotificationEvent(button, 'UserDataChanged', onUserDataChanged); - } - - const EmbyPlaystateButtonPrototype = Object.create(EmbyButtonPrototype); - - EmbyPlaystateButtonPrototype.createdCallback = function () { - // base method - if (EmbyButtonPrototype.createdCallback) { - EmbyButtonPrototype.createdCallback.call(this); - } - }; - - EmbyPlaystateButtonPrototype.attachedCallback = function () { - // base method - if (EmbyButtonPrototype.attachedCallback) { - EmbyButtonPrototype.attachedCallback.call(this); - } - - const itemId = this.getAttribute('data-id'); - const serverId = this.getAttribute('data-serverid'); - if (itemId && serverId) { - setState(this, this.getAttribute('data-played') === 'true', false); - bindEvents(this); - } - }; - - EmbyPlaystateButtonPrototype.detachedCallback = function () { - // base method - if (EmbyButtonPrototype.detachedCallback) { - EmbyButtonPrototype.detachedCallback.call(this); - } +EmbyPlaystateButtonPrototype.setItem = function (item) { + if (item) { + this.setAttribute('data-id', item.Id); + this.setAttribute('data-serverid', item.ServerId); + this.setAttribute('data-type', item.Type); + const played = item.UserData && item.UserData.Played; + setState(this, played); + bindEvents(this); + } else { + this.removeAttribute('data-id'); + this.removeAttribute('data-serverid'); + this.removeAttribute('data-type'); + this.removeAttribute('data-played'); clearEvents(this); - this.iconElement = null; - }; + } +}; - EmbyPlaystateButtonPrototype.setItem = function (item) { - if (item) { - this.setAttribute('data-id', item.Id); - this.setAttribute('data-serverid', item.ServerId); - this.setAttribute('data-type', item.Type); +document.registerElement('emby-playstatebutton', { + prototype: EmbyPlaystateButtonPrototype, + extends: 'button' +}); - const played = item.UserData && item.UserData.Played; - setState(this, played); - bindEvents(this); - } else { - this.removeAttribute('data-id'); - this.removeAttribute('data-serverid'); - this.removeAttribute('data-type'); - this.removeAttribute('data-played'); - clearEvents(this); - } - }; - - document.registerElement('emby-playstatebutton', { - prototype: EmbyPlaystateButtonPrototype, - extends: 'button' - }); - -/* eslint-enable indent */ diff --git a/src/elements/emby-progressbar/emby-progressbar.js b/src/elements/emby-progressbar/emby-progressbar.js index cad2f4bbfa..6fb1c0e5b2 100644 --- a/src/elements/emby-progressbar/emby-progressbar.js +++ b/src/elements/emby-progressbar/emby-progressbar.js @@ -1,42 +1,40 @@ -/* eslint-disable indent */ - const ProgressBarPrototype = Object.create(HTMLDivElement.prototype); +const ProgressBarPrototype = Object.create(HTMLDivElement.prototype); - function onAutoTimeProgress() { - const start = parseInt(this.getAttribute('data-starttime'), 10); - const end = parseInt(this.getAttribute('data-endtime'), 10); +function onAutoTimeProgress() { + const start = parseInt(this.getAttribute('data-starttime'), 10); + const end = parseInt(this.getAttribute('data-endtime'), 10); - const now = new Date().getTime(); - const total = end - start; - let pct = 100 * ((now - start) / total); + const now = new Date().getTime(); + const total = end - start; + let pct = 100 * ((now - start) / total); - pct = Math.min(100, pct); - pct = Math.max(0, pct); + pct = Math.min(100, pct); + pct = Math.max(0, pct); - const itemProgressBarForeground = this.querySelector('.itemProgressBarForeground'); - itemProgressBarForeground.style.width = pct + '%'; + const itemProgressBarForeground = this.querySelector('.itemProgressBarForeground'); + itemProgressBarForeground.style.width = pct + '%'; +} + +ProgressBarPrototype.attachedCallback = function () { + if (this.timeInterval) { + clearInterval(this.timeInterval); } - ProgressBarPrototype.attachedCallback = function () { - if (this.timeInterval) { - clearInterval(this.timeInterval); - } + if (this.getAttribute('data-automode') === 'time') { + this.timeInterval = setInterval(onAutoTimeProgress.bind(this), 60000); + } +}; - if (this.getAttribute('data-automode') === 'time') { - this.timeInterval = setInterval(onAutoTimeProgress.bind(this), 60000); - } - }; +ProgressBarPrototype.detachedCallback = function () { + if (this.timeInterval) { + clearInterval(this.timeInterval); + this.timeInterval = null; + } +}; - ProgressBarPrototype.detachedCallback = function () { - if (this.timeInterval) { - clearInterval(this.timeInterval); - this.timeInterval = null; - } - }; +document.registerElement('emby-progressbar', { + prototype: ProgressBarPrototype, + extends: 'div' +}); - document.registerElement('emby-progressbar', { - prototype: ProgressBarPrototype, - extends: 'div' - }); - -/* eslint-enable indent */ diff --git a/src/elements/emby-progressring/emby-progressring.js b/src/elements/emby-progressring/emby-progressring.js index 63d9fd2221..9ef8285250 100644 --- a/src/elements/emby-progressring/emby-progressring.js +++ b/src/elements/emby-progressring/emby-progressring.js @@ -4,98 +4,95 @@ import template from './emby-progressring.template.html'; import { getCurrentDateTimeLocale } from '../../scripts/globalize'; import { toPercent } from '../../utils/number.ts'; -/* eslint-disable indent */ +const EmbyProgressRing = Object.create(HTMLDivElement.prototype); - const EmbyProgressRing = Object.create(HTMLDivElement.prototype); +EmbyProgressRing.createdCallback = function () { + this.classList.add('progressring'); + this.setAttribute('dir', 'ltr'); + const instance = this; - EmbyProgressRing.createdCallback = function () { - this.classList.add('progressring'); - this.setAttribute('dir', 'ltr'); - const instance = this; + instance.innerHTML = template; - instance.innerHTML = template; - - if (window.MutationObserver) { - // create an observer instance - const observer = new MutationObserver(function (mutations) { - mutations.forEach(function () { - instance.setProgress(parseFloat(instance.getAttribute('data-progress') || '0')); - }); + if (window.MutationObserver) { + // create an observer instance + const observer = new MutationObserver(function (mutations) { + mutations.forEach(function () { + instance.setProgress(parseFloat(instance.getAttribute('data-progress') || '0')); }); + }); - // configuration of the observer: - const config = { attributes: true, childList: false, characterData: false }; + // configuration of the observer: + const config = { attributes: true, childList: false, characterData: false }; - // pass in the target node, as well as the observer options - observer.observe(instance, config); + // pass in the target node, as well as the observer options + observer.observe(instance, config); - instance.observer = observer; - } + instance.observer = observer; + } - instance.setProgress(parseFloat(instance.getAttribute('data-progress') || '0')); - }; + instance.setProgress(parseFloat(instance.getAttribute('data-progress') || '0')); +}; - EmbyProgressRing.setProgress = function (progress) { - progress = Math.floor(progress); +EmbyProgressRing.setProgress = function (progress) { + progress = Math.floor(progress); - let angle; + let angle; - if (progress < 25) { - angle = -90 + (progress / 100) * 360; + if (progress < 25) { + angle = -90 + (progress / 100) * 360; - this.querySelector('.animate-0-25-b').style.transform = 'rotate(' + angle + 'deg)'; + 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-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-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-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-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-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('.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 = toPercent(progress / 100, getCurrentDateTimeLocale()); - }; + this.querySelector('.progressring-text').innerHTML = toPercent(progress / 100, getCurrentDateTimeLocale()); +}; - EmbyProgressRing.attachedCallback = function () { - // no-op - }; +EmbyProgressRing.attachedCallback = function () { + // no-op +}; - EmbyProgressRing.detachedCallback = function () { - const observer = this.observer; +EmbyProgressRing.detachedCallback = function () { + const observer = this.observer; - if (observer) { - // later, you can stop observing - observer.disconnect(); + if (observer) { + // later, you can stop observing + observer.disconnect(); - this.observer = null; - } - }; + this.observer = null; + } +}; - document.registerElement('emby-progressring', { - prototype: EmbyProgressRing, - extends: 'div' - }); +document.registerElement('emby-progressring', { + prototype: EmbyProgressRing, + extends: 'div' +}); - export default EmbyProgressRing; +export default EmbyProgressRing; -/* eslint-enable indent */ diff --git a/src/elements/emby-radio/emby-radio.js b/src/elements/emby-radio/emby-radio.js index 0dadc38f99..10820c1c1a 100644 --- a/src/elements/emby-radio/emby-radio.js +++ b/src/elements/emby-radio/emby-radio.js @@ -3,80 +3,77 @@ import browser from '../../scripts/browser'; import 'webcomponents.js/webcomponents-lite'; import './emby-radio.scss'; -/* eslint-disable indent */ +const EmbyRadioPrototype = Object.create(HTMLInputElement.prototype); - const EmbyRadioPrototype = Object.create(HTMLInputElement.prototype); +function onKeyDown(e) { + // Don't submit form on enter + // Real (non-emulator) Tizen does nothing on Space + if (e.keyCode === 13 || (e.keyCode === 32 && browser.tizen)) { + e.preventDefault(); - function onKeyDown(e) { - // Don't submit form on enter - // Real (non-emulator) Tizen does nothing on Space - if (e.keyCode === 13 || (e.keyCode === 32 && browser.tizen)) { - e.preventDefault(); + if (!this.checked) { + this.checked = true; - if (!this.checked) { - this.checked = true; - - this.dispatchEvent(new CustomEvent('change', { - bubbles: true - })); - } - - return false; + this.dispatchEvent(new CustomEvent('change', { + bubbles: true + })); } + + return false; + } +} + +EmbyRadioPrototype.attachedCallback = function () { + const showFocus = !layoutManager.mobile; + + if (this.getAttribute('data-radio') === 'true') { + return; } - EmbyRadioPrototype.attachedCallback = function () { - const showFocus = !layoutManager.mobile; + this.setAttribute('data-radio', 'true'); - if (this.getAttribute('data-radio') === 'true') { - return; - } + this.classList.add('mdl-radio__button'); - this.setAttribute('data-radio', 'true'); + const labelElement = this.parentNode; + labelElement.classList.add('mdl-radio'); + labelElement.classList.add('mdl-js-radio'); + labelElement.classList.add('mdl-js-ripple-effect'); + if (showFocus) { + labelElement.classList.add('show-focus'); + } - this.classList.add('mdl-radio__button'); + const labelTextElement = labelElement.querySelector('span'); - const labelElement = this.parentNode; - labelElement.classList.add('mdl-radio'); - labelElement.classList.add('mdl-js-radio'); - labelElement.classList.add('mdl-js-ripple-effect'); - if (showFocus) { - labelElement.classList.add('show-focus'); - } + labelTextElement.classList.add('radioButtonLabel'); + labelTextElement.classList.add('mdl-radio__label'); - const labelTextElement = labelElement.querySelector('span'); + let html = ''; - labelTextElement.classList.add('radioButtonLabel'); - labelTextElement.classList.add('mdl-radio__label'); + html += '
'; - let html = ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; - html += '
'; + if (showFocus) { + html += '
'; + } - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; + html += '
'; - if (showFocus) { - html += '
'; - } + this.insertAdjacentHTML('afterend', html); - html += '
'; + this.addEventListener('keydown', onKeyDown); +}; - this.insertAdjacentHTML('afterend', html); +document.registerElement('emby-radio', { + prototype: EmbyRadioPrototype, + extends: 'input' +}); - this.addEventListener('keydown', onKeyDown); - }; - - document.registerElement('emby-radio', { - prototype: EmbyRadioPrototype, - extends: 'input' - }); - -/* eslint-enable indent */ diff --git a/src/elements/emby-ratingbutton/emby-ratingbutton.js b/src/elements/emby-ratingbutton/emby-ratingbutton.js index 0cc87df3c1..1c17836bc4 100644 --- a/src/elements/emby-ratingbutton/emby-ratingbutton.js +++ b/src/elements/emby-ratingbutton/emby-ratingbutton.js @@ -4,32 +4,119 @@ import Events from '../../utils/events.ts'; import EmbyButtonPrototype from '../emby-button/emby-button'; import ServerConnections from '../../components/ServerConnections'; -/* eslint-disable indent */ +function addNotificationEvent(instance, name, handler) { + const localHandler = handler.bind(instance); + Events.on(serverNotifications, name, localHandler); + instance[name] = localHandler; +} - function addNotificationEvent(instance, name, handler) { - const localHandler = handler.bind(instance); - Events.on(serverNotifications, name, localHandler); - instance[name] = localHandler; +function removeNotificationEvent(instance, name) { + const handler = instance[name]; + if (handler) { + Events.off(serverNotifications, name, handler); + instance[name] = null; + } +} + +function showPicker(button, apiClient, itemId, likes, isFavorite) { + return apiClient.updateFavoriteStatus(apiClient.getCurrentUserId(), itemId, !isFavorite); +} + +function onClick() { + const button = this; + const id = button.getAttribute('data-id'); + const serverId = button.getAttribute('data-serverid'); + const apiClient = ServerConnections.getApiClient(serverId); + + let likes = this.getAttribute('data-likes'); + const isFavorite = this.getAttribute('data-isfavorite') === 'true'; + if (likes === 'true') { + likes = true; + } else if (likes === 'false') { + likes = false; + } else { + likes = null; } - function removeNotificationEvent(instance, name) { - const handler = instance[name]; - if (handler) { - Events.off(serverNotifications, name, handler); - instance[name] = null; + showPicker(button, apiClient, id, likes, isFavorite).then(function (userData) { + setState(button, userData.Likes, userData.IsFavorite); + }); +} + +function onUserDataChanged(e, apiClient, userData) { + const button = this; + + if (userData.ItemId === button.getAttribute('data-id')) { + setState(button, userData.Likes, userData.IsFavorite); + } +} + +function setState(button, likes, isFavorite, updateAttribute) { + const icon = button.querySelector('.material-icons'); + + if (isFavorite) { + if (icon) { + icon.classList.add('favorite'); + icon.classList.add('ratingbutton-icon-withrating'); } + + button.classList.add('ratingbutton-withrating'); + } else { + if (icon) { + icon.classList.add('favorite'); + icon.classList.remove('ratingbutton-icon-withrating'); + } + button.classList.remove('ratingbutton-withrating'); } - function showPicker(button, apiClient, itemId, likes, isFavorite) { - return apiClient.updateFavoriteStatus(apiClient.getCurrentUserId(), itemId, !isFavorite); + if (updateAttribute !== false) { + button.setAttribute('data-isfavorite', isFavorite); + + button.setAttribute('data-likes', (likes === null ? '' : likes)); } - function onClick() { - const button = this; - const id = button.getAttribute('data-id'); - const serverId = button.getAttribute('data-serverid'); - const apiClient = ServerConnections.getApiClient(serverId); + setTitle(button, isFavorite); +} +function setTitle(button, isFavorite) { + button.title = isFavorite ? globalize.translate('Favorite') : globalize.translate('AddToFavorites'); + + const text = button.querySelector('.button-text'); + if (text) { + text.innerText = button.title; + } +} + +function clearEvents(button) { + button.removeEventListener('click', onClick); + removeNotificationEvent(button, 'UserDataChanged'); +} + +function bindEvents(button) { + clearEvents(button); + + button.addEventListener('click', onClick); + addNotificationEvent(button, 'UserDataChanged', onUserDataChanged); +} + +const EmbyRatingButtonPrototype = Object.create(EmbyButtonPrototype); + +EmbyRatingButtonPrototype.createdCallback = function () { + // base method + if (EmbyButtonPrototype.createdCallback) { + EmbyButtonPrototype.createdCallback.call(this); + } +}; + +EmbyRatingButtonPrototype.attachedCallback = function () { + // base method + if (EmbyButtonPrototype.attachedCallback) { + EmbyButtonPrototype.attachedCallback.call(this); + } + + const itemId = this.getAttribute('data-id'); + const serverId = this.getAttribute('data-serverid'); + if (itemId && serverId) { let likes = this.getAttribute('data-likes'); const isFavorite = this.getAttribute('data-isfavorite') === 'true'; if (likes === 'true') { @@ -40,131 +127,41 @@ import ServerConnections from '../../components/ServerConnections'; likes = null; } - showPicker(button, apiClient, id, likes, isFavorite).then(function (userData) { - setState(button, userData.Likes, userData.IsFavorite); - }); + setState(this, likes, isFavorite, false); + bindEvents(this); + } else { + setTitle(this); + } +}; + +EmbyRatingButtonPrototype.detachedCallback = function () { + // base method + if (EmbyButtonPrototype.detachedCallback) { + EmbyButtonPrototype.detachedCallback.call(this); } - function onUserDataChanged(e, apiClient, userData) { - const button = this; + clearEvents(this); +}; - if (userData.ItemId === button.getAttribute('data-id')) { - setState(button, userData.Likes, userData.IsFavorite); - } - } - - function setState(button, likes, isFavorite, updateAttribute) { - const icon = button.querySelector('.material-icons'); - - if (isFavorite) { - if (icon) { - icon.classList.add('favorite'); - icon.classList.add('ratingbutton-icon-withrating'); - } - - button.classList.add('ratingbutton-withrating'); - } else { - if (icon) { - icon.classList.add('favorite'); - icon.classList.remove('ratingbutton-icon-withrating'); - } - button.classList.remove('ratingbutton-withrating'); - } - - if (updateAttribute !== false) { - button.setAttribute('data-isfavorite', isFavorite); - - button.setAttribute('data-likes', (likes === null ? '' : likes)); - } - - setTitle(button, isFavorite); - } - - function setTitle(button, isFavorite) { - button.title = isFavorite ? globalize.translate('Favorite') : globalize.translate('AddToFavorites'); - - const text = button.querySelector('.button-text'); - if (text) { - text.innerText = button.title; - } - } - - function clearEvents(button) { - button.removeEventListener('click', onClick); - removeNotificationEvent(button, 'UserDataChanged'); - } - - function bindEvents(button) { - clearEvents(button); - - button.addEventListener('click', onClick); - addNotificationEvent(button, 'UserDataChanged', onUserDataChanged); - } - - const EmbyRatingButtonPrototype = Object.create(EmbyButtonPrototype); - - EmbyRatingButtonPrototype.createdCallback = function () { - // base method - if (EmbyButtonPrototype.createdCallback) { - EmbyButtonPrototype.createdCallback.call(this); - } - }; - - EmbyRatingButtonPrototype.attachedCallback = function () { - // base method - if (EmbyButtonPrototype.attachedCallback) { - EmbyButtonPrototype.attachedCallback.call(this); - } - - const itemId = this.getAttribute('data-id'); - const serverId = this.getAttribute('data-serverid'); - if (itemId && serverId) { - let likes = this.getAttribute('data-likes'); - const isFavorite = this.getAttribute('data-isfavorite') === 'true'; - if (likes === 'true') { - likes = true; - } else if (likes === 'false') { - likes = false; - } else { - likes = null; - } - - setState(this, likes, isFavorite, false); - bindEvents(this); - } else { - setTitle(this); - } - }; - - EmbyRatingButtonPrototype.detachedCallback = function () { - // base method - if (EmbyButtonPrototype.detachedCallback) { - EmbyButtonPrototype.detachedCallback.call(this); - } +EmbyRatingButtonPrototype.setItem = function (item) { + if (item) { + this.setAttribute('data-id', item.Id); + this.setAttribute('data-serverid', item.ServerId); + const userData = item.UserData || {}; + setState(this, userData.Likes, userData.IsFavorite); + bindEvents(this); + } else { + this.removeAttribute('data-id'); + this.removeAttribute('data-serverid'); + this.removeAttribute('data-likes'); + this.removeAttribute('data-isfavorite'); clearEvents(this); - }; + } +}; - EmbyRatingButtonPrototype.setItem = function (item) { - if (item) { - this.setAttribute('data-id', item.Id); - this.setAttribute('data-serverid', item.ServerId); +document.registerElement('emby-ratingbutton', { + prototype: EmbyRatingButtonPrototype, + extends: 'button' +}); - const userData = item.UserData || {}; - setState(this, userData.Likes, userData.IsFavorite); - bindEvents(this); - } else { - this.removeAttribute('data-id'); - this.removeAttribute('data-serverid'); - this.removeAttribute('data-likes'); - this.removeAttribute('data-isfavorite'); - clearEvents(this); - } - }; - - document.registerElement('emby-ratingbutton', { - prototype: EmbyRatingButtonPrototype, - extends: 'button' - }); - -/* eslint-enable indent */ diff --git a/src/elements/emby-scrollbuttons/emby-scrollbuttons.js b/src/elements/emby-scrollbuttons/emby-scrollbuttons.js index 3d98918d7a..770a435ef6 100644 --- a/src/elements/emby-scrollbuttons/emby-scrollbuttons.js +++ b/src/elements/emby-scrollbuttons/emby-scrollbuttons.js @@ -3,198 +3,195 @@ import 'webcomponents.js/webcomponents-lite'; import '../emby-button/paper-icon-button-light'; import globalize from '../../scripts/globalize'; -/* eslint-disable indent */ - const EmbyScrollButtonsPrototype = Object.create(HTMLDivElement.prototype); - EmbyScrollButtonsPrototype.createdCallback = function () { - // no-op - }; +EmbyScrollButtonsPrototype.createdCallback = function () { + // no-op +}; - function getScrollButtonHtml(direction) { - let html = ''; - const icon = direction === 'left' ? 'chevron_left' : 'chevron_right'; - const title = direction === 'left' ? globalize.translate('Previous') : globalize.translate('Next') ; +function getScrollButtonHtml(direction) { + let html = ''; + const icon = direction === 'left' ? 'chevron_left' : 'chevron_right'; + const title = direction === 'left' ? globalize.translate('Previous') : globalize.translate('Next') ; - html += `'; + html += `'; - return html; + return html; +} + +function getScrollPosition(parent) { + if (parent.getScrollPosition) { + return parent.getScrollPosition(); } - function getScrollPosition(parent) { - if (parent.getScrollPosition) { - return parent.getScrollPosition(); - } + return 0; +} +function getScrollWidth(parent) { + if (parent.getScrollSize) { + return parent.getScrollSize(); + } + + return 0; +} + +function updateScrollButtons(scrollButtons, scrollSize, scrollPos, scrollWidth) { + let localeAwarePos = scrollPos; + if (globalize.getIsElementRTL(scrollButtons)) { + localeAwarePos *= -1; + } + + // TODO: Check if hack is really needed + // hack alert add twenty for rounding errors + if (scrollWidth <= scrollSize + 20) { + scrollButtons.scrollButtonsLeft.classList.add('hide'); + scrollButtons.scrollButtonsRight.classList.add('hide'); + } else { + scrollButtons.scrollButtonsLeft.classList.remove('hide'); + scrollButtons.scrollButtonsRight.classList.remove('hide'); + } + + if (localeAwarePos > 0) { + scrollButtons.scrollButtonsLeft.disabled = false; + } else { + scrollButtons.scrollButtonsLeft.disabled = true; + } + + const scrollPosEnd = localeAwarePos + scrollSize; + if (scrollWidth > 0 && scrollPosEnd >= scrollWidth) { + scrollButtons.scrollButtonsRight.disabled = true; + } else { + scrollButtons.scrollButtonsRight.disabled = false; + } +} + +function onScroll() { + const scrollButtons = this; + const scroller = this.scroller; + + const scrollSize = getScrollSize(scroller); + const scrollPos = getScrollPosition(scroller); + const scrollWidth = getScrollWidth(scroller); + + updateScrollButtons(scrollButtons, scrollSize, scrollPos, scrollWidth); +} + +function getStyleValue(style, name) { + let value = style.getPropertyValue(name); + if (!value) { return 0; } - function getScrollWidth(parent) { - if (parent.getScrollSize) { - return parent.getScrollSize(); - } - + value = value.replace('px', ''); + if (!value) { return 0; } - function updateScrollButtons(scrollButtons, scrollSize, scrollPos, scrollWidth) { - let localeAwarePos = scrollPos; - if (globalize.getIsElementRTL(scrollButtons)) { - localeAwarePos *= -1; - } - - // TODO: Check if hack is really needed - // hack alert add twenty for rounding errors - if (scrollWidth <= scrollSize + 20) { - scrollButtons.scrollButtonsLeft.classList.add('hide'); - scrollButtons.scrollButtonsRight.classList.add('hide'); - } else { - scrollButtons.scrollButtonsLeft.classList.remove('hide'); - scrollButtons.scrollButtonsRight.classList.remove('hide'); - } - - if (localeAwarePos > 0) { - scrollButtons.scrollButtonsLeft.disabled = false; - } else { - scrollButtons.scrollButtonsLeft.disabled = true; - } - - const scrollPosEnd = localeAwarePos + scrollSize; - if (scrollWidth > 0 && scrollPosEnd >= scrollWidth) { - scrollButtons.scrollButtonsRight.disabled = true; - } else { - scrollButtons.scrollButtonsRight.disabled = false; - } + value = parseInt(value, 10); + if (isNaN(value)) { + return 0; } - function onScroll() { - const scrollButtons = this; - const scroller = this.scroller; + return value; +} - const scrollSize = getScrollSize(scroller); - const scrollPos = getScrollPosition(scroller); - const scrollWidth = getScrollWidth(scroller); +function getScrollSize(elem) { + let scrollSize = elem.offsetWidth; + let style = window.getComputedStyle(elem, null); - updateScrollButtons(scrollButtons, scrollSize, scrollPos, scrollWidth); + let paddingLeft = getStyleValue(style, 'padding-left'); + if (paddingLeft) { + scrollSize -= paddingLeft; } - function getStyleValue(style, name) { - let value = style.getPropertyValue(name); - if (!value) { - return 0; - } - - value = value.replace('px', ''); - if (!value) { - return 0; - } - - value = parseInt(value, 10); - if (isNaN(value)) { - return 0; - } - - return value; + let paddingRight = getStyleValue(style, 'padding-right'); + if (paddingRight) { + scrollSize -= paddingRight; } - function getScrollSize(elem) { - let scrollSize = elem.offsetWidth; - let style = window.getComputedStyle(elem, null); + const slider = elem.getScrollSlider(); + style = window.getComputedStyle(slider, null); - let paddingLeft = getStyleValue(style, 'padding-left'); - if (paddingLeft) { - scrollSize -= paddingLeft; - } - - let paddingRight = getStyleValue(style, 'padding-right'); - if (paddingRight) { - scrollSize -= paddingRight; - } - - const 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; + paddingLeft = getStyleValue(style, 'padding-left'); + if (paddingLeft) { + scrollSize -= paddingLeft; } - function onScrollButtonClick() { - const scroller = this.parentNode.nextSibling; - - const direction = this.getAttribute('data-direction'); - const scrollSize = getScrollSize(scroller); - const scrollPos = getScrollPosition(scroller); - - let newPos; - if (direction === 'left') { - newPos = Math.max(0, scrollPos - scrollSize); - } else { - newPos = scrollPos + scrollSize; - } - - if (globalize.getIsRTL() && direction === 'left') { - newPos = scrollPos + scrollSize; - } else if (globalize.getIsRTL()) { - newPos = Math.min(0, scrollPos - scrollSize); - } - - scroller.scrollToPosition(newPos, false); + paddingRight = getStyleValue(style, 'padding-right'); + if (paddingRight) { + scrollSize -= paddingRight; } - EmbyScrollButtonsPrototype.attachedCallback = function () { - const scroller = this.nextSibling; - this.scroller = scroller; + return scrollSize; +} - const parent = this.parentNode; - parent.classList.add('emby-scroller-container'); +function onScrollButtonClick() { + const scroller = this.parentNode.nextSibling; - this.innerHTML = getScrollButtonHtml('left') + getScrollButtonHtml('right'); + const direction = this.getAttribute('data-direction'); + const scrollSize = getScrollSize(scroller); + const scrollPos = getScrollPosition(scroller); - const buttons = this.querySelectorAll('.emby-scrollbuttons-button'); - buttons[0].addEventListener('click', onScrollButtonClick); - buttons[1].addEventListener('click', onScrollButtonClick); - this.scrollButtonsLeft = buttons[0]; - this.scrollButtonsRight = buttons[1]; + let newPos; + if (direction === 'left') { + newPos = Math.max(0, scrollPos - scrollSize); + } else { + newPos = scrollPos + scrollSize; + } - const scrollHandler = onScroll.bind(this); - this.scrollHandler = scrollHandler; - scroller.addScrollEventListener(scrollHandler, { + if (globalize.getIsRTL() && direction === 'left') { + newPos = scrollPos + scrollSize; + } else if (globalize.getIsRTL()) { + newPos = Math.min(0, scrollPos - scrollSize); + } + + scroller.scrollToPosition(newPos, false); +} + +EmbyScrollButtonsPrototype.attachedCallback = function () { + const scroller = this.nextSibling; + this.scroller = scroller; + + const parent = this.parentNode; + parent.classList.add('emby-scroller-container'); + + this.innerHTML = getScrollButtonHtml('left') + getScrollButtonHtml('right'); + + const buttons = this.querySelectorAll('.emby-scrollbuttons-button'); + buttons[0].addEventListener('click', onScrollButtonClick); + buttons[1].addEventListener('click', onScrollButtonClick); + this.scrollButtonsLeft = buttons[0]; + this.scrollButtonsRight = buttons[1]; + + const scrollHandler = onScroll.bind(this); + this.scrollHandler = scrollHandler; + scroller.addScrollEventListener(scrollHandler, { + capture: false, + passive: true + }); +}; + +EmbyScrollButtonsPrototype.detachedCallback = function () { + const parent = this.scroller; + this.scroller = null; + + const scrollHandler = this.scrollHandler; + if (parent && scrollHandler) { + parent.removeScrollEventListener(scrollHandler, { capture: false, passive: true }); - }; + } - EmbyScrollButtonsPrototype.detachedCallback = function () { - const parent = this.scroller; - this.scroller = null; + this.scrollHandler = null; + this.scrollButtonsLeft = null; + this.scrollButtonsRight = null; +}; - const scrollHandler = this.scrollHandler; - if (parent && scrollHandler) { - parent.removeScrollEventListener(scrollHandler, { - capture: false, - passive: true - }); - } +document.registerElement('emby-scrollbuttons', { + prototype: EmbyScrollButtonsPrototype, + extends: 'div' +}); - this.scrollHandler = null; - this.scrollButtonsLeft = null; - this.scrollButtonsRight = null; - }; - - document.registerElement('emby-scrollbuttons', { - prototype: EmbyScrollButtonsPrototype, - extends: 'div' - }); - -/* eslint-enable indent */ diff --git a/src/elements/emby-scroller/emby-scroller.js b/src/elements/emby-scroller/emby-scroller.js index 6a4c6064ec..eb5f3abfe4 100644 --- a/src/elements/emby-scroller/emby-scroller.js +++ b/src/elements/emby-scroller/emby-scroller.js @@ -7,195 +7,192 @@ import browser from '../../scripts/browser'; import 'webcomponents.js/webcomponents-lite'; import './emby-scroller.scss'; -/* eslint-disable indent */ +const ScrollerPrototype = Object.create(HTMLDivElement.prototype); - const ScrollerPrototype = Object.create(HTMLDivElement.prototype); +ScrollerPrototype.createdCallback = function () { + this.classList.add('emby-scroller'); +}; - ScrollerPrototype.createdCallback = function () { - this.classList.add('emby-scroller'); - }; - - function initCenterFocus(elem, scrollerInstance) { - dom.addEventListener(elem, 'focus', function (e) { - const 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); +function initCenterFocus(elem, scrollerInstance) { + dom.addEventListener(elem, 'focus', function (e) { + const focused = focusManager.focusableParent(e.target); + if (focused) { + scrollerInstance.toCenter(focused); } - }; - - 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) { - const 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(); - } - } - - ScrollerPrototype.attachedCallback = function () { - if (this.getAttribute('data-navcommands')) { - inputManager.on(this, onInputCommand); - } - - const horizontal = this.getAttribute('data-horizontal') !== 'false'; - - const slider = this.querySelector('.scrollSlider'); - - if (horizontal) { - slider.style['white-space'] = 'nowrap'; - } - - const scrollFrame = this; - const enableScrollButtons = layoutManager.desktop && horizontal && this.getAttribute('data-scrollbuttons') !== 'false'; - - const options = { - horizontal: horizontal, - mouseDragging: 1, - mouseWheel: this.getAttribute('data-mousewheel') !== 'false', - touchDragging: 1, - slidee: slider, - scrollBy: 200, - speed: horizontal ? 270 : 240, - elasticBounds: 1, - dragHandle: 1, - autoImmediate: true, - skipSlideToWhenVisible: this.getAttribute('data-skipfocuswhenvisible') === 'true', - dispatchScrollEvent: enableScrollButtons || 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(); - this.scroller.reload(); - - if (layoutManager.tv && this.getAttribute('data-centerfocus')) { - initCenterFocus(this, this.scroller); - } - - if (enableScrollButtons) { - loadScrollButtons(this); - } - }; - - function loadScrollButtons(buttonsScroller) { - import('../emby-scrollbuttons/emby-scrollbuttons').then(() => { - buttonsScroller.insertAdjacentHTML('beforebegin', '
'); - }); - } - - ScrollerPrototype.pause = function () { - const headroom = this.headroom; - if (headroom) { - headroom.pause(); - } - }; - - ScrollerPrototype.resume = function () { - const headroom = this.headroom; - if (headroom) { - headroom.resume(); - } - }; - - ScrollerPrototype.detachedCallback = function () { - if (this.getAttribute('data-navcommands')) { - inputManager.off(this, onInputCommand); - } - - const headroom = this.headroom; - if (headroom) { - headroom.destroy(); - this.headroom = null; - } - - const scrollerInstance = this.scroller; - if (scrollerInstance) { - scrollerInstance.destroy(); - this.scroller = null; - } - }; - - document.registerElement('emby-scroller', { - prototype: ScrollerPrototype, - extends: 'div' + }, { + 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) { + const 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(); + } +} + +ScrollerPrototype.attachedCallback = function () { + if (this.getAttribute('data-navcommands')) { + inputManager.on(this, onInputCommand); + } + + const horizontal = this.getAttribute('data-horizontal') !== 'false'; + + const slider = this.querySelector('.scrollSlider'); + + if (horizontal) { + slider.style['white-space'] = 'nowrap'; + } + + const scrollFrame = this; + const enableScrollButtons = layoutManager.desktop && horizontal && this.getAttribute('data-scrollbuttons') !== 'false'; + + const options = { + horizontal: horizontal, + mouseDragging: 1, + mouseWheel: this.getAttribute('data-mousewheel') !== 'false', + touchDragging: 1, + slidee: slider, + scrollBy: 200, + speed: horizontal ? 270 : 240, + elasticBounds: 1, + dragHandle: 1, + autoImmediate: true, + skipSlideToWhenVisible: this.getAttribute('data-skipfocuswhenvisible') === 'true', + dispatchScrollEvent: enableScrollButtons || 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(); + this.scroller.reload(); + + if (layoutManager.tv && this.getAttribute('data-centerfocus')) { + initCenterFocus(this, this.scroller); + } + + if (enableScrollButtons) { + loadScrollButtons(this); + } +}; + +function loadScrollButtons(buttonsScroller) { + import('../emby-scrollbuttons/emby-scrollbuttons').then(() => { + buttonsScroller.insertAdjacentHTML('beforebegin', '
'); + }); +} + +ScrollerPrototype.pause = function () { + const headroom = this.headroom; + if (headroom) { + headroom.pause(); + } +}; + +ScrollerPrototype.resume = function () { + const headroom = this.headroom; + if (headroom) { + headroom.resume(); + } +}; + +ScrollerPrototype.detachedCallback = function () { + if (this.getAttribute('data-navcommands')) { + inputManager.off(this, onInputCommand); + } + + const headroom = this.headroom; + if (headroom) { + headroom.destroy(); + this.headroom = null; + } + + const scrollerInstance = this.scroller; + if (scrollerInstance) { + scrollerInstance.destroy(); + this.scroller = null; + } +}; + +document.registerElement('emby-scroller', { + prototype: ScrollerPrototype, + extends: 'div' +}); -/* eslint-enable indent */ diff --git a/src/elements/emby-select/emby-select.js b/src/elements/emby-select/emby-select.js index b576042821..e4950ebf6c 100644 --- a/src/elements/emby-select/emby-select.js +++ b/src/elements/emby-select/emby-select.js @@ -4,152 +4,149 @@ import actionsheet from '../../components/actionSheet/actionSheet'; import './emby-select.scss'; import 'webcomponents.js/webcomponents-lite'; -/* eslint-disable indent */ +const EmbySelectPrototype = Object.create(HTMLSelectElement.prototype); - const 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; - } - - return !layoutManager.tv; +function enableNativeMenu() { + if (browser.edgeUwp || browser.xboxOne) { + return true; } - function triggerChange(select) { - const evt = document.createEvent('HTMLEvents'); - evt.initEvent('change', false, true); - select.dispatchEvent(evt); + // Doesn't seem to work at all + if (browser.tizen || browser.orsay || browser.web0s) { + return false; } - function setValue(select, value) { - select.value = value; + // Take advantage of the native input methods + if (browser.tv) { + return true; } - function showActionSheet(select) { - const labelElem = getLabel(select); - const title = labelElem ? (labelElem.textContent || labelElem.innerText) : null; + return !layoutManager.tv; +} - actionsheet.show({ - items: select.options, - positionTo: select, - title: title +function triggerChange(select) { + const evt = document.createEvent('HTMLEvents'); + evt.initEvent('change', false, true); + select.dispatchEvent(evt); +} - }).then(function (value) { - setValue(select, value); - triggerChange(select); - }); - } +function setValue(select, value) { + select.value = value; +} - function getLabel(select) { - let elem = select.previousSibling; - while (elem && elem.tagName !== 'LABEL') { - elem = elem.previousSibling; - } - return elem; - } +function showActionSheet(select) { + const labelElem = getLabel(select); + const title = labelElem ? (labelElem.textContent || labelElem.innerText) : null; - function onFocus() { - const label = getLabel(this); - if (label) { - label.classList.add('selectLabelFocused'); - } - } + actionsheet.show({ + items: select.options, + positionTo: select, + title: title - function onBlur() { - const 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; - } - } - - let inputId = 0; - - EmbySelectPrototype.createdCallback = function () { - if (!this.id) { - this.id = 'embyselect' + inputId; - inputId++; - } - - this.classList.add('emby-select-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'); - - const label = this.ownerDocument.createElement('label'); - label.innerText = 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', '
0
'); - } - }; - - EmbySelectPrototype.setLabel = function (text) { - const label = this.parentNode?.querySelector('label'); - - label.innerText = text; - }; - - document.registerElement('emby-select', { - prototype: EmbySelectPrototype, - extends: 'select' + }).then(function (value) { + setValue(select, value); + triggerChange(select); }); +} + +function getLabel(select) { + let elem = select.previousSibling; + while (elem && elem.tagName !== 'LABEL') { + elem = elem.previousSibling; + } + return elem; +} + +function onFocus() { + const label = getLabel(this); + if (label) { + label.classList.add('selectLabelFocused'); + } +} + +function onBlur() { + const 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; + } +} + +let inputId = 0; + +EmbySelectPrototype.createdCallback = function () { + if (!this.id) { + this.id = 'embyselect' + inputId; + inputId++; + } + + this.classList.add('emby-select-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'); + + const label = this.ownerDocument.createElement('label'); + label.innerText = 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', '
0
'); + } +}; + +EmbySelectPrototype.setLabel = function (text) { + const label = this.parentNode?.querySelector('label'); + + label.innerText = text; +}; + +document.registerElement('emby-select', { + prototype: EmbySelectPrototype, + extends: 'select' +}); -/* eslint-enable indent */ diff --git a/src/elements/emby-slider/emby-slider.js b/src/elements/emby-slider/emby-slider.js index 7ce7b02ab6..bdba41638c 100644 --- a/src/elements/emby-slider/emby-slider.js +++ b/src/elements/emby-slider/emby-slider.js @@ -7,567 +7,564 @@ import 'webcomponents.js/webcomponents-lite'; import '../emby-input/emby-input'; import globalize from '../../scripts/globalize'; -/* eslint-disable indent */ +const EmbySliderPrototype = Object.create(HTMLInputElement.prototype); - const EmbySliderPrototype = Object.create(HTMLInputElement.prototype); +let supportsValueSetOverride = false; - let supportsValueSetOverride = false; - - if (Object.getOwnPropertyDescriptor && Object.defineProperty) { - const descriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value'); - // descriptor returning null in webos - if (descriptor && descriptor.configurable) { - supportsValueSetOverride = true; - } +if (Object.getOwnPropertyDescriptor && Object.defineProperty) { + const descriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value'); + // descriptor returning null in webos + if (descriptor && descriptor.configurable) { + supportsValueSetOverride = true; } +} - /** +/** * Returns slider fraction corresponding to client position. * * @param {Object} range slider itself * @param {number} clientX client X-coordinate * @return {number} slider fraction */ - function mapClientToFraction(range, clientX) { - const rect = range.sliderBubbleTrack.getBoundingClientRect(); +function mapClientToFraction(range, clientX) { + const rect = range.sliderBubbleTrack.getBoundingClientRect(); - let fraction = (clientX - rect.left) / rect.width; - if (globalize.getIsElementRTL(range)) - fraction = (rect.right - clientX) / rect.width; + let fraction = (clientX - rect.left) / rect.width; + if (globalize.getIsElementRTL(range)) + fraction = (rect.right - clientX) / rect.width; - // Snap to step - const valueRange = range.max - range.min; - if (range.step !== 'any' && valueRange !== 0) { - const step = (range.step || 1) / valueRange; - fraction = Math.round(fraction / step) * step; - } - - return Math.min(Math.max(fraction, 0), 1); + // Snap to step + const valueRange = range.max - range.min; + if (range.step !== 'any' && valueRange !== 0) { + const step = (range.step || 1) / valueRange; + fraction = Math.round(fraction / step) * step; } - /** + return Math.min(Math.max(fraction, 0), 1); +} + +/** * Returns slider value corresponding to slider fraction. * * @param {Object} range slider itself * @param {number} fraction slider fraction * @return {number} slider value */ - function mapFractionToValue(range, fraction) { - let value = (range.max - range.min) * fraction; +function mapFractionToValue(range, fraction) { + let value = (range.max - range.min) * fraction; - // Snap to step - if (range.step !== 'any') { - const step = range.step || 1; - value = Math.round(value / step) * step; - } - - value += parseFloat(range.min); - - return Math.min(Math.max(value, range.min), range.max); + // Snap to step + if (range.step !== 'any') { + const step = range.step || 1; + value = Math.round(value / step) * step; } - /** + value += parseFloat(range.min); + + return Math.min(Math.max(value, range.min), range.max); +} + +/** * Returns slider fraction corresponding to slider value. * * @param {Object} range slider itself * @param {number} value slider value (snapped to step) * @return {number} slider fraction */ - function mapValueToFraction(range, value) { - const valueRange = range.max - range.min; - const fraction = valueRange !== 0 ? (value - range.min) / valueRange : 0; - return Math.min(Math.max(fraction, 0), 1); - } +function mapValueToFraction(range, value) { + const valueRange = range.max - range.min; + const fraction = valueRange !== 0 ? (value - range.min) / valueRange : 0; + return Math.min(Math.max(fraction, 0), 1); +} - /** +/** * Updates progress bar. * * @param {boolean} [isValueSet] update by 'valueset' event or by timer */ - function updateValues(isValueSet) { - // Do not update values by 'valueset' in case of soft-implemented dragging - if (!!isValueSet && (!!this.keyboardDragging || !!this.touched)) { +function updateValues(isValueSet) { + // Do not update values by 'valueset' in case of soft-implemented dragging + if (!!isValueSet && (!!this.keyboardDragging || !!this.touched)) { + return; + } + + const range = this; + const value = range.value; + + // put this on a callback. Doing it within the event sometimes causes the slider to get hung up and not respond + // Keep only one per slider frame request + cancelAnimationFrame(range.updateValuesFrame); + range.updateValuesFrame = requestAnimationFrame(function () { + const backgroundLower = range.backgroundLower; + + if (backgroundLower) { + let fraction = (value - range.min) / (range.max - range.min); + + fraction *= 100; + backgroundLower.style.width = fraction + '%'; + } + + if (range.markerContainerElement) { + if (!range.triedAddingMarkers) { + addMarkers(range); + } + updateMarkers(range, value); + } + }); +} + +function updateBubble(range, value, bubble) { + requestAnimationFrame(function () { + const bubbleTrackRect = range.sliderBubbleTrack.getBoundingClientRect(); + const bubbleRect = bubble.getBoundingClientRect(); + + let bubblePos = bubbleTrackRect.width * value / 100; + if (globalize.getIsElementRTL(range)) { + bubblePos = bubbleTrackRect.width - bubblePos; + } + bubblePos = Math.min(Math.max(bubblePos, bubbleRect.width / 2), bubbleTrackRect.width - bubbleRect.width / 2); + + bubble.style.left = bubblePos + 'px'; + + if (range.getBubbleHtml) { + value = range.getBubbleHtml(value); + } else { + if (range.getBubbleText) { + value = range.getBubbleText(value); + } else { + value = mapFractionToValue(range, value / 100).toLocaleString(); + } + value = '

' + value + '

'; + } + + bubble.innerHTML = value; + }); +} + +function setMarker(range, valueMarker, marker, valueProgress) { + requestAnimationFrame(function () { + const bubbleTrackRect = range.sliderBubbleTrack.getBoundingClientRect(); + const markerRect = marker.getBoundingClientRect(); + + if (!bubbleTrackRect.width || !markerRect.width) { + // width is not set, most probably because the OSD is currently hidden return; } - const range = this; - const value = range.value; + let markerPos = (bubbleTrackRect.width * valueMarker / 100) - markerRect.width / 2; + markerPos = Math.min(Math.max(markerPos, - markerRect.width / 2), bubbleTrackRect.width - markerRect.width / 2); - // put this on a callback. Doing it within the event sometimes causes the slider to get hung up and not respond - // Keep only one per slider frame request - cancelAnimationFrame(range.updateValuesFrame); - range.updateValuesFrame = requestAnimationFrame(function () { - const backgroundLower = range.backgroundLower; + marker.style.left = markerPos + 'px'; - if (backgroundLower) { - let fraction = (value - range.min) / (range.max - range.min); + if (valueProgress >= valueMarker) { + marker.classList.remove('unwatched'); + marker.classList.add('watched'); + } else { + marker.classList.add('unwatched'); + marker.classList.remove('watched'); + } + }); +} - fraction *= 100; - backgroundLower.style.width = fraction + '%'; - } - - if (range.markerContainerElement) { - if (!range.triedAddingMarkers) { - addMarkers(range); - } - updateMarkers(range, value); - } - }); - } - - function updateBubble(range, value, bubble) { - requestAnimationFrame(function () { - const bubbleTrackRect = range.sliderBubbleTrack.getBoundingClientRect(); - const bubbleRect = bubble.getBoundingClientRect(); - - let bubblePos = bubbleTrackRect.width * value / 100; - if (globalize.getIsElementRTL(range)) { - bubblePos = bubbleTrackRect.width - bubblePos; - } - bubblePos = Math.min(Math.max(bubblePos, bubbleRect.width / 2), bubbleTrackRect.width - bubbleRect.width / 2); - - bubble.style.left = bubblePos + 'px'; - - if (range.getBubbleHtml) { - value = range.getBubbleHtml(value); - } else { - if (range.getBubbleText) { - value = range.getBubbleText(value); - } else { - value = mapFractionToValue(range, value / 100).toLocaleString(); - } - value = '

' + value + '

'; - } - - bubble.innerHTML = value; - }); - } - - function setMarker(range, valueMarker, marker, valueProgress) { - requestAnimationFrame(function () { - const bubbleTrackRect = range.sliderBubbleTrack.getBoundingClientRect(); - const markerRect = marker.getBoundingClientRect(); - - if (!bubbleTrackRect.width || !markerRect.width) { - // width is not set, most probably because the OSD is currently hidden - return; - } - - let markerPos = (bubbleTrackRect.width * valueMarker / 100) - markerRect.width / 2; - markerPos = Math.min(Math.max(markerPos, - markerRect.width / 2), bubbleTrackRect.width - markerRect.width / 2); - - marker.style.left = markerPos + 'px'; - - if (valueProgress >= valueMarker) { - marker.classList.remove('unwatched'); - marker.classList.add('watched'); - } else { - marker.classList.add('unwatched'); - marker.classList.remove('watched'); - } - }); - } - - function updateMarkers(range, currentValue) { - if (range.markerInfo && range.markerInfo.length && range.markerElements && range.markerElements.length) { - for (let i = 0, length = range.markerElements.length; i < length; i++) { - if (range.markerInfo.length > i) { - setMarker(range, mapFractionToValue(range, range.markerInfo[i].progress), range.markerElements[i], currentValue); - } +function updateMarkers(range, currentValue) { + if (range.markerInfo && range.markerInfo.length && range.markerElements && range.markerElements.length) { + for (let i = 0, length = range.markerElements.length; i < length; i++) { + if (range.markerInfo.length > i) { + setMarker(range, mapFractionToValue(range, range.markerInfo[i].progress), range.markerElements[i], currentValue); } } } +} - function addMarkers(range) { - range.markerInfo = []; - if (range.getMarkerInfo) { - range.markerInfo = range.getMarkerInfo(); - } - - function getMarkerHtml(markerInfo) { - let markerTypeSpecificClasses = ''; - - if (markerInfo.className === 'chapterMarker') { - markerTypeSpecificClasses = markerInfo.className; - - if (typeof markerInfo.name === 'string' && markerInfo.name.length) { - // limit the class length in case the name contains half a novel - markerTypeSpecificClasses = `${markerInfo.className} marker-${markerInfo.name.substring(0, 100).toLowerCase().replace(' ', '-')}`; - } - } - - return ``; - } - - range.markerInfo.forEach(info => { - range.markerContainerElement.insertAdjacentHTML('beforeend', getMarkerHtml(info)); - }); - - range.markerElements = range.markerContainerElement.querySelectorAll('.sliderMarker'); - range.triedAddingMarkers = true; +function addMarkers(range) { + range.markerInfo = []; + if (range.getMarkerInfo) { + range.markerInfo = range.getMarkerInfo(); } - EmbySliderPrototype.attachedCallback = function () { - if (this.getAttribute('data-embyslider') === 'true') { - return; - } + function getMarkerHtml(markerInfo) { + let markerTypeSpecificClasses = ''; - this.setAttribute('data-embyslider', 'true'); + if (markerInfo.className === 'chapterMarker') { + markerTypeSpecificClasses = markerInfo.className; - this.classList.add('mdl-slider'); - this.classList.add('mdl-js-slider'); - - if (browser.edge) { - this.classList.add('slider-browser-edge'); - } - if (!layoutManager.mobile) { - this.classList.add('mdl-slider-hoverthumb'); - } - if (layoutManager.tv) { - this.classList.add('show-focus'); - } - - const topContainer = dom.parentWithClass(this, 'sliderContainer-settings'); - - if (topContainer && this.getAttribute('label')) { - const label = this.ownerDocument.createElement('label'); - label.innerText = this.getAttribute('label'); - label.classList.add('sliderLabel'); - label.htmlFor = this.id; - topContainer.insertBefore(label, topContainer.firstChild); - } - - const containerElement = this.parentNode; - containerElement.classList.add('mdl-slider-container'); - - let htmlToInsert = ''; - - htmlToInsert += '
'; - htmlToInsert += '
'; - htmlToInsert += '
'; - - // the more of these, the more ranges we can display - htmlToInsert += '
'; - - htmlToInsert += '
'; - - htmlToInsert += '
'; - htmlToInsert += '
'; - htmlToInsert += '
'; - - htmlToInsert += '
'; - - containerElement.insertAdjacentHTML('beforeend', htmlToInsert); - - this.sliderBubbleTrack = containerElement.querySelector('.sliderBubbleTrack'); - this.backgroundLower = containerElement.querySelector('.mdl-slider-background-lower'); - this.backgroundUpper = containerElement.querySelector('.mdl-slider-background-upper'); - const sliderBubble = containerElement.querySelector('.sliderBubble'); - - let hasHideBubbleClass = sliderBubble.classList.contains('hide'); - - this.markerContainerElement = containerElement.querySelector('.sliderMarkerContainer'); - - dom.addEventListener(this, 'input', function () { - this.dragging = true; - - if (this.dataset.sliderKeepProgress !== 'true') { - updateValues.call(this); + if (typeof markerInfo.name === 'string' && markerInfo.name.length) { + // limit the class length in case the name contains half a novel + markerTypeSpecificClasses = `${markerInfo.className} marker-${markerInfo.name.substring(0, 100).toLowerCase().replace(' ', '-')}`; } + } + + return ``; + } + + range.markerInfo.forEach(info => { + range.markerContainerElement.insertAdjacentHTML('beforeend', getMarkerHtml(info)); + }); + + range.markerElements = range.markerContainerElement.querySelectorAll('.sliderMarker'); + range.triedAddingMarkers = true; +} + +EmbySliderPrototype.attachedCallback = function () { + if (this.getAttribute('data-embyslider') === 'true') { + return; + } + + this.setAttribute('data-embyslider', 'true'); + + this.classList.add('mdl-slider'); + this.classList.add('mdl-js-slider'); + + if (browser.edge) { + this.classList.add('slider-browser-edge'); + } + if (!layoutManager.mobile) { + this.classList.add('mdl-slider-hoverthumb'); + } + if (layoutManager.tv) { + this.classList.add('show-focus'); + } + + const topContainer = dom.parentWithClass(this, 'sliderContainer-settings'); + + if (topContainer && this.getAttribute('label')) { + const label = this.ownerDocument.createElement('label'); + label.innerText = this.getAttribute('label'); + label.classList.add('sliderLabel'); + label.htmlFor = this.id; + topContainer.insertBefore(label, topContainer.firstChild); + } + + const containerElement = this.parentNode; + containerElement.classList.add('mdl-slider-container'); + + let htmlToInsert = ''; + + htmlToInsert += '
'; + htmlToInsert += '
'; + htmlToInsert += '
'; + + // the more of these, the more ranges we can display + htmlToInsert += '
'; + + htmlToInsert += '
'; + + htmlToInsert += '
'; + htmlToInsert += '
'; + htmlToInsert += '
'; + + htmlToInsert += '
'; + + containerElement.insertAdjacentHTML('beforeend', htmlToInsert); + + this.sliderBubbleTrack = containerElement.querySelector('.sliderBubbleTrack'); + this.backgroundLower = containerElement.querySelector('.mdl-slider-background-lower'); + this.backgroundUpper = containerElement.querySelector('.mdl-slider-background-upper'); + const sliderBubble = containerElement.querySelector('.sliderBubble'); + + let hasHideBubbleClass = sliderBubble.classList.contains('hide'); + + this.markerContainerElement = containerElement.querySelector('.sliderMarkerContainer'); + + dom.addEventListener(this, 'input', function () { + this.dragging = true; + + if (this.dataset.sliderKeepProgress !== 'true') { + updateValues.call(this); + } + + const bubbleValue = mapValueToFraction(this, this.value) * 100; + updateBubble(this, bubbleValue, sliderBubble); + + if (hasHideBubbleClass) { + sliderBubble.classList.remove('hide'); + hasHideBubbleClass = false; + } + }, { + passive: true + }); + + dom.addEventListener(this, 'change', function () { + this.dragging = false; + + if (this.dataset.sliderKeepProgress === 'true') { + updateValues.call(this); + } + + sliderBubble.classList.add('hide'); + hasHideBubbleClass = true; + }, { + passive: true + }); + + /* eslint-disable-next-line compat/compat */ + dom.addEventListener(this, (window.PointerEvent ? 'pointermove' : 'mousemove'), function (e) { + if (!this.dragging) { + const bubbleValue = mapClientToFraction(this, e.clientX) * 100; - const bubbleValue = mapValueToFraction(this, this.value) * 100; updateBubble(this, bubbleValue, sliderBubble); if (hasHideBubbleClass) { sliderBubble.classList.remove('hide'); hasHideBubbleClass = false; } - }, { - passive: true - }); + } + }, { + passive: true + }); - dom.addEventListener(this, 'change', function () { - this.dragging = false; + /* eslint-disable-next-line compat/compat */ + dom.addEventListener(this, (window.PointerEvent ? 'pointerleave' : 'mouseleave'), function () { + sliderBubble.classList.add('hide'); + hasHideBubbleClass = true; + }, { + passive: true + }); - if (this.dataset.sliderKeepProgress === 'true') { - updateValues.call(this); + // HACK: iPhone/iPad do not change input by touch + if (browser.iOS) { + dom.addEventListener(this, 'touchstart', function (e) { + if (e.targetTouches.length !== 1) { + return; } - sliderBubble.classList.add('hide'); - hasHideBubbleClass = true; + this.touched = true; + + const fraction = mapClientToFraction(this, e.targetTouches[0].clientX); + this.value = mapFractionToValue(this, fraction); + + this.dispatchEvent(new Event('input', { + bubbles: true, + cancelable: false + })); + + // Prevent 'pointermove' and 'click' after 'touch*' + // FIXME: Still have some 'pointermove' and 'click' that bypass 'touchstart' + e.preventDefault(); }, { - passive: true + capture: true }); - /* eslint-disable-next-line compat/compat */ - dom.addEventListener(this, (window.PointerEvent ? 'pointermove' : 'mousemove'), function (e) { - if (!this.dragging) { - const bubbleValue = mapClientToFraction(this, e.clientX) * 100; - - updateBubble(this, bubbleValue, sliderBubble); - - if (hasHideBubbleClass) { - sliderBubble.classList.remove('hide'); - hasHideBubbleClass = false; - } + dom.addEventListener(this, 'touchmove', function (e) { + if (!this.touched || e.targetTouches.length !== 1) { + return; } + + const fraction = mapClientToFraction(this, e.targetTouches[0].clientX); + this.value = mapFractionToValue(this, fraction); + + this.dispatchEvent(new Event('input', { + bubbles: true, + cancelable: false + })); }, { passive: true }); - /* eslint-disable-next-line compat/compat */ - dom.addEventListener(this, (window.PointerEvent ? 'pointerleave' : 'mouseleave'), function () { - sliderBubble.classList.add('hide'); - hasHideBubbleClass = true; - }, { - passive: true - }); + dom.addEventListener(this, 'touchend', function () { + const range = this; - // HACK: iPhone/iPad do not change input by touch - if (browser.iOS) { - dom.addEventListener(this, 'touchstart', function (e) { - if (e.targetTouches.length !== 1) { - return; - } + setTimeout(function () { + range.touched = false; - this.touched = true; - - const fraction = mapClientToFraction(this, e.targetTouches[0].clientX); - this.value = mapFractionToValue(this, fraction); - - this.dispatchEvent(new Event('input', { + range.dispatchEvent(new Event('change', { bubbles: true, cancelable: false })); + }, 0); + }, { + passive: true + }); + } - // Prevent 'pointermove' and 'click' after 'touch*' - // FIXME: Still have some 'pointermove' and 'click' that bypass 'touchstart' - e.preventDefault(); - }, { - capture: true - }); + if (supportsValueSetOverride) { + this.addEventListener('valueset', updateValues.bind(this, true)); + } else { + startInterval(this); + } +}; - dom.addEventListener(this, 'touchmove', function (e) { - if (!this.touched || e.targetTouches.length !== 1) { - return; - } - - const fraction = mapClientToFraction(this, e.targetTouches[0].clientX); - this.value = mapFractionToValue(this, fraction); - - this.dispatchEvent(new Event('input', { - bubbles: true, - cancelable: false - })); - }, { - passive: true - }); - - dom.addEventListener(this, 'touchend', function () { - const range = this; - - setTimeout(function () { - range.touched = false; - - range.dispatchEvent(new Event('change', { - bubbles: true, - cancelable: false - })); - }, 0); - }, { - passive: true - }); - } - - if (supportsValueSetOverride) { - this.addEventListener('valueset', updateValues.bind(this, true)); - } else { - startInterval(this); - } - }; - - /** +/** * Keyboard dragging timeout. * After this delay "change" event will be fired. */ - const KeyboardDraggingTimeout = 1000; +const KeyboardDraggingTimeout = 1000; - /** +/** * Keyboard dragging timer. */ - let keyboardDraggingTimer; +let keyboardDraggingTimer; - /** +/** * Start keyboard dragging. * * @param {Object} elem slider itself */ - function startKeyboardDragging(elem) { - elem.keyboardDragging = true; +function startKeyboardDragging(elem) { + elem.keyboardDragging = true; - clearTimeout(keyboardDraggingTimer); - keyboardDraggingTimer = setTimeout(function () { - finishKeyboardDragging(elem); - }, KeyboardDraggingTimeout); - } + clearTimeout(keyboardDraggingTimer); + keyboardDraggingTimer = setTimeout(function () { + finishKeyboardDragging(elem); + }, KeyboardDraggingTimeout); +} - /** +/** * Finish keyboard dragging. * * @param {Object} elem slider itself */ - function finishKeyboardDragging(elem) { - clearTimeout(keyboardDraggingTimer); - keyboardDraggingTimer = undefined; +function finishKeyboardDragging(elem) { + clearTimeout(keyboardDraggingTimer); + keyboardDraggingTimer = undefined; - elem.keyboardDragging = false; + elem.keyboardDragging = false; - const event = new Event('change', { - bubbles: true, - cancelable: false - }); - elem.dispatchEvent(event); - } + const event = new Event('change', { + bubbles: true, + cancelable: false + }); + elem.dispatchEvent(event); +} - /** +/** * Do step by delta. * * @param {Object} elem slider itself * @param {number} delta step amount */ - function stepKeyboard(elem, delta) { - startKeyboardDragging(elem); +function stepKeyboard(elem, delta) { + startKeyboardDragging(elem); - elem.value = Math.max(elem.min, Math.min(elem.max, parseFloat(elem.value) + delta)); + elem.value = Math.max(elem.min, Math.min(elem.max, parseFloat(elem.value) + delta)); - const event = new Event('input', { - bubbles: true, - cancelable: false - }); - elem.dispatchEvent(event); - } + const event = new Event('input', { + bubbles: true, + cancelable: false + }); + elem.dispatchEvent(event); +} - /** +/** * Handle KeyDown event */ - function onKeyDown(e) { - switch (keyboardnavigation.getKeyName(e)) { - case 'ArrowLeft': - case 'Left': - stepKeyboard(this, -this.keyboardStepDown || -1); - e.preventDefault(); - e.stopPropagation(); - break; - case 'ArrowRight': - case 'Right': - stepKeyboard(this, this.keyboardStepUp || 1); - e.preventDefault(); - e.stopPropagation(); - break; - } +function onKeyDown(e) { + switch (keyboardnavigation.getKeyName(e)) { + case 'ArrowLeft': + case 'Left': + stepKeyboard(this, -this.keyboardStepDown || -1); + e.preventDefault(); + e.stopPropagation(); + break; + case 'ArrowRight': + case 'Right': + stepKeyboard(this, this.keyboardStepUp || 1); + e.preventDefault(); + e.stopPropagation(); + break; } +} - /** +/** * Enable keyboard dragging. */ - EmbySliderPrototype.enableKeyboardDragging = function () { - if (!this.keyboardDraggingEnabled) { - this.addEventListener('keydown', onKeyDown); - this.keyboardDraggingEnabled = true; - } - }; +EmbySliderPrototype.enableKeyboardDragging = function () { + if (!this.keyboardDraggingEnabled) { + this.addEventListener('keydown', onKeyDown); + this.keyboardDraggingEnabled = true; + } +}; - /** +/** * Set steps for keyboard input. * * @param {number} stepDown step to reduce * @param {number} stepUp step to increase */ - EmbySliderPrototype.setKeyboardSteps = function (stepDown, stepUp) { - this.keyboardStepDown = stepDown || stepUp || 1; - this.keyboardStepUp = stepUp || stepDown || 1; - }; +EmbySliderPrototype.setKeyboardSteps = function (stepDown, stepUp) { + this.keyboardStepDown = stepDown || stepUp || 1; + this.keyboardStepUp = stepUp || stepDown || 1; +}; - function setRange(elem, startPercent, endPercent) { - const style = elem.style; - if (globalize.getIsRTL()) - style.right = Math.max(startPercent, 0) + '%'; - else - style.left = Math.max(startPercent, 0) + '%'; +function setRange(elem, startPercent, endPercent) { + const style = elem.style; + if (globalize.getIsRTL()) + style.right = Math.max(startPercent, 0) + '%'; + else + style.left = Math.max(startPercent, 0) + '%'; - const widthPercent = endPercent - startPercent; - style.width = Math.max(Math.min(widthPercent, 100), 0) + '%'; + const widthPercent = endPercent - startPercent; + style.width = Math.max(Math.min(widthPercent, 100), 0) + '%'; +} + +function mapRangesFromRuntimeToPercent(ranges, runtime) { + if (!runtime) { + return []; } - 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) { - const elem = this.backgroundUpper; - if (!elem) { - return; - } - - if (runtime != null) { - ranges = mapRangesFromRuntimeToPercent(ranges, runtime); - - position = (position / runtime) * 100; - } - - for (const range of ranges) { - if (position != null && position >= range.end) { - continue; - } - - setRange(elem, range.start, range.end); - return; - } - - setRange(elem, 0, 0); - }; - - EmbySliderPrototype.setIsClear = function (isClear) { - const 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) { - const interval = range.interval; - if (interval) { - clearInterval(interval); - } - range.interval = setInterval(updateValues.bind(range, true), 100); - } - - EmbySliderPrototype.detachedCallback = function () { - const interval = this.interval; - if (interval) { - clearInterval(interval); - } - this.interval = null; - this.backgroundUpper = null; - this.backgroundLower = null; - }; - - document.registerElement('emby-slider', { - prototype: EmbySliderPrototype, - extends: 'input' + return ranges.map(function (r) { + return { + start: (r.start / runtime) * 100, + end: (r.end / runtime) * 100 + }; }); +} + +EmbySliderPrototype.setBufferedRanges = function (ranges, runtime, position) { + const elem = this.backgroundUpper; + if (!elem) { + return; + } + + if (runtime != null) { + ranges = mapRangesFromRuntimeToPercent(ranges, runtime); + + position = (position / runtime) * 100; + } + + for (const range of ranges) { + if (position != null && position >= range.end) { + continue; + } + + setRange(elem, range.start, range.end); + return; + } + + setRange(elem, 0, 0); +}; + +EmbySliderPrototype.setIsClear = function (isClear) { + const 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) { + const interval = range.interval; + if (interval) { + clearInterval(interval); + } + range.interval = setInterval(updateValues.bind(range, true), 100); +} + +EmbySliderPrototype.detachedCallback = function () { + const interval = this.interval; + if (interval) { + clearInterval(interval); + } + this.interval = null; + this.backgroundUpper = null; + this.backgroundLower = null; +}; + +document.registerElement('emby-slider', { + prototype: EmbySliderPrototype, + extends: 'input' +}); -/* eslint-enable indent */ diff --git a/src/elements/emby-tabs/emby-tabs.js b/src/elements/emby-tabs/emby-tabs.js index 7c07796f74..22d12e75af 100644 --- a/src/elements/emby-tabs/emby-tabs.js +++ b/src/elements/emby-tabs/emby-tabs.js @@ -7,334 +7,332 @@ import layoutManager from '../../components/layoutManager'; import './emby-tabs.scss'; import '../../styles/scrollstyles.scss'; -/* eslint-disable indent */ - const EmbyTabs = Object.create(HTMLDivElement.prototype); - const buttonClass = 'emby-tab-button'; - const activeButtonClass = buttonClass + '-active'; +const EmbyTabs = Object.create(HTMLDivElement.prototype); +const buttonClass = 'emby-tab-button'; +const activeButtonClass = buttonClass + '-active'; - function setActiveTabButton(newButton) { - newButton.classList.add(activeButtonClass); +function setActiveTabButton(newButton) { + newButton.classList.add(activeButtonClass); +} + +function getTabPanel() { + return null; +} + +function removeActivePanelClass() { + const tabPanel = getTabPanel(); + if (tabPanel) { + tabPanel.classList.remove('is-active'); + } +} + +function fadeInRight(elem) { + const pct = browser.mobile ? '4%' : '0.5%'; + + const 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(); } - function getTabPanel() { - return null; + const newPanel = getTabPanel(); + + if (newPanel) { + // animate new panel ? + if (newPanel.animate) { + fadeInRight(newPanel); + } + + newPanel.classList.add('is-active'); } +} - function removeActivePanelClass() { - const tabPanel = getTabPanel(); - if (tabPanel) { - tabPanel.classList.remove('is-active'); - } - } +function onClick(e) { + const tabs = this; - function fadeInRight(elem) { - const pct = browser.mobile ? '4%' : '0.5%'; + const current = tabs.querySelector('.' + activeButtonClass); + const tabButton = dom.parentWithClass(e.target, buttonClass); - const 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(); + if (tabButton && tabButton !== current) { + if (current) { + current.classList.remove(activeButtonClass); } - const newPanel = getTabPanel(); + const previousIndex = current ? parseInt(current.getAttribute('data-index'), 10) : null; - if (newPanel) { - // animate new panel ? - if (newPanel.animate) { - fadeInRight(newPanel); - } + setActiveTabButton(tabButton); - newPanel.classList.add('is-active'); - } - } + const index = parseInt(tabButton.getAttribute('data-index'), 10); - function onClick(e) { - const tabs = this; + triggerBeforeTabChange(tabs, index, previousIndex); - const current = tabs.querySelector('.' + activeButtonClass); - const tabButton = dom.parentWithClass(e.target, buttonClass); - - if (tabButton && tabButton !== current) { - if (current) { - current.classList.remove(activeButtonClass); - } - - const previousIndex = current ? parseInt(current.getAttribute('data-index'), 10) : null; - - setActiveTabButton(tabButton); - - const index = parseInt(tabButton.getAttribute('data-index'), 10); - - 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 onFocusIn(e) { - const tabs = this; - const tabButton = dom.parentWithClass(e.target, buttonClass); - if (tabButton && tabs.scroller) { - tabs.scroller.toCenter(tabButton, false); - } - } - - function onFocusOut(e) { - const parentContainer = e.target.parentNode; - const previousFocus = parentContainer.querySelector('.lastFocused'); - if (previousFocus) { - previousFocus.classList.remove('lastFocused'); - } - e.target.classList.add('lastFocused'); - } - - function initScroller(tabs) { - if (tabs.scroller) { - return; - } - - const 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 - }); - - if (layoutManager.tv) { - dom.addEventListener(this, 'focusin', onFocusIn, { passive: true }); - } - - dom.addEventListener(this, 'focusout', onFocusOut); - }; - - EmbyTabs.focus = function () { - const selectedTab = this.querySelector('.' + activeButtonClass); - const lastFocused = this.querySelector('.lastFocused'); - - if (lastFocused) { - focusManager.focus(lastFocused); - } else if (selectedTab) { - focusManager.focus(selectedTab); - } else { - focusManager.autoFocus(this); - } - }; - - EmbyTabs.refresh = function () { - if (this.scroller) { - this.scroller.reload(); - } - }; - - EmbyTabs.attachedCallback = function () { - initScroller(this); - - const current = this.querySelector('.' + activeButtonClass); - const currentIndex = current ? parseInt(current.getAttribute('data-index'), 10) : parseInt(this.getAttribute('data-index') || '0', 10); - - if (currentIndex !== -1) { - this.selectedTabIndex = currentIndex; - - const tabButtons = this.querySelectorAll('.' + buttonClass); - - const newTabButton = tabButtons[currentIndex]; - - if (newTabButton) { - setActiveTabButton(newTabButton); - } - } - - 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 - }); - - if (layoutManager.tv) { - dom.removeEventListener(this, 'focusin', onFocusIn, { passive: true }); - } - }; - - function getSelectedTabButton(elem) { - return elem.querySelector('.' + activeButtonClass); - } - - EmbyTabs.selectedIndex = function (selected, triggerEvent) { - const tabs = this; - - if (selected == null) { - return tabs.selectedTabIndex || 0; - } - - const current = tabs.selectedIndex(); - - tabs.selectedTabIndex = selected; - - const tabButtons = tabs.querySelectorAll('.' + buttonClass); - - if (current === selected || triggerEvent === false) { - triggerBeforeTabChange(tabs, selected, current); + // 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: selected + selectedTabIndex: index, + previousIndex: previousIndex } })); + }, 120); - const currentTabButton = tabButtons[current]; - setActiveTabButton(tabButtons[selected]); - - if (current !== selected && currentTabButton) { - currentTabButton.classList.remove(activeButtonClass); - } - } else { - onClick.call(tabs, { - target: tabButtons[selected] - }); + if (tabs.scroller) { + tabs.scroller.toCenter(tabButton, false); } - }; + } +} - function getSibling(elem, method) { - let sibling = elem[method]; +function onFocusIn(e) { + const tabs = this; + const tabButton = dom.parentWithClass(e.target, buttonClass); + if (tabButton && tabs.scroller) { + tabs.scroller.toCenter(tabButton, false); + } +} - while (sibling) { - if (sibling.classList.contains(buttonClass) && !sibling.classList.contains('hide')) { - return sibling; - } +function onFocusOut(e) { + const parentContainer = e.target.parentNode; + const previousFocus = parentContainer.querySelector('.lastFocused'); + if (previousFocus) { + previousFocus.classList.remove('lastFocused'); + } + e.target.classList.add('lastFocused'); +} - sibling = sibling[method]; - } - - return null; +function initScroller(tabs) { + if (tabs.scroller) { + return; } - EmbyTabs.selectNext = function () { - const current = getSelectedTabButton(this); + const 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, - const sibling = getSibling(current, 'nextSibling'); + // 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'); + } +} - if (sibling) { - onClick.call(this, { - target: sibling - }); +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 + }); + + if (layoutManager.tv) { + dom.addEventListener(this, 'focusin', onFocusIn, { passive: true }); + } + + dom.addEventListener(this, 'focusout', onFocusOut); +}; + +EmbyTabs.focus = function () { + const selectedTab = this.querySelector('.' + activeButtonClass); + const lastFocused = this.querySelector('.lastFocused'); + + if (lastFocused) { + focusManager.focus(lastFocused); + } else if (selectedTab) { + focusManager.focus(selectedTab); + } else { + focusManager.autoFocus(this); + } +}; + +EmbyTabs.refresh = function () { + if (this.scroller) { + this.scroller.reload(); + } +}; + +EmbyTabs.attachedCallback = function () { + initScroller(this); + + const current = this.querySelector('.' + activeButtonClass); + const currentIndex = current ? parseInt(current.getAttribute('data-index'), 10) : parseInt(this.getAttribute('data-index') || '0', 10); + + if (currentIndex !== -1) { + this.selectedTabIndex = currentIndex; + + const tabButtons = this.querySelectorAll('.' + buttonClass); + + const newTabButton = tabButtons[currentIndex]; + + if (newTabButton) { + setActiveTabButton(newTabButton); } - }; + } - EmbyTabs.selectPrevious = function () { - const current = getSelectedTabButton(this); + if (!this.readyFired) { + this.readyFired = true; + this.dispatchEvent(new CustomEvent('ready', {})); + } +}; - const sibling = getSibling(current, 'previousSibling'); +EmbyTabs.detachedCallback = function () { + if (this.scroller) { + this.scroller.destroy(); + this.scroller = null; + } - if (sibling) { - onClick.call(this, { - target: sibling - }); - } - }; + dom.removeEventListener(this, 'click', onClick, { + passive: true + }); - EmbyTabs.triggerBeforeTabChange = function () { - const tabs = this; + if (layoutManager.tv) { + dom.removeEventListener(this, 'focusin', onFocusIn, { passive: true }); + } +}; - triggerBeforeTabChange(tabs, tabs.selectedIndex()); - }; +function getSelectedTabButton(elem) { + return elem.querySelector('.' + activeButtonClass); +} - EmbyTabs.triggerTabChange = function () { - const tabs = this; +EmbyTabs.selectedIndex = function (selected, triggerEvent) { + const tabs = this; + + if (selected == null) { + return tabs.selectedTabIndex || 0; + } + + const current = tabs.selectedIndex(); + + tabs.selectedTabIndex = selected; + + const tabButtons = tabs.querySelectorAll('.' + buttonClass); + + if (current === selected || triggerEvent === false) { + triggerBeforeTabChange(tabs, selected, current); tabs.dispatchEvent(new CustomEvent('tabchange', { detail: { - selectedTabIndex: tabs.selectedIndex() + selectedTabIndex: selected } })); - }; - EmbyTabs.setTabEnabled = function (index, enabled) { - const btn = this.querySelector('.emby-tab-button[data-index="' + index + '"]'); + const currentTabButton = tabButtons[current]; + setActiveTabButton(tabButtons[selected]); - if (enabled) { - btn.classList.remove('hide'); - } else { - btn.classList.remove('add'); + if (current !== selected && currentTabButton) { + currentTabButton.classList.remove(activeButtonClass); } - }; + } else { + onClick.call(tabs, { + target: tabButtons[selected] + }); + } +}; - document.registerElement('emby-tabs', { - prototype: EmbyTabs, - extends: 'div' - }); +function getSibling(elem, method) { + let sibling = elem[method]; + + while (sibling) { + if (sibling.classList.contains(buttonClass) && !sibling.classList.contains('hide')) { + return sibling; + } + + sibling = sibling[method]; + } + + return null; +} + +EmbyTabs.selectNext = function () { + const current = getSelectedTabButton(this); + + const sibling = getSibling(current, 'nextSibling'); + + if (sibling) { + onClick.call(this, { + target: sibling + }); + } +}; + +EmbyTabs.selectPrevious = function () { + const current = getSelectedTabButton(this); + + const sibling = getSibling(current, 'previousSibling'); + + if (sibling) { + onClick.call(this, { + target: sibling + }); + } +}; + +EmbyTabs.triggerBeforeTabChange = function () { + const tabs = this; + + triggerBeforeTabChange(tabs, tabs.selectedIndex()); +}; + +EmbyTabs.triggerTabChange = function () { + const tabs = this; + + tabs.dispatchEvent(new CustomEvent('tabchange', { + detail: { + selectedTabIndex: tabs.selectedIndex() + } + })); +}; + +EmbyTabs.setTabEnabled = function (index, enabled) { + const 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' +}); -/* eslint-enable indent */ diff --git a/src/elements/emby-textarea/emby-textarea.js b/src/elements/emby-textarea/emby-textarea.js index ed48f415ff..4acf38b594 100644 --- a/src/elements/emby-textarea/emby-textarea.js +++ b/src/elements/emby-textarea/emby-textarea.js @@ -19,119 +19,117 @@ function calculateOffset(textarea) { return offset; } -/* eslint-disable indent */ - function autoGrow(textarea, maxLines) { - const self = this; +function autoGrow(textarea, maxLines) { + const self = this; - if (maxLines === undefined) { - maxLines = 999; - } - - let offset; - function reset() { - textarea.rows = 1; - offset = calculateOffset(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; - } - let newHeight = 0; - - 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*/; - } - 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(); + if (maxLines === undefined) { + maxLines = 999; } - const EmbyTextAreaPrototype = Object.create(HTMLTextAreaElement.prototype); - - let elementId = 0; - - if (Object.getOwnPropertyDescriptor && Object.defineProperty) { - const descriptor = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value'); - - // descriptor returning null in webos - if (descriptor && descriptor.configurable) { - const 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); - } + let offset; + function reset() { + textarea.rows = 1; + offset = calculateOffset(textarea); + self.rows = textarea.rows || 1; + self.lineHeight = (textarea.scrollHeight / self.rows) - (offset / self.rows); + self.maxAllowedHeight = (self.lineHeight * maxLines) - offset; } - EmbyTextAreaPrototype.createdCallback = function () { - if (!this.id) { - this.id = 'embytextarea' + elementId; - elementId++; + function autogrowFn() { + if (!self.lineHeight || self.lineHeight <= 0) { + reset(); } - }; - - EmbyTextAreaPrototype.attachedCallback = function () { - if (this.classList.contains('emby-textarea')) { + if (self.lineHeight <= 0) { + textarea.style.overflowY = 'scroll'; + textarea.style.height = 'auto'; + textarea.rows = 3; return; } + let newHeight = 0; - this.rows = 1; - this.classList.add('emby-textarea'); + 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*/; + } + textarea.style.height = newHeight + 'px'; + } - const parentNode = this.parentNode; - const label = this.ownerDocument.createElement('label'); - label.innerText = this.getAttribute('label') || ''; - label.classList.add('textareaLabel'); + // Call autogrowFn() when textarea's value is changed + textarea.addEventListener('input', autogrowFn); + textarea.addEventListener('focus', autogrowFn); + textarea.addEventListener('valueset', autogrowFn); - label.htmlFor = this.id; - parentNode.insertBefore(label, this); + autogrowFn(); +} - this.addEventListener('focus', function () { - label.classList.add('textareaLabelFocused'); - label.classList.remove('textareaLabelUnfocused'); - }); - this.addEventListener('blur', function () { - label.classList.remove('textareaLabelFocused'); - label.classList.add('textareaLabelUnfocused'); - }); +const EmbyTextAreaPrototype = Object.create(HTMLTextAreaElement.prototype); - this.label = function (text) { - label.innerText = text; +let elementId = 0; + +if (Object.getOwnPropertyDescriptor && Object.defineProperty) { + const descriptor = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value'); + + // descriptor returning null in webos + if (descriptor && descriptor.configurable) { + const baseSetMethod = descriptor.set; + descriptor.set = function (value) { + baseSetMethod.call(this, value); + + this.dispatchEvent(new CustomEvent('valueset', { + bubbles: false, + cancelable: false + })); }; - new autoGrow(this); - }; + Object.defineProperty(HTMLTextAreaElement.prototype, 'value', descriptor); + } +} - document.registerElement('emby-textarea', { - prototype: EmbyTextAreaPrototype, - extends: 'textarea' +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'); + + const parentNode = this.parentNode; + const label = this.ownerDocument.createElement('label'); + label.innerText = 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'); }); -/* eslint-enable indent */ + this.label = function (text) { + label.innerText = text; + }; + + new autoGrow(this); +}; + +document.registerElement('emby-textarea', { + prototype: EmbyTextAreaPrototype, + extends: 'textarea' +}); + diff --git a/src/elements/emby-toggle/emby-toggle.js b/src/elements/emby-toggle/emby-toggle.js index bcc90724bb..e259c8f366 100644 --- a/src/elements/emby-toggle/emby-toggle.js +++ b/src/elements/emby-toggle/emby-toggle.js @@ -1,51 +1,48 @@ import './emby-toggle.scss'; import 'webcomponents.js/webcomponents-lite'; -/* eslint-disable indent */ +const EmbyTogglePrototype = Object.create(HTMLInputElement.prototype); - const EmbyTogglePrototype = Object.create(HTMLInputElement.prototype); +function onKeyDown(e) { + // Don't submit form on enter + if (e.keyCode === 13) { + e.preventDefault(); - function onKeyDown(e) { - // Don't submit form on enter - if (e.keyCode === 13) { - e.preventDefault(); + this.checked = !this.checked; - this.checked = !this.checked; + this.dispatchEvent(new CustomEvent('change', { + bubbles: true + })); - this.dispatchEvent(new CustomEvent('change', { - bubbles: true - })); + return false; + } +} - return false; - } +EmbyTogglePrototype.attachedCallback = function () { + if (this.getAttribute('data-embytoggle') === 'true') { + return; } - EmbyTogglePrototype.attachedCallback = function () { - if (this.getAttribute('data-embytoggle') === 'true') { - return; - } + this.setAttribute('data-embytoggle', 'true'); - this.setAttribute('data-embytoggle', 'true'); + this.classList.add('mdl-switch__input'); - this.classList.add('mdl-switch__input'); + const labelElement = this.parentNode; + labelElement.classList.add('mdl-switch'); + labelElement.classList.add('mdl-js-switch'); - const labelElement = this.parentNode; - labelElement.classList.add('mdl-switch'); - labelElement.classList.add('mdl-js-switch'); + const labelTextElement = labelElement.querySelector('span'); - const labelTextElement = labelElement.querySelector('span'); + labelElement.insertAdjacentHTML('beforeend', '
'); - labelElement.insertAdjacentHTML('beforeend', '
'); + labelTextElement.classList.add('toggleButtonLabel'); + labelTextElement.classList.add('mdl-switch__label'); - labelTextElement.classList.add('toggleButtonLabel'); - labelTextElement.classList.add('mdl-switch__label'); + this.addEventListener('keydown', onKeyDown); +}; - this.addEventListener('keydown', onKeyDown); - }; +document.registerElement('emby-toggle', { + prototype: EmbyTogglePrototype, + extends: 'input' +}); - document.registerElement('emby-toggle', { - prototype: EmbyTogglePrototype, - extends: 'input' - }); - -/* eslint-enable indent */ diff --git a/src/plugins/backdropScreensaver/plugin.js b/src/plugins/backdropScreensaver/plugin.js index bc4f435fed..3cccc8db7b 100644 --- a/src/plugins/backdropScreensaver/plugin.js +++ b/src/plugins/backdropScreensaver/plugin.js @@ -1,4 +1,4 @@ -/* eslint-disable indent */ + import ServerConnections from '../../components/ServerConnections'; import { PluginType } from '../../types/plugin.ts'; @@ -9,44 +9,43 @@ class BackdropScreensaver { this.id = 'backdropscreensaver'; this.supportsAnonymous = false; } - show() { - const query = { - ImageTypes: 'Backdrop', - EnableImageTypes: 'Backdrop', - IncludeItemTypes: 'Movie,Series,MusicArtist', - SortBy: 'Random', - Recursive: true, - Fields: 'Taglines', - ImageTypeLimit: 1, - StartIndex: 0, - Limit: 200 - }; + show() { + const query = { + ImageTypes: 'Backdrop', + EnableImageTypes: 'Backdrop', + IncludeItemTypes: 'Movie,Series,MusicArtist', + SortBy: 'Random', + Recursive: true, + Fields: 'Taglines', + ImageTypeLimit: 1, + StartIndex: 0, + Limit: 200 + }; - const apiClient = ServerConnections.currentApiClient(); - apiClient.getItems(apiClient.getCurrentUserId(), query).then((result) => { - if (result.Items.length) { - import('../../components/slideshow/slideshow').then(({ default: Slideshow }) => { - const newSlideShow = new Slideshow({ - showTitle: true, - cover: true, - items: result.Items - }); + const apiClient = ServerConnections.currentApiClient(); + apiClient.getItems(apiClient.getCurrentUserId(), query).then((result) => { + if (result.Items.length) { + import('../../components/slideshow/slideshow').then(({ default: Slideshow }) => { + const newSlideShow = new Slideshow({ + showTitle: true, + cover: true, + items: result.Items + }); - newSlideShow.show(); - this.currentSlideshow = newSlideShow; - }).catch(console.error); - } - }); - } - - hide() { - if (this.currentSlideshow) { - this.currentSlideshow.hide(); - this.currentSlideshow = null; + newSlideShow.show(); + this.currentSlideshow = newSlideShow; + }).catch(console.error); } - return Promise.resolve(); - } + }); } -/* eslint-enable indent */ + + hide() { + if (this.currentSlideshow) { + this.currentSlideshow.hide(); + this.currentSlideshow = null; + } + return Promise.resolve(); + } +} export default BackdropScreensaver; diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index ad39f19ea4..b67238458b 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -55,1606 +55,1604 @@ function resolveUrl(url) { }); } -/* eslint-disable indent */ - function tryRemoveElement(elem) { - const parentNode = elem.parentNode; - if (parentNode) { - // Seeing crashes in edge webview - try { - parentNode.removeChild(elem); - } catch (err) { - console.error(`error removing dialog element: ${err}`); - } + const parentNode = elem.parentNode; + if (parentNode) { + // Seeing crashes in edge webview + try { + parentNode.removeChild(elem); + } catch (err) { + console.error(`error removing dialog element: ${err}`); } } +} - function enableNativeTrackSupport(currentSrc, track) { - if (track?.DeliveryMethod === 'Embed') { - return true; - } - - if (browser.firefox && (currentSrc || '').toLowerCase().includes('.m3u8')) { - return false; - } - - if (browser.ps4) { - return false; - } - - if (browser.web0s) { - return false; - } - - // Edge is randomly not rendering subtitles - if (browser.edge) { - return false; - } - - if (browser.iOS && (browser.iosVersion || 10) < 10) { - // works in the browser but not the native app - return false; - } - - if (track) { - const format = (track.Codec || '').toLowerCase(); - if (format === 'ssa' || format === 'ass') { - return false; - } - } - +function enableNativeTrackSupport(currentSrc, track) { + if (track?.DeliveryMethod === 'Embed') { return true; } - function requireHlsPlayer(callback) { - import('hls.js').then(({ default: hls }) => { - hls.DefaultConfig.lowLatencyMode = false; - hls.DefaultConfig.backBufferLength = Infinity; - hls.DefaultConfig.liveBackBufferLength = 90; - window.Hls = hls; - callback(); - }); + if (browser.firefox && (currentSrc || '').toLowerCase().includes('.m3u8')) { + return false; } - function getMediaStreamAudioTracks(mediaSource) { - return mediaSource.MediaStreams.filter(function (s) { - return s.Type === 'Audio'; - }); + if (browser.ps4) { + return false; } - function getMediaStreamTextTracks(mediaSource) { - return mediaSource.MediaStreams.filter(function (s) { - return s.Type === 'Subtitle'; - }); + if (browser.web0s) { + return false; } - function zoomIn(elem) { - return new Promise(resolve => { - const duration = 240; - elem.style.animation = `htmlvideoplayer-zoomin ${duration}ms ease-in normal`; - dom.addEventListener(elem, dom.whichAnimationEvent(), resolve, { - once: true - }); - }); + // Edge is randomly not rendering subtitles + if (browser.edge) { + return false; } - function normalizeTrackEventText(text, useHtml) { - const result = text.replace(/\\N/gi, '\n').replace(/\r/gi, ''); - return useHtml ? result.replace(/\n/gi, '
') : result; + if (browser.iOS && (browser.iosVersion || 10) < 10) { + // works in the browser but not the native app + return false; } - function getTextTrackUrl(track, item, format) { - if (itemHelper.isLocalItem(item) && track.Path) { - return track.Path; + if (track) { + const format = (track.Codec || '').toLowerCase(); + if (format === 'ssa' || format === 'ass') { + return false; } - - let url = playbackManager.getSubtitleUrl(track, item.ServerId); - if (format) { - url = url.replace('.vtt', format); - } - - return url; } - function getDefaultProfile() { - return profileBuilder({}); + return true; +} + +function requireHlsPlayer(callback) { + import('hls.js').then(({ default: hls }) => { + hls.DefaultConfig.lowLatencyMode = false; + hls.DefaultConfig.backBufferLength = Infinity; + hls.DefaultConfig.liveBackBufferLength = 90; + window.Hls = hls; + callback(); + }); +} + +function getMediaStreamAudioTracks(mediaSource) { + return mediaSource.MediaStreams.filter(function (s) { + return s.Type === 'Audio'; + }); +} + +function getMediaStreamTextTracks(mediaSource) { + return mediaSource.MediaStreams.filter(function (s) { + return s.Type === 'Subtitle'; + }); +} + +function zoomIn(elem) { + return new Promise(resolve => { + const duration = 240; + elem.style.animation = `htmlvideoplayer-zoomin ${duration}ms ease-in normal`; + dom.addEventListener(elem, dom.whichAnimationEvent(), resolve, { + once: true + }); + }); +} + +function normalizeTrackEventText(text, useHtml) { + const result = text.replace(/\\N/gi, '\n').replace(/\r/gi, ''); + return useHtml ? result.replace(/\n/gi, '
') : result; +} + +function getTextTrackUrl(track, item, format) { + if (itemHelper.isLocalItem(item) && track.Path) { + return track.Path; } - const PRIMARY_TEXT_TRACK_INDEX = 0; - const SECONDARY_TEXT_TRACK_INDEX = 1; + let url = playbackManager.getSubtitleUrl(track, item.ServerId); + if (format) { + url = url.replace('.vtt', format); + } - export class HtmlVideoPlayer { - /** + return url; +} + +function getDefaultProfile() { + return profileBuilder({}); +} + +const PRIMARY_TEXT_TRACK_INDEX = 0; +const SECONDARY_TEXT_TRACK_INDEX = 1; + +export class HtmlVideoPlayer { + /** * @type {string} */ - name; - /** + name; + /** * @type {string} */ - type = PluginType.MediaPlayer; - /** + type = PluginType.MediaPlayer; + /** * @type {string} */ - id = 'htmlvideoplayer'; - /** + id = 'htmlvideoplayer'; + /** * Let any players created by plugins take priority * * @type {number} */ - priority = 1; - /** + priority = 1; + /** * @type {boolean} */ - isFetching = false; - /** + isFetching = false; + /** * @type {HTMLDivElement | null | undefined} */ - #videoDialog; - /** + #videoDialog; + /** * @type {number | undefined} */ - #subtitleTrackIndexToSetOnPlaying; - /** + #subtitleTrackIndexToSetOnPlaying; + /** * @type {number | undefined} */ - #secondarySubtitleTrackIndexToSetOnPlaying; - /** + #secondarySubtitleTrackIndexToSetOnPlaying; + /** * @type {number | null} */ - #audioTrackIndexToSetOnPlaying; - /** + #audioTrackIndexToSetOnPlaying; + /** * @type {null | undefined} */ - #currentClock; - /** + #currentClock; + /** * @type {any | null | undefined} */ - #currentAssRenderer; - /** + #currentAssRenderer; + /** * @type {null | undefined} */ - #customTrackIndex; - /** + #customTrackIndex; + /** * @type {number | undefined} */ - #customSecondaryTrackIndex; - /** + #customSecondaryTrackIndex; + /** * @type {boolean | undefined} */ - #showTrackOffset; - /** + #showTrackOffset; + /** * @type {number | undefined} */ - #currentTrackOffset; - /** + #currentTrackOffset; + /** * @type {HTMLElement | null | undefined} */ - #secondaryTrackOffset; - /** + #secondaryTrackOffset; + /** * @type {HTMLElement | null | undefined} */ - #videoSubtitlesElem; - /** + #videoSubtitlesElem; + /** * @type {HTMLElement | null | undefined} */ - #videoSecondarySubtitlesElem; - /** + #videoSecondarySubtitlesElem; + /** * @type {any | null | undefined} */ - #currentTrackEvents; - /** + #currentTrackEvents; + /** * @type {any | null | undefined} */ - #currentSecondaryTrackEvents; - /** + #currentSecondaryTrackEvents; + /** * @type {string[] | undefined} */ - #supportedFeatures; - /** + #supportedFeatures; + /** * @type {HTMLVideoElement | null | undefined} */ - #mediaElement; - /** + #mediaElement; + /** * @type {number} */ - #fetchQueue = 0; - /** + #fetchQueue = 0; + /** * @type {string | undefined} */ - #currentSrc; - /** + #currentSrc; + /** * @type {boolean | undefined} */ - #started; - /** + #started; + /** * @type {boolean | undefined} */ - #timeUpdated; - /** + #timeUpdated; + /** * @type {number | null | undefined} */ - #currentTime; - /** + #currentTime; + /** * @type {any | undefined} */ - #flvPlayer; - /** + #flvPlayer; + /** * @private (used in other files) * @type {any | undefined} */ - _hlsPlayer; - /** + _hlsPlayer; + /** * @private (used in other files) * @type {any | null | undefined} */ - _castPlayer; - /** + _castPlayer; + /** * @private (used in other files) * @type {any | undefined} */ - _currentPlayOptions; - /** + _currentPlayOptions; + /** * @type {any | undefined} */ - #lastProfile; + #lastProfile; - constructor() { - if (browser.edgeUwp) { - this.name = 'Windows Video Player'; - } else { - this.name = 'Html Video Player'; - } + constructor() { + if (browser.edgeUwp) { + this.name = 'Windows Video Player'; + } else { + this.name = 'Html Video Player'; } + } - currentSrc() { - return this.#currentSrc; - } + currentSrc() { + return this.#currentSrc; + } - /** + /** * @private */ - incrementFetchQueue() { - if (this.#fetchQueue <= 0) { - this.isFetching = true; - Events.trigger(this, 'beginFetch'); - } - - this.#fetchQueue++; + incrementFetchQueue() { + if (this.#fetchQueue <= 0) { + this.isFetching = true; + Events.trigger(this, 'beginFetch'); } - /** + this.#fetchQueue++; + } + + /** * @private */ - decrementFetchQueue() { - this.#fetchQueue--; + decrementFetchQueue() { + this.#fetchQueue--; - if (this.#fetchQueue <= 0) { - this.isFetching = false; - Events.trigger(this, 'endFetch'); - } + if (this.#fetchQueue <= 0) { + this.isFetching = false; + Events.trigger(this, 'endFetch'); } + } - /** + /** * @private */ - updateVideoUrl(streamInfo) { - const isHls = streamInfo.url.toLowerCase().includes('.m3u8'); + updateVideoUrl(streamInfo) { + const isHls = streamInfo.url.toLowerCase().includes('.m3u8'); - const mediaSource = streamInfo.mediaSource; - const item = streamInfo.item; + const mediaSource = streamInfo.mediaSource; + const item = streamInfo.item; - // Huge hack alert. Safari doesn't seem to like if the segments aren't available right away when playback starts - // This will start the transcoding process before actually feeding the video url into the player - // Edit: Also seeing stalls from hls.js - if (mediaSource && item && !mediaSource.RunTimeTicks && isHls && streamInfo.playMethod === 'Transcode' && (browser.iOS || browser.osx)) { - const hlsPlaylistUrl = streamInfo.url.replace('master.m3u8', 'live.m3u8'); + // Huge hack alert. Safari doesn't seem to like if the segments aren't available right away when playback starts + // This will start the transcoding process before actually feeding the video url into the player + // Edit: Also seeing stalls from hls.js + if (mediaSource && item && !mediaSource.RunTimeTicks && isHls && streamInfo.playMethod === 'Transcode' && (browser.iOS || browser.osx)) { + const hlsPlaylistUrl = streamInfo.url.replace('master.m3u8', 'live.m3u8'); - loading.show(); + loading.show(); - console.debug(`prefetching hls playlist: ${hlsPlaylistUrl}`); + console.debug(`prefetching hls playlist: ${hlsPlaylistUrl}`); - return ServerConnections.getApiClient(item.ServerId).ajax({ + return ServerConnections.getApiClient(item.ServerId).ajax({ - type: 'GET', - url: hlsPlaylistUrl + type: 'GET', + url: hlsPlaylistUrl - }).then(function () { - console.debug(`completed prefetching hls playlist: ${hlsPlaylistUrl}`); + }).then(function () { + console.debug(`completed prefetching hls playlist: ${hlsPlaylistUrl}`); - loading.hide(); - streamInfo.url = hlsPlaylistUrl; - }, function () { - console.error(`error prefetching hls playlist: ${hlsPlaylistUrl}`); + loading.hide(); + streamInfo.url = hlsPlaylistUrl; + }, function () { + console.error(`error prefetching hls playlist: ${hlsPlaylistUrl}`); - loading.hide(); - }); - } else { - return Promise.resolve(); - } - } - - play(options) { - this.#started = false; - this.#timeUpdated = false; - - this.#currentTime = null; - - if (options.resetSubtitleOffset !== false) this.resetSubtitleOffset(); - - return this.createMediaElement(options).then(elem => { - return this.updateVideoUrl(options).then(() => { - return this.setCurrentSrc(elem, options); - }); + loading.hide(); }); + } else { + return Promise.resolve(); } + } - /** + play(options) { + this.#started = false; + this.#timeUpdated = false; + + this.#currentTime = null; + + if (options.resetSubtitleOffset !== false) this.resetSubtitleOffset(); + + return this.createMediaElement(options).then(elem => { + return this.updateVideoUrl(options).then(() => { + return this.setCurrentSrc(elem, options); + }); + }); + } + + /** * @private */ - setSrcWithFlvJs(elem, options, url) { - return import('flv.js').then(({ default: flvjs }) => { - const flvPlayer = flvjs.createPlayer({ - type: 'flv', - url: url - }, - { - seekType: 'range', - lazyLoad: false - }); + setSrcWithFlvJs(elem, options, url) { + return import('flv.js').then(({ default: flvjs }) => { + const flvPlayer = flvjs.createPlayer({ + type: 'flv', + url: url + }, + { + seekType: 'range', + lazyLoad: false + }); - flvPlayer.attachMediaElement(elem); - flvPlayer.load(); + flvPlayer.attachMediaElement(elem); + flvPlayer.load(); - this.#flvPlayer = flvPlayer; + this.#flvPlayer = flvPlayer; + + // This is needed in setCurrentTrackElement + this.#currentSrc = url; + + return flvPlayer.play(); + }); + } + + /** + * @private + */ + setSrcWithHlsJs(elem, options, url) { + return new Promise((resolve, reject) => { + requireHlsPlayer(async () => { + let maxBufferLength = 30; + + // Some browsers cannot handle huge fragments in high bitrate. + // This issue usually happens when using HWA encoders with a high bitrate setting. + // Limit the BufferLength to 6s, it works fine when playing 4k 120Mbps over HLS on chrome. + // https://github.com/video-dev/hls.js/issues/876 + if ((browser.chrome || browser.edgeChromium || browser.firefox) && playbackManager.getMaxStreamingBitrate(this) >= 25000000) { + maxBufferLength = 6; + } + + const includeCorsCredentials = await getIncludeCorsCredentials(); + + const hls = new Hls({ + manifestLoadingTimeOut: 20000, + maxBufferLength: maxBufferLength, + xhrSetup(xhr) { + xhr.withCredentials = includeCorsCredentials; + } + }); + hls.loadSource(url); + hls.attachMedia(elem); + + bindEventsToHlsPlayer(this, hls, elem, this.onError, resolve, reject); + + this._hlsPlayer = hls; // This is needed in setCurrentTrackElement this.#currentSrc = url; - - return flvPlayer.play(); }); - } + }); + } - /** + /** * @private */ - setSrcWithHlsJs(elem, options, url) { - return new Promise((resolve, reject) => { - requireHlsPlayer(async () => { - let maxBufferLength = 30; + async setCurrentSrc(elem, options) { + elem.removeEventListener('error', this.onError); - // Some browsers cannot handle huge fragments in high bitrate. - // This issue usually happens when using HWA encoders with a high bitrate setting. - // Limit the BufferLength to 6s, it works fine when playing 4k 120Mbps over HLS on chrome. - // https://github.com/video-dev/hls.js/issues/876 - if ((browser.chrome || browser.edgeChromium || browser.firefox) && playbackManager.getMaxStreamingBitrate(this) >= 25000000) { - maxBufferLength = 6; - } + let val = options.url; + console.debug(`playing url: ${val}`); - const includeCorsCredentials = await getIncludeCorsCredentials(); - - const hls = new Hls({ - manifestLoadingTimeOut: 20000, - maxBufferLength: maxBufferLength, - xhrSetup(xhr) { - xhr.withCredentials = includeCorsCredentials; - } - }); - hls.loadSource(url); - hls.attachMedia(elem); - - bindEventsToHlsPlayer(this, hls, elem, this.onError, resolve, reject); - - this._hlsPlayer = hls; - - // This is needed in setCurrentTrackElement - this.#currentSrc = url; - }); - }); + // Convert to seconds + const seconds = (options.playerStartPositionTicks || 0) / 10000000; + if (seconds) { + val += `#t=${seconds}`; } - /** - * @private - */ - async setCurrentSrc(elem, options) { - elem.removeEventListener('error', this.onError); + destroyHlsPlayer(this); + destroyFlvPlayer(this); + destroyCastPlayer(this); - let val = options.url; - console.debug(`playing url: ${val}`); + let secondaryTrackValid = true; - // Convert to seconds - const seconds = (options.playerStartPositionTicks || 0) / 10000000; - if (seconds) { - val += `#t=${seconds}`; - } - - destroyHlsPlayer(this); - destroyFlvPlayer(this); - destroyCastPlayer(this); - - let secondaryTrackValid = true; - - this.#subtitleTrackIndexToSetOnPlaying = options.mediaSource.DefaultSubtitleStreamIndex == null ? -1 : options.mediaSource.DefaultSubtitleStreamIndex; - if (this.#subtitleTrackIndexToSetOnPlaying != null && this.#subtitleTrackIndexToSetOnPlaying >= 0) { - const initialSubtitleStream = options.mediaSource.MediaStreams[this.#subtitleTrackIndexToSetOnPlaying]; - if (!initialSubtitleStream || initialSubtitleStream.DeliveryMethod === 'Encode') { - this.#subtitleTrackIndexToSetOnPlaying = -1; - secondaryTrackValid = false; - } - // secondary track should not be shown if primary track is no longer a valid pair - if (initialSubtitleStream && !playbackManager.trackHasSecondarySubtitleSupport(initialSubtitleStream, this)) { - secondaryTrackValid = false; - } - } else { + this.#subtitleTrackIndexToSetOnPlaying = options.mediaSource.DefaultSubtitleStreamIndex == null ? -1 : options.mediaSource.DefaultSubtitleStreamIndex; + if (this.#subtitleTrackIndexToSetOnPlaying != null && this.#subtitleTrackIndexToSetOnPlaying >= 0) { + const initialSubtitleStream = options.mediaSource.MediaStreams[this.#subtitleTrackIndexToSetOnPlaying]; + if (!initialSubtitleStream || initialSubtitleStream.DeliveryMethod === 'Encode') { + this.#subtitleTrackIndexToSetOnPlaying = -1; secondaryTrackValid = false; } + // secondary track should not be shown if primary track is no longer a valid pair + if (initialSubtitleStream && !playbackManager.trackHasSecondarySubtitleSupport(initialSubtitleStream, this)) { + secondaryTrackValid = false; + } + } else { + secondaryTrackValid = false; + } - this.#audioTrackIndexToSetOnPlaying = options.playMethod === 'Transcode' ? null : options.mediaSource.DefaultAudioStreamIndex; + this.#audioTrackIndexToSetOnPlaying = options.playMethod === 'Transcode' ? null : options.mediaSource.DefaultAudioStreamIndex; - this._currentPlayOptions = options; + this._currentPlayOptions = options; - if (secondaryTrackValid) { - this.#secondarySubtitleTrackIndexToSetOnPlaying = options.mediaSource.DefaultSecondarySubtitleStreamIndex == null ? -1 : options.mediaSource.DefaultSecondarySubtitleStreamIndex; - if (this.#secondarySubtitleTrackIndexToSetOnPlaying != null && this.#secondarySubtitleTrackIndexToSetOnPlaying >= 0) { - const initialSecondarySubtitleStream = options.mediaSource.MediaStreams[this.#secondarySubtitleTrackIndexToSetOnPlaying]; - if (!initialSecondarySubtitleStream || !playbackManager.trackHasSecondarySubtitleSupport(initialSecondarySubtitleStream, this)) { - this.#secondarySubtitleTrackIndexToSetOnPlaying = -1; - } + if (secondaryTrackValid) { + this.#secondarySubtitleTrackIndexToSetOnPlaying = options.mediaSource.DefaultSecondarySubtitleStreamIndex == null ? -1 : options.mediaSource.DefaultSecondarySubtitleStreamIndex; + if (this.#secondarySubtitleTrackIndexToSetOnPlaying != null && this.#secondarySubtitleTrackIndexToSetOnPlaying >= 0) { + const initialSecondarySubtitleStream = options.mediaSource.MediaStreams[this.#secondarySubtitleTrackIndexToSetOnPlaying]; + if (!initialSecondarySubtitleStream || !playbackManager.trackHasSecondarySubtitleSupport(initialSecondarySubtitleStream, this)) { + this.#secondarySubtitleTrackIndexToSetOnPlaying = -1; } - } else { - this.#secondarySubtitleTrackIndexToSetOnPlaying = -1; + } + } else { + this.#secondarySubtitleTrackIndexToSetOnPlaying = -1; + } + + const crossOrigin = getCrossOriginValue(options.mediaSource); + if (crossOrigin) { + elem.crossOrigin = crossOrigin; + } + + if (enableHlsJsPlayer(options.mediaSource.RunTimeTicks, 'Video') && val.includes('.m3u8')) { + return this.setSrcWithHlsJs(elem, options, val); + } else if (options.playMethod !== 'Transcode' && options.mediaSource.Container === 'flv') { + return this.setSrcWithFlvJs(elem, options, val); + } else { + elem.autoplay = true; + + const includeCorsCredentials = await getIncludeCorsCredentials(); + if (includeCorsCredentials) { + // Safari will not send cookies without this + elem.crossOrigin = 'use-credentials'; } - const crossOrigin = getCrossOriginValue(options.mediaSource); - if (crossOrigin) { - elem.crossOrigin = crossOrigin; - } + return applySrc(elem, val, options).then(() => { + this.#currentSrc = val; - if (enableHlsJsPlayer(options.mediaSource.RunTimeTicks, 'Video') && val.includes('.m3u8')) { - return this.setSrcWithHlsJs(elem, options, val); - } else if (options.playMethod !== 'Transcode' && options.mediaSource.Container === 'flv') { - return this.setSrcWithFlvJs(elem, options, val); - } else { - elem.autoplay = true; + return playWithPromise(elem, this.onError); + }); + } + } - const includeCorsCredentials = await getIncludeCorsCredentials(); - if (includeCorsCredentials) { - // Safari will not send cookies without this - elem.crossOrigin = 'use-credentials'; - } + setSubtitleStreamIndex(index) { + this.setCurrentTrackElement(index); + } - return applySrc(elem, val, options).then(() => { - this.#currentSrc = val; + setSecondarySubtitleStreamIndex(index) { + this.setCurrentTrackElement(index, SECONDARY_TEXT_TRACK_INDEX); + } - return playWithPromise(elem, this.onError); + resetSubtitleOffset() { + this.#currentTrackOffset = 0; + this.#secondaryTrackOffset = 0; + this.#showTrackOffset = false; + } + + enableShowingSubtitleOffset() { + this.#showTrackOffset = true; + } + + disableShowingSubtitleOffset() { + this.#showTrackOffset = false; + } + + isShowingSubtitleOffsetEnabled() { + return this.#showTrackOffset; + } + + /** + * @private + */ + getTextTracks() { + const videoElement = this.#mediaElement; + if (videoElement) { + return Array.from(videoElement.textTracks) + .filter(function (trackElement) { + // get showing .vtt textTack + return trackElement.mode === 'showing'; }); - } + } else { + return null; } + } - setSubtitleStreamIndex(index) { - this.setCurrentTrackElement(index); - } + setSubtitleOffset = debounce(this._setSubtitleOffset, 100); - setSecondarySubtitleStreamIndex(index) { - this.setCurrentTrackElement(index, SECONDARY_TEXT_TRACK_INDEX); - } - - resetSubtitleOffset() { - this.#currentTrackOffset = 0; - this.#secondaryTrackOffset = 0; - this.#showTrackOffset = false; - } - - enableShowingSubtitleOffset() { - this.#showTrackOffset = true; - } - - disableShowingSubtitleOffset() { - this.#showTrackOffset = false; - } - - isShowingSubtitleOffsetEnabled() { - return this.#showTrackOffset; - } - - /** + /** * @private */ - getTextTracks() { - const videoElement = this.#mediaElement; - if (videoElement) { - return Array.from(videoElement.textTracks) - .filter(function (trackElement) { - // get showing .vtt textTack - return trackElement.mode === 'showing'; - }); + _setSubtitleOffset(offset) { + const offsetValue = parseFloat(offset); + + // if .ass currently rendering + if (this.#currentAssRenderer) { + this.updateCurrentTrackOffset(offsetValue); + this.#currentAssRenderer.timeOffset = (this._currentPlayOptions.transcodingOffsetTicks || 0) / 10000000 + offsetValue; + } else { + const trackElements = this.getTextTracks(); + // if .vtt currently rendering + if (trackElements?.length > 0) { + trackElements.forEach((trackElement, index) => { + this.setTextTrackSubtitleOffset(trackElement, offsetValue, index); + }); + } else if (this.#currentTrackEvents || this.#currentSecondaryTrackEvents) { + this.#currentTrackEvents && this.setTrackEventsSubtitleOffset(this.#currentTrackEvents, offsetValue, PRIMARY_TEXT_TRACK_INDEX); + this.#currentSecondaryTrackEvents && this.setTrackEventsSubtitleOffset(this.#currentSecondaryTrackEvents, offsetValue, SECONDARY_TEXT_TRACK_INDEX); } else { - return null; + console.debug('No available track, cannot apply offset: ', offsetValue); } } + } - setSubtitleOffset = debounce(this._setSubtitleOffset, 100); - - /** + /** * @private */ - _setSubtitleOffset(offset) { - const offsetValue = parseFloat(offset); - - // if .ass currently rendering - if (this.#currentAssRenderer) { - this.updateCurrentTrackOffset(offsetValue); - this.#currentAssRenderer.timeOffset = (this._currentPlayOptions.transcodingOffsetTicks || 0) / 10000000 + offsetValue; - } else { - const trackElements = this.getTextTracks(); - // if .vtt currently rendering - if (trackElements?.length > 0) { - trackElements.forEach((trackElement, index) => { - this.setTextTrackSubtitleOffset(trackElement, offsetValue, index); - }); - } else if (this.#currentTrackEvents || this.#currentSecondaryTrackEvents) { - this.#currentTrackEvents && this.setTrackEventsSubtitleOffset(this.#currentTrackEvents, offsetValue, PRIMARY_TEXT_TRACK_INDEX); - this.#currentSecondaryTrackEvents && this.setTrackEventsSubtitleOffset(this.#currentSecondaryTrackEvents, offsetValue, SECONDARY_TEXT_TRACK_INDEX); - } else { - console.debug('No available track, cannot apply offset: ', offsetValue); - } - } + updateCurrentTrackOffset(offsetValue, currentTrackIndex = PRIMARY_TEXT_TRACK_INDEX) { + let offsetToCompare = this.#currentTrackOffset; + if (this.isSecondaryTrack(currentTrackIndex)) { + offsetToCompare = this.#secondaryTrackOffset; } - /** - * @private - */ - updateCurrentTrackOffset(offsetValue, currentTrackIndex = PRIMARY_TEXT_TRACK_INDEX) { - let offsetToCompare = this.#currentTrackOffset; - if (this.isSecondaryTrack(currentTrackIndex)) { - offsetToCompare = this.#secondaryTrackOffset; - } + let relativeOffset = offsetValue; + const newTrackOffset = offsetValue; - let relativeOffset = offsetValue; - const newTrackOffset = offsetValue; - - if (offsetToCompare) { - relativeOffset -= offsetToCompare; - } - - if (this.isSecondaryTrack(currentTrackIndex)) { - this.#secondaryTrackOffset = newTrackOffset; - } else { - this.#currentTrackOffset = newTrackOffset; - } - - // relative to currentTrackOffset - return relativeOffset; + if (offsetToCompare) { + relativeOffset -= offsetToCompare; } - /** + if (this.isSecondaryTrack(currentTrackIndex)) { + this.#secondaryTrackOffset = newTrackOffset; + } else { + this.#currentTrackOffset = newTrackOffset; + } + + // relative to currentTrackOffset + return relativeOffset; + } + + /** * @private * These browsers will not clear the existing active cue when setting an offset * for native TextTracks. * Any previous text tracks that are on the screen when the offset changes will remain next * to the new tracks until they reach the end time of the new offset's instance of the track. */ - requiresHidingActiveCuesOnOffsetChange() { - return !!browser.firefox; - } + requiresHidingActiveCuesOnOffsetChange() { + return !!browser.firefox; + } - /** + /** * @private */ - hideTextTrackWithActiveCues(currentTrack) { - if (currentTrack.activeCues) { - currentTrack.mode = 'hidden'; - } + hideTextTrackWithActiveCues(currentTrack) { + if (currentTrack.activeCues) { + currentTrack.mode = 'hidden'; } + } - /** + /** * Forces the active cue to clear by disabling then re-enabling the track. * The track mode is reverted inside of a 0ms timeout to free up the track * and allow it to disable and clear the active cue. * @private */ - forceClearTextTrackActiveCues(currentTrack) { - if (currentTrack.activeCues) { - currentTrack.mode = 'disabled'; - setTimeout(() => { - currentTrack.mode = 'showing'; - }, 0); - } + forceClearTextTrackActiveCues(currentTrack) { + if (currentTrack.activeCues) { + currentTrack.mode = 'disabled'; + setTimeout(() => { + currentTrack.mode = 'showing'; + }, 0); } + } - /** + /** * @private */ - setTextTrackSubtitleOffset(currentTrack, offsetValue, currentTrackIndex) { - if (currentTrack.cues) { - offsetValue = this.updateCurrentTrackOffset(offsetValue, currentTrackIndex); - if (offsetValue === 0) { - return; - } - - const shouldClearActiveCues = this.requiresHidingActiveCuesOnOffsetChange(); - if (shouldClearActiveCues) { - this.hideTextTrackWithActiveCues(currentTrack); - } - - Array.from(currentTrack.cues) - .forEach(function (cue) { - cue.startTime -= offsetValue; - cue.endTime -= offsetValue; - }); - - if (shouldClearActiveCues) { - this.forceClearTextTrackActiveCues(currentTrack); - } + setTextTrackSubtitleOffset(currentTrack, offsetValue, currentTrackIndex) { + if (currentTrack.cues) { + offsetValue = this.updateCurrentTrackOffset(offsetValue, currentTrackIndex); + if (offsetValue === 0) { + return; } - } - /** - * @private - */ - setTrackEventsSubtitleOffset(trackEvents, offsetValue, currentTrackIndex) { - if (Array.isArray(trackEvents)) { - offsetValue = this.updateCurrentTrackOffset(offsetValue, currentTrackIndex) * 1e7; // ticks - if (offsetValue === 0) { - return; - } - trackEvents.forEach(function (trackEvent) { - trackEvent.StartPositionTicks -= offsetValue; - trackEvent.EndPositionTicks -= offsetValue; + const shouldClearActiveCues = this.requiresHidingActiveCuesOnOffsetChange(); + if (shouldClearActiveCues) { + this.hideTextTrackWithActiveCues(currentTrack); + } + + Array.from(currentTrack.cues) + .forEach(function (cue) { + cue.startTime -= offsetValue; + cue.endTime -= offsetValue; }); + + if (shouldClearActiveCues) { + this.forceClearTextTrackActiveCues(currentTrack); } } + } - getSubtitleOffset() { - return this.#currentTrackOffset; - } - - isPrimaryTrack(textTrackIndex) { - return textTrackIndex === PRIMARY_TEXT_TRACK_INDEX; - } - - isSecondaryTrack(textTrackIndex) { - return textTrackIndex === SECONDARY_TEXT_TRACK_INDEX; - } - - /** + /** * @private */ - isAudioStreamSupported(stream, deviceProfile, container) { - const codec = (stream.Codec || '').toLowerCase(); - - if (!codec) { - return true; + setTrackEventsSubtitleOffset(trackEvents, offsetValue, currentTrackIndex) { + if (Array.isArray(trackEvents)) { + offsetValue = this.updateCurrentTrackOffset(offsetValue, currentTrackIndex) * 1e7; // ticks + if (offsetValue === 0) { + return; } + trackEvents.forEach(function (trackEvent) { + trackEvent.StartPositionTicks -= offsetValue; + trackEvent.EndPositionTicks -= offsetValue; + }); + } + } - if (!deviceProfile) { - // This should never happen - return true; - } + getSubtitleOffset() { + return this.#currentTrackOffset; + } - const profiles = deviceProfile.DirectPlayProfiles || []; + isPrimaryTrack(textTrackIndex) { + return textTrackIndex === PRIMARY_TEXT_TRACK_INDEX; + } - return profiles.some(function (p) { - return p.Type === 'Video' + isSecondaryTrack(textTrackIndex) { + return textTrackIndex === SECONDARY_TEXT_TRACK_INDEX; + } + + /** + * @private + */ + isAudioStreamSupported(stream, deviceProfile, container) { + const codec = (stream.Codec || '').toLowerCase(); + + if (!codec) { + return true; + } + + if (!deviceProfile) { + // This should never happen + return true; + } + + const profiles = deviceProfile.DirectPlayProfiles || []; + + return profiles.some(function (p) { + return p.Type === 'Video' && includesAny((p.Container || '').toLowerCase(), container) && includesAny((p.AudioCodec || '').toLowerCase(), codec); - }); - } + }); + } - /** + /** * @private */ - getSupportedAudioStreams() { - const profile = this.#lastProfile; + getSupportedAudioStreams() { + const profile = this.#lastProfile; - const mediaSource = this._currentPlayOptions.mediaSource; - const container = mediaSource.Container.toLowerCase(); + const mediaSource = this._currentPlayOptions.mediaSource; + const container = mediaSource.Container.toLowerCase(); - return getMediaStreamAudioTracks(mediaSource).filter((stream) => { - return this.isAudioStreamSupported(stream, profile, container); - }); + return getMediaStreamAudioTracks(mediaSource).filter((stream) => { + return this.isAudioStreamSupported(stream, profile, container); + }); + } + + setAudioStreamIndex(index) { + const streams = this.getSupportedAudioStreams(); + + if (streams.length < 2) { + // If there's only one supported stream then trust that the player will handle it on it's own + return; } - setAudioStreamIndex(index) { - const streams = this.getSupportedAudioStreams(); + let audioIndex = -1; - if (streams.length < 2) { - // If there's only one supported stream then trust that the player will handle it on it's own - return; + for (const stream of streams) { + audioIndex++; + + if (stream.Index === index) { + break; } + } - let audioIndex = -1; + if (audioIndex === -1) { + return; + } - for (const stream of streams) { - audioIndex++; + const elem = this.#mediaElement; + if (!elem) { + return; + } - if (stream.Index === index) { - break; - } - } + // https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/audioTracks - if (audioIndex === -1) { - return; - } - - const elem = this.#mediaElement; - if (!elem) { - return; - } - - // https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/audioTracks - - /** + /** * @type {ArrayLike|any[]} */ - const elemAudioTracks = elem.audioTracks || []; - console.debug(`found ${elemAudioTracks.length} audio tracks`); + const elemAudioTracks = elem.audioTracks || []; + console.debug(`found ${elemAudioTracks.length} audio tracks`); - for (const [i, audioTrack] of Array.from(elemAudioTracks).entries()) { - if (audioIndex === i) { - console.debug(`setting audio track ${i} to enabled`); - audioTrack.enabled = true; - } else { - console.debug(`setting audio track ${i} to disabled`); - audioTrack.enabled = false; - } - } - } - - stop(destroyPlayer) { - const elem = this.#mediaElement; - const src = this.#currentSrc; - - if (elem) { - if (src) { - elem.pause(); - } - - onEndedInternal(this, elem, this.onError); - } - - this.destroyCustomTrack(elem); - - if (destroyPlayer) { - this.destroy(); - } - - return Promise.resolve(); - } - - destroy() { - this.setSubtitleOffset.cancel(); - - destroyHlsPlayer(this); - destroyFlvPlayer(this); - - setBackdropTransparency(TRANSPARENCY_LEVEL.None); - document.body.classList.remove('hide-scroll'); - - const videoElement = this.#mediaElement; - - if (videoElement) { - this.#mediaElement = null; - - this.destroyCustomTrack(videoElement); - videoElement.removeEventListener('timeupdate', this.onTimeUpdate); - videoElement.removeEventListener('ended', this.onEnded); - videoElement.removeEventListener('volumechange', this.onVolumeChange); - videoElement.removeEventListener('pause', this.onPause); - videoElement.removeEventListener('playing', this.onPlaying); - videoElement.removeEventListener('play', this.onPlay); - videoElement.removeEventListener('click', this.onClick); - videoElement.removeEventListener('dblclick', this.onDblClick); - videoElement.removeEventListener('waiting', this.onWaiting); - videoElement.removeEventListener('error', this.onError); // bound in htmlMediaHelper - - resetSrc(videoElement); - - videoElement.parentNode.removeChild(videoElement); - } - - const dlg = this.#videoDialog; - if (dlg) { - this.#videoDialog = null; - dlg.parentNode.removeChild(dlg); - } - - if (Screenfull.isEnabled) { - Screenfull.exit(); + for (const [i, audioTrack] of Array.from(elemAudioTracks).entries()) { + if (audioIndex === i) { + console.debug(`setting audio track ${i} to enabled`); + audioTrack.enabled = true; } else { - // iOS Safari - if (document.webkitIsFullScreen && document.webkitCancelFullscreen) { - document.webkitCancelFullscreen(); - } + console.debug(`setting audio track ${i} to disabled`); + audioTrack.enabled = false; } } + } + + stop(destroyPlayer) { + const elem = this.#mediaElement; + const src = this.#currentSrc; + + if (elem) { + if (src) { + elem.pause(); + } - /** - * @private - * @param e {Event} The event received from the `