diff --git a/package.json b/package.json index 5ad12d2011..ad9b19d242 100644 --- a/package.json +++ b/package.json @@ -111,6 +111,8 @@ "src/components/fetchhelper.js", "src/components/filterdialog/filterdialog.js", "src/components/groupedcards.js", + "src/components/guide/guide.js", + "src/components/guide/guide-settings.js", "src/components/homeScreenSettings/homeScreenSettings.js", "src/components/homesections/homesections.js", "src/components/htmlMediaHelper.js", diff --git a/src/components/guide/guide-settings.js b/src/components/guide/guide-settings.js index c3ba49f283..b62604ab98 100644 --- a/src/components/guide/guide-settings.js +++ b/src/components/guide/guide-settings.js @@ -1,147 +1,153 @@ -define(['dialogHelper', 'globalize', 'userSettings', 'layoutManager', 'connectionManager', 'require', 'loading', 'scrollHelper', 'emby-checkbox', 'emby-radio', 'css!./../formdialog', 'material-icons'], function (dialogHelper, globalize, userSettings, layoutManager, connectionManager, require, loading, scrollHelper) { - 'use strict'; +import dialogHelper from 'dialogHelper'; +import globalize from 'globalize'; +import * as userSettings from 'userSettings'; +import layoutManager from 'layoutManager'; +import scrollHelper from 'scrollHelper'; +import 'emby-checkbox'; +import 'emby-radio'; +import 'css!./../formdialog'; +import 'material-icons'; - function saveCategories(context, options) { - var categories = []; +function saveCategories(context, options) { + const categories = []; - var chkCategorys = context.querySelectorAll('.chkCategory'); - for (var i = 0, length = chkCategorys.length; i < length; i++) { - var type = chkCategorys[i].getAttribute('data-type'); + const chkCategorys = context.querySelectorAll('.chkCategory'); + for (let i = 0, length = chkCategorys.length; i < length; i++) { + const type = chkCategorys[i].getAttribute('data-type'); - if (chkCategorys[i].checked) { - categories.push(type); - } - } - - if (categories.length >= 4) { - categories.push('series'); - } - - // differentiate between none and all - categories.push('all'); - options.categories = categories; - } - - function loadCategories(context, options) { - var selectedCategories = options.categories || []; - - var chkCategorys = context.querySelectorAll('.chkCategory'); - for (var i = 0, length = chkCategorys.length; i < length; i++) { - var type = chkCategorys[i].getAttribute('data-type'); - - chkCategorys[i].checked = !selectedCategories.length || selectedCategories.indexOf(type) !== -1; + if (chkCategorys[i].checked) { + categories.push(type); } } - function save(context) { - var i; - var length; + if (categories.length >= 4) { + categories.push('series'); + } - var chkIndicators = context.querySelectorAll('.chkIndicator'); - for (i = 0, length = chkIndicators.length; i < length; i++) { - var type = chkIndicators[i].getAttribute('data-type'); - userSettings.set('guide-indicator-' + type, chkIndicators[i].checked); + // differentiate between none and all + categories.push('all'); + options.categories = categories; +} + +function loadCategories(context, options) { + const selectedCategories = options.categories || []; + + const chkCategorys = context.querySelectorAll('.chkCategory'); + for (let i = 0, length = chkCategorys.length; i < length; i++) { + const type = chkCategorys[i].getAttribute('data-type'); + + chkCategorys[i].checked = !selectedCategories.length || selectedCategories.indexOf(type) !== -1; + } +} + +function save(context) { + let i; + let length; + + const chkIndicators = context.querySelectorAll('.chkIndicator'); + for (i = 0, length = chkIndicators.length; i < length; i++) { + const type = chkIndicators[i].getAttribute('data-type'); + userSettings.set('guide-indicator-' + type, chkIndicators[i].checked); + } + + userSettings.set('guide-colorcodedbackgrounds', context.querySelector('.chkColorCodedBackgrounds').checked); + userSettings.set('livetv-favoritechannelsattop', context.querySelector('.chkFavoriteChannelsAtTop').checked); + + const sortBys = context.querySelectorAll('.chkSortOrder'); + for (i = 0, length = sortBys.length; i < length; i++) { + if (sortBys[i].checked) { + userSettings.set('livetv-channelorder', sortBys[i].value); + break; } + } +} - userSettings.set('guide-colorcodedbackgrounds', context.querySelector('.chkColorCodedBackgrounds').checked); - userSettings.set('livetv-favoritechannelsattop', context.querySelector('.chkFavoriteChannelsAtTop').checked); +function load(context) { + let i; + let length; - var sortBys = context.querySelectorAll('.chkSortOrder'); - for (i = 0, length = sortBys.length; i < length; i++) { - if (sortBys[i].checked) { - userSettings.set('livetv-channelorder', sortBys[i].value); - break; - } + const chkIndicators = context.querySelectorAll('.chkIndicator'); + for (i = 0, length = chkIndicators.length; i < length; i++) { + const type = chkIndicators[i].getAttribute('data-type'); + + if (chkIndicators[i].getAttribute('data-default') === 'true') { + chkIndicators[i].checked = userSettings.get('guide-indicator-' + type) !== 'false'; + } else { + chkIndicators[i].checked = userSettings.get('guide-indicator-' + type) === 'true'; } } - function load(context) { - var i; - var length; + context.querySelector('.chkColorCodedBackgrounds').checked = userSettings.get('guide-colorcodedbackgrounds') === 'true'; + context.querySelector('.chkFavoriteChannelsAtTop').checked = userSettings.get('livetv-favoritechannelsattop') !== 'false'; - var chkIndicators = context.querySelectorAll('.chkIndicator'); - for (i = 0, length = chkIndicators.length; i < length; i++) { - var type = chkIndicators[i].getAttribute('data-type'); + const sortByValue = userSettings.get('livetv-channelorder') || 'Number'; - if (chkIndicators[i].getAttribute('data-default') === 'true') { - chkIndicators[i].checked = userSettings.get('guide-indicator-' + type) !== 'false'; + const sortBys = context.querySelectorAll('.chkSortOrder'); + for (i = 0, length = sortBys.length; i < length; i++) { + sortBys[i].checked = sortBys[i].value === sortByValue; + } +} + +function showEditor(options) { + return new Promise(function (resolve, reject) { + let settingsChanged = false; + + import('text!./guide-settings.template.html').then(({default: template}) => { + const dialogOptions = { + removeOnClose: true, + scrollY: false + }; + + if (layoutManager.tv) { + dialogOptions.size = 'fullscreen'; } else { - chkIndicators[i].checked = userSettings.get('guide-indicator-' + type) === 'true'; + dialogOptions.size = 'small'; } - } - context.querySelector('.chkColorCodedBackgrounds').checked = userSettings.get('guide-colorcodedbackgrounds') === 'true'; - context.querySelector('.chkFavoriteChannelsAtTop').checked = userSettings.get('livetv-favoritechannelsattop') !== 'false'; + const dlg = dialogHelper.createDialog(dialogOptions); - var sortByValue = userSettings.get('livetv-channelorder') || 'Number'; + dlg.classList.add('formDialog'); - var sortBys = context.querySelectorAll('.chkSortOrder'); - for (i = 0, length = sortBys.length; i < length; i++) { - sortBys[i].checked = sortBys[i].value === sortByValue; - } - } + let html = ''; - function showEditor(options) { - return new Promise(function (resolve, reject) { - var settingsChanged = false; + html += globalize.translateHtml(template, 'core'); - require(['text!./guide-settings.template.html'], function (template) { - var dialogOptions = { - removeOnClose: true, - scrollY: false - }; + dlg.innerHTML = html; - if (layoutManager.tv) { - dialogOptions.size = 'fullscreen'; - } else { - dialogOptions.size = 'small'; - } - - var dlg = dialogHelper.createDialog(dialogOptions); - - dlg.classList.add('formDialog'); - - var html = ''; - - html += globalize.translateHtml(template, 'core'); - - dlg.innerHTML = html; - - dlg.addEventListener('change', function () { - settingsChanged = true; - }); - - dlg.addEventListener('close', function () { - if (layoutManager.tv) { - scrollHelper.centerFocus.off(dlg.querySelector('.formDialogContent'), false); - } - - save(dlg); - saveCategories(dlg, options); - - if (settingsChanged) { - resolve(); - } else { - reject(); - } - }); - - dlg.querySelector('.btnCancel').addEventListener('click', function () { - dialogHelper.close(dlg); - }); - - if (layoutManager.tv) { - scrollHelper.centerFocus.on(dlg.querySelector('.formDialogContent'), false); - } - - load(dlg); - loadCategories(dlg, options); - dialogHelper.open(dlg); + dlg.addEventListener('change', function () { + settingsChanged = true; }); - }); - } - return { - show: showEditor - }; -}); + dlg.addEventListener('close', function () { + if (layoutManager.tv) { + scrollHelper.centerFocus.off(dlg.querySelector('.formDialogContent'), false); + } + + save(dlg); + saveCategories(dlg, options); + + if (settingsChanged) { + resolve(); + } else { + reject(); + } + }); + + dlg.querySelector('.btnCancel').addEventListener('click', function () { + dialogHelper.close(dlg); + }); + + if (layoutManager.tv) { + scrollHelper.centerFocus.on(dlg.querySelector('.formDialogContent'), false); + } + + load(dlg); + loadCategories(dlg, options); + dialogHelper.open(dlg); + }); + }); +} + +export default { + show: showEditor +}; diff --git a/src/components/guide/guide.js b/src/components/guide/guide.js index 61caa9188f..e48d4646c0 100644 --- a/src/components/guide/guide.js +++ b/src/components/guide/guide.js @@ -1,1178 +1,1198 @@ -define(['require', 'inputManager', 'browser', 'globalize', 'connectionManager', 'scrollHelper', 'serverNotifications', 'loading', 'datetime', 'focusManager', 'playbackManager', 'userSettings', 'imageLoader', 'events', 'layoutManager', 'itemShortcuts', 'dom', 'css!./guide.css', 'programStyles', 'material-icons', 'scrollStyles', 'emby-programcell', 'emby-button', 'paper-icon-button-light', 'emby-tabs', 'emby-scroller', 'flexStyles', 'webcomponents'], function (require, inputManager, browser, globalize, connectionManager, scrollHelper, serverNotifications, loading, datetime, focusManager, playbackManager, userSettings, imageLoader, events, layoutManager, itemShortcuts, dom) { - 'use strict'; +import inputManager from 'inputManager'; +import browser from 'browser'; +import globalize from 'globalize'; +import connectionManager from 'connectionManager'; +import scrollHelper from 'scrollHelper'; +import serverNotifications from 'serverNotifications'; +import loading from 'loading'; +import datetime from 'datetime'; +import focusManager from 'focusManager'; +import playbackManager from 'playbackManager'; +import * as userSettings from 'userSettings'; +import imageLoader from 'imageLoader'; +import events from 'events'; +import layoutManager from 'layoutManager'; +import itemShortcuts from 'itemShortcuts'; +import dom from 'dom'; +import 'css!./guide.css'; +import 'programStyles'; +import 'material-icons'; +import 'scrollStyles'; +import 'emby-programcell'; +import 'emby-button'; +import 'paper-icon-button-light'; +import 'emby-tabs'; +import 'emby-scroller'; +import 'flexStyles'; +import 'webcomponents'; - playbackManager = playbackManager.default || playbackManager; - browser = browser.default || browser; - loading = loading.default || loading; - - function showViewSettings(instance) { - require(['guide-settings-dialog'], function (guideSettingsDialog) { - guideSettingsDialog.show(instance.categoryOptions).then(function () { - instance.refresh(); - }); +function showViewSettings(instance) { + import('guide-settings-dialog').then(({default: guideSettingsDialog}) => { + guideSettingsDialog.show(instance.categoryOptions).then(function () { + instance.refresh(); }); + }); +} + +function updateProgramCellOnScroll(cell, scrollPct) { + let left = cell.posLeft; + if (!left) { + left = parseFloat(cell.style.left.replace('%', '')); + cell.posLeft = left; + } + let width = cell.posWidth; + if (!width) { + width = parseFloat(cell.style.width.replace('%', '')); + cell.posWidth = width; } - function updateProgramCellOnScroll(cell, scrollPct) { - var left = cell.posLeft; - if (!left) { - left = parseFloat(cell.style.left.replace('%', '')); - cell.posLeft = left; - } - var width = cell.posWidth; - if (!width) { - width = parseFloat(cell.style.width.replace('%', '')); - cell.posWidth = width; - } + const right = left + width; + const newPct = Math.max(Math.min(scrollPct, right), left); - var right = left + width; - var newPct = Math.max(Math.min(scrollPct, right), left); + const offset = newPct - left; + const pctOfWidth = (offset / width) * 100; - var offset = newPct - left; - var pctOfWidth = (offset / width) * 100; - - var guideProgramName = cell.guideProgramName; - if (!guideProgramName) { - guideProgramName = cell.querySelector('.guideProgramName'); - cell.guideProgramName = guideProgramName; - } - - var caret = cell.caret; - if (!caret) { - caret = cell.querySelector('.guide-programNameCaret'); - cell.caret = caret; - } - - if (guideProgramName) { - if (pctOfWidth > 0 && pctOfWidth <= 100) { - guideProgramName.style.transform = 'translateX(' + pctOfWidth + '%)'; - caret.classList.remove('hide'); - } else { - guideProgramName.style.transform = 'none'; - caret.classList.add('hide'); - } - } + let guideProgramName = cell.guideProgramName; + if (!guideProgramName) { + guideProgramName = cell.querySelector('.guideProgramName'); + cell.guideProgramName = guideProgramName; } - var isUpdatingProgramCellScroll = false; - function updateProgramCellsOnScroll(programGrid, programCells) { - if (isUpdatingProgramCellScroll) { - return; - } - - isUpdatingProgramCellScroll = true; - - requestAnimationFrame(function () { - var scrollLeft = programGrid.scrollLeft; - - var scrollPct = scrollLeft ? (scrollLeft / programGrid.scrollWidth) * 100 : 0; - - for (var i = 0, length = programCells.length; i < length; i++) { - updateProgramCellOnScroll(programCells[i], scrollPct); - } - - isUpdatingProgramCellScroll = false; - }); + let caret = cell.caret; + if (!caret) { + caret = cell.querySelector('.guide-programNameCaret'); + cell.caret = caret; } - function onProgramGridClick(e) { - if (!layoutManager.tv) { - return; - } - - var programCell = dom.parentWithClass(e.target, 'programCell'); - if (programCell) { - var startDate = programCell.getAttribute('data-startdate'); - var endDate = programCell.getAttribute('data-enddate'); - startDate = datetime.parseISO8601Date(startDate, { toLocal: true }).getTime(); - endDate = datetime.parseISO8601Date(endDate, { toLocal: true }).getTime(); - - var now = new Date().getTime(); - if (now >= startDate && now < endDate) { - var channelId = programCell.getAttribute('data-channelid'); - var serverId = programCell.getAttribute('data-serverid'); - - e.preventDefault(); - e.stopPropagation(); - - playbackManager.play({ - ids: [channelId], - serverId: serverId - }); - } + if (guideProgramName) { + if (pctOfWidth > 0 && pctOfWidth <= 100) { + guideProgramName.style.transform = 'translateX(' + pctOfWidth + '%)'; + caret.classList.remove('hide'); + } else { + guideProgramName.style.transform = 'none'; + caret.classList.add('hide'); } } +} - function Guide(options) { - var self = this; - var items = {}; +let isUpdatingProgramCellScroll = false; +function updateProgramCellsOnScroll(programGrid, programCells) { + if (isUpdatingProgramCellScroll) { + return; + } - self.options = options; - self.categoryOptions = { categories: [] }; + isUpdatingProgramCellScroll = true; - // 30 mins - var cellCurationMinutes = 30; - var cellDurationMs = cellCurationMinutes * 60 * 1000; - var msPerDay = 86400000; + requestAnimationFrame(function () { + const scrollLeft = programGrid.scrollLeft; - var currentDate; - var currentStartIndex = 0; - var currentChannelLimit = 0; - var autoRefreshInterval; - var programCells; - var lastFocusDirection; - var programGrid; + const scrollPct = scrollLeft ? (scrollLeft / programGrid.scrollWidth) * 100 : 0; - self.refresh = function () { - currentDate = null; - reloadPage(options.element); - restartAutoRefresh(); - }; - - self.pause = function () { - stopAutoRefresh(); - }; - - self.resume = function (refreshData) { - if (refreshData) { - self.refresh(); - } else { - restartAutoRefresh(); - } - }; - - self.destroy = function () { - stopAutoRefresh(); - - events.off(serverNotifications, 'TimerCreated', onTimerCreated); - events.off(serverNotifications, 'SeriesTimerCreated', onSeriesTimerCreated); - events.off(serverNotifications, 'TimerCancelled', onTimerCancelled); - events.off(serverNotifications, 'SeriesTimerCancelled', onSeriesTimerCancelled); - - setScrollEvents(options.element, false); - itemShortcuts.off(options.element); - items = {}; - }; - - function restartAutoRefresh() { - stopAutoRefresh(); - - var intervalMs = 60000 * 15; // (minutes) - - autoRefreshInterval = setInterval(function () { - self.refresh(); - }, intervalMs); + for (let i = 0, length = programCells.length; i < length; i++) { + updateProgramCellOnScroll(programCells[i], scrollPct); } - function stopAutoRefresh() { - if (autoRefreshInterval) { - clearInterval(autoRefreshInterval); - autoRefreshInterval = null; - } - } - - function normalizeDateToTimeslot(date) { - var minutesOffset = date.getMinutes() - cellCurationMinutes; - - if (minutesOffset >= 0) { - date.setHours(date.getHours(), cellCurationMinutes, 0, 0); - } else { - date.setHours(date.getHours(), 0, 0, 0); - } - - return date; - } - - function showLoading() { - loading.show(); - } - - function hideLoading() { - loading.hide(); - } - - function reloadGuide(context, newStartDate, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, focusProgramOnRender) { - var apiClient = connectionManager.getApiClient(options.serverId); - - var channelQuery = { - - StartIndex: 0, - EnableFavoriteSorting: userSettings.get('livetv-favoritechannelsattop') !== 'false' - }; - - channelQuery.UserId = apiClient.getCurrentUserId(); - - var channelLimit = 500; - currentChannelLimit = channelLimit; - - showLoading(); - - channelQuery.StartIndex = currentStartIndex; - channelQuery.Limit = channelLimit; - channelQuery.AddCurrentProgram = false; - channelQuery.EnableUserData = false; - channelQuery.EnableImageTypes = 'Primary'; - - var categories = self.categoryOptions.categories || []; - var displayMovieContent = !categories.length || categories.indexOf('movies') !== -1; - var displaySportsContent = !categories.length || categories.indexOf('sports') !== -1; - var displayNewsContent = !categories.length || categories.indexOf('news') !== -1; - var displayKidsContent = !categories.length || categories.indexOf('kids') !== -1; - var displaySeriesContent = !categories.length || categories.indexOf('series') !== -1; - - if (displayMovieContent && displaySportsContent && displayNewsContent && displayKidsContent) { - channelQuery.IsMovie = null; - channelQuery.IsSports = null; - channelQuery.IsKids = null; - channelQuery.IsNews = null; - channelQuery.IsSeries = null; - } else { - if (displayNewsContent) { - channelQuery.IsNews = true; - } - if (displaySportsContent) { - channelQuery.IsSports = true; - } - if (displayKidsContent) { - channelQuery.IsKids = true; - } - if (displayMovieContent) { - channelQuery.IsMovie = true; - } - if (displaySeriesContent) { - channelQuery.IsSeries = true; - } - } - - if (userSettings.get('livetv-channelorder') === 'DatePlayed') { - channelQuery.SortBy = 'DatePlayed'; - channelQuery.SortOrder = 'Descending'; - } else { - channelQuery.SortBy = null; - channelQuery.SortOrder = null; - } - - var date = newStartDate; - // Add one second to avoid getting programs that are just ending - date = new Date(date.getTime() + 1000); - - // Subtract to avoid getting programs that are starting when the grid ends - var nextDay = new Date(date.getTime() + msPerDay - 2000); - - // Normally we'd want to just let responsive css handle this, - // but since mobile browsers are often underpowered, - // it can help performance to get them out of the markup - var allowIndicators = dom.getWindowSize().innerWidth >= 600; - - var renderOptions = { - showHdIcon: allowIndicators && userSettings.get('guide-indicator-hd') === 'true', - showLiveIndicator: allowIndicators && userSettings.get('guide-indicator-live') !== 'false', - showPremiereIndicator: allowIndicators && userSettings.get('guide-indicator-premiere') !== 'false', - showNewIndicator: allowIndicators && userSettings.get('guide-indicator-new') !== 'false', - showRepeatIndicator: allowIndicators && userSettings.get('guide-indicator-repeat') === 'true', - showEpisodeTitle: layoutManager.tv ? false : true - }; - - apiClient.getLiveTvChannels(channelQuery).then(function (channelsResult) { - var btnPreviousPage = context.querySelector('.btnPreviousPage'); - var btnNextPage = context.querySelector('.btnNextPage'); - - if (channelsResult.TotalRecordCount > channelLimit) { - context.querySelector('.guideOptions').classList.remove('hide'); - - btnPreviousPage.classList.remove('hide'); - btnNextPage.classList.remove('hide'); - - if (channelQuery.StartIndex) { - context.querySelector('.btnPreviousPage').disabled = false; - } else { - context.querySelector('.btnPreviousPage').disabled = true; - } - - if ((channelQuery.StartIndex + channelLimit) < channelsResult.TotalRecordCount) { - btnNextPage.disabled = false; - } else { - btnNextPage.disabled = true; - } - } else { - context.querySelector('.guideOptions').classList.add('hide'); - } - - var programFields = []; - - var programQuery = { - UserId: apiClient.getCurrentUserId(), - MaxStartDate: nextDay.toISOString(), - MinEndDate: date.toISOString(), - channelIds: channelsResult.Items.map(function (c) { - return c.Id; - }).join(','), - ImageTypeLimit: 1, - EnableImages: false, - //EnableImageTypes: layoutManager.tv ? "Primary,Backdrop" : "Primary", - SortBy: 'StartDate', - EnableTotalRecordCount: false, - EnableUserData: false - }; - - if (renderOptions.showHdIcon) { - programFields.push('IsHD'); - } - - if (programFields.length) { - programQuery.Fields = programFields.join(''); - } - - apiClient.getLiveTvPrograms(programQuery).then(function (programsResult) { - renderGuide(context, date, channelsResult.Items, programsResult.Items, renderOptions, apiClient, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, focusProgramOnRender); - - hideLoading(); - }); - }); - } - - function getDisplayTime(date) { - if ((typeof date).toString().toLowerCase() === 'string') { - try { - date = datetime.parseISO8601Date(date, { toLocal: true }); - } catch (err) { - return date; - } - } - - return datetime.getDisplayTime(date).toLowerCase(); - } - - function getTimeslotHeadersHtml(startDate, endDateTime) { - var html = ''; - - // clone - startDate = new Date(startDate.getTime()); - - html += '
'; - - while (startDate.getTime() < endDateTime) { - html += '
'; - - html += getDisplayTime(startDate); - html += '
'; - - // Add 30 mins - startDate.setTime(startDate.getTime() + cellDurationMs); - } - - return html; - } - - function parseDates(program) { - if (!program.StartDateLocal) { - try { - program.StartDateLocal = datetime.parseISO8601Date(program.StartDate, { toLocal: true }); - } catch (err) { - console.error('error parsing timestamp for start date'); - } - } - - if (!program.EndDateLocal) { - try { - program.EndDateLocal = datetime.parseISO8601Date(program.EndDate, { toLocal: true }); - } catch (err) { - console.error('error parsing timestamp for end date'); - } - } - - return null; - } - - function getTimerIndicator(item) { - var status; - - 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 ''; - } - - return ''; - } - - return ''; - } - - function getChannelProgramsHtml(context, date, channel, programs, options, listInfo) { - var html = ''; - - var startMs = date.getTime(); - var endMs = startMs + msPerDay - 1; - - var outerCssClass = layoutManager.tv ? 'channelPrograms channelPrograms-tv' : 'channelPrograms'; - - html += '
'; - - var clickAction = layoutManager.tv ? 'link' : 'programdialog'; - - var categories = self.categoryOptions.categories || []; - var displayMovieContent = !categories.length || categories.indexOf('movies') !== -1; - var displaySportsContent = !categories.length || categories.indexOf('sports') !== -1; - var displayNewsContent = !categories.length || categories.indexOf('news') !== -1; - var displayKidsContent = !categories.length || categories.indexOf('kids') !== -1; - var displaySeriesContent = !categories.length || categories.indexOf('series') !== -1; - var enableColorCodedBackgrounds = userSettings.get('guide-colorcodedbackgrounds') === 'true'; - - var programsFound; - var now = new Date().getTime(); - - for (var i = listInfo.startIndex, length = programs.length; i < length; i++) { - var program = programs[i]; - - if (program.ChannelId !== channel.Id) { - if (programsFound) { - break; - } - - continue; - } - - programsFound = true; - listInfo.startIndex++; - - parseDates(program); - - var startDateLocalMs = program.StartDateLocal.getTime(); - var endDateLocalMs = program.EndDateLocal.getTime(); - - if (endDateLocalMs < startMs) { - continue; - } - - if (startDateLocalMs > endMs) { - break; - } - - items[program.Id] = program; - - var renderStartMs = Math.max(startDateLocalMs, startMs); - var startPercent = (startDateLocalMs - startMs) / msPerDay; - startPercent *= 100; - startPercent = Math.max(startPercent, 0); - - var renderEndMs = Math.min(endDateLocalMs, endMs); - var endPercent = (renderEndMs - renderStartMs) / msPerDay; - endPercent *= 100; - - var cssClass = 'programCell itemAction'; - var accentCssClass = null; - var displayInnerContent = true; - - if (program.IsKids) { - displayInnerContent = displayKidsContent; - accentCssClass = 'kids'; - } else if (program.IsSports) { - displayInnerContent = displaySportsContent; - accentCssClass = 'sports'; - } else if (program.IsNews) { - displayInnerContent = displayNewsContent; - accentCssClass = 'news'; - } else if (program.IsMovie) { - displayInnerContent = displayMovieContent; - accentCssClass = 'movie'; - } else if (program.IsSeries) { - displayInnerContent = displaySeriesContent; - } else { - displayInnerContent = displayMovieContent && displayNewsContent && displaySportsContent && displayKidsContent && displaySeriesContent; - } - - if (displayInnerContent && enableColorCodedBackgrounds && accentCssClass) { - cssClass += ' programCell-' + accentCssClass; - } - - if (now >= startDateLocalMs && now < endDateLocalMs) { - cssClass += ' programCell-active'; - } - - var timerAttributes = ''; - if (program.TimerId) { - timerAttributes += ' data-timerid="' + program.TimerId + '"'; - } - if (program.SeriesTimerId) { - timerAttributes += ' data-seriestimerid="' + program.SeriesTimerId + '"'; - } - - var isAttribute = endPercent >= 2 ? ' is="emby-programcell"' : ''; - - html += ''; - - if (displayInnerContent) { - var guideProgramNameClass = 'guideProgramName'; - - html += '
'; - - html += '
'; - - html += '
' + program.Name; - - var indicatorHtml = null; - if (program.IsLive && options.showLiveIndicator) { - indicatorHtml = '' + globalize.translate('Live') + ''; - } else if (program.IsPremiere && options.showPremiereIndicator) { - indicatorHtml = '' + globalize.translate('Premiere') + ''; - } else if (program.IsSeries && !program.IsRepeat && options.showNewIndicator) { - indicatorHtml = '' + globalize.translate('AttributeNew') + ''; - } else if (program.IsSeries && program.IsRepeat && options.showRepeatIndicator) { - indicatorHtml = '' + globalize.translate('Repeat') + ''; - } - html += indicatorHtml || ''; - - if ((program.EpisodeTitle && options.showEpisodeTitle)) { - html += '
'; - - if (program.EpisodeTitle && options.showEpisodeTitle) { - html += '' + program.EpisodeTitle + ''; - } - html += '
'; - } - - html += '
'; - - if (program.IsHD && options.showHdIcon) { - if (layoutManager.tv) { - html += '
HD
'; - } else { - html += '
HD
'; - } - } - - html += getTimerIndicator(program); - - html += '
'; - } - - html += ''; - } - - html += '
'; - - return html; - } - - function renderChannelHeaders(context, channels, apiClient) { - var html = ''; - - for (var i = 0, length = channels.length; i < length; i++) { - var channel = channels[i]; - var hasChannelImage = channel.ImageTags.Primary; - - var cssClass = 'guide-channelHeaderCell itemAction'; - - if (layoutManager.tv) { - cssClass += ' guide-channelHeaderCell-tv'; - } - - var title = []; - if (channel.ChannelNumber) { - title.push(channel.ChannelNumber); - } - if (channel.Name) { - title.push(channel.Name); - } - - html += ''; - } - - var channelList = context.querySelector('.channelsContainer'); - channelList.innerHTML = html; - imageLoader.lazyChildren(channelList); - } - - function renderPrograms(context, date, channels, programs, options) { - var listInfo = { - startIndex: 0 - }; - - var html = []; - - for (var i = 0, length = channels.length; i < length; i++) { - html.push(getChannelProgramsHtml(context, date, channels[i], programs, options, listInfo)); - } - - programGrid.innerHTML = html.join(''); - - programCells = programGrid.querySelectorAll('[is=emby-programcell]'); - - updateProgramCellsOnScroll(programGrid, programCells); - } - - function getProgramSortOrder(program, channels) { - var channelId = program.ChannelId; - var channelIndex = -1; - - for (var i = 0, length = channels.length; i < length; i++) { - if (channelId === channels[i].Id) { - channelIndex = i; - break; - } - } - - var start = datetime.parseISO8601Date(program.StartDate, { toLocal: true }); - - return (channelIndex * 10000000) + (start.getTime() / 60000); - } - - function renderGuide(context, date, channels, programs, renderOptions, apiClient, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, focusProgramOnRender) { - programs.sort(function (a, b) { - return getProgramSortOrder(a, channels) - getProgramSortOrder(b, channels); - }); - - var activeElement = document.activeElement; - var itemId = activeElement && activeElement.getAttribute ? activeElement.getAttribute('data-id') : null; - var channelRowId = null; - - if (activeElement) { - channelRowId = dom.parentWithClass(activeElement, 'channelPrograms'); - channelRowId = channelRowId && channelRowId.getAttribute ? channelRowId.getAttribute('data-channelid') : null; - } - - renderChannelHeaders(context, channels, apiClient); - - var startDate = date; - var endDate = new Date(startDate.getTime() + msPerDay); - context.querySelector('.timeslotHeaders').innerHTML = getTimeslotHeadersHtml(startDate, endDate); - items = {}; - renderPrograms(context, date, channels, programs, renderOptions); - - if (focusProgramOnRender) { - focusProgram(context, itemId, channelRowId, focusToTimeMs, startTimeOfDayMs); - } - - scrollProgramGridToTimeMs(context, scrollToTimeMs, startTimeOfDayMs); - } - - function scrollProgramGridToTimeMs(context, scrollToTimeMs, startTimeOfDayMs) { - scrollToTimeMs -= startTimeOfDayMs; - - var pct = scrollToTimeMs / msPerDay; - - programGrid.scrollTop = 0; - - var scrollPos = pct * programGrid.scrollWidth; - - nativeScrollTo(programGrid, scrollPos, true); - } - - function focusProgram(context, itemId, channelRowId, focusToTimeMs, startTimeOfDayMs) { - var focusElem; - if (itemId) { - focusElem = context.querySelector('[data-id="' + itemId + '"]'); - } - - if (focusElem) { - focusManager.focus(focusElem); - } else { - var autoFocusParent; - - if (channelRowId) { - autoFocusParent = context.querySelector('[data-channelid="' + channelRowId + '"]'); - } - - if (!autoFocusParent) { - autoFocusParent = programGrid; - } - - focusToTimeMs -= startTimeOfDayMs; - - var pct = (focusToTimeMs / msPerDay) * 100; - - var programCell = autoFocusParent.querySelector('.programCell'); - - while (programCell) { - var left = (programCell.style.left || '').replace('%', ''); - left = left ? parseFloat(left) : 0; - var width = (programCell.style.width || '').replace('%', ''); - width = width ? parseFloat(width) : 0; - - if (left >= pct || (left + width) >= pct) { - break; - } - programCell = programCell.nextSibling; - } - - if (programCell) { - focusManager.focus(programCell); - } else { - focusManager.autoFocus(autoFocusParent, true); - } - } - } - - function nativeScrollTo(container, pos, horizontal) { - if (container.scrollTo) { - if (horizontal) { - container.scrollTo(pos, 0); - } else { - container.scrollTo(0, pos); - } - } else { - if (horizontal) { - container.scrollLeft = Math.round(pos); - } else { - container.scrollTop = Math.round(pos); - } - } - } - - var lastGridScroll = 0; - var lastHeaderScroll = 0; - var scrollXPct = 0; - function onProgramGridScroll(context, elem, timeslotHeaders) { - if ((new Date().getTime() - lastHeaderScroll) >= 1000) { - lastGridScroll = new Date().getTime(); - - var scrollLeft = elem.scrollLeft; - scrollXPct = (scrollLeft * 100) / elem.scrollWidth; - nativeScrollTo(timeslotHeaders, scrollLeft, true); - } - - updateProgramCellsOnScroll(elem, programCells); - } - - function onTimeslotHeadersScroll(context, elem) { - if ((new Date().getTime() - lastGridScroll) >= 1000) { - lastHeaderScroll = new Date().getTime(); - nativeScrollTo(programGrid, elem.scrollLeft, true); - } - } - - function changeDate(page, date, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, focusProgramOnRender) { - var newStartDate = normalizeDateToTimeslot(date); - currentDate = newStartDate; - - reloadGuide(page, newStartDate, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, focusProgramOnRender); - } - - function getDateTabText(date, isActive, tabIndex) { - var cssClass = isActive ? 'emby-tab-button guide-date-tab-button emby-tab-button-active' : 'emby-tab-button guide-date-tab-button'; - - var html = ''; - - return html; - } - - function setDateRange(page, guideInfo) { - var today = new Date(); - var nowHours = today.getHours(); - today.setHours(nowHours, 0, 0, 0); - - var start = datetime.parseISO8601Date(guideInfo.StartDate, { toLocal: true }); - var end = datetime.parseISO8601Date(guideInfo.EndDate, { toLocal: true }); - - start.setHours(nowHours, 0, 0, 0); - end.setHours(0, 0, 0, 0); - - if (start.getTime() >= end.getTime()) { - end.setDate(start.getDate() + 1); - } - - start = new Date(Math.max(today, start)); - - var dateTabsHtml = ''; - var tabIndex = 0; - - // TODO: Use date-fns - var date = new Date(); - - if (currentDate) { - date.setTime(currentDate.getTime()); - } - - date.setHours(nowHours, 0, 0, 0); - - var startTimeOfDayMs = (start.getHours() * 60 * 60 * 1000); - startTimeOfDayMs += start.getMinutes() * 60 * 1000; - - while (start <= end) { - var isActive = date.getDate() === start.getDate() && date.getMonth() === start.getMonth() && date.getFullYear() === start.getFullYear(); - - dateTabsHtml += getDateTabText(start, isActive, tabIndex); - - start.setDate(start.getDate() + 1); - start.setHours(0, 0, 0, 0); - tabIndex++; - } - - page.querySelector('.emby-tabs-slider').innerHTML = dateTabsHtml; - page.querySelector('.guideDateTabs').refresh(); - - var newDate = new Date(); - var newDateHours = newDate.getHours(); - var scrollToTimeMs = newDateHours * 60 * 60 * 1000; - - var minutes = newDate.getMinutes(); - if (minutes >= 30) { - scrollToTimeMs += 30 * 60 * 1000; - } - - var focusToTimeMs = ((newDateHours * 60) + minutes) * 60 * 1000; - changeDate(page, date, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, layoutManager.tv); - } - - function reloadPage(page) { - showLoading(); - - var apiClient = connectionManager.getApiClient(options.serverId); - - apiClient.getLiveTvGuideInfo().then(function (guideInfo) { - setDateRange(page, guideInfo); - }); - } - - function getChannelProgramsFocusableElements(container) { - var elements = container.querySelectorAll('.programCell'); - - var list = []; - // add 1 to avoid programs that are out of view to the left - var currentScrollXPct = scrollXPct + 1; - - for (var i = 0, length = elements.length; i < length; i++) { - var elem = elements[i]; - - var left = (elem.style.left || '').replace('%', ''); - left = left ? parseFloat(left) : 0; - var width = (elem.style.width || '').replace('%', ''); - width = width ? parseFloat(width) : 0; - - if ((left + width) >= currentScrollXPct) { - list.push(elem); - } - } - - return list; - } - - function onInputCommand(e) { - var target = e.target; - var programCell = dom.parentWithClass(target, 'programCell'); - var container; - var channelPrograms; - var focusableElements; - var newRow; - - switch (e.detail.command) { - case 'up': - if (programCell) { - container = programGrid; - channelPrograms = dom.parentWithClass(programCell, 'channelPrograms'); - - newRow = channelPrograms.previousSibling; - if (newRow) { - focusableElements = getChannelProgramsFocusableElements(newRow); - if (focusableElements.length) { - container = newRow; - } else { - focusableElements = null; - } - } else { - container = null; - } - } else { - container = null; - } - lastFocusDirection = e.detail.command; - - focusManager.moveUp(target, { - container: container, - focusableElements: focusableElements - }); - break; - case 'down': - if (programCell) { - container = programGrid; - channelPrograms = dom.parentWithClass(programCell, 'channelPrograms'); - - newRow = channelPrograms.nextSibling; - if (newRow) { - focusableElements = getChannelProgramsFocusableElements(newRow); - if (focusableElements.length) { - container = newRow; - } else { - focusableElements = null; - } - } else { - container = null; - } - } else { - container = null; - } - lastFocusDirection = e.detail.command; - - focusManager.moveDown(target, { - container: container, - focusableElements: focusableElements - }); - break; - case 'left': - container = programCell ? dom.parentWithClass(programCell, 'channelPrograms') : null; - // allow left outside the channelProgramsContainer when the first child is currently focused - if (container && !programCell.previousSibling) { - container = null; - } - lastFocusDirection = e.detail.command; - - focusManager.moveLeft(target, { - container: container - }); - break; - case 'right': - container = programCell ? dom.parentWithClass(programCell, 'channelPrograms') : null; - lastFocusDirection = e.detail.command; - - focusManager.moveRight(target, { - container: container - }); - break; - default: - return; - } + isUpdatingProgramCellScroll = false; + }); +} + +function onProgramGridClick(e) { + if (!layoutManager.tv) { + return; + } + + const programCell = dom.parentWithClass(e.target, 'programCell'); + if (programCell) { + let startDate = programCell.getAttribute('data-startdate'); + let endDate = programCell.getAttribute('data-enddate'); + startDate = datetime.parseISO8601Date(startDate, { toLocal: true }).getTime(); + endDate = datetime.parseISO8601Date(endDate, { toLocal: true }).getTime(); + + const now = new Date().getTime(); + if (now >= startDate && now < endDate) { + const channelId = programCell.getAttribute('data-channelid'); + const serverId = programCell.getAttribute('data-serverid'); e.preventDefault(); e.stopPropagation(); - } - function onScrollerFocus(e) { - var target = e.target; - var programCell = dom.parentWithClass(target, 'programCell'); - - if (programCell) { - var focused = target; - - var id = focused.getAttribute('data-id'); - var item = items[id]; - - if (item) { - events.trigger(self, 'focus', [ - { - item: item - }]); - } - } - - if (lastFocusDirection === 'left') { - if (programCell) { - scrollHelper.toStart(programGrid, programCell, true, true); - } - } else if (lastFocusDirection === 'right') { - if (programCell) { - scrollHelper.toCenter(programGrid, programCell, true, true); - } - } else if (lastFocusDirection === 'up' || lastFocusDirection === 'down') { - var verticalScroller = dom.parentWithClass(target, 'guideVerticalScroller'); - if (verticalScroller) { - var focusedElement = programCell || dom.parentWithTag(target, 'BUTTON'); - verticalScroller.toCenter(focusedElement, true); - } - } - } - - function setScrollEvents(view, enabled) { - if (layoutManager.tv) { - var guideVerticalScroller = view.querySelector('.guideVerticalScroller'); - - if (enabled) { - inputManager.on(guideVerticalScroller, onInputCommand); - } else { - inputManager.off(guideVerticalScroller, onInputCommand); - } - } - } - - function onTimerCreated(e, apiClient, data) { - var programId = data.ProgramId; - // This could be null, not supported by all tv providers - var newTimerId = data.Id; - - // find guide cells by program id, ensure timer icon - var cells = options.element.querySelectorAll('.programCell[data-id="' + programId + '"]'); - for (var i = 0, length = cells.length; i < length; i++) { - var cell = cells[i]; - - var icon = cell.querySelector('.timerIcon'); - if (!icon) { - cell.querySelector('.guideProgramName').insertAdjacentHTML('beforeend', ''); - } - - if (newTimerId) { - cell.setAttribute('data-timerid', newTimerId); - } - } - } - - function onSeriesTimerCreated(e, apiClient, data) { - } - - function onTimerCancelled(e, apiClient, data) { - var id = data.Id; - // find guide cells by timer id, remove timer icon - var cells = options.element.querySelectorAll('.programCell[data-timerid="' + id + '"]'); - for (var i = 0, length = cells.length; i < length; i++) { - var cell = cells[i]; - var icon = cell.querySelector('.timerIcon'); - if (icon) { - icon.parentNode.removeChild(icon); - } - cell.removeAttribute('data-timerid'); - } - } - - function onSeriesTimerCancelled(e, apiClient, data) { - var id = data.Id; - // find guide cells by timer id, remove timer icon - var cells = options.element.querySelectorAll('.programCell[data-seriestimerid="' + id + '"]'); - for (var i = 0, length = cells.length; i < length; i++) { - var cell = cells[i]; - var icon = cell.querySelector('.seriesTimerIcon'); - if (icon) { - icon.parentNode.removeChild(icon); - } - cell.removeAttribute('data-seriestimerid'); - } - } - - require(['text!./tvguide.template.html'], function (template) { - var context = options.element; - - context.classList.add('tvguide'); - - context.innerHTML = globalize.translateHtml(template, 'core'); - - programGrid = context.querySelector('.programGrid'); - var timeslotHeaders = context.querySelector('.timeslotHeaders'); - - if (layoutManager.tv) { - dom.addEventListener(context.querySelector('.guideVerticalScroller'), 'focus', onScrollerFocus, { - capture: true, - passive: true - }); - } else if (layoutManager.desktop) { - timeslotHeaders.classList.add('timeslotHeaders-desktop'); - } - - if (browser.iOS || browser.osx) { - context.querySelector('.channelsContainer').classList.add('noRubberBanding'); - - programGrid.classList.add('noRubberBanding'); - } - - dom.addEventListener(programGrid, 'scroll', function (e) { - onProgramGridScroll(context, this, timeslotHeaders); - }, { - passive: true + playbackManager.play({ + ids: [channelId], + serverId: serverId }); + } + } +} - dom.addEventListener(timeslotHeaders, 'scroll', function () { - onTimeslotHeadersScroll(context, this); - }, { - passive: true - }); +function Guide(options) { + const self = this; + let items = {}; - programGrid.addEventListener('click', onProgramGridClick); + self.options = options; + self.categoryOptions = { categories: [] }; - context.querySelector('.btnNextPage').addEventListener('click', function () { - currentStartIndex += currentChannelLimit; - reloadPage(context); - restartAutoRefresh(); - }); + // 30 mins + const cellCurationMinutes = 30; + const cellDurationMs = cellCurationMinutes * 60 * 1000; + const msPerDay = 86400000; - context.querySelector('.btnPreviousPage').addEventListener('click', function () { - currentStartIndex = Math.max(currentStartIndex - currentChannelLimit, 0); - reloadPage(context); - restartAutoRefresh(); - }); + let currentDate; + let currentStartIndex = 0; + let currentChannelLimit = 0; + let autoRefreshInterval; + let programCells; + let lastFocusDirection; + let programGrid; - context.querySelector('.btnGuideViewSettings').addEventListener('click', function () { - showViewSettings(self); - restartAutoRefresh(); - }); + self.refresh = function () { + currentDate = null; + reloadPage(options.element); + restartAutoRefresh(); + }; - context.querySelector('.guideDateTabs').addEventListener('tabchange', function (e) { - var allTabButtons = e.target.querySelectorAll('.guide-date-tab-button'); - - var tabButton = allTabButtons[parseInt(e.detail.selectedTabIndex)]; - if (tabButton) { - var previousButton = e.detail.previousIndex == null ? null : allTabButtons[parseInt(e.detail.previousIndex)]; - - var date = new Date(); - date.setTime(parseInt(tabButton.getAttribute('data-date'))); - - var scrollWidth = programGrid.scrollWidth; - var scrollToTimeMs; - if (scrollWidth) { - scrollToTimeMs = (programGrid.scrollLeft / scrollWidth) * msPerDay; - } else { - scrollToTimeMs = 0; - } - - if (previousButton) { - var previousDate = new Date(); - previousDate.setTime(parseInt(previousButton.getAttribute('data-date'))); - - scrollToTimeMs += (previousDate.getHours() * 60 * 60 * 1000); - scrollToTimeMs += (previousDate.getMinutes() * 60 * 1000); - } - - var startTimeOfDayMs = (date.getHours() * 60 * 60 * 1000); - startTimeOfDayMs += (date.getMinutes() * 60 * 1000); - - changeDate(context, date, scrollToTimeMs, scrollToTimeMs, startTimeOfDayMs, false); - } - }); - - setScrollEvents(context, true); - itemShortcuts.on(context); - - events.trigger(self, 'load'); - - events.on(serverNotifications, 'TimerCreated', onTimerCreated); - events.on(serverNotifications, 'SeriesTimerCreated', onSeriesTimerCreated); - events.on(serverNotifications, 'TimerCancelled', onTimerCancelled); - events.on(serverNotifications, 'SeriesTimerCancelled', onSeriesTimerCancelled); + self.pause = function () { + stopAutoRefresh(); + }; + self.resume = function (refreshData) { + if (refreshData) { self.refresh(); + } else { + restartAutoRefresh(); + } + }; + + self.destroy = function () { + stopAutoRefresh(); + + events.off(serverNotifications, 'TimerCreated', onTimerCreated); + events.off(serverNotifications, 'SeriesTimerCreated', onSeriesTimerCreated); + events.off(serverNotifications, 'TimerCancelled', onTimerCancelled); + events.off(serverNotifications, 'SeriesTimerCancelled', onSeriesTimerCancelled); + + setScrollEvents(options.element, false); + itemShortcuts.off(options.element); + items = {}; + }; + + function restartAutoRefresh() { + stopAutoRefresh(); + + const intervalMs = 60000 * 15; // (minutes) + + autoRefreshInterval = setInterval(function () { + self.refresh(); + }, intervalMs); + } + + function stopAutoRefresh() { + if (autoRefreshInterval) { + clearInterval(autoRefreshInterval); + autoRefreshInterval = null; + } + } + + function normalizeDateToTimeslot(date) { + const minutesOffset = date.getMinutes() - cellCurationMinutes; + + if (minutesOffset >= 0) { + date.setHours(date.getHours(), cellCurationMinutes, 0, 0); + } else { + date.setHours(date.getHours(), 0, 0, 0); + } + + return date; + } + + function showLoading() { + loading.show(); + } + + function hideLoading() { + loading.hide(); + } + + function reloadGuide(context, newStartDate, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, focusProgramOnRender) { + const apiClient = connectionManager.getApiClient(options.serverId); + + const channelQuery = { + + StartIndex: 0, + EnableFavoriteSorting: userSettings.get('livetv-favoritechannelsattop') !== 'false' + }; + + channelQuery.UserId = apiClient.getCurrentUserId(); + + const channelLimit = 500; + currentChannelLimit = channelLimit; + + showLoading(); + + channelQuery.StartIndex = currentStartIndex; + channelQuery.Limit = channelLimit; + channelQuery.AddCurrentProgram = false; + channelQuery.EnableUserData = false; + channelQuery.EnableImageTypes = 'Primary'; + + const categories = self.categoryOptions.categories || []; + const displayMovieContent = !categories.length || categories.indexOf('movies') !== -1; + const displaySportsContent = !categories.length || categories.indexOf('sports') !== -1; + const displayNewsContent = !categories.length || categories.indexOf('news') !== -1; + const displayKidsContent = !categories.length || categories.indexOf('kids') !== -1; + const displaySeriesContent = !categories.length || categories.indexOf('series') !== -1; + + if (displayMovieContent && displaySportsContent && displayNewsContent && displayKidsContent) { + channelQuery.IsMovie = null; + channelQuery.IsSports = null; + channelQuery.IsKids = null; + channelQuery.IsNews = null; + channelQuery.IsSeries = null; + } else { + if (displayNewsContent) { + channelQuery.IsNews = true; + } + if (displaySportsContent) { + channelQuery.IsSports = true; + } + if (displayKidsContent) { + channelQuery.IsKids = true; + } + if (displayMovieContent) { + channelQuery.IsMovie = true; + } + if (displaySeriesContent) { + channelQuery.IsSeries = true; + } + } + + if (userSettings.get('livetv-channelorder') === 'DatePlayed') { + channelQuery.SortBy = 'DatePlayed'; + channelQuery.SortOrder = 'Descending'; + } else { + channelQuery.SortBy = null; + channelQuery.SortOrder = null; + } + + let date = newStartDate; + // Add one second to avoid getting programs that are just ending + date = new Date(date.getTime() + 1000); + + // Subtract to avoid getting programs that are starting when the grid ends + const nextDay = new Date(date.getTime() + msPerDay - 2000); + + // Normally we'd want to just let responsive css handle this, + // but since mobile browsers are often underpowered, + // it can help performance to get them out of the markup + const allowIndicators = dom.getWindowSize().innerWidth >= 600; + + const renderOptions = { + showHdIcon: allowIndicators && userSettings.get('guide-indicator-hd') === 'true', + showLiveIndicator: allowIndicators && userSettings.get('guide-indicator-live') !== 'false', + showPremiereIndicator: allowIndicators && userSettings.get('guide-indicator-premiere') !== 'false', + showNewIndicator: allowIndicators && userSettings.get('guide-indicator-new') !== 'false', + showRepeatIndicator: allowIndicators && userSettings.get('guide-indicator-repeat') === 'true', + showEpisodeTitle: layoutManager.tv ? false : true + }; + + apiClient.getLiveTvChannels(channelQuery).then(function (channelsResult) { + const btnPreviousPage = context.querySelector('.btnPreviousPage'); + const btnNextPage = context.querySelector('.btnNextPage'); + + if (channelsResult.TotalRecordCount > channelLimit) { + context.querySelector('.guideOptions').classList.remove('hide'); + + btnPreviousPage.classList.remove('hide'); + btnNextPage.classList.remove('hide'); + + if (channelQuery.StartIndex) { + context.querySelector('.btnPreviousPage').disabled = false; + } else { + context.querySelector('.btnPreviousPage').disabled = true; + } + + if ((channelQuery.StartIndex + channelLimit) < channelsResult.TotalRecordCount) { + btnNextPage.disabled = false; + } else { + btnNextPage.disabled = true; + } + } else { + context.querySelector('.guideOptions').classList.add('hide'); + } + + const programFields = []; + + const programQuery = { + UserId: apiClient.getCurrentUserId(), + MaxStartDate: nextDay.toISOString(), + MinEndDate: date.toISOString(), + channelIds: channelsResult.Items.map(function (c) { + return c.Id; + }).join(','), + ImageTypeLimit: 1, + EnableImages: false, + //EnableImageTypes: layoutManager.tv ? "Primary,Backdrop" : "Primary", + SortBy: 'StartDate', + EnableTotalRecordCount: false, + EnableUserData: false + }; + + if (renderOptions.showHdIcon) { + programFields.push('IsHD'); + } + + if (programFields.length) { + programQuery.Fields = programFields.join(''); + } + + apiClient.getLiveTvPrograms(programQuery).then(function (programsResult) { + renderGuide(context, date, channelsResult.Items, programsResult.Items, renderOptions, apiClient, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, focusProgramOnRender); + + hideLoading(); + }); }); } - return Guide; -}); + function getDisplayTime(date) { + if ((typeof date).toString().toLowerCase() === 'string') { + try { + date = datetime.parseISO8601Date(date, { toLocal: true }); + } catch (err) { + return date; + } + } + + return datetime.getDisplayTime(date).toLowerCase(); + } + + function getTimeslotHeadersHtml(startDate, endDateTime) { + let html = ''; + + // clone + startDate = new Date(startDate.getTime()); + + html += '
'; + + while (startDate.getTime() < endDateTime) { + html += '
'; + + html += getDisplayTime(startDate); + html += '
'; + + // Add 30 mins + startDate.setTime(startDate.getTime() + cellDurationMs); + } + + return html; + } + + function parseDates(program) { + if (!program.StartDateLocal) { + try { + program.StartDateLocal = datetime.parseISO8601Date(program.StartDate, { toLocal: true }); + } catch (err) { + console.error('error parsing timestamp for start date'); + } + } + + if (!program.EndDateLocal) { + try { + program.EndDateLocal = datetime.parseISO8601Date(program.EndDate, { toLocal: true }); + } catch (err) { + console.error('error parsing timestamp for end date'); + } + } + + return null; + } + + function getTimerIndicator(item) { + let status; + + 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 ''; + } + + return ''; + } + + return ''; + } + + function getChannelProgramsHtml(context, date, channel, programs, options, listInfo) { + let html = ''; + + const startMs = date.getTime(); + const endMs = startMs + msPerDay - 1; + + const outerCssClass = layoutManager.tv ? 'channelPrograms channelPrograms-tv' : 'channelPrograms'; + + html += '
'; + + const clickAction = layoutManager.tv ? 'link' : 'programdialog'; + + const categories = self.categoryOptions.categories || []; + const displayMovieContent = !categories.length || categories.indexOf('movies') !== -1; + const displaySportsContent = !categories.length || categories.indexOf('sports') !== -1; + const displayNewsContent = !categories.length || categories.indexOf('news') !== -1; + const displayKidsContent = !categories.length || categories.indexOf('kids') !== -1; + const displaySeriesContent = !categories.length || categories.indexOf('series') !== -1; + const enableColorCodedBackgrounds = userSettings.get('guide-colorcodedbackgrounds') === 'true'; + + let programsFound; + const now = new Date().getTime(); + + for (let i = listInfo.startIndex, length = programs.length; i < length; i++) { + const program = programs[i]; + + if (program.ChannelId !== channel.Id) { + if (programsFound) { + break; + } + + continue; + } + + programsFound = true; + listInfo.startIndex++; + + parseDates(program); + + const startDateLocalMs = program.StartDateLocal.getTime(); + const endDateLocalMs = program.EndDateLocal.getTime(); + + if (endDateLocalMs < startMs) { + continue; + } + + if (startDateLocalMs > endMs) { + break; + } + + items[program.Id] = program; + + const renderStartMs = Math.max(startDateLocalMs, startMs); + let startPercent = (startDateLocalMs - startMs) / msPerDay; + startPercent *= 100; + startPercent = Math.max(startPercent, 0); + + const renderEndMs = Math.min(endDateLocalMs, endMs); + let endPercent = (renderEndMs - renderStartMs) / msPerDay; + endPercent *= 100; + + let cssClass = 'programCell itemAction'; + let accentCssClass = null; + let displayInnerContent = true; + + if (program.IsKids) { + displayInnerContent = displayKidsContent; + accentCssClass = 'kids'; + } else if (program.IsSports) { + displayInnerContent = displaySportsContent; + accentCssClass = 'sports'; + } else if (program.IsNews) { + displayInnerContent = displayNewsContent; + accentCssClass = 'news'; + } else if (program.IsMovie) { + displayInnerContent = displayMovieContent; + accentCssClass = 'movie'; + } else if (program.IsSeries) { + displayInnerContent = displaySeriesContent; + } else { + displayInnerContent = displayMovieContent && displayNewsContent && displaySportsContent && displayKidsContent && displaySeriesContent; + } + + if (displayInnerContent && enableColorCodedBackgrounds && accentCssClass) { + cssClass += ' programCell-' + accentCssClass; + } + + if (now >= startDateLocalMs && now < endDateLocalMs) { + cssClass += ' programCell-active'; + } + + let timerAttributes = ''; + if (program.TimerId) { + timerAttributes += ' data-timerid="' + program.TimerId + '"'; + } + if (program.SeriesTimerId) { + timerAttributes += ' data-seriestimerid="' + program.SeriesTimerId + '"'; + } + + const isAttribute = endPercent >= 2 ? ' is="emby-programcell"' : ''; + + html += ''; + + if (displayInnerContent) { + const guideProgramNameClass = 'guideProgramName'; + + html += '
'; + + html += '
'; + + html += '
' + program.Name; + + let indicatorHtml = null; + if (program.IsLive && options.showLiveIndicator) { + indicatorHtml = '' + globalize.translate('Live') + ''; + } else if (program.IsPremiere && options.showPremiereIndicator) { + indicatorHtml = '' + globalize.translate('Premiere') + ''; + } else if (program.IsSeries && !program.IsRepeat && options.showNewIndicator) { + indicatorHtml = '' + globalize.translate('AttributeNew') + ''; + } else if (program.IsSeries && program.IsRepeat && options.showRepeatIndicator) { + indicatorHtml = '' + globalize.translate('Repeat') + ''; + } + html += indicatorHtml || ''; + + if ((program.EpisodeTitle && options.showEpisodeTitle)) { + html += '
'; + + if (program.EpisodeTitle && options.showEpisodeTitle) { + html += '' + program.EpisodeTitle + ''; + } + html += '
'; + } + + html += '
'; + + if (program.IsHD && options.showHdIcon) { + if (layoutManager.tv) { + html += '
HD
'; + } else { + html += '
HD
'; + } + } + + html += getTimerIndicator(program); + + html += '
'; + } + + html += ''; + } + + html += '
'; + + return html; + } + + function renderChannelHeaders(context, channels, apiClient) { + let html = ''; + + for (let i = 0, length = channels.length; i < length; i++) { + const channel = channels[i]; + const hasChannelImage = channel.ImageTags.Primary; + + let cssClass = 'guide-channelHeaderCell itemAction'; + + if (layoutManager.tv) { + cssClass += ' guide-channelHeaderCell-tv'; + } + + const title = []; + if (channel.ChannelNumber) { + title.push(channel.ChannelNumber); + } + if (channel.Name) { + title.push(channel.Name); + } + + html += ''; + } + + const channelList = context.querySelector('.channelsContainer'); + channelList.innerHTML = html; + imageLoader.lazyChildren(channelList); + } + + function renderPrograms(context, date, channels, programs, options) { + const listInfo = { + startIndex: 0 + }; + + const html = []; + + for (let i = 0, length = channels.length; i < length; i++) { + html.push(getChannelProgramsHtml(context, date, channels[i], programs, options, listInfo)); + } + + programGrid.innerHTML = html.join(''); + + programCells = programGrid.querySelectorAll('[is=emby-programcell]'); + + updateProgramCellsOnScroll(programGrid, programCells); + } + + function getProgramSortOrder(program, channels) { + const channelId = program.ChannelId; + let channelIndex = -1; + + for (let i = 0, length = channels.length; i < length; i++) { + if (channelId === channels[i].Id) { + channelIndex = i; + break; + } + } + + const start = datetime.parseISO8601Date(program.StartDate, { toLocal: true }); + + return (channelIndex * 10000000) + (start.getTime() / 60000); + } + + function renderGuide(context, date, channels, programs, renderOptions, apiClient, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, focusProgramOnRender) { + programs.sort(function (a, b) { + return getProgramSortOrder(a, channels) - getProgramSortOrder(b, channels); + }); + + const activeElement = document.activeElement; + const itemId = activeElement && activeElement.getAttribute ? activeElement.getAttribute('data-id') : null; + let channelRowId = null; + + if (activeElement) { + channelRowId = dom.parentWithClass(activeElement, 'channelPrograms'); + channelRowId = channelRowId && channelRowId.getAttribute ? channelRowId.getAttribute('data-channelid') : null; + } + + renderChannelHeaders(context, channels, apiClient); + + const startDate = date; + const endDate = new Date(startDate.getTime() + msPerDay); + context.querySelector('.timeslotHeaders').innerHTML = getTimeslotHeadersHtml(startDate, endDate); + items = {}; + renderPrograms(context, date, channels, programs, renderOptions); + + if (focusProgramOnRender) { + focusProgram(context, itemId, channelRowId, focusToTimeMs, startTimeOfDayMs); + } + + scrollProgramGridToTimeMs(context, scrollToTimeMs, startTimeOfDayMs); + } + + function scrollProgramGridToTimeMs(context, scrollToTimeMs, startTimeOfDayMs) { + scrollToTimeMs -= startTimeOfDayMs; + + const pct = scrollToTimeMs / msPerDay; + + programGrid.scrollTop = 0; + + const scrollPos = pct * programGrid.scrollWidth; + + nativeScrollTo(programGrid, scrollPos, true); + } + + function focusProgram(context, itemId, channelRowId, focusToTimeMs, startTimeOfDayMs) { + let focusElem; + if (itemId) { + focusElem = context.querySelector('[data-id="' + itemId + '"]'); + } + + if (focusElem) { + focusManager.focus(focusElem); + } else { + let autoFocusParent; + + if (channelRowId) { + autoFocusParent = context.querySelector('[data-channelid="' + channelRowId + '"]'); + } + + if (!autoFocusParent) { + autoFocusParent = programGrid; + } + + focusToTimeMs -= startTimeOfDayMs; + + const pct = (focusToTimeMs / msPerDay) * 100; + + let programCell = autoFocusParent.querySelector('.programCell'); + + while (programCell) { + let left = (programCell.style.left || '').replace('%', ''); + left = left ? parseFloat(left) : 0; + let width = (programCell.style.width || '').replace('%', ''); + width = width ? parseFloat(width) : 0; + + if (left >= pct || (left + width) >= pct) { + break; + } + programCell = programCell.nextSibling; + } + + if (programCell) { + focusManager.focus(programCell); + } else { + focusManager.autoFocus(autoFocusParent, true); + } + } + } + + function nativeScrollTo(container, pos, horizontal) { + if (container.scrollTo) { + if (horizontal) { + container.scrollTo(pos, 0); + } else { + container.scrollTo(0, pos); + } + } else { + if (horizontal) { + container.scrollLeft = Math.round(pos); + } else { + container.scrollTop = Math.round(pos); + } + } + } + + let lastGridScroll = 0; + let lastHeaderScroll = 0; + let scrollXPct = 0; + function onProgramGridScroll(context, elem, timeslotHeaders) { + if ((new Date().getTime() - lastHeaderScroll) >= 1000) { + lastGridScroll = new Date().getTime(); + + const scrollLeft = elem.scrollLeft; + scrollXPct = (scrollLeft * 100) / elem.scrollWidth; + nativeScrollTo(timeslotHeaders, scrollLeft, true); + } + + updateProgramCellsOnScroll(elem, programCells); + } + + function onTimeslotHeadersScroll(context, elem) { + if ((new Date().getTime() - lastGridScroll) >= 1000) { + lastHeaderScroll = new Date().getTime(); + nativeScrollTo(programGrid, elem.scrollLeft, true); + } + } + + function changeDate(page, date, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, focusProgramOnRender) { + const newStartDate = normalizeDateToTimeslot(date); + currentDate = newStartDate; + + reloadGuide(page, newStartDate, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, focusProgramOnRender); + } + + function getDateTabText(date, isActive, tabIndex) { + const cssClass = isActive ? 'emby-tab-button guide-date-tab-button emby-tab-button-active' : 'emby-tab-button guide-date-tab-button'; + + let html = ''; + + return html; + } + + function setDateRange(page, guideInfo) { + const today = new Date(); + const nowHours = today.getHours(); + today.setHours(nowHours, 0, 0, 0); + + let start = datetime.parseISO8601Date(guideInfo.StartDate, { toLocal: true }); + const end = datetime.parseISO8601Date(guideInfo.EndDate, { toLocal: true }); + + start.setHours(nowHours, 0, 0, 0); + end.setHours(0, 0, 0, 0); + + if (start.getTime() >= end.getTime()) { + end.setDate(start.getDate() + 1); + } + + start = new Date(Math.max(today, start)); + + let dateTabsHtml = ''; + let tabIndex = 0; + + // TODO: Use date-fns + const date = new Date(); + + if (currentDate) { + date.setTime(currentDate.getTime()); + } + + date.setHours(nowHours, 0, 0, 0); + + let startTimeOfDayMs = (start.getHours() * 60 * 60 * 1000); + startTimeOfDayMs += start.getMinutes() * 60 * 1000; + + while (start <= end) { + const isActive = date.getDate() === start.getDate() && date.getMonth() === start.getMonth() && date.getFullYear() === start.getFullYear(); + + dateTabsHtml += getDateTabText(start, isActive, tabIndex); + + start.setDate(start.getDate() + 1); + start.setHours(0, 0, 0, 0); + tabIndex++; + } + + page.querySelector('.emby-tabs-slider').innerHTML = dateTabsHtml; + page.querySelector('.guideDateTabs').refresh(); + + const newDate = new Date(); + const newDateHours = newDate.getHours(); + let scrollToTimeMs = newDateHours * 60 * 60 * 1000; + + const minutes = newDate.getMinutes(); + if (minutes >= 30) { + scrollToTimeMs += 30 * 60 * 1000; + } + + const focusToTimeMs = ((newDateHours * 60) + minutes) * 60 * 1000; + changeDate(page, date, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, layoutManager.tv); + } + + function reloadPage(page) { + showLoading(); + + const apiClient = connectionManager.getApiClient(options.serverId); + + apiClient.getLiveTvGuideInfo().then(function (guideInfo) { + setDateRange(page, guideInfo); + }); + } + + function getChannelProgramsFocusableElements(container) { + const elements = container.querySelectorAll('.programCell'); + + const list = []; + // add 1 to avoid programs that are out of view to the left + const currentScrollXPct = scrollXPct + 1; + + for (let i = 0, length = elements.length; i < length; i++) { + const elem = elements[i]; + + let left = (elem.style.left || '').replace('%', ''); + left = left ? parseFloat(left) : 0; + let width = (elem.style.width || '').replace('%', ''); + width = width ? parseFloat(width) : 0; + + if ((left + width) >= currentScrollXPct) { + list.push(elem); + } + } + + return list; + } + + function onInputCommand(e) { + const target = e.target; + const programCell = dom.parentWithClass(target, 'programCell'); + let container; + let channelPrograms; + let focusableElements; + let newRow; + + switch (e.detail.command) { + case 'up': + if (programCell) { + container = programGrid; + channelPrograms = dom.parentWithClass(programCell, 'channelPrograms'); + + newRow = channelPrograms.previousSibling; + if (newRow) { + focusableElements = getChannelProgramsFocusableElements(newRow); + if (focusableElements.length) { + container = newRow; + } else { + focusableElements = null; + } + } else { + container = null; + } + } else { + container = null; + } + lastFocusDirection = e.detail.command; + + focusManager.moveUp(target, { + container: container, + focusableElements: focusableElements + }); + break; + case 'down': + if (programCell) { + container = programGrid; + channelPrograms = dom.parentWithClass(programCell, 'channelPrograms'); + + newRow = channelPrograms.nextSibling; + if (newRow) { + focusableElements = getChannelProgramsFocusableElements(newRow); + if (focusableElements.length) { + container = newRow; + } else { + focusableElements = null; + } + } else { + container = null; + } + } else { + container = null; + } + lastFocusDirection = e.detail.command; + + focusManager.moveDown(target, { + container: container, + focusableElements: focusableElements + }); + break; + case 'left': + container = programCell ? dom.parentWithClass(programCell, 'channelPrograms') : null; + // allow left outside the channelProgramsContainer when the first child is currently focused + if (container && !programCell.previousSibling) { + container = null; + } + lastFocusDirection = e.detail.command; + + focusManager.moveLeft(target, { + container: container + }); + break; + case 'right': + container = programCell ? dom.parentWithClass(programCell, 'channelPrograms') : null; + lastFocusDirection = e.detail.command; + + focusManager.moveRight(target, { + container: container + }); + break; + default: + return; + } + + e.preventDefault(); + e.stopPropagation(); + } + + function onScrollerFocus(e) { + const target = e.target; + const programCell = dom.parentWithClass(target, 'programCell'); + + if (programCell) { + const focused = target; + + const id = focused.getAttribute('data-id'); + const item = items[id]; + + if (item) { + events.trigger(self, 'focus', [ + { + item: item + }]); + } + } + + if (lastFocusDirection === 'left') { + if (programCell) { + scrollHelper.toStart(programGrid, programCell, true, true); + } + } else if (lastFocusDirection === 'right') { + if (programCell) { + scrollHelper.toCenter(programGrid, programCell, true, true); + } + } else if (lastFocusDirection === 'up' || lastFocusDirection === 'down') { + const verticalScroller = dom.parentWithClass(target, 'guideVerticalScroller'); + if (verticalScroller) { + const focusedElement = programCell || dom.parentWithTag(target, 'BUTTON'); + verticalScroller.toCenter(focusedElement, true); + } + } + } + + function setScrollEvents(view, enabled) { + if (layoutManager.tv) { + const guideVerticalScroller = view.querySelector('.guideVerticalScroller'); + + if (enabled) { + inputManager.on(guideVerticalScroller, onInputCommand); + } else { + inputManager.off(guideVerticalScroller, onInputCommand); + } + } + } + + function onTimerCreated(e, apiClient, data) { + const programId = data.ProgramId; + // This could be null, not supported by all tv providers + const newTimerId = data.Id; + + // find guide cells by program id, ensure timer icon + const cells = options.element.querySelectorAll('.programCell[data-id="' + programId + '"]'); + for (let i = 0, length = cells.length; i < length; i++) { + const cell = cells[i]; + + const icon = cell.querySelector('.timerIcon'); + if (!icon) { + cell.querySelector('.guideProgramName').insertAdjacentHTML('beforeend', ''); + } + + if (newTimerId) { + cell.setAttribute('data-timerid', newTimerId); + } + } + } + + function onSeriesTimerCreated(e, apiClient, data) { + } + + function onTimerCancelled(e, apiClient, data) { + const id = data.Id; + // find guide cells by timer id, remove timer icon + const cells = options.element.querySelectorAll('.programCell[data-timerid="' + id + '"]'); + for (let i = 0, length = cells.length; i < length; i++) { + const cell = cells[i]; + const icon = cell.querySelector('.timerIcon'); + if (icon) { + icon.parentNode.removeChild(icon); + } + cell.removeAttribute('data-timerid'); + } + } + + function onSeriesTimerCancelled(e, apiClient, data) { + const id = data.Id; + // find guide cells by timer id, remove timer icon + const cells = options.element.querySelectorAll('.programCell[data-seriestimerid="' + id + '"]'); + for (let i = 0, length = cells.length; i < length; i++) { + const cell = cells[i]; + const icon = cell.querySelector('.seriesTimerIcon'); + if (icon) { + icon.parentNode.removeChild(icon); + } + cell.removeAttribute('data-seriestimerid'); + } + } + + import('text!./tvguide.template.html').then(({default: template}) => { + const context = options.element; + + context.classList.add('tvguide'); + + context.innerHTML = globalize.translateHtml(template, 'core'); + + programGrid = context.querySelector('.programGrid'); + const timeslotHeaders = context.querySelector('.timeslotHeaders'); + + if (layoutManager.tv) { + dom.addEventListener(context.querySelector('.guideVerticalScroller'), 'focus', onScrollerFocus, { + capture: true, + passive: true + }); + } else if (layoutManager.desktop) { + timeslotHeaders.classList.add('timeslotHeaders-desktop'); + } + + if (browser.iOS || browser.osx) { + context.querySelector('.channelsContainer').classList.add('noRubberBanding'); + + programGrid.classList.add('noRubberBanding'); + } + + dom.addEventListener(programGrid, 'scroll', function (e) { + onProgramGridScroll(context, this, timeslotHeaders); + }, { + passive: true + }); + + dom.addEventListener(timeslotHeaders, 'scroll', function () { + onTimeslotHeadersScroll(context, this); + }, { + passive: true + }); + + programGrid.addEventListener('click', onProgramGridClick); + + context.querySelector('.btnNextPage').addEventListener('click', function () { + currentStartIndex += currentChannelLimit; + reloadPage(context); + restartAutoRefresh(); + }); + + context.querySelector('.btnPreviousPage').addEventListener('click', function () { + currentStartIndex = Math.max(currentStartIndex - currentChannelLimit, 0); + reloadPage(context); + restartAutoRefresh(); + }); + + context.querySelector('.btnGuideViewSettings').addEventListener('click', function () { + showViewSettings(self); + restartAutoRefresh(); + }); + + context.querySelector('.guideDateTabs').addEventListener('tabchange', function (e) { + const allTabButtons = e.target.querySelectorAll('.guide-date-tab-button'); + + const tabButton = allTabButtons[parseInt(e.detail.selectedTabIndex)]; + if (tabButton) { + const previousButton = e.detail.previousIndex == null ? null : allTabButtons[parseInt(e.detail.previousIndex)]; + + const date = new Date(); + date.setTime(parseInt(tabButton.getAttribute('data-date'))); + + const scrollWidth = programGrid.scrollWidth; + let scrollToTimeMs; + if (scrollWidth) { + scrollToTimeMs = (programGrid.scrollLeft / scrollWidth) * msPerDay; + } else { + scrollToTimeMs = 0; + } + + if (previousButton) { + const previousDate = new Date(); + previousDate.setTime(parseInt(previousButton.getAttribute('data-date'))); + + scrollToTimeMs += (previousDate.getHours() * 60 * 60 * 1000); + scrollToTimeMs += (previousDate.getMinutes() * 60 * 1000); + } + + let startTimeOfDayMs = (date.getHours() * 60 * 60 * 1000); + startTimeOfDayMs += (date.getMinutes() * 60 * 1000); + + changeDate(context, date, scrollToTimeMs, scrollToTimeMs, startTimeOfDayMs, false); + } + }); + + setScrollEvents(context, true); + itemShortcuts.on(context); + + events.trigger(self, 'load'); + + events.on(serverNotifications, 'TimerCreated', onTimerCreated); + events.on(serverNotifications, 'SeriesTimerCreated', onSeriesTimerCreated); + events.on(serverNotifications, 'TimerCancelled', onTimerCancelled); + events.on(serverNotifications, 'SeriesTimerCancelled', onSeriesTimerCancelled); + + self.refresh(); + }); +} + +export default Guide; diff --git a/src/controllers/livetv/livetvguide.js b/src/controllers/livetv/livetvguide.js index ec7a7a3f81..f89c2693be 100644 --- a/src/controllers/livetv/livetvguide.js +++ b/src/controllers/livetv/livetvguide.js @@ -7,7 +7,7 @@ define(['tvguide'], function (tvguide) { self.renderTab = function () { if (!guideInstance) { - guideInstance = new tvguide({ + guideInstance = new tvguide.default({ element: tabContent, serverId: ApiClient.serverId() });