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

Merge remote-tracking branch 'upstream/master' into fonts

This commit is contained in:
nyanmisaka 2020-08-19 17:25:25 +08:00
commit f1804009e6
326 changed files with 21514 additions and 23576 deletions

View file

@ -0,0 +1,26 @@
<div id="apiKeysPage" data-role="page" class="page type-interior advancedConfigurationPage fullWidthContent">
<div>
<div class="content-primary">
<div class="detailSectionHeader">
<h2 style="margin:.6em 0;vertical-align:middle;display:inline-block;">${HeaderApiKeys}</h2>
<button is="emby-button" type="button" class="fab btnNewKey submit" style="margin-left:1em;" title="${Add}">
<span class="material-icons add" aria-hidden="true"></span>
</button>
</div>
<p>${HeaderApiKeysHelp}</p>
<br />
<table class="tblApiKeys detailTable">
<caption class="clipForScreenReader">${ApiKeysCaption}</caption>
<thead>
<tr>
<th scope="col" class="detailTableHeaderCell"></th>
<th scope="col" class="detailTableHeaderCell">${HeaderApiKey}</th>
<th scope="col" class="detailTableHeaderCell">${HeaderApp}</th>
<th scope="col" class="detailTableHeaderCell">${HeaderDateIssued}</th>
</tr>
</thead>
<tbody class="resultBody"></tbody>
</table>
</div>
</div>
</div>

View file

@ -0,0 +1,119 @@
<div id="dashboardPage" data-role="page" class="page type-interior dashboardHomePage fullWidthContent">
<div class="content-primary">
<div class="dashboardSections" style="padding-top:.5em;">
<div class="dashboardColumn dashboardColumn-2-60 dashboardColumn-3-46">
<div class="dashboardSection">
<a is="emby-linkbutton" href="dashboardgeneral.html" class="button-flat sectionTitleTextButton">
<h3>${TabServer}</h3>
<span class="material-icons chevron_right"></span>
</a>
<div class="paperList" style="padding: 1em;">
<p id="serverName"></p>
<p id="versionNumber"></p>
<p id="operatingSystem"></p>
<p id="architecture"></p>
</div>
<div style="margin-top:1em;">
<button is="emby-button" type="button" id="btnRestartServer" class="raised" onclick="DashboardPage.restart(this);" style="margin-left:0;">
<span>${ButtonRestart}</span>
</button>
<button is="emby-button" type="button" id="btnShutdown" class="raised" onclick="DashboardPage.shutdown(this);">
<span>${ButtonShutdown}</span>
</button>
</div>
<div style="margin-top: 2em;" class="runningTasksContainer hide">
<h3>${HeaderRunningTasks}</h3>
<div id="divRunningTasks" class="paperList" style="padding: 1em;">
</div>
</div>
</div>
<div class="dashboardSection">
<a is="emby-linkbutton" href="devices.html" class="button-flat sectionTitleTextButton">
<h3>${HeaderActiveDevices}</h3>
<span class="material-icons chevron_right"></span>
</a>
<div class="activeDevices itemsContainer vertical-wrap">
</div>
</div>
</div>
<div class="dashboardColumn dashboardColumn-2-40 dashboardColumn-3-27">
<div class="dashboardSection">
<a is="emby-linkbutton" href="serveractivity.html?useractivity=true" class="button-flat sectionTitleTextButton">
<h3>${HeaderActivity}</h3>
<span class="material-icons chevron_right"></span>
</a>
<div class="paperList userActivityItems" data-activitylimit="7" data-useractivity="true">
</div>
</div>
</div>
<div class="dashboardColumn dashboardColumn-3-27">
<div class="dashboardSection activeRecordingsSection hide">
<h3>${HeaderActiveRecordings}</h3>
<div class="activeRecordingItems vertical-wrap" is="emby-itemscontainer">
</div>
</div>
<div class="dashboardSection serverActivitySection hide activityContainer">
<a is="emby-linkbutton" href="serveractivity.html?useractivity=false" class="button-flat sectionTitleTextButton">
<h3>${Alerts}</h3>
<span class="material-icons chevron_right"></span>
</a>
<div class="paperList serverActivityItems" data-activitylimit="4" data-useractivity="false">
</div>
</div>
<div class="dashboardSection">
<a is="emby-linkbutton" href="dashboardgeneral.html" class="button-flat sectionTitleTextButton">
<h3>${HeaderPaths}</h3>
<span class="material-icons chevron_right"></span>
</a>
<div class="paperList">
<div class="listItem listItem-border">
<div class="listItemBody two-line">
<div class="listItemBodyText secondary" style="margin:0;">${LabelCache}</div>
<div class="listItemBodyText" id="cachePath"></div>
</div>
</div>
<div class="listItem listItem-border">
<div class="listItemBody two-line">
<div class="listItemBodyText secondary" style="margin:0;">${LabelLogs}</div>
<div class="listItemBodyText" id="logPath"></div>
</div>
</div>
<div class="listItem listItem-border">
<div class="listItemBody two-line">
<div class="listItemBodyText secondary" style="margin:0;">${LabelMetadata}</div>
<div class="listItemBodyText" id="metadataPath"></div>
</div>
</div>
<div class="listItem listItem-border">
<div class="listItemBody two-line">
<div class="listItemBodyText secondary" style="margin:0;">${LabelTranscodes}</div>
<div class="listItemBodyText" id="transcodePath"></div>
</div>
</div>
<div class="listItem listItem-border">
<div class="listItemBody two-line">
<div class="listItemBodyText secondary" style="margin:0;">${LabelWeb}</div>
<div class="listItemBodyText" id="webPath"></div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="dashboardFooter">
<div style="height:1px;" class="ui-bar-inherit"></div>
<div style="margin-top:1em;">
<a is="emby-linkbutton" rel="noopener noreferrer" class="button-link" href="https://jellyfin.org" target="_blank">Jellyfin</a>
</div>
</div>
</div>
</div>

View file

@ -24,7 +24,7 @@ import 'emby-itemscontainer';
function showPlaybackInfo(btn, session) {
import('alert').then(({default: alert}) => {
let title;
let text = [];
const text = [];
const displayPlayMethod = playMethodHelper.getDisplayPlayMethod(session);
if (displayPlayMethod === 'DirectStream') {
@ -313,7 +313,7 @@ import 'emby-itemscontainer';
btnCssClass = session.TranscodingInfo && session.TranscodingInfo.TranscodeReasons && session.TranscodingInfo.TranscodeReasons.length ? '' : ' hide';
html += '<button is="paper-icon-button-light" class="sessionCardButton btnSessionInfo paper-icon-button-light ' + btnCssClass + '" title="' + globalize.translate('ViewPlaybackInfo') + '"><span class="material-icons info"></span></button>';
btnCssClass = session.ServerId && -1 !== session.SupportedCommands.indexOf('DisplayMessage') && session.DeviceId !== connectionManager.deviceId() ? '' : ' hide';
btnCssClass = session.ServerId && session.SupportedCommands.indexOf('DisplayMessage') !== -1 && session.DeviceId !== connectionManager.deviceId() ? '' : ' hide';
html += '<button is="paper-icon-button-light" class="sessionCardButton btnSessionSendMessage paper-icon-button-light ' + btnCssClass + '" title="' + globalize.translate('SendMessage') + '"><span class="material-icons message"></span></button>';
html += '</div>';
@ -346,7 +346,7 @@ import 'emby-itemscontainer';
function renderRunningTasks(view, tasks) {
let html = '';
tasks = tasks.filter(function (task) {
if ('Idle' != task.State) {
if (task.State != 'Idle') {
return !task.IsHidden;
}
@ -551,7 +551,7 @@ import 'emby-itemscontainer';
row.classList.remove('playingSession');
}
if (session.ServerId && -1 !== session.SupportedCommands.indexOf('DisplayMessage') && session.DeviceId !== connectionManager.deviceId()) {
if (session.ServerId && session.SupportedCommands.indexOf('DisplayMessage') !== -1 && session.DeviceId !== connectionManager.deviceId()) {
row.querySelector('.btnSessionSendMessage').classList.remove('hide');
} else {
row.querySelector('.btnSessionSendMessage').classList.add('hide');
@ -737,7 +737,7 @@ import 'emby-itemscontainer';
shutdown: function (btn) {
import('confirm').then(({default: confirm}) => {
confirm({
title: globalize.translate('HeaderShutdown'),
title: globalize.translate('ButtonShutdown'),
text: globalize.translate('MessageConfirmShutdown'),
confirmText: globalize.translate('ButtonShutdown'),
primary: 'delete'

View file

@ -0,0 +1,24 @@
<div id="devicePage" data-role="page" class="page type-interior devicesPage noSecondaryNavPage">
<div>
<div class="content-primary">
<form class="deviceForm">
<div class="verticalSection verticalSection-extrabottompadding">
<div class="sectionTitleContainer flex align-items-center">
<h2 class="sectionTitle reportedName"></h2>
<a is="emby-linkbutton" rel="noopener noreferrer" class="raised button-alt headerHelpButton" target="_blank" href="https://docs.jellyfin.org/general/server/devices.html">${Help}</a>
</div>
<div class="inputContainer">
<input is="emby-input" type="text" id="txtCustomName" label="${LabelCustomDeviceDisplayName}" />
<div class="fieldDescription">${LabelCustomDeviceDisplayNameHelp}</div>
</div>
</div>
<div>
<button is="emby-button" type="submit" class="raised button-submit block">
<span>${Save}</span>
</button>
</div>
</form>
</div>
</div>
</div>

View file

@ -0,0 +1,13 @@
<div id="devicesPage" data-role="page" class="page type-interior devicesPage noSecondaryNavPage">
<div>
<div class="content-primary">
<div class="verticalSection verticalSection">
<div class="sectionTitleContainer sectionTitleContainer-cards flex align-items-center">
<h2 class="sectionTitle sectionTitle-cards">${HeaderDevices}</h2>
<a is="emby-linkbutton" rel="noopener noreferrer" class="raised button-alt headerHelpButton" target="_blank" href="https://docs.jellyfin.org/general/server/devices.html">${Help}</a>
</div>
</div>
<div is="emby-itemscontainer" class="devicesList vertical-wrap" data-multiselect="false"></div>
</div>
</div>
</div>

View file

@ -21,7 +21,7 @@ import 'cardStyle';
confirm({
text: msg,
title: globalize.translate('HeaderDeleteDevice'),
confirmText: globalize.translate('ButtonDelete'),
confirmText: globalize.translate('Delete'),
primary: 'delete'
}).then(function () {
loading.show();
@ -38,7 +38,7 @@ import 'cardStyle';
}
function showDeviceMenu(view, btn, deviceId) {
let menuItems = [];
const menuItems = [];
if (canEdit) {
menuItems.push({

View file

@ -0,0 +1,584 @@
<div id="dlnaProfilePage" data-role="page" class="page type-interior dlnaPage withTabs">
<div data-role="content">
<div class="content-primary">
<form class="dlnaProfileForm" style="max-width: 650px;">
<div class="verticalSection">
<div class="sectionTitleContainer flex align-items-center">
<h2 class="sectionTitle">${HeaderProfileInformation}</h2>
</div>
</div>
<div data-role="controlgroup" data-type="horizontal" data-mini="true">
<a href="#" is="emby-linkbutton" data-role="button" class="radioTabButton" id="radioInfo" data-value="tabInfo">${TabInfo}</a>
<a href="#" is="emby-linkbutton" data-role="button" class="radioTabButton" id="radioDirectPlay" data-value="tabDirectPlayProfiles">${TabDirectPlay}</a>
<a href="#" is="emby-linkbutton" data-role="button" class="radioTabButton" id="radioTranscoding" data-value="tabTranscodingProfiles">${Transcoding}</a>
<a href="#" is="emby-linkbutton" data-role="button" class="radioTabButton" id="radioContainers" data-value="tabContainerProfiles">${TabContainers}</a>
<a href="#" is="emby-linkbutton" data-role="button" class="radioTabButton" id="radioCodecs" data-value="tabCodecProfiles">${TabCodecs}</a>
<a href="#" is="emby-linkbutton" data-role="button" class="radioTabButton" id="radioMediaProfiles" data-value="tabMediaProfiles">${TabResponses}</a>
</div>
<br />
<div class="tabContent tabInfo">
<div class="inputContainer">
<input is="emby-input" type="text" id="txtName" required="required" label="${LabelName}" />
</div>
<div class="selectContainer">
<select is="emby-select" id="selectUser" label="${LabelUserLibrary}"></select>
<div class="fieldDescription">${LabelUserLibraryHelp}</div>
</div>
<div>
<h3 class="checkboxListLabel">${LabelSupportedMediaTypes}</h3>
<div class="checkboxList paperList checkboxList-paperList">
<label>
<input is="emby-checkbox" type="checkbox" id="chkAudio" data-value="Audio" class="chkMediaType" />
<span>${OptionProfileAudio}</span>
</label>
<label>
<input is="emby-checkbox" type="checkbox" id="chkPhoto" data-value="Photo" class="chkMediaType" />
<span>${OptionProfilePhoto}</span>
</label>
<label>
<input is="emby-checkbox" type="checkbox" id="chkVideo" data-value="Video" class="chkMediaType" />
<span>${OptionProfileVideo}</span>
</label>
</div>
</div>
<br />
<div class="inputContainer">
<input is="emby-input" type="number" id="txtMaxAllowedBitrate" pattern="[0-9]*" min="1" label="${LabelMaxStreamingBitrate}" />
<div class="fieldDescription">${LabelMaxStreamingBitrateHelp}</div>
</div>
<div class="inputContainer">
<input is="emby-input" type="number" id="txtMusicStreamingTranscodingBitrate" pattern="[0-9]*" min="1" label="${LabelMusicStreamingTranscodingBitrate}" />
<div class="fieldDescription">${LabelMusicStreamingTranscodingBitrateHelp}</div>
</div>
<div style="display:none;">
<label for="chkIgnoreTranscodeByteRangeRequests">${OptionIgnoreTranscodeByteRangeRequests}</label>
<input type="checkbox" id="chkIgnoreTranscodeByteRangeRequests" data-mini="true" />
<div class="fieldDescription">${OptionIgnoreTranscodeByteRangeRequestsHelp}</div>
</div>
<div is="emby-collapse" title="${HeaderIdentification}">
<div class="collapseContent">
<h3>${HeaderIdentificationCriteriaHelp}</h3>
<div class="inputContainer">
<input is="emby-input" type="text" id="txtIdFriendlyName" label="${LabelFriendlyName}" />
<div class="fieldDescription">${LabelIdentificationFieldHelp}</div>
</div>
<div class="inputContainer">
<input is="emby-input" type="text" id="txtIdManufacturer" label="${LabelManufacturer}" />
<div class="fieldDescription">${LabelIdentificationFieldHelp}</div>
</div>
<div class="inputContainer">
<input is="emby-input" type="text" id="txtIdManufacturerUrl" label="${LabelManufacturerUrl}" />
<div class="fieldDescription">${LabelIdentificationFieldHelp}</div>
</div>
<div class="inputContainer">
<input is="emby-input" type="text" id="txtIdModelName" label="${LabelModelName}" />
<div class="fieldDescription">${LabelIdentificationFieldHelp}</div>
</div>
<div class="inputContainer">
<input is="emby-input" type="text" id="txtIdModelNumber" label="${LabelModelNumber}" />
<div class="fieldDescription">${LabelIdentificationFieldHelp}</div>
</div>
<div class="inputContainer">
<input is="emby-input" type="text" id="txtIdModelDesription" label="${LabelModelDescription}" />
<div class="fieldDescription">${LabelIdentificationFieldHelp}</div>
</div>
<div class="inputContainer">
<input is="emby-input" type="text" id="txtIdModelUrl" label="${LabelModelUrl}" />
<div class="fieldDescription">${LabelIdentificationFieldHelp}</div>
</div>
<div class="inputContainer">
<input is="emby-input" type="text" id="txtIdSerialNumber" label="${LabelSerialNumber}" />
<div class="fieldDescription">${LabelIdentificationFieldHelp}</div>
</div>
<div class="inputContainer">
<input is="emby-input" type="text" id="txtIdDeviceDescription" label="${LabelDeviceDescription}" />
<div class="fieldDescription">${LabelIdentificationFieldHelp}</div>
</div>
<div>
<h2 style="vertical-align:middle;display:inline-block;">${HeaderHttpHeaders}</h2>
<button is="emby-button" type="button" class="fab btnAddIdentificationHttpHeader submit sectionTitleButton" style="margin-left:1em;" title="${Add}">
<span class="material-icons add"></span>
</button>
</div>
<div class="httpHeaderIdentificationList"></div>
</div>
</div>
<div is="emby-collapse" title="${Display}">
<div class="collapseContent">
<br />
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="chkRequiresPlainFolders" />
<span>${OptionPlainStorageFolders}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${OptionPlainStorageFoldersHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="chkRequiresPlainVideoItems" />
<span>${OptionPlainVideoItems}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${OptionPlainVideoItemsHelp}</div>
</div>
</div>
</div>
<div is="emby-collapse" title="${HeaderImageSettings}">
<div class="collapseContent">
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="chkEnableAlbumArtInDidl" data-mini="true" />
<span>${LabelEmbedAlbumArtDidl}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${LabelEmbedAlbumArtDidlHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="chkEnableSingleImageLimit" data-mini="true" />
<span>${LabelEnableSingleImageInDidlLimit}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${LabelEnableSingleImageInDidlLimitHelp}</div>
</div>
<div class="inputContainer">
<input type="text" is="emby-input" id="txtAlbumArtPn" label="${LabelAlbumArtPN}" />
<div class="fieldDescription">${LabelAlbumArtHelp}</div>
</div>
<div class="inputContainer">
<input type="number" is="emby-input" id="txtAlbumArtMaxWidth" pattern="[0-9]*" min="1" label="${LabelAlbumArtMaxWidth}" />
<div class="fieldDescription">${LabelAlbumArtMaxWidthHelp}</div>
</div>
<div class="inputContainer">
<input type="number" is="emby-input" id="txtAlbumArtMaxHeight" pattern="[0-9]*" min="1" label="${LabelAlbumArtMaxHeight}" />
<div class="fieldDescription">${LabelAlbumArtMaxHeightHelp}</div>
</div>
<div class="inputContainer">
<input type="number" is="emby-input" id="txtIconMaxWidth" pattern="[0-9]*" min="1" label="${LabelIconMaxWidth}" />
<div class="fieldDescription">${LabelIconMaxWidthHelp}</div>
</div>
<div class="inputContainer">
<input type="number" is="emby-input" id="txtIconMaxHeight" pattern="[0-9]*" min="1" label="${LabelIconMaxHeight}" />
<div class="fieldDescription">${LabelIconMaxHeightHelp}</div>
</div>
</div>
</div>
<div is="emby-collapse" title="${HeaderServerSettings}">
<div class="collapseContent">
<p>${HeaderProfileServerSettingsHelp}</p>
<div class="inputContainer">
<input is="emby-input" type="text" id="txtInfoFriendlyName" label="${LabelFriendlyName}" />
</div>
<div class="inputContainer">
<input is="emby-input" type="text" id="txtInfoManufacturer" label="${LabelManufacturer}" />
</div>
<div class="inputContainer">
<input is="emby-input" type="text" id="txtInfoManufacturerUrl" label="${LabelManufacturerUrl}" />
</div>
<div class="inputContainer">
<input is="emby-input" type="text" id="txtInfoModelName" label="${LabelModelName}" />
</div>
<div class="inputContainer">
<input is="emby-input" type="text" id="txtInfoModelNumber" label="${LabelModelNumber}" />
</div>
<div class="inputContainer">
<input is="emby-input" type="text" id="txtInfoModelDesription" label="${LabelModelDescription}" />
</div>
<div class="inputContainer">
<input is="emby-input" type="text" id="txtInfoModelUrl" label="${LabelModelUrl}" />
</div>
<div class="inputContainer">
<input is="emby-input" type="text" id="txtInfoSerialNumber" label="${LabelSerialNumber}" />
</div>
<div class="inputContainer">
<input is="emby-input" type="text" id="txtProtocolInfo" label="${LabelProtocolInfo}" />
<div class="fieldDescription">${LabelProtocolInfoHelp}</div>
</div>
<div class="inputContainer">
<input is="emby-input" type="text" id="txtXDlnaCap" label="${LabelXDlnaCap}" />
<div class="fieldDescription">${LabelXDlnaCapHelp}</div>
</div>
<div class="inputContainer">
<input is="emby-input" type="text" id="txtXDlnaDoc" label="${LabelXDlnaDoc}" />
<div class="fieldDescription">${LabelXDlnaDocHelp}</div>
</div>
<div class="inputContainer">
<input is="emby-input" type="text" id="txtSonyAggregationFlags" label="${LabelSonyAggregationFlags}" />
<div class="fieldDescription">${LabelSonyAggregationFlagsHelp}</div>
</div>
</div>
</div>
<div is="emby-collapse" title="${HeaderSubtitleProfiles}">
<div class="collapseContent">
<p>${HeaderSubtitleProfilesHelp}</p>
<button is="emby-button" type="button" class="raised submit block btnAddSubtitleProfile">
<span>${Add}</span>
</button>
<div class="subtitleProfileList"></div>
<br />
</div>
</div>
<div is="emby-collapse" title="${HeaderXmlSettings}">
<div class="collapseContent">
<div>
<h2 style="vertical-align:middle;display:inline-block;">${HeaderXmlDocumentAttributes}</h2>
<button is="emby-button" type="button" class="fab btnAddXmlDocumentAttribute submit sectionTitleButton" style="margin-left:1em;" title="${Add}">
<span class="material-icons add"></span>
</button>
</div>
<div class="xmlDocumentAttributeList"></div>
<div class="fieldDescription">${XmlDocumentAttributeListHelp}</div>
<br />
</div>
</div>
</div>
<div class="tabContent tabDirectPlayProfiles">
<p>${HeaderDirectPlayProfileHelp}</p>
<button is="emby-button" class="raised submit block btnAddDirectPlayProfile" type="button" data-mini="true" data-icon="plus">${New}</button>
<br />
<div class="directPlayProfiles"></div>
</div>
<div class="tabContent tabTranscodingProfiles">
<p>${HeaderTranscodingProfileHelp}</p>
<button is="emby-button" class="raised submit block btnAddTranscodingProfile" type="button" data-mini="true" data-icon="plus">${New}</button>
<br />
<div class="transcodingProfiles"></div>
</div>
<div class="tabContent tabContainerProfiles">
<p>${HeaderContainerProfileHelp}</p>
<button is="emby-button" class="raised submit block btnAddContainerProfile" type="button" data-mini="true" data-icon="plus">${New}</button>
<br />
<div class="containerProfiles"></div>
</div>
<div class="tabContent tabCodecProfiles">
<p>${HeaderCodecProfileHelp}</p>
<button is="emby-button" class="raised submit block btnAddCodecProfile" type="button" data-icon="plus">${New}</button>
<br />
<div class="codecProfiles"></div>
</div>
<div class="tabContent tabMediaProfiles">
<p>${HeaderResponseProfileHelp}</p>
<button is="emby-button" class="raised submit block btnAddResponseProfile" type="button" data-mini="true" data-icon="plus">${New}</button>
<br />
<div class="mediaProfiles"></div>
</div>
<br />
<div>
<button is="emby-button" type="submit" class="raised button-submit block">
<span>${Save}</span>
</button>
<button is="emby-button" type="button" class="button-cancel raised block" onclick="Dashboard.navigate('dlnaprofiles.html');">
<span>${ButtonCancel}</span>
</button>
</div>
</form>
</div>
</div>
<div data-role="popup" id="popupEditDirectPlayProfile" class="dialog dialog-fixedSize dialog-medium hide" style="position: fixed; top: 10%;">
<form class="editDirectPlayProfileForm" style="padding:1em;">
<div class="ui-bar-a">
<h3 class="sectionTitle">${HeaderDirectPlayProfile}</h3>
</div>
<div data-role="content">
<div class="selectContainer">
<select id="selectDirectPlayProfileType" name="selectDirectPlayProfileType" is="emby-select" label="${LabelType}">
<option value="Audio">${OptionProfileAudio}</option>
<option value="Photo">${OptionProfilePhoto}</option>
<option value="Video">${OptionProfileVideo}</option>
</select>
</div>
<div class="inputContainer">
<input is="emby-input" type="text" id="txtDirectPlayContainer" label="${LabelProfileContainer}" />
<div class="fieldDescription">${LabelProfileContainersHelp}</div>
</div>
<div id="fldDirectPlayVideoCodec" style="margin: 1em 0;">
<div class="inputContainer">
<input is="emby-input" type="text" id="txtDirectPlayVideoCodec" label="${LabelProfileVideoCodecs}" />
<div class="fieldDescription">${LabelProfileCodecsHelp}</div>
</div>
</div>
<div id="fldDirectPlayAudioCodec" style="margin: 1em 0 2em;">
<div class="inputContainer">
<input is="emby-input" type="text" id="txtDirectPlayAudioCodec" label="${LabelProfileAudioCodecs}" />
<div class="fieldDescription">${LabelProfileCodecsHelp}</div>
</div>
</div>
<p>
<button is="emby-button" type="submit" class="raised button-submit block" data-icon="check" data-mini="true">
<span>${ButtonOk}</span>
</button>
<button is="emby-button" type="button" class="raised button-cancel block" data-icon="delete" onclick="$(this).parents('.dialog').addClass('hide');" data-mini="true">
<span>${ButtonCancel}</span>
</button>
</p>
</div>
</form>
</div>
<div data-role="popup" id="transcodingProfilePopup" class="dialog dialog-fixedSize dialog-medium hide" style="position: fixed; top: 10%;">
<form class="transcodingProfileForm" style="padding:1em;">
<div class="ui-bar-a">
<h3 class="sectionTitle">${HeaderTranscodingProfile}</h3>
</div>
<div data-role="content">
<div data-role="controlgroup" data-type="horizontal" data-mini="true">
<input type="radio" name="radioTranscodingTab" class="radioTabButton" id="radioTranscodingBasics" value="tabTranscodingBasics">
<label for="radioTranscodingBasics">${TabInfo}</label>
<input type="radio" name="radioTranscodingTab" class="radioTabButton" id="radioTranscodingAdvanced" value="tabTranscodingAdvanced">
<label for="radioTranscodingAdvanced">${TabAdvanced}</label>
</div>
<br />
<div class="tabContent tabTranscodingBasics" style="display: none;">
<div class="selectContainer">
<select id="selectTranscodingProfileType" name="selectTranscodingProfileType" is="emby-select" label="${LabelType}">
<option value="Audio">${OptionProfileAudio}</option>
<option value="Photo">${OptionProfilePhoto}</option>
<option value="Video">${OptionProfileVideo}</option>
</select>
</div>
<div id="fldTranscodingProtocol" style="margin: 1em 0;">
<div class="selectContainer">
<select id="selectTranscodingProtocol" name="selectTranscodingProtocol" is="emby-select" label="${LabelProtocol}">
<option value="Http">${OptionProtocolHttp}</option>
<option value="Hls">${OptionProtocolHls}</option>
</select>
</div>
</div>
<div class="inputContainer">
<input is="emby-input" type="text" id="txtTranscodingContainer" label="${LabelTranscodingContainer}"; required="required" />
</div>
<div id="fldTranscodingVideoCodec" style="margin: 1em 0;">
<div class="inputContainer">
<input is="emby-input" type="text" id="txtTranscodingVideoCodec" label="${LabelTranscodingVideoCodec}" />
</div>
</div>
<div id="fldTranscodingAudioCodec" style="margin: 1em 0;">
<div class="inputContainer">
<input is="emby-input" type="text" id="txtTranscodingAudioCodec" label="${LabelTranscodingAudioCodec}" />
</div>
</div>
</div>
<div class="tabContent tabTranscodingAdvanced" style="display: none;">
<div id="fldEnableMpegtsM2TsMode" style="margin: 1em 0;">
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="chkEnableMpegtsM2TsMode" />
<span>${OptionEnableM2tsMode}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${OptionEnableM2tsModeHelp}</div>
</div>
</div>
<div id="fldEstimateContentLength" style="margin: 1em 0;">
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="chkEstimateContentLength" />
<span>${OptionEstimateContentLength}</span>
</label>
</div>
</div>
<div id="fldReportByteRangeRequests" style="margin: 1em 0;">
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="chkReportByteRangeRequests" />
<span>${OptionReportByteRangeSeekingWhenTranscoding}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${OptionReportByteRangeSeekingWhenTranscodingHelp}</div>
</div>
</div>
</div>
<p>
<button is="emby-button" type="submit" class="raised button-submit block" data-icon="check" data-mini="true">
<span>${ButtonOk}</span>
</button>
<button is="emby-button" type="button" class="raised button-cancel block" data-icon="delete" onclick="$(this).parents('.dialog').addClass('hide');" data-mini="true">
<span>${ButtonCancel}</span>
</button>
</p>
</div>
</form>
</div>
<div data-role="popup" id="containerProfilePopup" class="dialog dialog-fixedSize dialog-medium hide" style="position: fixed; top: 10%;">
<form class="containerProfileForm" style="padding:1em;">
<div class="ui-bar-a">
<h3 class="sectionTitle">${HeaderContainerProfile}</h3>
</div>
<div data-role="content">
<p>${HeaderContainerProfileHelp}</p>
<div class="tabContent tabContainerBasics">
<div class="selectContainer">
<select id="selectContainerProfileType" name="selectContainerProfileType" is="emby-select" label="${LabelType}">
<option value="Audio">${OptionProfileAudio}</option>
<option value="Photo">${OptionProfilePhoto}</option>
<option value="Video">${OptionProfileVideo}</option>
</select>
</div>
<div class="inputContainer">
<input is="emby-input" type="text" id="txtContainerProfileContainer" label="${LabelProfileContainer}" />
<div class="fieldDescription">${LabelProfileContainersHelp}</div>
</div>
</div>
<div class="tabContent tabContainerConditions" style="display: none;"></div>
<p>
<button is="emby-button" type="submit" class="raised button-submit block" data-icon="check" data-mini="true">
<span>${ButtonOk}</span>
</button>
<button is="emby-button" type="button" class="raised button-cancel block" data-icon="delete" onclick="$(this).parents('.dialog').addClass('hide');" data-mini="true">
<span>${ButtonCancel}</span>
</button>
</p>
</div>
</form>
</div>
<div data-role="popup" id="codecProfilePopup" class="dialog dialog-fixedSize dialog-medium hide" style="position: fixed; top: 10%;">
<form class="codecProfileForm" style="padding:1em;">
<div class="ui-bar-a">
<h3 class="sectionTitle">${HeaderCodecProfile}</h3>
</div>
<div data-role="content">
<p>${HeaderCodecProfileHelp}</p>
<div class="selectContainer">
<select id="selectCodecProfileType" name="selectCodecProfileType" is="emby-select" label="${LabelType}">
<option value="Video">${OptionProfileVideo}</option>
<option value="VideoAudio">${OptionProfileVideoAudio}</option>
<option value="Audio">${OptionProfileAudio}</option>
</select>
</div>
<div class="inputContainer">
<input is="emby-input" type="text" id="txtCodecProfileCodec" label="${LabelProfileCodecs}" />
<div class="fieldDescription">${LabelProfileCodecsHelp}</div>
</div>
<p>
<button is="emby-button" type="submit" class="raised button-submit block" data-icon="check" data-mini="true">
<span>${ButtonOk}</span>
</button>
<button is="emby-button" type="button" class="raised button-cancel block" data-icon="delete" onclick="$(this).parents('.dialog').addClass('hide');" data-mini="true">
<span>${ButtonCancel}</span>
</button>
</p>
</div>
</form>
</div>
<div data-role="popup" id="responseProfilePopup" class="dialog dialog-fixedSize dialog-medium hide" style="position: fixed; top: 10%;">
<form class="editResponseProfileForm" style="padding:1em;">
<div class="ui-bar-a">
<h3 class="sectionTitle">${HeaderResponseProfile}</h3>
</div>
<div data-role="content">
<div class="selectContainer">
<select id="selectResponseProfileType" name="selectResponseProfileType" is="emby-select" label="${LabelType}">
<option value="Audio">${OptionProfileAudio}</option>
<option value="Photo">${OptionProfilePhoto}</option>
<option value="Video">${OptionProfileVideo}</option>
</select>
</div>
<div class="inputContainer">
<input is="emby-input" type="text" id="txtResponseProfileContainer" label="${LabelProfileContainer}" />
<div class="fieldDescription">${LabelProfileContainersHelp}</div>
</div>
<div id="fldResponseProfileVideoCodec" style="margin: 1em 0;">
<div class="inputContainer">
<input is="emby-input" type="text" id="txtResponseProfileVideoCodec" label="${LabelProfileVideoCodecs}" />
<div class="fieldDescription">${LabelProfileCodecsHelp}</div>
</div>
</div>
<div id="fldResponseProfileAudioCodec" style="margin: 1em 0 2em;">
<div class="inputContainer">
<input is="emby-input" type="text" id="txtResponseProfileAudioCodec" label="${LabelProfileAudioCodecs}" />
<div class="fieldDescription">${LabelProfileCodecsHelp}</div>
</div>
</div>
<p>
<button is="emby-button" type="submit" class="raised button-submit block" data-icon="check" data-mini="true">
<span>${ButtonOk}</span>
</button>
<button is="emby-button" type="button" class="raised button-cancel block" data-icon="delete" onclick="$(this).parents('.dialog').addClass('hide');" data-mini="true">
<span>${ButtonCancel}</span>
</button>
</p>
</div>
</form>
</div>
<div data-role="popup" id="identificationHeaderPopup" class="dialog dialog-fixedSize dialog-medium hide" style="position: fixed; top: 10%;">
<form class="identificationHeaderForm" style="padding:1em;">
<div class="ui-bar-a">
<h3 class="sectionTitle">${HeaderIdentificationHeader}</h3>
</div>
<div data-role="content">
<div class="inputContainer">
<input is="emby-input" type="text" id="txtIdentificationHeaderName" label="${LabelName}" />
</div>
<div class="inputContainer">
<input is="emby-input" type="text" id="txtIdentificationHeaderValue" label="${LabelValue}" />
</div>
<div class="selectContainer">
<select id="selectMatchType" name="selectMatchType" is="emby-select" label="${LabelMatchType}">
<option value="Equals">${OptionEquals}</option>
<option value="Regex">${OptionRegex}</option>
<option value="Substring">${OptionSubstring}</option>
</select>
</div>
<p>
<button is="emby-button" type="submit" class="raised button-submit block" data-icon="check" data-mini="true">
<span>${ButtonOk}</span>
</button>
<button is="emby-button" type="button" class="raised button-cancel block" data-icon="delete" onclick="$(this).parents('.dialog').addClass('hide');" data-mini="true">
<span>${ButtonCancel}</span>
</button>
</p>
</div>
</form>
</div>
<div data-role="popup" id="xmlAttributePopup" class="dialog dialog-fixedSize dialog-medium hide" style="position: fixed; top: 10%;">
<form class="xmlAttributeForm" style="padding:1em;">
<div class="ui-bar-a">
<h3 class="sectionTitle">${HeaderXmlDocumentAttribute}</h3>
</div>
<div data-role="content">
<div class="inputContainer">
<input is="emby-input" type="text" id="txtXmlAttributeName" label="${LabelName}" />
</div>
<div class="inputContainer">
<input is="emby-input" type="text" id="txtXmlAttributeValue" label="${LabelValue}" />
</div>
<p>
<button is="emby-button" type="submit" class="raised button-submit block" data-icon="check" data-mini="true">
<span>${ButtonOk}</span>
</button>
<button is="emby-button" type="button" class="raised button-cancel block" data-icon="delete" onclick="$(this).parents('.dialog').addClass('hide');" data-mini="true">
<span>${ButtonCancel}</span>
</button>
</p>
</div>
</form>
</div>
<div data-role="popup" id="subtitleProfilePopup" class="dialog dialog-fixedSize dialog-medium hide" style="position: fixed; top: 10%;">
<form class="subtitleProfileForm" style="padding:1em;">
<div class="ui-bar-a">
<h3 class="sectionTitle">${HeaderSubtitleProfile}</h3>
</div>
<div data-role="content">
<div class="inputContainer">
<input is="emby-input" type="text" id="txtSubtitleProfileFormat" label="${LabelFormat}" />
<div class="fieldDescription">${LabelSubtitleFormatHelp}</div>
</div>
<div class="selectContainer">
<select id="selectSubtitleProfileMethod" name="selectSubtitleProfileMethod" is="emby-select" label="${LabelMethod}">
<option value="Embed">${OptionEmbedSubtitles}</option>
<option value="External">${OptionExternallyDownloaded}</option>
<option value="Hls">${OptionHlsSegmentedSubtitles}</option>
</select>
</div>
<div class="selectContainer">
<select id="selectSubtitleProfileDidlMode" name="selectSubtitleProfileDidlMode" is="emby-select" label="${LabelDidlMode}">
<option value="">${OptionResElement}</option>
<option value="CaptionInfoEx">${OptionCaptionInfoExSamsung}</option>
</select>
</div>
<p>
<button is="emby-button" type="submit" class="raised button-submit block" data-icon="check" data-mini="true">
<span>${ButtonOk}</span>
</button>
<button is="emby-button" type="button" class="raised button-cancel block" data-icon="delete" onclick="$(this).parents('.dialog').addClass('hide');" data-mini="true">
<span>${ButtonCancel}</span>
</button>
</p>
</div>
</form>
</div>
</div>

View file

@ -29,7 +29,7 @@ import 'listViewStyle';
function renderProfile(page, profile, users) {
$('#txtName', page).val(profile.Name);
$('.chkMediaType', page).each(function () {
this.checked = -1 != (profile.SupportedMediaTypes || '').split(',').indexOf(this.getAttribute('data-value'));
this.checked = (profile.SupportedMediaTypes || '').split(',').indexOf(this.getAttribute('data-value')) != -1;
});
$('#chkEnableAlbumArtInDidl', page).prop('checked', profile.EnableAlbumArtInDidl);
$('#chkEnableSingleImageLimit', page).prop('checked', profile.EnableSingleAlbumArtLimit);
@ -111,7 +111,7 @@ import 'listViewStyle';
}
function editIdentificationHeader(page, header) {
isSubProfileNew = null == header;
isSubProfileNew = header == null;
header = header || {};
currentSubProfile = header;
const popup = $('#identificationHeaderPopup', page);
@ -156,7 +156,7 @@ import 'listViewStyle';
}
function editXmlDocumentAttribute(page, attribute) {
isSubProfileNew = null == attribute;
isSubProfileNew = attribute == null;
attribute = attribute || {};
currentSubProfile = attribute;
const popup = $('#xmlAttributePopup', page);
@ -204,7 +204,7 @@ import 'listViewStyle';
}
function editSubtitleProfile(page, profile) {
isSubProfileNew = null == profile;
isSubProfileNew = profile == null;
profile = profile || {};
currentSubProfile = profile;
const popup = $('#subtitleProfilePopup', page);
@ -266,11 +266,11 @@ import 'listViewStyle';
html += '<a is="emby-linkbutton" href="#" class="lnkEditSubProfile" data-profileindex="' + index + '">';
html += '<p>' + globalize.translate('ValueContainer', profile.Container || allText) + '</p>';
if ('Video' == profile.Type) {
if (profile.Type == 'Video') {
html += '<p>' + globalize.translate('ValueVideoCodec', profile.VideoCodec || allText) + '</p>';
html += '<p>' + globalize.translate('ValueAudioCodec', profile.AudioCodec || allText) + '</p>';
} else {
if ('Audio' == profile.Type) {
if (profile.Type == 'Audio') {
html += '<p>' + globalize.translate('ValueCodec', profile.AudioCodec || allText) + '</p>';
}
}
@ -298,7 +298,7 @@ import 'listViewStyle';
}
function editDirectPlayProfile(page, directPlayProfile) {
isSubProfileNew = null == directPlayProfile;
isSubProfileNew = directPlayProfile == null;
directPlayProfile = directPlayProfile || {};
currentSubProfile = directPlayProfile;
const popup = $('#popupEditDirectPlayProfile', page);
@ -315,7 +315,7 @@ import 'listViewStyle';
let currentType;
for (let i = 0, length = profiles.length; i < length; i++) {
let profile = profiles[i];
const profile = profiles[i];
if (profile.Type !== currentType) {
html += '<li data-role="list-divider">' + profile.Type + '</li>';
@ -327,11 +327,11 @@ import 'listViewStyle';
html += '<p>Protocol: ' + (profile.Protocol || 'Http') + '</p>';
html += '<p>' + globalize.translate('ValueContainer', profile.Container || allText) + '</p>';
if ('Video' == profile.Type) {
if (profile.Type == 'Video') {
html += '<p>' + globalize.translate('ValueVideoCodec', profile.VideoCodec || allText) + '</p>';
html += '<p>' + globalize.translate('ValueAudioCodec', profile.AudioCodec || allText) + '</p>';
} else {
if ('Audio' == profile.Type) {
if (profile.Type == 'Audio') {
html += '<p>' + globalize.translate('ValueCodec', profile.AudioCodec || allText) + '</p>';
}
}
@ -354,7 +354,7 @@ import 'listViewStyle';
}
function editTranscodingProfile(page, transcodingProfile) {
isSubProfileNew = null == transcodingProfile;
isSubProfileNew = transcodingProfile == null;
transcodingProfile = transcodingProfile || {};
currentSubProfile = transcodingProfile;
const popup = $('#transcodingProfilePopup', page);
@ -365,7 +365,7 @@ import 'listViewStyle';
$('#selectTranscodingProtocol', popup).val(transcodingProfile.Protocol || 'Http');
$('#chkEnableMpegtsM2TsMode', popup).prop('checked', transcodingProfile.EnableMpegtsM2TsMode || false);
$('#chkEstimateContentLength', popup).prop('checked', transcodingProfile.EstimateContentLength || false);
$('#chkReportByteRangeRequests', popup).prop('checked', 'Bytes' == transcodingProfile.TranscodeSeekInfo);
$('#chkReportByteRangeRequests', popup).prop('checked', transcodingProfile.TranscodeSeekInfo == 'Bytes');
$('.radioTabButton:first', popup).trigger('click');
openPopup(popup[0]);
}
@ -401,7 +401,7 @@ import 'listViewStyle';
let currentType;
for (let i = 0, length = profiles.length; i < length; i++) {
let profile = profiles[i];
const profile = profiles[i];
if (profile.Type !== currentType) {
html += '<li data-role="list-divider">' + profile.Type + '</li>';
@ -443,7 +443,7 @@ import 'listViewStyle';
}
function editContainerProfile(page, containerProfile) {
isSubProfileNew = null == containerProfile;
isSubProfileNew = containerProfile == null;
containerProfile = containerProfile || {};
currentSubProfile = containerProfile;
const popup = $('#containerProfilePopup', page);
@ -472,7 +472,7 @@ import 'listViewStyle';
let currentType;
for (let i = 0, length = profiles.length; i < length; i++) {
let profile = profiles[i];
const profile = profiles[i];
const type = profile.Type.replace('VideoAudio', 'Video Audio');
if (type !== currentType) {
@ -515,7 +515,7 @@ import 'listViewStyle';
}
function editCodecProfile(page, codecProfile) {
isSubProfileNew = null == codecProfile;
isSubProfileNew = codecProfile == null;
codecProfile = codecProfile || {};
currentSubProfile = codecProfile;
const popup = $('#codecProfilePopup', page);
@ -555,11 +555,11 @@ import 'listViewStyle';
html += '<a is="emby-linkbutton" href="#" class="lnkEditSubProfile" data-profileindex="' + i + '">';
html += '<p>' + globalize.translate('ValueContainer', profile.Container || allText) + '</p>';
if ('Video' == profile.Type) {
if (profile.Type == 'Video') {
html += '<p>' + globalize.translate('ValueVideoCodec', profile.VideoCodec || allText) + '</p>';
html += '<p>' + globalize.translate('ValueAudioCodec', profile.AudioCodec || allText) + '</p>';
} else {
if ('Audio' == profile.Type) {
if (profile.Type == 'Audio') {
html += '<p>' + globalize.translate('ValueCodec', profile.AudioCodec || allText) + '</p>';
}
}
@ -595,7 +595,7 @@ import 'listViewStyle';
}
function editResponseProfile(page, responseProfile) {
isSubProfileNew = null == responseProfile;
isSubProfileNew = responseProfile == null;
responseProfile = responseProfile || {};
currentSubProfile = responseProfile;
const popup = $('#responseProfilePopup', page);
@ -696,33 +696,33 @@ import 'listViewStyle';
let currentProfile;
let currentSubProfile;
let isSubProfileNew;
const allText = globalize.translate('LabelAll');
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 = 'A' == this.tagName ? this.getAttribute('data-value') : this.value;
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 ('Video' == this.value) {
if (this.value == 'Video') {
$('#fldDirectPlayVideoCodec', page).show();
} else {
$('#fldDirectPlayVideoCodec', page).hide();
}
if ('Photo' == this.value) {
if (this.value == 'Photo') {
$('#fldDirectPlayAudioCodec', page).hide();
} else {
$('#fldDirectPlayAudioCodec', page).show();
}
});
$('#selectTranscodingProfileType', page).on('change', function () {
if ('Video' == this.value) {
if (this.value == 'Video') {
$('#fldTranscodingVideoCodec', page).show();
$('#fldTranscodingProtocol', page).show();
$('#fldEnableMpegtsM2TsMode', page).show();
@ -732,7 +732,7 @@ import 'listViewStyle';
$('#fldEnableMpegtsM2TsMode', page).hide();
}
if ('Photo' == this.value) {
if (this.value == 'Photo') {
$('#fldTranscodingAudioCodec', page).hide();
$('#fldEstimateContentLength', page).hide();
$('#fldReportByteRangeRequests', page).hide();
@ -743,13 +743,13 @@ import 'listViewStyle';
}
});
$('#selectResponseProfileType', page).on('change', function () {
if ('Video' == this.value) {
if (this.value == 'Video') {
$('#fldResponseProfileVideoCodec', page).show();
} else {
$('#fldResponseProfileVideoCodec', page).hide();
}
if ('Photo' == this.value) {
if (this.value == 'Photo') {
$('#fldResponseProfileAudioCodec', page).hide();
} else {
$('#fldResponseProfileAudioCodec', page).show();

View file

@ -0,0 +1,32 @@
<div id="dlnaProfilesPage" data-role="page" class="page type-interior dlnaPage withTabs">
<div>
<div class="content-primary">
<div class="readOnlyContent">
<div class="verticalSection verticalSection-extrabottompadding">
<div class="sectionTitleContainer flex align-items-center">
<h2 class="sectionTitle">${HeaderCustomDlnaProfiles}</h2>
<a is="emby-linkbutton" href="dlnaprofile.html" class="fab submit" style="margin:0 0 0 1em">
<span class="material-icons add"></span>
</a>
</div>
<p>${CustomDlnaProfilesHelp}</p>
<div class="customProfiles"></div>
</div>
<div class="verticalSection">
<div class="sectionTitleContainer flex align-items-center">
<h2 class="sectionTitle">${HeaderSystemDlnaProfiles}</h2>
</div>
<p>${SystemDlnaProfilesHelp}</p>
<div class="systemProfiles"></div>
</div>
</div>
</div>
</div>
</div>

View file

@ -18,13 +18,13 @@ import 'emby-button';
function renderUserProfiles(page, profiles) {
renderProfiles(page, page.querySelector('.customProfiles'), profiles.filter(function (p) {
return 'User' == p.Type;
return p.Type == 'User';
}));
}
function renderSystemProfiles(page, profiles) {
renderProfiles(page, page.querySelector('.systemProfiles'), profiles.filter(function (p) {
return 'System' == p.Type;
return p.Type == 'System';
}));
}
@ -36,7 +36,7 @@ import 'emby-button';
}
for (let i = 0, length = profiles.length; i < length; i++) {
let profile = profiles[i];
const profile = profiles[i];
html += '<div class="listItem listItem-border">';
html += '<span class="listItemIcon material-icons live_tv"></span>';
html += '<div class="listItemBody two-line">';
@ -45,8 +45,8 @@ import 'emby-button';
html += '</a>';
html += '</div>';
if ('User' == profile.Type) {
html += '<button type="button" is="paper-icon-button-light" class="btnDeleteProfile" data-profileid="' + profile.Id + '" title="' + globalize.translate('ButtonDelete') + '"><span class="material-icons delete"></span></button>';
if (profile.Type == 'User') {
html += '<button type="button" is="paper-icon-button-light" class="btnDeleteProfile" data-profileid="' + profile.Id + '" title="' + globalize.translate('Delete') + '"><span class="material-icons delete"></span></button>';
}
html += '</div>';

View file

@ -0,0 +1,69 @@
<div id="dlnaSettingsPage" data-role="page" class="page type-interior withTabs">
<div>
<div class="content-primary">
<form class="dlnaSettingsForm">
<div class="verticalSection">
<div class="sectionTitleContainer flex align-items-center">
<h2 class="sectionTitle">${TabSettings}</h2>
<a is="emby-linkbutton" rel="noopener noreferrer" class="raised button-alt headerHelpButton" target="_blank" href="https://docs.jellyfin.org/general/networking/dlna.html">${Help}</a>
</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input type="checkbox" is="emby-checkbox" id="chkEnablePlayTo" />
<span>${LabelEnableDlnaPlayTo}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${LabelEnableDlnaPlayToHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input type="checkbox" is="emby-checkbox" id="chkEnableDlnaDebugLogging" />
<span>${LabelEnableDlnaDebugLogging}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${LabelEnableDlnaDebugLoggingHelp}</div>
</div>
<div class="inputContainer">
<input is="emby-input" type="number" id="txtClientDiscoveryInterval" min="1" max="300" label="${LabelEnableDlnaClientDiscoveryInterval}" />
<div class="fieldDescription">${LabelEnableDlnaClientDiscoveryIntervalHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input type="checkbox" is="emby-checkbox" id="chkEnableServer" />
<span>${LabelEnableDlnaServer}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${LabelEnableDlnaServerHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input type="checkbox" is="emby-checkbox" id="chkBlastAliveMessages" />
<span>${LabelEnableBlastAliveMessages}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${LabelEnableBlastAliveMessagesHelp}</div>
</div>
<div class="inputContainer">
<input is="emby-input" type="number" id="txtBlastInterval" min="1" max="3600" label="${LabelBlastMessageInterval}" />
<div class="fieldDescription">${LabelBlastMessageIntervalHelp}</div>
</div>
<div class="selectContainer">
<select is="emby-select" id="selectUser" data-mini="true" label="${LabelDefaultUser}"></select>
<div class="fieldDescription">${LabelDefaultUserHelp}</div>
</div>
<div>
<button is="emby-button" type="submit" class="raised button-submit block">
<span>${Save}</span>
</button>
</div>
</form>
</div>
</div>
</div>

View file

@ -0,0 +1,201 @@
<div id="encodingSettingsPage" data-role="page" class="page type-interior playbackConfigurationPage withTabs">
<div>
<div class="content-primary">
<form class="encodingSettingsForm">
<div class="verticalSection">
<div class="sectionTitleContainer flex align-items-center">
<h2 class="sectionTitle">${Transcoding}</h2>
<a is="emby-linkbutton" rel="noopener noreferrer" class="raised button-alt headerHelpButton" target="_blank" href="https://docs.jellyfin.org/general/server/transcoding.html">${Help}</a>
</div>
</div>
<div class="selectContainer">
<select is="emby-select" id="selectVideoDecoder" label="${LabelHardwareAccelerationType}">
<option value="">${OptionNone}</option>
<option value="amf">AMD AMF</option>
<option value="qsv">Intel Quick Sync</option>
<option value="mediacodec">MediaCodec Android</option>
<option value="omx">OpenMAX OMX</option>
<option value="nvenc">Nvidia NVENC</option>
<option value="vaapi">Video Acceleration API (VAAPI)</option>
<option value="h264_v4l2m2m">Exynos V4L2 MFC</option>
<option value="videotoolbox">Video ToolBox</option>
</select>
<div class="fieldDescription">
<a is="emby-linkbutton" rel="noopener noreferrer" class="button-link" href="https://docs.jellyfin.org/general/administration/hardware-acceleration.html" target="_blank">${LabelHardwareAccelerationTypeHelp}</a>
</div>
</div>
<div class="inputContainer hide fldVaapiDevice">
<input is="emby-input" type="text" id="txtVaapiDevice" label="${LabelVaapiDevice}" />
<div class="fieldDescription">${LabelVaapiDeviceHelp}</div>
</div>
<div class="hardwareAccelerationOptions hide">
<div class="checkboxListContainer decodingCodecsList">
<h3 class="checkboxListLabel">${LabelEnableHardwareDecodingFor}</h3>
<div class="checkboxList">
<label>
<input type="checkbox" is="emby-checkbox" class="chkDecodeCodec" data-codec="h264" data-types="amf,qsv,nvenc,vaapi,omx,mediacodec,videotoolbox" />
<span>H264</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="chkDecodeCodec" data-codec="hevc" data-types="amf,qsv,nvenc,vaapi,mediacodec,videotoolbox" />
<span>HEVC</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="chkDecodeCodec" data-codec="mpeg2video" data-types="amf,qsv,nvenc,vaapi,omx,mediacodec,videotoolbox" />
<span>MPEG2</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="chkDecodeCodec" data-codec="mpeg4" data-types="amf,nvenc,omx,mediacodec,videotoolbox" />
<span>MPEG4</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="chkDecodeCodec" data-codec="vc1" data-types="amf,qsv,nvenc,vaapi,omx,videotoolbox" />
<span>VC1</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="chkDecodeCodec" data-codec="vp8" data-types="qsv,nvenc,vaapi,mediacodec,videotoolbox" />
<span>VP8</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="chkDecodeCodec" data-codec="vp9" data-types="amf,qsv,nvenc,vaapi,mediacodec,videotoolbox" />
<span>VP9</span>
</label>
</div>
</div>
<div class="checkboxListContainer">
<label>
<input type="checkbox" is="emby-checkbox" id="chkDecodingColorDepth10Hevc" />
<span>${EnableDecodingColorDepth10Hevc}</span>
</label>
</div>
<div class="checkboxListContainer">
<label>
<input type="checkbox" is="emby-checkbox" id="chkDecodingColorDepth10Vp9" />
<span>${EnableDecodingColorDepth10Vp9}</span>
</label>
</div>
<div class="checkboxListContainer">
<div class="checkboxList">
<label>
<input type="checkbox" is="emby-checkbox" id="chkHardwareEncoding" />
<span>${EnableHardwareEncoding}</span>
</label>
</div>
</div>
</div>
<div class="selectContainer">
<select is="emby-select" id="selectThreadCount" label="${LabelTranscodingThreadCount}">
<option value="-1">${OptionAuto}</option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
<option value="7">7</option>
<option value="8">8</option>
<option value="0">${OptionMax}</option>
</select>
<div class="fieldDescription">${LabelTranscodingThreadCountHelp}</div>
</div>
<div class="inputContainer fldEncoderPath">
<div style="display: flex; align-items: center;">
<div style="flex-grow:1;">
<input is="emby-input" class="txtEncoderPath" label="${LabelffmpegPath}" autocomplete="off" />
</div>
<button type="button" is="paper-icon-button-light" id="btnSelectEncoderPath" class="emby-input-iconbutton"><span class="material-icons search"></span></button>
</div>
<div class="fieldDescription">
<div>${LabelffmpegPathHelp}</div>
</div>
</div>
<div class="inputContainer">
<div style="display: flex; align-items: center;">
<div style="flex-grow:1;">
<input is="emby-input" id="txtTranscodingTempPath" label="${LabelTranscodePath}" autocomplete="off" />
</div>
<button type="button" is="paper-icon-button-light" id="btnSelectTranscodingTempPath" class="emby-input-iconbutton"><span class="material-icons search"></span></button>
</div>
<div class="fieldDescription">${LabelTranscodingTempPathHelp}</div>
</div>
<div class="inputContainer">
<div style="display: flex; align-items: center;">
<div style="flex-grow:1;">
<input is="emby-input" id="txtFallbackFontPath" label="${LabelFallbackFontPath}" autocomplete="off" />
</div>
<button type="button" is="paper-icon-button-light" id="btnSelectFallbackFontPath" class="emby-input-iconbutton"><span class="material-icons search"></span></button>
</div>
<div class="fieldDescription">${LabelFallbackFontPathHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="chkEnableFallbackFont" />
<span>${EnableFallbackFont}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${EnableFallbackFontHelp}</div>
</div>
<div class="inputContainer">
<input is="emby-input" type="number" id="txtDownMixAudioBoost" pattern="[0-9]*" required="required" min=".5" max="3" step=".1" label="${LabelDownMixAudioScale}" />
<div class="fieldDescription">${LabelDownMixAudioScaleHelp}</div>
</div>
<div class="selectContainer">
<select is="emby-select" id="selectEncoderPreset" label="${LabelEncoderPreset}">
<option value="">${OptionAuto}</option>
<option value="veryslow">veryslow</option>
<option value="slower">slower</option>
<option value="slow">slow</option>
<option value="medium">medium</option>
<option value="fast">fast</option>
<option value="faster">faster</option>
<option value="veryfast">veryfast</option>
<option value="superfast">superfast</option>
<option value="ultrafast">ultrafast</option>
</select>
<div class="fieldDescription">${EncoderPresetHelp}</div>
</div>
<div class="inputContainer">
<input is="emby-input" type="number" id="txtH264Crf" pattern="[0-9]*" min="0" max="51" step="1" label="${LabelH264Crf}" />
<div class="fieldDescription">${H264CrfHelp}</div>
</div>
<div class="selectContainer">
<select is="emby-select" id="selectDeinterlaceMethod" label="${LabelDeinterlaceMethod}">
<option value="yadif">${Yadif}</option>
<option value="yadif_bob">${YadifBob}</option>
</select>
<div class="fieldDescription">${DeinterlaceMethodHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="chkEnableSubtitleExtraction" />
<span>${AllowOnTheFlySubtitleExtraction}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${AllowOnTheFlySubtitleExtractionHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="chkEnableThrottling" />
<span>${AllowFfmpegThrottling}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${AllowFfmpegThrottlingHelp}</div>
</div>
<div>
<button is="emby-button" type="submit" class="raised button-submit block">
<span>${Save}</span>
</button>
</div>
</form>
</div>
</div>
</div>

View file

@ -8,7 +8,7 @@ import libraryMenu from 'libraryMenu';
function loadPage(page, config, systemInfo) {
Array.prototype.forEach.call(page.querySelectorAll('.chkDecodeCodec'), function (c) {
c.checked = -1 !== (config.HardwareDecodingCodecs || []).indexOf(c.getAttribute('data-codec'));
c.checked = (config.HardwareDecodingCodecs || []).indexOf(c.getAttribute('data-codec')) !== -1;
});
page.querySelector('#chkDecodingColorDepth10Hevc').checked = config.EnableDecodingColorDepth10Hevc;
page.querySelector('#chkDecodingColorDepth10Vp9').checked = config.EnableDecodingColorDepth10Vp9;
@ -47,10 +47,10 @@ import libraryMenu from 'libraryMenu';
return ApiClient.ajax({
url: ApiClient.getUrl('System/MediaEncoder/Path'),
type: 'POST',
data: {
data: JSON.stringify({
Path: form.querySelector('.txtEncoderPath').value,
PathType: 'Custom'
}
})
}).then(Dashboard.processServerConfigurationUpdateResult, onSaveEncodingPathFailure);
});
}
@ -85,7 +85,7 @@ import libraryMenu from 'libraryMenu';
updateEncoder(form);
}, function () {
import('alert').then(({default: alert}) => {
alert(globalize.translate('DefaultErrorMessage'));
alert(globalize.translate('ErrorDefault'));
});
Dashboard.processServerConfigurationUpdateResult();
@ -111,7 +111,7 @@ import libraryMenu from 'libraryMenu';
value = value || '';
let any;
Array.prototype.forEach.call(context.querySelectorAll('.chkDecodeCodec'), function (c) {
if (-1 === c.getAttribute('data-types').split(',').indexOf(value)) {
if (c.getAttribute('data-types').split(',').indexOf(value) === -1) {
dom.parentWithTag(c, 'LABEL').classList.add('hide');
} else {
dom.parentWithTag(c, 'LABEL').classList.remove('hide');
@ -142,7 +142,7 @@ import libraryMenu from 'libraryMenu';
$(document).on('pageinit', '#encodingSettingsPage', function () {
const page = this;
page.querySelector('#selectVideoDecoder').addEventListener('change', function () {
if ('vaapi' == this.value) {
if (this.value == 'vaapi') {
page.querySelector('.fldVaapiDevice').classList.remove('hide');
page.querySelector('#txtVaapiDevice').setAttribute('required', 'required');
} else {

View file

@ -0,0 +1,72 @@
<div id="dashboardGeneralPage" data-role="page" class="page type-interior dashboardHomePage">
<div>
<div class="content-primary">
<form class="dashboardGeneralForm">
<div class="verticalSection">
<div class="sectionTitleContainer flex align-items-center">
<h2 class="sectionTitle">${TabSettings}</h2>
<a is="emby-linkbutton" rel="noopener noreferrer" class="raised button-alt headerHelpButton" target="_blank" href="https://docs.jellyfin.org/general/server/settings.html">${Help}</a>
</div>
</div>
<div class="verticalSection">
<div class="inputContainer">
<input is="emby-input" type="text" id="txtServerName" label="${LabelServerName}" />
<div class="fieldDescription">${LabelServerNameHelp}</div>
</div>
<div class="selectContainer">
<select is="emby-select" id="selectLocalizationLanguage" label="${LabelPreferredDisplayLanguage}"></select>
<div class="fieldDescription">
<div>${LabelPreferredDisplayLanguageHelp}</div>
<div style="margin-top: .25em;">
<a is="emby-linkbutton" rel="noopener noreferrer" class="button-link" href="https://docs.jellyfin.org/general/contributing/index.html" target="_blank">${LearnHowYouCanContribute}</a>
</div>
</div>
</div>
</div>
<div class="verticalSection verticalSection-extrabottompadding">
<h2>${HeaderPaths}</h2>
<div class="inputContainer">
<div style="display: flex; align-items: center;">
<div style="flex-grow:1;">
<input is="emby-input" id="txtCachePath" label="${LabelCachePath}" autocomplete="off" />
</div>
<button type="button" is="paper-icon-button-light" id="btnSelectCachePath" title="${ButtonSelectDirectory}" class="emby-input-iconbutton"><span class="material-icons search"></span></button>
</div>
<div class="fieldDescription">${LabelCachePathHelp}</div>
</div>
<div class="inputContainer">
<div style="display: flex; align-items: center;">
<div style="flex-grow:1;">
<input is="emby-input" id="txtMetadataPath" label="${LabelMetadataPath}" autocomplete="off" />
</div>
<button type="button" is="paper-icon-button-light" id="btnSelectMetadataPath" title="${ButtonSelectDirectory}" class="emby-input-iconbutton"><span class="material-icons search"></span></button>
</div>
<div class="fieldDescription">${LabelMetadataPathHelp}</div>
<input type="hidden" id="txtMetadataNetworkPath" />
</div>
</div>
<div class="verticalSection">
<h2>${HeaderBranding}</h2>
<div class="inputContainer">
<input is="emby-input" type="text" id="txtLoginDisclaimer" label="${LabelLoginDisclaimer}" />
<div class="fieldDescription">${LabelLoginDisclaimerHelp}</div>
</div>
<div class="inputContainer customCssContainer">
<textarea is="emby-textarea" id="txtCustomCss" label="${LabelCustomCss}" class="textarea-mono"></textarea>
<div class="fieldDescription">${LabelCustomCssHelp}</div>
</div>
</div>
<br />
<div>
<button is="emby-button" type="submit" class="raised button-submit block">
<span>${Save}</span>
</button>
</div>
</form>
</div>
</div>
</div>

View file

@ -52,7 +52,7 @@ import 'emby-button';
});
}, function () {
import('alert').then(({default: alert}) => {
alert(globalize.translate('DefaultErrorMessage'));
alert(globalize.translate('ErrorDefault'));
});
Dashboard.processServerConfigurationUpdateResult();

View file

@ -0,0 +1,15 @@
<div id="mediaLibraryPage" data-role="page" class="page type-interior mediaLibraryPage librarySectionPage withTabs fullWidthContent">
<div>
<div class="content-primary">
<div class="padded-top padded-bottom">
<button is="emby-button" type="button" class="raised btnRefresh">
<span>${ButtonScanAllLibraries}</span>
</button>
<progress max="100" min="0" style="display: inline-block; vertical-align: middle;" class="refreshProgress"></progress>
<a is="emby-linkbutton" rel="noopener noreferrer" class="raised button-alt" target="_blank" href="https://docs.jellyfin.org/general/server/libraries.html">${Help}</a>
</div>
<div id="divVirtualFolders"></div>
</div>
</div>
</div>

View file

@ -156,7 +156,7 @@ import 'emby-itemrefreshindicator';
}
function shouldRefreshLibraryAfterChanges(page) {
return 'mediaLibraryPage' === page.id;
return page.id === 'mediaLibraryPage';
}
function reloadVirtualFolders(page, virtualFolders) {
@ -286,7 +286,7 @@ import 'emby-itemrefreshindicator';
if (hasCardImageContainer) {
html += '<div class="cardIndicators backdropCardIndicators">';
html += '<div is="emby-itemrefreshindicator"' + (virtualFolder.RefreshProgress || virtualFolder.RefreshStatus && 'Idle' !== virtualFolder.RefreshStatus ? '' : ' class="hide"') + ' data-progress="' + (virtualFolder.RefreshProgress || 0) + '" data-status="' + virtualFolder.RefreshStatus + '"></div>';
html += '<div is="emby-itemrefreshindicator"' + (virtualFolder.RefreshProgress || virtualFolder.RefreshStatus && virtualFolder.RefreshStatus !== 'Idle' ? '' : ' class="hide"') + ' data-progress="' + (virtualFolder.RefreshProgress || 0) + '" data-status="' + virtualFolder.RefreshStatus + '"></div>';
html += '</div>';
html += '</div>';
}
@ -363,10 +363,10 @@ import 'emby-itemrefreshindicator';
name: globalize.translate('HeaderLibraries')
}, {
href: 'librarydisplay.html',
name: globalize.translate('TabDisplay')
name: globalize.translate('Display')
}, {
href: 'metadataimages.html',
name: globalize.translate('TabMetadata')
name: globalize.translate('Metadata')
}, {
href: 'metadatanfo.html',
name: globalize.translate('TabNfoSettings')

View file

@ -0,0 +1,57 @@
<div id="libraryDisplayPage" data-role="page" class="page type-interior librarySectionPage withTabs">
<div>
<div class="content-primary">
<form>
<div class="selectContainer">
<select is="emby-select" id="selectDateAdded" data-mini="true" label="${LabelDateAddedBehavior}">
<option value="0">${OptionDateAddedImportTime}</option>
<option value="1">${OptionDateAddedFileTime}</option>
</select>
<div class="fieldDescription">${LabelDateAddedBehaviorHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input type="checkbox" is="emby-checkbox" class="chkFolderView" />
<span>${OptionDisplayFolderView}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${OptionDisplayFolderViewHelp}</div>
</div>
<label class="checkboxContainer">
<input type="checkbox" is="emby-checkbox" class="chkDisplaySpecialsWithinSeasons"/>
<span>${LabelDisplaySpecialsWithinSeasons}</span>
</label>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input type="checkbox" is="emby-checkbox" class="chkGroupMoviesIntoCollections" />
<span>${LabelGroupMoviesIntoCollections}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${LabelGroupMoviesIntoCollectionsHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input class="chkExternalContentInSuggestions" type="checkbox" is="emby-checkbox" />
<span>${OptionEnableExternalContentInSuggestions}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${OptionEnableExternalContentInSuggestionsHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription fldSaveMetadataHidden hide">
<label>
<input type="checkbox" is="emby-checkbox" class="chkAirDays" id="chkSaveMetadataHidden" data-filter="Sunday" />
<span>${OptionSaveMetadataAsHidden}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${OptionSaveMetadataAsHiddenHelp}</div>
</div>
<br/>
<button is="emby-button" type="submit" class="raised button-submit block">
<span>${Save}</span>
</button>
</form>
</div>
</div>
</div>

View file

@ -12,10 +12,10 @@ import 'emby-button';
name: globalize.translate('HeaderLibraries')
}, {
href: 'librarydisplay.html',
name: globalize.translate('TabDisplay')
name: globalize.translate('Display')
}, {
href: 'metadataimages.html',
name: globalize.translate('TabMetadata')
name: globalize.translate('Metadata')
}, {
href: 'metadatanfo.html',
name: globalize.translate('TabNfoSettings')
@ -48,7 +48,7 @@ import 'emby-button';
ApiClient.updateServerConfiguration(config).then(Dashboard.processServerConfigurationUpdateResult);
});
ApiClient.getNamedConfiguration('metadata').then(function(config) {
config.UseFileCreationTimeForDateAdded = '1' === $('#selectDateAdded', form).val();
config.UseFileCreationTimeForDateAdded = $('#selectDateAdded', form).val() === '1';
ApiClient.updateNamedConfiguration('metadata', config);
});
@ -61,7 +61,7 @@ import 'emby-button';
libraryMenu.setTabs('librarysetup', 1, getTabs);
loadData();
ApiClient.getSystemInfo().then(function(info) {
if ('Windows' === info.OperatingSystem) {
if (info.OperatingSystem === 'Windows') {
view.querySelector('.fldSaveMetadataHidden').classList.remove('hide');
} else {
view.querySelector('.fldSaveMetadataHidden').classList.add('hide');

View file

@ -0,0 +1,8 @@
<div id="logPage" data-role="page" class="page type-interior">
<div>
<div class="content-primary">
<div class="serverLogs readOnlyContent">
</div>
</div>
</div>
</div>

View file

@ -55,10 +55,10 @@ import 'listViewStyle';
name: globalize.translate('HeaderLibraries')
}, {
href: 'librarydisplay.html',
name: globalize.translate('TabDisplay')
name: globalize.translate('Display')
}, {
href: 'metadataimages.html',
name: globalize.translate('TabMetadata')
name: globalize.translate('Metadata')
}, {
href: 'metadatanfo.html',
name: globalize.translate('TabNfoSettings')

View file

@ -0,0 +1,25 @@
<div id="metadataImagesConfigurationPage" data-role="page" class="page type-interior metadataConfigurationPage withTabs">
<div>
<div class="content-primary">
<form class="metadataImagesConfigurationForm">
<h2 style="margin-top:0;">${HeaderPreferredMetadataLanguage}</h2>
<p style="margin:1.5em 0;">${DefaultMetadataLangaugeDescription}</p>
<div class="selectContainer">
<select is="emby-select" id="selectLanguage" required="required" label="${LabelLanguage}"></select>
</div>
<div class="selectContainer">
<select is="emby-select" id="selectCountry" required="required" label="${LabelCountry}"></select>
</div>
<br />
<div><button is="emby-button" type="submit" class="raised button-submit block"><span>${Save}</span></button></div>
</form>
</div>
</div>
</div>

View file

@ -0,0 +1,49 @@
<div id="metadataNfoPage" data-role="page" class="page type-interior metadataConfigurationPage withTabs">
<div>
<div class="content-primary">
<form class="metadataNfoForm">
<p>${HeaderKodiMetadataHelp}</p>
<br />
<div class="selectContainer">
<select is="emby-select" name="selectUser" id="selectUser" label="${LabelKodiMetadataUser}"></select>
<div class="fieldDescription">${LabelKodiMetadataUserHelp}</div>
</div>
<div class="selectContainer">
<select is="emby-select" name="selectReleaseDateFormat" id="selectReleaseDateFormat" label="${LabelKodiMetadataDateFormat}">
<option value="yyyy-MM-dd">yyyy-MM-dd</option>
</select>
<div class="fieldDescription">${LabelKodiMetadataDateFormatHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input type="checkbox" is="emby-checkbox" id="chkSaveImagePaths" />
<span>${LabelKodiMetadataSaveImagePaths}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${LabelKodiMetadataSaveImagePathsHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input type="checkbox" is="emby-checkbox" id="chkEnablePathSubstitution" />
<span>${LabelKodiMetadataEnablePathSubstitution}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">
<div>${LabelKodiMetadataEnablePathSubstitutionHelp}</div>
</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input type="checkbox" is="emby-checkbox" id="chkEnableExtraThumbs" />
<span>${LabelKodiMetadataEnableExtraThumbs}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${LabelKodiMetadataEnableExtraThumbsHelp}</div>
</div>
<div><button is="emby-button" type="submit" class="raised button-submit block"><span>${Save}</span></button></div>
</form>
</div>
</div>
</div>

View file

@ -52,10 +52,10 @@ import globalize from 'globalize';
name: globalize.translate('HeaderLibraries')
}, {
href: 'librarydisplay.html',
name: globalize.translate('TabDisplay')
name: globalize.translate('Display')
}, {
href: 'metadataimages.html',
name: globalize.translate('TabMetadata')
name: globalize.translate('Metadata')
}, {
href: 'metadatanfo.html',
name: globalize.translate('TabNfoSettings')

View file

@ -0,0 +1,121 @@
<div id="networkingPage" data-role="page" class="page type-interior advancedConfigurationPage">
<div>
<div class="content-primary">
<form class="dashboardHostingForm">
<div class="verticalSection verticalSection-extrabottompadding">
<div class="sectionTitleContainer flex align-items-center">
<h2 class="sectionTitle">${TabNetworking}</h2>
<a is="emby-linkbutton" rel="noopener noreferrer" class="raised button-alt headerHelpButton" target="_blank" href="https://docs.jellyfin.org/general/networking/index.html">${Help}</a>
</div>
<fieldset class='verticalSection verticalSection-extrabottompadding'>
<legend><h3>${HeaderServerAddressSettings}</h3></legend>
<div class="inputContainer">
<input is="emby-input" type="number" id="txtPortNumber" label="${LabelLocalHttpServerPortNumber}" pattern="[0-9]*" required="required" min="1" max="65535" />
<div class="fieldDescription">${LabelLocalHttpServerPortNumberHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input type="checkbox" is="emby-checkbox" id="chkEnableHttps" />
<span>${LabelEnableHttps}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${LabelEnableHttpsHelp}</div>
</div>
<div class="inputContainer">
<input is="emby-input" type="number" id="txtHttpsPort" pattern="[0-9]*" required="required" min="1" max="65535" label="${LabelHttpsPort}" />
<div class="fieldDescription">${LabelHttpsPortHelp}</div>
</div>
<div class="inputContainer fldBaseUrl">
<input is="emby-input" id="txtBaseUrl" type="text" label="${LabelBaseUrl}" />
<div class="fieldDescription">${LabelBaseUrlHelp}</div>
</div>
<div class="inputContainer">
<input is="emby-input" type="text" id="txtLocalAddress" label="${LabelBindToLocalNetworkAddress}" />
<div class="fieldDescription">${LabelBindToLocalNetworkAddressHelp}</div>
</div>
<div class="inputContainer">
<input is="emby-input" type="text" id="txtLanNetworks" label="${LabelLanNetworks}" />
<div class="fieldDescription">${LanNetworksHelp}</div>
</div>
</fieldset>
<fieldset class='verticalSection verticalSection-extrabottompadding'>
<legend><h3>${HeaderHttpsSettings}</h3></legend>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input type="checkbox" is="emby-checkbox" id="chkRequireHttps" />
<span>${LabelRequireHttps}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${LabelRequireHttpsHelp}</div>
</div>
<div class="inputContainer fldCertificatePath">
<div style="display: flex; align-items: center;">
<div style="flex-grow:1;">
<input is="emby-input" type="text" id="txtCertificatePath" label="${LabelCustomCertificatePath}" autocomplete="off" />
</div>
<button type="button" is="paper-icon-button-light" id="btnSelectCertPath" title="${ButtonSelectDirectory}" class="emby-input-iconbutton"><span class="material-icons search"></span></button>
</div>
<div class="fieldDescription">${LabelCustomCertificatePathHelp}</div>
</div>
<div class="inputContainer fldCertPassword">
<input is="emby-input" id="txtCertPassword" type="password" label="${LabelCertificatePassword}" autocomplete="new-password" />
<div class="fieldDescription">${LabelCertificatePasswordHelp}</div>
</div>
</fieldset>
<fieldset class='verticalSection verticalSection-extrabottompadding'>
<legend><h3>${HeaderRemoteAccessSettings}</h3></legend>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input type="checkbox" is="emby-checkbox" id="chkRemoteAccess" />
<span>${AllowRemoteAccess}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${AllowRemoteAccessHelp}</div>
</div>
<div class="inputContainer fldExternalAddressFilter hide">
<input is="emby-input" type="text" id="txtExternalAddressFilter" label="${LabelAllowedRemoteAddresses}" />
<div class="fieldDescription">${AllowedRemoteAddressesHelp}</div>
</div>
<div class="selectContainer fldExternalAddressFilterMode hide">
<select is="emby-select" id="selectExternalAddressFilterMode" label="${LabelAllowedRemoteAddressesMode}">
<option value="whitelist">${Whitelist}</option>
<option value="blacklist">${Blacklist}</option>
</select>
</div>
<div class="checkboxContainer checkboxContainer-withDescription fldEnableUpnp hide">
<label>
<input type="checkbox" is="emby-checkbox" id="chkEnableUpnp" />
<span>${LabelEnableAutomaticPortMap}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${LabelEnableAutomaticPortMapHelp}</div>
</div>
<div class="inputContainer fldPublicPort hide">
<input is="emby-input" type="number" label="${LabelPublicHttpPort}" id="txtPublicPort" pattern="[0-9]*" required="required" min="1" max="65535" />
<div class="fieldDescription">${LabelPublicHttpPortHelp}</div>
</div>
<div class="inputContainer fldPublicHttpsPort hide">
<input is="emby-input" type="number" id="txtPublicHttpsPort" pattern="[0-9]*" required="required" min="1" max="65535" label="${LabelPublicHttpsPort}" />
<div class="fieldDescription">${LabelPublicHttpsPortHelp}</div>
</div>
</fieldset>
</div>
<div>
<button is="emby-button" type="submit" class="raised button-submit block">
<span>${Save}</span>
</button>
</div>
</form>
</div>
</div>
</div>

View file

@ -30,7 +30,7 @@ import 'emby-select';
}).filter(function (s) {
return s.length > 0;
});
config.IsRemoteIPFilterBlacklist = 'blacklist' === form.querySelector('#selectExternalAddressFilterMode').value;
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;
@ -110,7 +110,7 @@ import 'emby-select';
page.querySelector('#txtLanNetworks').value = (config.LocalNetworkSubnets || []).join(', ');
page.querySelector('#txtExternalAddressFilter').value = (config.RemoteIPFilter || []).join(', ');
page.querySelector('#selectExternalAddressFilterMode').value = config.IsRemoteIPFilterBlacklist ? 'blacklist' : 'whitelist';
page.querySelector('#chkRemoteAccess').checked = null == config.EnableRemoteAccess || config.EnableRemoteAccess;
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;

View file

@ -54,7 +54,7 @@
<div>
<br />
<button is="emby-button" type="submit" class="raised button-submit block">
<span>${ButtonSave}</span>
<span>${Save}</span>
</button>
<button is="emby-button" type="button" class="raised button-cancel block btnCancel" onclick="history.back();">

View file

@ -1,119 +1,118 @@
define(['jQuery', 'emby-checkbox'], function ($) {
'use strict';
import $ from 'jQuery';
import 'emby-checkbox';
function fillItems(elem, items, cssClass, idPrefix, currentList, isEnabledList) {
var html = '<div class="checkboxList paperList" style="padding: .5em 1em;">';
html += items.map(function (u) {
var isChecked = isEnabledList ? currentList.indexOf(u.Id) != -1 : currentList.indexOf(u.Id) == -1;
var checkedHtml = isChecked ? ' checked="checked"' : '';
return '<label><input is="emby-checkbox" class="' + cssClass + '" type="checkbox" data-itemid="' + u.Id + '"' + checkedHtml + '/><span>' + u.Name + '</span></label>';
}).join('');
html += '</div>';
elem.html(html).trigger('create');
}
function fillItems(elem, items, cssClass, idPrefix, currentList, isEnabledList) {
let html = '<div class="checkboxList paperList" style="padding: .5em 1em;">';
html += items.map(function (u) {
const isChecked = isEnabledList ? currentList.indexOf(u.Id) != -1 : currentList.indexOf(u.Id) == -1;
const checkedHtml = isChecked ? ' checked="checked"' : '';
return '<label><input is="emby-checkbox" class="' + cssClass + '" type="checkbox" data-itemid="' + u.Id + '"' + checkedHtml + '/><span>' + u.Name + '</span></label>';
}).join('');
html += '</div>';
elem.html(html).trigger('create');
}
function reload(page) {
var type = getParameterByName('type');
var promise1 = ApiClient.getUsers();
var promise2 = ApiClient.getNamedConfiguration(notificationsConfigurationKey);
var promise3 = ApiClient.getJSON(ApiClient.getUrl('Notifications/Types'));
var promise4 = ApiClient.getJSON(ApiClient.getUrl('Notifications/Services'));
Promise.all([promise1, promise2, promise3, promise4]).then(function (responses) {
var users = responses[0];
var notificationOptions = responses[1];
var types = responses[2];
var services = responses[3];
var notificationConfig = notificationOptions.Options.filter(function (n) {
return n.Type == type;
})[0];
var typeInfo = types.filter(function (n) {
return n.Type == type;
})[0] || {};
function reload(page) {
const type = getParameterByName('type');
const promise1 = ApiClient.getUsers();
const promise2 = ApiClient.getNamedConfiguration(notificationsConfigurationKey);
const promise3 = ApiClient.getJSON(ApiClient.getUrl('Notifications/Types'));
const promise4 = ApiClient.getJSON(ApiClient.getUrl('Notifications/Services'));
Promise.all([promise1, promise2, promise3, promise4]).then(function (responses) {
const users = responses[0];
const notificationOptions = responses[1];
const types = responses[2];
const services = responses[3];
let notificationConfig = notificationOptions.Options.filter(function (n) {
return n.Type == type;
})[0];
const typeInfo = types.filter(function (n) {
return n.Type == type;
})[0] || {};
if (typeInfo.IsBasedOnUserEvent) {
$('.monitorUsers', page).show();
} else {
$('.monitorUsers', page).hide();
}
if (typeInfo.IsBasedOnUserEvent) {
$('.monitorUsers', page).show();
} else {
$('.monitorUsers', page).hide();
}
$('.notificationType', page).html(typeInfo.Name || 'Unknown Notification');
$('.notificationType', page).html(typeInfo.Name || 'Unknown Notification');
if (!notificationConfig) {
notificationConfig = {
DisabledMonitorUsers: [],
SendToUsers: [],
DisabledServices: [],
SendToUserMode: 'Admins'
};
}
if (!notificationConfig) {
notificationConfig = {
DisabledMonitorUsers: [],
SendToUsers: [],
DisabledServices: [],
SendToUserMode: 'Admins'
};
}
fillItems($('.monitorUsersList', page), users, 'chkMonitor', 'chkMonitor', notificationConfig.DisabledMonitorUsers);
fillItems($('.sendToUsersList', page), users, 'chkSendTo', 'chkSendTo', notificationConfig.SendToUsers, true);
fillItems($('.servicesList', page), services, 'chkService', 'chkService', notificationConfig.DisabledServices);
$('#chkEnabled', page).prop('checked', notificationConfig.Enabled || false);
$('#selectUsers', page).val(notificationConfig.SendToUserMode).trigger('change');
});
}
function save(page) {
var type = getParameterByName('type');
var promise1 = ApiClient.getNamedConfiguration(notificationsConfigurationKey);
// TODO: Check if this promise is really needed, as it's unused.
var promise2 = ApiClient.getJSON(ApiClient.getUrl('Notifications/Types'));
Promise.all([promise1, promise2]).then(function (responses) {
var notificationOptions = responses[0];
var notificationConfig = notificationOptions.Options.filter(function (n) {
return n.Type == type;
})[0];
if (!notificationConfig) {
notificationConfig = {
Type: type
};
notificationOptions.Options.push(notificationConfig);
}
notificationConfig.Enabled = $('#chkEnabled', page).is(':checked');
notificationConfig.SendToUserMode = $('#selectUsers', page).val();
notificationConfig.DisabledMonitorUsers = $('.chkMonitor', page).get().filter(function (c) {
return !c.checked;
}).map(function (c) {
return c.getAttribute('data-itemid');
});
notificationConfig.SendToUsers = $('.chkSendTo', page).get().filter(function (c) {
return c.checked;
}).map(function (c) {
return c.getAttribute('data-itemid');
});
notificationConfig.DisabledServices = $('.chkService', page).get().filter(function (c) {
return !c.checked;
}).map(function (c) {
return c.getAttribute('data-itemid');
});
ApiClient.updateNamedConfiguration(notificationsConfigurationKey, notificationOptions).then(function (r) {
Dashboard.processServerConfigurationUpdateResult();
Dashboard.navigate('notificationsettings.html');
});
});
}
function onSubmit() {
save($(this).parents('.page'));
return false;
}
var notificationsConfigurationKey = 'notifications';
$(document).on('pageinit', '#notificationSettingPage', function () {
var page = this;
$('#selectUsers', page).on('change', function () {
if ('Custom' == this.value) {
$('.selectCustomUsers', page).show();
} else {
$('.selectCustomUsers', page).hide();
}
});
$('.notificationSettingForm').off('submit', onSubmit).on('submit', onSubmit);
}).on('pageshow', '#notificationSettingPage', function () {
reload(this);
fillItems($('.monitorUsersList', page), users, 'chkMonitor', 'chkMonitor', notificationConfig.DisabledMonitorUsers);
fillItems($('.sendToUsersList', page), users, 'chkSendTo', 'chkSendTo', notificationConfig.SendToUsers, true);
fillItems($('.servicesList', page), services, 'chkService', 'chkService', notificationConfig.DisabledServices);
$('#chkEnabled', page).prop('checked', notificationConfig.Enabled || false);
$('#selectUsers', page).val(notificationConfig.SendToUserMode).trigger('change');
});
}
function save(page) {
const type = getParameterByName('type');
const promise1 = ApiClient.getNamedConfiguration(notificationsConfigurationKey);
// TODO: Check if this promise is really needed, as it's unused.
const promise2 = ApiClient.getJSON(ApiClient.getUrl('Notifications/Types'));
Promise.all([promise1, promise2]).then(function (responses) {
const notificationOptions = responses[0];
let notificationConfig = notificationOptions.Options.filter(function (n) {
return n.Type == type;
})[0];
if (!notificationConfig) {
notificationConfig = {
Type: type
};
notificationOptions.Options.push(notificationConfig);
}
notificationConfig.Enabled = $('#chkEnabled', page).is(':checked');
notificationConfig.SendToUserMode = $('#selectUsers', page).val();
notificationConfig.DisabledMonitorUsers = $('.chkMonitor', page).get().filter(function (c) {
return !c.checked;
}).map(function (c) {
return c.getAttribute('data-itemid');
});
notificationConfig.SendToUsers = $('.chkSendTo', page).get().filter(function (c) {
return c.checked;
}).map(function (c) {
return c.getAttribute('data-itemid');
});
notificationConfig.DisabledServices = $('.chkService', page).get().filter(function (c) {
return !c.checked;
}).map(function (c) {
return c.getAttribute('data-itemid');
});
ApiClient.updateNamedConfiguration(notificationsConfigurationKey, notificationOptions).then(function (r) {
Dashboard.processServerConfigurationUpdateResult();
Dashboard.navigate('notificationsettings.html');
});
});
}
function onSubmit() {
save($(this).parents('.page'));
return false;
}
const notificationsConfigurationKey = 'notifications';
$(document).on('pageinit', '#notificationSettingPage', function () {
const page = this;
$('#selectUsers', page).on('change', function () {
if (this.value == 'Custom') {
$('.selectCustomUsers', page).show();
} else {
$('.selectCustomUsers', page).hide();
}
});
$('.notificationSettingForm').off('submit', onSubmit).on('submit', onSubmit);
}).on('pageshow', '#notificationSettingPage', function () {
reload(this);
});

View file

@ -1,60 +1,61 @@
define(['loading', 'libraryMenu', 'globalize', 'listViewStyle', 'emby-button'], function(loading, libraryMenu, globalize) {
'use strict';
import loading from 'loading';
import globalize from 'globalize';
import 'listViewStyle';
import 'emby-button';
function reload(page) {
loading.show();
ApiClient.getJSON(ApiClient.getUrl('Notifications/Types')).then(function(list) {
var html = '';
var lastCategory = '';
var showHelp = true;
html += list.map(function(notification) {
var itemHtml = '';
if (notification.Category !== lastCategory) {
lastCategory = notification.Category;
if (lastCategory) {
itemHtml += '</div>';
itemHtml += '</div>';
}
itemHtml += '<div class="verticalSection verticalSection-extrabottompadding">';
itemHtml += '<div class="sectionTitleContainer" style="margin-bottom:1em;">';
itemHtml += '<h2 class="sectionTitle">';
itemHtml += notification.Category;
itemHtml += '</h2>';
if (showHelp) {
showHelp = false;
itemHtml += '<a is="emby-linkbutton" class="raised button-alt headerHelpButton" target="_blank" href="https://docs.jellyfin.org/general/server/notifications.html">';
itemHtml += globalize.translate('Help');
itemHtml += '</a>';
}
function reload(page) {
loading.show();
ApiClient.getJSON(ApiClient.getUrl('Notifications/Types')).then(function (list) {
let html = '';
let lastCategory = '';
let showHelp = true;
html += list.map(function (notification) {
let itemHtml = '';
if (notification.Category !== lastCategory) {
lastCategory = notification.Category;
if (lastCategory) {
itemHtml += '</div>';
itemHtml += '</div>';
itemHtml += '<div class="paperList">';
}
itemHtml += '<a class="listItem listItem-border" is="emby-linkbutton" data-ripple="false" href="notificationsetting.html?type=' + notification.Type + '">';
if (notification.Enabled) {
itemHtml += '<span class="listItemIcon material-icons notifications_active"></span>';
} else {
itemHtml += '<span class="listItemIcon material-icons notifications_off" style="background-color:#999;"></span>';
itemHtml += '<div class="verticalSection verticalSection-extrabottompadding">';
itemHtml += '<div class="sectionTitleContainer" style="margin-bottom:1em;">';
itemHtml += '<h2 class="sectionTitle">';
itemHtml += notification.Category;
itemHtml += '</h2>';
if (showHelp) {
showHelp = false;
itemHtml += '<a is="emby-linkbutton" class="raised button-alt headerHelpButton" target="_blank" href="https://docs.jellyfin.org/general/server/notifications.html">';
itemHtml += globalize.translate('Help');
itemHtml += '</a>';
}
itemHtml += '<div class="listItemBody">';
itemHtml += '<div class="listItemBodyText">' + notification.Name + '</div>';
itemHtml += '</div>';
itemHtml += '<button type="button" is="paper-icon-button-light"><span class="material-icons mode_edit"></span></button>';
itemHtml += '</a>';
return itemHtml;
}).join('');
if (list.length) {
html += '</div>';
html += '</div>';
itemHtml += '<div class="paperList">';
}
page.querySelector('.notificationList').innerHTML = html;
loading.hide();
});
}
itemHtml += '<a class="listItem listItem-border" is="emby-linkbutton" data-ripple="false" href="notificationsetting.html?type=' + notification.Type + '">';
if (notification.Enabled) {
itemHtml += '<span class="listItemIcon material-icons notifications_active"></span>';
} else {
itemHtml += '<span class="listItemIcon material-icons notifications_off" style="background-color:#999;"></span>';
}
itemHtml += '<div class="listItemBody">';
itemHtml += '<div class="listItemBodyText">' + notification.Name + '</div>';
itemHtml += '</div>';
itemHtml += '<button type="button" is="paper-icon-button-light"><span class="material-icons mode_edit"></span></button>';
itemHtml += '</a>';
return itemHtml;
}).join('');
return function(view, params) {
view.addEventListener('viewshow', function() {
reload(view);
});
};
});
if (list.length) {
html += '</div>';
html += '</div>';
}
page.querySelector('.notificationList').innerHTML = html;
loading.hide();
});
}
export default function (view, params) {
view.addEventListener('viewshow', function () {
reload(view);
});
}

View file

@ -0,0 +1,30 @@
<div id="playbackConfigurationPage" data-role="page" class="page type-interior playbackConfigurationPage withTabs">
<div>
<div class="content-primary">
<form class="playbackConfigurationForm">
<div class="sectionTitleContainer flex align-items-center">
<h2 class="sectionTitle">${TabResumeSettings}</h2>
</div>
<div class="inputContainer">
<input is="emby-input" type="number" id="txtMinResumePct" name="txtMinResumePct" pattern="[0-9]*" required min="0" max="100" label="${LabelMinResumePercentage}"></input>
<div class="fieldDescription">
${LabelMinResumePercentageHelp}
</div>
</div>
<div class="inputContainer">
<input is="emby-input" type="number" id="txtMaxResumePct" name="txtMaxResumePct" pattern="[0-9]*" required min="1" max="100" label="${LabelMaxResumePercentage}"></input>
<div class="fieldDescription">
${LabelMaxResumePercentageHelp}
</div>
</div>
<div class="inputContainer">
<input is="emby-input" type="number" id="txtMinResumeDuration" name="txtMinResumeDuration" pattern="[0-9]*" required min="0" label="${LabelMinResumeDuration}"></input>
<div class="fieldDescription">
${LabelMinResumeDurationHelp}
</div>
</div>
<div><button is="emby-button" type="submit" class="raised button-submit block"><span>${Save}</span></button></div>
</form>
</div>
</div>
</div>

View file

@ -1,141 +1,146 @@
define(['jQuery', 'loading', 'libraryMenu', 'globalize', 'connectionManager', 'emby-button'], function ($, loading, libraryMenu, globalize, connectionManager) {
'use strict';
import $ from 'jQuery';
import loading from 'loading';
import globalize from 'globalize';
import 'emby-button';
function populateHistory(packageInfo, page) {
var html = '';
var length = Math.min(packageInfo.versions.length, 10);
function populateHistory(packageInfo, page) {
let html = '';
const length = Math.min(packageInfo.versions.length, 10);
for (var i = 0; i < length; i++) {
var version = packageInfo.versions[i];
html += '<h2 style="margin:.5em 0;">' + version.version + '</h2>';
html += '<div style="margin-bottom:1.5em;">' + version.changelog + '</div>';
}
$('#revisionHistory', page).html(html);
for (let i = 0; i < length; i++) {
const version = packageInfo.versions[i];
html += '<h2 style="margin:.5em 0;">' + version.version + '</h2>';
html += '<div style="margin-bottom:1.5em;">' + version.changelog + '</div>';
}
function populateVersions(packageInfo, page, installedPlugin) {
var html = '';
$('#revisionHistory', page).html(html);
}
for (var i = 0; i < packageInfo.versions.length; i++) {
var version = packageInfo.versions[i];
html += '<option value="' + version.version + '">' + version.version + '</option>';
}
function populateVersions(packageInfo, page, installedPlugin) {
let html = '';
var selectmenu = $('#selectVersion', page).html(html);
if (!installedPlugin) {
$('#pCurrentVersion', page).hide().html('');
}
var packageVersion = packageInfo.versions[0];
if (packageVersion) {
selectmenu.val(packageVersion.version);
}
for (let i = 0; i < packageInfo.versions.length; i++) {
const version = packageInfo.versions[i];
html += '<option value="' + version.version + '">' + version.version + '</option>';
}
function renderPackage(pkg, installedPlugins, page) {
var installedPlugin = installedPlugins.filter(function (ip) {
return ip.Name == pkg.name;
})[0];
const selectmenu = $('#selectVersion', page).html(html);
populateVersions(pkg, page, installedPlugin);
populateHistory(pkg, page);
$('.pluginName', page).html(pkg.name);
$('#btnInstallDiv', page).removeClass('hide');
$('#pSelectVersion', page).removeClass('hide');
if (pkg.overview) {
$('#overview', page).show().html(pkg.overview);
} else {
$('#overview', page).hide();
}
$('#description', page).html(pkg.description);
$('#developer', page).html(pkg.owner);
if (installedPlugin) {
var currentVersionText = globalize.translate('MessageYouHaveVersionInstalled', '<strong>' + installedPlugin.Version + '</strong>');
$('#pCurrentVersion', page).show().html(currentVersionText);
} else {
$('#pCurrentVersion', page).hide().html('');
}
loading.hide();
if (!installedPlugin) {
$('#pCurrentVersion', page).hide().html('');
}
function alertText(options) {
require(['alert'], function ({default: alert}) {
alert(options);
});
const packageVersion = packageInfo.versions[0];
if (packageVersion) {
selectmenu.val(packageVersion.version);
}
}
function renderPackage(pkg, installedPlugins, page) {
const installedPlugin = installedPlugins.filter(function (ip) {
return ip.Name == pkg.name;
})[0];
populateVersions(pkg, page, installedPlugin);
populateHistory(pkg, page);
$('.pluginName', page).html(pkg.name);
$('#btnInstallDiv', page).removeClass('hide');
$('#pSelectVersion', page).removeClass('hide');
if (pkg.overview) {
$('#overview', page).show().html(pkg.overview);
} else {
$('#overview', page).hide();
}
function performInstallation(page, name, guid, version) {
var developer = $('#developer', page).html().toLowerCase();
$('#description', page).html(pkg.description);
$('#developer', page).html(pkg.owner);
var alertCallback = function () {
loading.show();
page.querySelector('#btnInstall').disabled = true;
ApiClient.installPlugin(name, guid, version).then(function () {
loading.hide();
alertText(globalize.translate('PluginInstalledMessage'));
});
};
if (installedPlugin) {
const currentVersionText = globalize.translate('MessageYouHaveVersionInstalled', '<strong>' + installedPlugin.Version + '</strong>');
$('#pCurrentVersion', page).show().html(currentVersionText);
} else {
$('#pCurrentVersion', page).hide().html('');
}
if (developer !== 'jellyfin') {
loading.hide();
}
function alertText(options) {
import('alert').then(({default: alert}) => {
alert(options);
});
}
function performInstallation(page, name, guid, version) {
const developer = $('#developer', page).html().toLowerCase();
const alertCallback = function () {
loading.show();
page.querySelector('#btnInstall').disabled = true;
ApiClient.installPlugin(name, guid, version).then(() => {
loading.hide();
var msg = globalize.translate('MessagePluginInstallDisclaimer');
msg += '<br/>';
msg += '<br/>';
msg += globalize.translate('PleaseConfirmPluginInstallation');
require(['confirm'], function (confirm) {
confirm.default(msg, globalize.translate('HeaderConfirmPluginInstallation')).then(function () {
alertCallback();
}, function () {
console.debug('plugin not installed');
});
});
} else {
alertCallback();
}
}
return function (view, params) {
$('.addPluginForm', view).on('submit', function () {
loading.show();
var page = $(this).parents('#addPluginPage')[0];
var name = params.name;
var guid = params.guid;
ApiClient.getInstalledPlugins().then(function (plugins) {
var installedPlugin = plugins.filter(function (plugin) {
return plugin.Name == name;
})[0];
var version = $('#selectVersion', page).val();
if (installedPlugin && installedPlugin.Version === version) {
loading.hide();
Dashboard.alert({
message: globalize.translate('MessageAlreadyInstalled'),
title: globalize.translate('HeaderPluginInstallation')
});
} else {
performInstallation(page, name, guid, version);
}
});
return false;
});
view.addEventListener('viewshow', function () {
var page = this;
loading.show();
var name = params.name;
var guid = params.guid;
var promise1 = ApiClient.getPackageInfo(name, guid);
var promise2 = ApiClient.getInstalledPlugins();
Promise.all([promise1, promise2]).then(function (responses) {
renderPackage(responses[0], responses[1], page);
});
alertText(globalize.translate('MessagePluginInstalled'));
}).catch(() => {
alertText(globalize.translate('MessagePluginInstallError'));
});
};
});
if (developer !== 'jellyfin') {
loading.hide();
let msg = globalize.translate('MessagePluginInstallDisclaimer');
msg += '<br/>';
msg += '<br/>';
msg += globalize.translate('PleaseConfirmPluginInstallation');
import('confirm').then(({default: confirm}) => {
confirm(msg, globalize.translate('HeaderConfirmPluginInstallation')).then(function () {
alertCallback();
}).catch(() => {
console.debug('plugin not installed');
});
});
} else {
alertCallback();
}
}
export default function(view, params) {
$('.addPluginForm', view).on('submit', function () {
loading.show();
const page = $(this).parents('#addPluginPage')[0];
const name = params.name;
const guid = params.guid;
ApiClient.getInstalledPlugins().then(function (plugins) {
const installedPlugin = plugins.filter(function (plugin) {
return plugin.Name == name;
})[0];
const version = $('#selectVersion', page).val();
if (installedPlugin && installedPlugin.Version === version) {
loading.hide();
Dashboard.alert({
message: globalize.translate('MessageAlreadyInstalled'),
title: globalize.translate('HeaderPluginInstallation')
});
} else {
performInstallation(page, name, guid, version);
}
}).catch(() => {
alertText(globalize.translate('MessageGetInstalledPluginsError'));
});
return false;
});
view.addEventListener('viewshow', function () {
const page = this;
loading.show();
const name = params.name;
const guid = params.guid;
const promise1 = ApiClient.getPackageInfo(name, guid);
const promise2 = ApiClient.getInstalledPlugins();
Promise.all([promise1, promise2]).then(function (responses) {
renderPackage(responses[0], responses[1], page);
});
});
}

View file

@ -1,141 +1,142 @@
define(['loading', 'libraryMenu', 'globalize', 'cardStyle', 'emby-button', 'emby-checkbox', 'emby-select'], function (loading, libraryMenu, globalize) {
'use strict';
import loading from 'loading';
import libraryMenu from 'libraryMenu';
import globalize from 'globalize';
import 'cardStyle';
import 'emby-button';
import 'emby-checkbox';
import 'emby-select';
function reloadList(page) {
loading.show();
var promise1 = ApiClient.getAvailablePlugins();
var promise2 = ApiClient.getInstalledPlugins();
Promise.all([promise1, promise2]).then(function (responses) {
populateList({
catalogElement: page.querySelector('#pluginTiles'),
noItemsElement: page.querySelector('#noPlugins'),
availablePlugins: responses[0],
installedPlugins: responses[1]
});
function reloadList(page) {
loading.show();
const promise1 = ApiClient.getAvailablePlugins();
const promise2 = ApiClient.getInstalledPlugins();
Promise.all([promise1, promise2]).then(function (responses) {
populateList({
catalogElement: page.querySelector('#pluginTiles'),
noItemsElement: page.querySelector('#noPlugins'),
availablePlugins: responses[0],
installedPlugins: responses[1]
});
});
}
function getHeaderText(category) {
category = category.replace(' ', '');
// TODO: Replace with switch
if (category === 'Channel') {
category = 'Channels';
} else if (category === 'Theme') {
category = 'Themes';
} else if (category === 'LiveTV') {
category = 'LiveTV';
} else if (category === 'ScreenSaver') {
category = 'HeaderScreenSavers';
}
function getHeaderText(category) {
category = category.replace(' ', '');
if ('Channel' === category) {
category = 'Channels';
} else if ('Theme' === category) {
category = 'Themes';
} else if ('LiveTV' === category) {
category = 'HeaderLiveTV';
} else if ('ScreenSaver' === category) {
category = 'HeaderScreenSavers';
return globalize.translate(category);
}
function populateList(options) {
const availablePlugins = options.availablePlugins;
const installedPlugins = options.installedPlugins;
availablePlugins.forEach(function (plugin, index, array) {
plugin.category = plugin.category || 'General';
plugin.categoryDisplayName = getHeaderText(plugin.category);
array[index] = plugin;
});
availablePlugins.sort(function (a, b) {
if (a.category > b.category) {
return 1;
} else if (b.category > a.category) {
return -1;
}
if (a.name > b.name) {
return 1;
} else if (b.name > a.name) {
return -1;
}
return 0;
});
return globalize.translate(category);
}
let currentCategory = null;
let html = '';
function populateList(options) {
var availablePlugins = options.availablePlugins;
var installedPlugins = options.installedPlugins;
availablePlugins.forEach(function (plugin, index, array) {
plugin.category = plugin.category || 'General';
plugin.categoryDisplayName = getHeaderText(plugin.category);
array[index] = plugin;
});
availablePlugins.sort(function (a, b) {
if (a.category > b.category) {
return 1;
} else if (b.category > a.category) {
return -1;
for (let i = 0; i < availablePlugins.length; i++) {
const plugin = availablePlugins[i];
const category = plugin.categoryDisplayName;
if (category != currentCategory) {
if (currentCategory) {
html += '</div>';
html += '</div>';
}
if (a.name > b.name) {
return 1;
} else if (b.name > a.name) {
return -1;
}
return 0;
});
var currentCategory = null;
var html = '';
for (var i = 0; i < availablePlugins.length; i++) {
var plugin = availablePlugins[i];
var category = plugin.categoryDisplayName;
if (category != currentCategory) {
if (currentCategory) {
html += '</div>';
html += '</div>';
}
html += '<div class="verticalSection">';
html += '<h2 class="sectionTitle sectionTitle-cards">' + category + '</h2>';
html += '<div class="itemsContainer vertical-wrap">';
currentCategory = category;
}
html += getPluginHtml(plugin, options, installedPlugins);
html += '<div class="verticalSection">';
html += '<h2 class="sectionTitle sectionTitle-cards">' + category + '</h2>';
html += '<div class="itemsContainer vertical-wrap">';
currentCategory = category;
}
html += '</div>';
html += '</div>';
html += getPluginHtml(plugin, options, installedPlugins);
}
html += '</div>';
html += '</div>';
if (!availablePlugins.length && options.noItemsElement) {
options.noItemsElement.classList.remove('hide');
}
options.catalogElement.innerHTML = html;
loading.hide();
if (!availablePlugins.length && options.noItemsElement) {
options.noItemsElement.classList.remove('hide');
}
function getPluginHtml(plugin, options, installedPlugins) {
var html = '';
var href = plugin.externalUrl ? plugin.externalUrl : 'addplugin.html?name=' + encodeURIComponent(plugin.name) + '&guid=' + plugin.guid;
options.catalogElement.innerHTML = html;
loading.hide();
}
if (options.context) {
href += '&context=' + options.context;
}
function getPluginHtml(plugin, options, installedPlugins) {
let html = '';
let href = plugin.externalUrl ? plugin.externalUrl : 'addplugin.html?name=' + encodeURIComponent(plugin.name) + '&guid=' + plugin.guid;
var target = plugin.externalUrl ? ' target="_blank"' : '';
html += "<div class='card backdropCard'>";
html += '<div class="cardBox visualCardBox">';
html += '<div class="cardScalable visualCardBox-cardScalable">';
html += '<div class="cardPadder cardPadder-backdrop"></div>';
html += '<a class="cardContent cardImageContainer" is="emby-linkbutton" href="' + href + '"' + target + '>';
html += '<span class="cardImageIcon material-icons folder"></span>';
html += '</a>';
html += '</div>';
html += '<div class="cardFooter">';
html += "<div class='cardText'>";
html += plugin.name;
html += '</div>';
var installedPlugin = installedPlugins.filter(function (ip) {
return ip.Id == plugin.guid;
})[0];
html += "<div class='cardText cardText-secondary'>";
html += installedPlugin ? globalize.translate('LabelVersionInstalled', installedPlugin.Version) : '&nbsp;';
html += '</div>';
html += '</div>';
html += '</div>';
return html += '</div>';
if (options.context) {
href += '&context=' + options.context;
}
function getTabs() {
return [{
href: 'installedplugins.html',
name: globalize.translate('TabMyPlugins')
}, {
href: 'availableplugins.html',
name: globalize.translate('TabCatalog')
}, {
href: 'repositories.html',
name: globalize.translate('TabRepositories')
}];
}
const target = plugin.externalUrl ? ' target="_blank"' : '';
html += "<div class='card backdropCard'>";
html += '<div class="cardBox visualCardBox">';
html += '<div class="cardScalable visualCardBox-cardScalable">';
html += '<div class="cardPadder cardPadder-backdrop"></div>';
html += '<a class="cardContent cardImageContainer" is="emby-linkbutton" href="' + href + '"' + target + '>';
html += '<span class="cardImageIcon material-icons folder"></span>';
html += '</a>';
html += '</div>';
html += '<div class="cardFooter">';
html += "<div class='cardText'>";
html += plugin.name;
html += '</div>';
const installedPlugin = installedPlugins.filter(function (ip) {
return ip.Id == plugin.guid;
})[0];
html += "<div class='cardText cardText-secondary'>";
html += installedPlugin ? globalize.translate('LabelVersionInstalled', installedPlugin.Version) : '&nbsp;';
html += '</div>';
html += '</div>';
html += '</div>';
return html += '</div>';
}
window.PluginCatalog = {
renderCatalog: populateList
};
function getTabs() {
return [{
href: 'installedplugins.html',
name: globalize.translate('TabMyPlugins')
}, {
href: 'availableplugins.html',
name: globalize.translate('TabCatalog')
}, {
href: 'repositories.html',
name: globalize.translate('TabRepositories')
}];
}
return function (view, params) {
view.addEventListener('viewshow', function () {
libraryMenu.setTabs('plugins', 1, getTabs);
reloadList(this);
});
};
});
export default function (view) {
view.addEventListener('viewshow', function () {
libraryMenu.setTabs('plugins', 1, getTabs);
reloadList(this);
});
}

View file

@ -1,190 +1,193 @@
define(['loading', 'libraryMenu', 'dom', 'globalize', 'cardStyle', 'emby-button'], function (loading, libraryMenu, dom, globalize) {
'use strict';
import loading from 'loading';
import libraryMenu from 'libraryMenu';
import dom from 'dom';
import globalize from 'globalize';
import 'cardStyle';
import 'emby-button';
function deletePlugin(page, uniqueid, name) {
var msg = globalize.translate('UninstallPluginConfirmation', name);
function deletePlugin(page, uniqueid, name) {
const msg = globalize.translate('UninstallPluginConfirmation', name);
require(['confirm'], function (confirm) {
confirm.default({
title: globalize.translate('UninstallPluginHeader'),
text: msg,
primary: 'delete',
confirmText: globalize.translate('UninstallPluginHeader')
}).then(function () {
loading.show();
ApiClient.uninstallPlugin(uniqueid).then(function () {
reloadList(page);
});
import('confirm').then(({default: confirm}) => {
confirm.default({
title: globalize.translate('HeaderUninstallPlugin'),
text: msg,
primary: 'delete',
confirmText: globalize.translate('HeaderUninstallPlugin')
}).then(function () {
loading.show();
ApiClient.uninstallPlugin(uniqueid).then(function () {
reloadList(page);
});
});
}
});
}
function showNoConfigurationMessage() {
Dashboard.alert({
message: globalize.translate('NoPluginConfigurationMessage')
});
}
function showNoConfigurationMessage() {
Dashboard.alert({
message: globalize.translate('MessageNoPluginConfiguration')
});
}
function showConnectMessage() {
Dashboard.alert({
message: globalize.translate('MessagePluginConfigurationRequiresLocalAccess')
});
}
function showConnectMessage() {
Dashboard.alert({
message: globalize.translate('MessagePluginConfigurationRequiresLocalAccess')
});
}
function getPluginCardHtml(plugin, pluginConfigurationPages) {
var configPage = pluginConfigurationPages.filter(function (pluginConfigurationPage) {
return pluginConfigurationPage.PluginId == plugin.Id;
})[0];
var configPageUrl = configPage ? Dashboard.getConfigurationPageUrl(configPage.Name) : null;
var html = '';
html += "<div data-id='" + plugin.Id + "' data-name='" + plugin.Name + "' data-removable='" + plugin.CanUninstall + "' class='card backdropCard'>";
html += '<div class="cardBox visualCardBox">';
html += '<div class="cardScalable">';
html += '<div class="cardPadder cardPadder-backdrop"></div>';
html += configPageUrl ? '<a class="cardContent cardImageContainer" is="emby-linkbutton" href="' + configPageUrl + '">' : '<div class="cardContent noConfigPluginCard noHoverEffect cardImageContainer emby-button">';
html += '<span class="cardImageIcon material-icons folder"></span>';
html += configPageUrl ? '</a>' : '</div>';
function getPluginCardHtml(plugin, pluginConfigurationPages) {
const configPage = pluginConfigurationPages.filter(function (pluginConfigurationPage) {
return pluginConfigurationPage.PluginId == plugin.Id;
})[0];
const configPageUrl = configPage ? Dashboard.getConfigurationPageUrl(configPage.Name) : null;
let html = '';
html += "<div data-id='" + plugin.Id + "' data-name='" + plugin.Name + "' data-removable='" + plugin.CanUninstall + "' class='card backdropCard'>";
html += '<div class="cardBox visualCardBox">';
html += '<div class="cardScalable">';
html += '<div class="cardPadder cardPadder-backdrop"></div>';
html += configPageUrl ? '<a class="cardContent cardImageContainer" is="emby-linkbutton" href="' + configPageUrl + '">' : '<div class="cardContent noConfigPluginCard noHoverEffect cardImageContainer emby-button">';
html += '<span class="cardImageIcon material-icons folder"></span>';
html += configPageUrl ? '</a>' : '</div>';
html += '</div>';
html += '<div class="cardFooter">';
if (configPage || plugin.CanUninstall) {
html += '<div style="text-align:right; float:right;padding-top:5px;">';
html += '<button type="button" is="paper-icon-button-light" class="btnCardMenu autoSize"><span class="material-icons more_vert"></span></button>';
html += '</div>';
html += '<div class="cardFooter">';
}
if (configPage || plugin.CanUninstall) {
html += '<div style="text-align:right; float:right;padding-top:5px;">';
html += '<button type="button" is="paper-icon-button-light" class="btnCardMenu autoSize"><span class="material-icons more_vert"></span></button>';
html += '</div>';
html += "<div class='cardText'>";
html += configPage && configPage.DisplayName ? configPage.DisplayName : plugin.Name;
html += '</div>';
html += "<div class='cardText cardText-secondary'>";
html += plugin.Version;
html += '</div>';
html += '</div>';
html += '</div>';
html += '</div>';
return html;
}
function renderPlugins(page, plugins) {
ApiClient.getJSON(ApiClient.getUrl('web/configurationpages') + '?pageType=PluginConfiguration').then(function (configPages) {
populateList(page, plugins, configPages);
});
}
function populateList(page, plugins, pluginConfigurationPages) {
plugins = plugins.sort(function (plugin1, plugin2) {
if (plugin1.Name > plugin2.Name) {
return 1;
}
html += "<div class='cardText'>";
html += configPage && configPage.DisplayName ? configPage.DisplayName : plugin.Name;
html += '</div>';
html += "<div class='cardText cardText-secondary'>";
html += plugin.Version;
html += '</div>';
html += '</div>';
html += '</div>';
html += '</div>';
return html;
}
function renderPlugins(page, plugins) {
ApiClient.getJSON(ApiClient.getUrl('web/configurationpages') + '?pageType=PluginConfiguration').then(function (configPages) {
populateList(page, plugins, configPages);
});
}
function populateList(page, plugins, pluginConfigurationPages) {
plugins = plugins.sort(function (plugin1, plugin2) {
if (plugin1.Name > plugin2.Name) {
return 1;
}
return -1;
});
var html = plugins.map(function (p) {
return getPluginCardHtml(p, pluginConfigurationPages);
}).join('');
var installedPluginsElement = page.querySelector('.installedPlugins');
installedPluginsElement.removeEventListener('click', onInstalledPluginsClick);
installedPluginsElement.addEventListener('click', onInstalledPluginsClick);
if (plugins.length) {
installedPluginsElement.classList.add('itemsContainer');
installedPluginsElement.classList.add('vertical-wrap');
} else {
html += '<div class="centerMessage">';
html += '<h1>' + globalize.translate('MessageNoPluginsInstalled') + '</h1>';
html += '<p><a is="emby-linkbutton" class="button-link" href="availableplugins.html">';
html += globalize.translate('BrowsePluginCatalogMessage');
html += '</a></p>';
html += '</div>';
}
installedPluginsElement.innerHTML = html;
loading.hide();
}
function showPluginMenu(page, elem) {
var card = dom.parentWithClass(elem, 'card');
var id = card.getAttribute('data-id');
var name = card.getAttribute('data-name');
var removable = card.getAttribute('data-removable');
var configHref = card.querySelector('.cardContent').getAttribute('href');
var menuItems = [];
if (configHref) {
menuItems.push({
name: globalize.translate('ButtonSettings'),
id: 'open',
icon: 'mode_edit'
});
}
if (removable === 'true') {
menuItems.push({
name: globalize.translate('ButtonUninstall'),
id: 'delete',
icon: 'delete'
});
}
require(['actionsheet'], function (actionsheet) {
actionsheet.show({
items: menuItems,
positionTo: elem,
callback: function (resultId) {
switch (resultId) {
case 'open':
Dashboard.navigate(configHref);
break;
case 'delete':
deletePlugin(page, id, name);
break;
}
}
});
});
}
function reloadList(page) {
loading.show();
ApiClient.getInstalledPlugins().then(function (plugins) {
renderPlugins(page, plugins);
});
}
function getTabs() {
return [{
href: 'installedplugins.html',
name: globalize.translate('TabMyPlugins')
}, {
href: 'availableplugins.html',
name: globalize.translate('TabCatalog')
}, {
href: 'repositories.html',
name: globalize.translate('TabRepositories')
}];
}
function onInstalledPluginsClick(e) {
if (dom.parentWithClass(e.target, 'noConfigPluginCard')) {
showNoConfigurationMessage();
} else if (dom.parentWithClass(e.target, 'connectModePluginCard')) {
showConnectMessage();
} else {
var btnCardMenu = dom.parentWithClass(e.target, 'btnCardMenu');
if (btnCardMenu) {
showPluginMenu(dom.parentWithClass(btnCardMenu, 'page'), btnCardMenu);
}
}
}
pageIdOn('pageshow', 'pluginsPage', function () {
libraryMenu.setTabs('plugins', 0, getTabs);
reloadList(this);
return -1;
});
window.PluginsPage = {
renderPlugins: renderPlugins
};
let html = plugins.map(function (p) {
return getPluginCardHtml(p, pluginConfigurationPages);
}).join('');
const installedPluginsElement = page.querySelector('.installedPlugins');
installedPluginsElement.removeEventListener('click', onInstalledPluginsClick);
installedPluginsElement.addEventListener('click', onInstalledPluginsClick);
if (plugins.length) {
installedPluginsElement.classList.add('itemsContainer');
installedPluginsElement.classList.add('vertical-wrap');
} else {
html += '<div class="centerMessage">';
html += '<h1>' + globalize.translate('MessageNoPluginsInstalled') + '</h1>';
html += '<p><a is="emby-linkbutton" class="button-link" href="availableplugins.html">';
html += globalize.translate('MessageBrowsePluginCatalog');
html += '</a></p>';
html += '</div>';
}
installedPluginsElement.innerHTML = html;
loading.hide();
}
function showPluginMenu(page, elem) {
const card = dom.parentWithClass(elem, 'card');
const id = card.getAttribute('data-id');
const name = card.getAttribute('data-name');
const removable = card.getAttribute('data-removable');
const configHref = card.querySelector('.cardContent').getAttribute('href');
const menuItems = [];
if (configHref) {
menuItems.push({
name: globalize.translate('ButtonSettings'),
id: 'open',
icon: 'mode_edit'
});
}
if (removable === 'true') {
menuItems.push({
name: globalize.translate('ButtonUninstall'),
id: 'delete',
icon: 'delete'
});
}
import('actionsheet').then(({default: actionsheet}) => {
actionsheet.show({
items: menuItems,
positionTo: elem,
callback: function (resultId) {
switch (resultId) {
case 'open':
Dashboard.navigate(configHref);
break;
case 'delete':
deletePlugin(page, id, name);
break;
}
}
});
});
}
function reloadList(page) {
loading.show();
ApiClient.getInstalledPlugins().then(function (plugins) {
renderPlugins(page, plugins);
});
}
function getTabs() {
return [{
href: 'installedplugins.html',
name: globalize.translate('TabMyPlugins')
}, {
href: 'availableplugins.html',
name: globalize.translate('TabCatalog')
}, {
href: 'repositories.html',
name: globalize.translate('TabRepositories')
}];
}
function onInstalledPluginsClick(e) {
if (dom.parentWithClass(e.target, 'noConfigPluginCard')) {
showNoConfigurationMessage();
} else if (dom.parentWithClass(e.target, 'connectModePluginCard')) {
showConnectMessage();
} else {
const btnCardMenu = dom.parentWithClass(e.target, 'btnCardMenu');
if (btnCardMenu) {
showPluginMenu(dom.parentWithClass(btnCardMenu, 'page'), btnCardMenu);
}
}
}
pageIdOn('pageshow', 'pluginsPage', function () {
libraryMenu.setTabs('plugins', 0, getTabs);
reloadList(this);
});
window.PluginsPage = {
renderPlugins: renderPlugins
};

View file

@ -3,7 +3,7 @@
<div class="content-primary">
<div class="sectionTitleContainer flex align-items-center">
<h2 class="sectionTitle">${TabRepositories}</h2>
<button is="emby-button" type="button" class="fab btnNewRepository submit" style="margin-left:1em;" title="${ButtonAdd}">
<button is="emby-button" type="button" class="fab btnNewRepository submit" style="margin-left:1em;" title="${Add}">
<span class="material-icons add" aria-hidden="true"></span>
</button>
</div>

View file

@ -69,7 +69,7 @@ function getRepositoryHtml(repository) {
html += `<h3 class="listItemBodyText">${repository.Name}</h3>`;
html += `<div class="listItemBodyText secondary">${repository.Url}</div>`;
html += '</div>';
html += `<button type="button" is="paper-icon-button-light" id="${repository.Url}" class="btnDelete" title="${globalize.translate('ButtonDelete')}"><span class="material-icons delete"></span></button>`;
html += `<button type="button" is="paper-icon-button-light" id="${repository.Url}" class="btnDelete" title="${globalize.translate('Delete')}"><span class="material-icons delete"></span></button>`;
html += '</div>';
return html;
@ -105,7 +105,7 @@ export default function(view, params) {
});
view.querySelector('.btnNewRepository').addEventListener('click', () => {
let dialog = dialogHelper.createDialog({
const dialog = dialogHelper.createDialog({
scrollY: false,
size: 'large',
modal: false,
@ -127,7 +127,7 @@ export default function(view, params) {
html += `<input is="emby-input" id="txtRepositoryUrl" label="${globalize.translate('LabelRepositoryUrl')}" type="url" required />`;
html += `<div class="fieldDescription">${globalize.translate('LabelRepositoryUrlHelp')}</div>`;
html += '</div>';
html += `<button is="emby-button" type="submit" class="raised button-submit block"><span>${globalize.translate('ButtonSave')}</span></button>`;
html += `<button is="emby-button" type="submit" class="raised button-submit block"><span>${globalize.translate('Save')}</span></button>`;
html += '</div>';
html += '</form>';

View file

@ -0,0 +1,85 @@
<div id="scheduledTaskPage" data-role="page" class="page type-interior scheduledTasksConfigurationPage">
<div>
<div class="content-primary">
<div class="verticalSection">
<div class="sectionTitleContainer flex align-items-center">
<h2 class="sectionTitle taskName"></h2>
<a is="emby-linkbutton" rel="noopener noreferrer" class="raised button-alt headerHelpButton" target="_blank" href="https://docs.jellyfin.org/general/server/tasks.html">${Help}</a>
</div>
<p id="pTaskDescription"></p>
</div>
<div class="readOnlyContent">
<div>
<h2 style="vertical-align: middle; display: inline-block;">${HeaderTaskTriggers}</h2>
<button is="emby-button" type="button" class="fab fab-mini btnAddTrigger submit" style="margin-left: 1em;" title="${ButtonAddScheduledTaskTrigger}">
<span class="material-icons add"></span>
</button>
</div>
<div class="taskTriggers"></div>
</div>
</div>
</div>
<div data-role="popup" id="popupAddTrigger" class="dialog dialog-fixedSize dialog-medium hide" style="position: fixed; top: 10%;">
<form class="addTriggerForm" style="padding:1em;">
<div class="ui-bar-a">
<h3>${HeaderAddScheduledTaskTrigger}</h3>
</div>
<div data-role="content">
<div class="selectContainer">
<select is="emby-select" id="selectTriggerType" class="selectTriggerType" label="${LabelTriggerType}">
<option value="DailyTrigger">${OptionDaily}</option>
<option value="WeeklyTrigger">${OptionWeekly}</option>
<option value="IntervalTrigger">${OptionOnInterval}</option>
<option value="StartupTrigger">${OnApplicationStartup}</option>
</select>
</div>
<div id="fldDayOfWeek" class="selectContainer">
<select is="emby-select" id="selectDayOfWeek" name="selectDayOfWeek" label="${LabelDay}">
<option value="Sunday">${Sunday}</option>
<option value="Monday">${Monday}</option>
<option value="Tuesday">${Tuesday}</option>
<option value="Wednesday">${Wednesday}</option>
<option value="Thursday">${Thursday}</option>
<option value="Friday">${Friday}</option>
<option value="Saturday">${Saturday}</option>
</select>
</div>
<div id="fldTimeOfDay" class="selectContainer">
<select is="emby-select" id="selectTimeOfDay" label="${LabelTime}"></select>
</div>
<div id="fldSelectSystemEvent" class="selectContainer">
<select is="emby-select" id="selectSystemEvent" name="selectSystemEvent" label="${LabelEvent}">
<option value="WakeFromSleep">${OptionWakeFromSleep}</option>
</select>
</div>
<div id="fldSelectInterval" class="selectContainer">
<select is="emby-select" id="selectInterval" label="${LabelEveryXMinutes}">
<option value="9000000000">15 minutes</option>
<option value="18000000000">30 minutes</option>
<option value="27000000000">45 minutes</option>
<option value="36000000000">1 hour</option>
<option value="72000000000">2 hours</option>
<option value="108000000000">3 hours</option>
<option value="144000000000">4 hours</option>
<option value="216000000000">6 hours</option>
<option value="288000000000">8 hours</option>
<option value="432000000000">12 hours</option>
<option value="864000000000">24 hours</option>
</select>
</div>
<div class="inputContainer">
<input is="emby-input" id="txtTimeLimit" type="number" pattern="[0-9]*" min="1" step=".5" label="${LabelTimeLimitHours}" />
</div>
<div>
<button is="emby-button" type="submit" class="raised button-submit block" data-icon="check">
<span>${Add}</span>
</button>
<button is="emby-button" type="button" class="raised button-cancel block" data-icon="delete" onclick="$(this).parents('.dialog').addClass('hide');">
<span>${ButtonCancel}</span>
</button>
</div>
</div>
</form>
</div>
</div>

View file

@ -33,7 +33,7 @@ import 'emby-select';
const ScheduledTaskPage = {
refreshScheduledTask: function (view) {
loading.show();
let id = getParameterByName('id');
const id = getParameterByName('id');
ApiClient.getScheduledTask(id).then(function (task) {
ScheduledTaskPage.loadScheduledTask(view, task);
});
@ -75,7 +75,7 @@ import 'emby-select';
}
html += '</div>';
html += '<button class="btnDeleteTrigger" data-index="' + i + '" type="button" is="paper-icon-button-light" title="' + globalize.translate('ButtonDelete') + '"><span class="material-icons delete"></span></button>';
html += '<button class="btnDeleteTrigger" data-index="' + i + '" type="button" is="paper-icon-button-light" title="' + globalize.translate('Delete') + '"><span class="material-icons delete"></span></button>';
html += '</div>';
}
@ -84,16 +84,16 @@ import 'emby-select';
},
// TODO: Replace this mess with date-fns and remove datetime completely
getTriggerFriendlyName: function (trigger) {
if ('DailyTrigger' == trigger.Type) {
if (trigger.Type == 'DailyTrigger') {
return globalize.translate('DailyAt', ScheduledTaskPage.getDisplayTime(trigger.TimeOfDayTicks));
}
if ('WeeklyTrigger' == trigger.Type) {
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 ('SystemEventTrigger' == trigger.Type && 'WakeFromSleep' == trigger.SystemEvent) {
if (trigger.Type == 'SystemEventTrigger' && trigger.SystemEvent == 'WakeFromSleep') {
return globalize.translate('OnWakeFromSleep');
}
@ -143,7 +143,7 @@ import 'emby-select';
},
deleteTrigger: function (view, index) {
loading.show();
let id = getParameterByName('id');
const id = getParameterByName('id');
ApiClient.getScheduledTask(id).then(function (task) {
task.Triggers.remove(index);
ApiClient.updateScheduledTaskTriggers(task.Id, task.Triggers).then(function () {
@ -211,7 +211,7 @@ import 'emby-select';
export default function (view, params) {
function onSubmit(e) {
loading.show();
let id = getParameterByName('id');
const id = getParameterByName('id');
ApiClient.getScheduledTask(id).then(function (task) {
task.Triggers.push(ScheduledTaskPage.getTriggerToAdd(view));
ApiClient.updateScheduledTaskTriggers(task.Id, task.Triggers).then(function () {

View file

@ -0,0 +1,20 @@
<div id="scheduledTasksPage" data-role="page" class="page type-interior scheduledTasksConfigurationPage">
<style>
.taskProgressOuter {
height: 6px;
background: #eee;
border-radius: 2px;
}
.taskProgressInner {
border-radius: 2px;
height: 100%;
background: #00a4dc;
}
</style>
<div>
<div class="content-primary">
<div class="divScheduledTasks readOnlyContent"></div>
</div>
</div>
</div>

View file

@ -103,7 +103,7 @@ import 'emby-button';
}
function setTaskButtonIcon(button, icon) {
let inner = button.querySelector('.material-icons');
const inner = button.querySelector('.material-icons');
inner.classList.remove('stop', 'play_arrow');
inner.classList.add(icon);
}
@ -160,7 +160,7 @@ import 'emby-button';
$('.divScheduledTasks', view).on('click', '.btnStartTask', function() {
const button = this;
let id = button.getAttribute('data-taskid');
const id = button.getAttribute('data-taskid');
ApiClient.startScheduledTask(id).then(function() {
updateTaskButton(button, 'Running');
reloadList(view);
@ -169,7 +169,7 @@ import 'emby-button';
$('.divScheduledTasks', view).on('click', '.btnStopTask', function() {
const button = this;
let id = button.getAttribute('data-taskid');
const id = button.getAttribute('data-taskid');
ApiClient.stopScheduledTask(id).then(function() {
updateTaskButton(button, '');
reloadList(view);

View file

@ -0,0 +1,12 @@
<div id="serverActivityPage" data-role="page" class="page type-interior serverActivityPage noSecondaryNavPage" data-title="${HeaderActivity}">
<div>
<div class="content-primary">
<div class="verticalSection">
<h2 class="sectionTitle"></h2>
</div>
<div class="readOnlyContent">
<div class="paperList activityItems" data-activitylimit="100"></div>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,20 @@
<div id="streamingSettingsPage" data-role="page" class="page type-interior playbackConfigurationPage withTabs">
<div>
<div class="content-primary">
<form class="streamingSettingsForm">
<div class="verticalSection">
<div class="sectionTitleContainer flex align-items-center">
<h2 class="sectionTitle">${TabStreaming}</h2>
</div>
<div class="inputContainer">
<input is="emby-input" type="number" id="txtRemoteClientBitrateLimit" pattern="[0-9]*" min="0" step=".25" label="${LabelRemoteClientBitrateLimit}" />
<div class="fieldDescription">${LabelRemoteClientBitrateLimitHelp}</div>
</div>
</div>
<button is="emby-button" type="submit" class="raised button-submit block">
<span>${Save}</span>
</button>
</form>
</div>
</div>
</div>

View file

@ -0,0 +1,205 @@
<div id="editUserPage" data-role="page" class="page type-interior">
<div>
<div class="content-primary">
<div class="verticalSection">
<div class="sectionTitleContainer flex align-items-center">
<h2 class="sectionTitle username"></h2>
<a is="emby-linkbutton" rel="noopener noreferrer" class="raised button-alt headerHelpButton" target="_blank" href="https://docs.jellyfin.org/general/server/users/">${Help}</a>
</div>
</div>
<div data-role="controlgroup" data-type="horizontal" class="localnav" id="userProfileNavigation" data-mini="true">
<a href="#" is="emby-linkbutton" data-role="button" class="ui-btn-active">${TabProfile}</a>
<a href="#" is="emby-linkbutton" data-role="button" onclick="Dashboard.navigate('userlibraryaccess.html', true);">${TabAccess}</a>
<a href="#" is="emby-linkbutton" data-role="button" onclick="Dashboard.navigate('userparentalcontrol.html', true);">${TabParentalControl}</a>
<a href="#" is="emby-linkbutton" data-role="button" onclick="Dashboard.navigate('userpassword.html', true);">${HeaderPassword}</a>
</div>
<p class="lnkEditUserPreferencesContainer">
<a class="lnkEditUserPreferences button-link" href="#" is="emby-linkbutton">${ButtonEditOtherUserPreferences}</a>
</p>
<form class="editUserProfileForm">
<div class="disabledUserBanner" style="display: none;">
<div class="btn btnDarkAccent btnStatic">
<div>
${HeaderThisUserIsCurrentlyDisabled}
</div>
<div style="margin-top: 5px;">
${MessageReenableUser}
</div>
</div>
</div>
<div id="fldUserName" class="inputContainer">
<input is="emby-input" id="txtUserName" required type="text" label="${LabelName}" />
</div>
<div class="selectContainer fldSelectLoginProvider hide">
<select class="selectLoginProvider" is="emby-select" label="${LabelAuthProvider}"></select>
<div class="fieldDescription">${AuthProviderHelp}</div>
</div>
<div class="selectContainer fldSelectPasswordResetProvider hide">
<select class="selectPasswordResetProvider" is="emby-select" label="${LabelPasswordResetProvider}"></select>
<div class="fieldDescription">${PasswordResetProviderHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription fldRemoteAccess hide">
<label>
<input type="checkbox" is="emby-checkbox" id="chkRemoteAccess" />
<span>${AllowRemoteAccess}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${AllowRemoteAccessHelp}</div>
</div>
<label class="checkboxContainer">
<input type="checkbox" is="emby-checkbox" id="chkIsAdmin" />
<span>${OptionAllowUserToManageServer}</span>
</label>
<div id="featureAccessFields" class="verticalSection">
<h2 class="paperListLabel">${HeaderFeatureAccess}</h2>
<div class="checkboxList paperList" style="padding:.5em 1em;">
<label>
<input type="checkbox" is="emby-checkbox" id="chkEnableLiveTvAccess" />
<span>${OptionAllowBrowsingLiveTv}</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" id="chkManageLiveTv" />
<span>${OptionAllowManageLiveTv}</span>
</label>
</div>
</div>
<div class="verticalSection">
<h2 class="paperListLabel">${HeaderPlayback}</h2>
<div class="checkboxList paperList" style="padding:.5em 1em;">
<label>
<input type="checkbox" is="emby-checkbox" id="chkEnableMediaPlayback" />
<span>${OptionAllowMediaPlayback}</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" id="chkEnableAudioPlaybackTranscoding" />
<span>${OptionAllowAudioPlaybackTranscoding}</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" id="chkEnableVideoPlaybackTranscoding" />
<span>${OptionAllowVideoPlaybackTranscoding}</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" id="chkEnableVideoPlaybackRemuxing" />
<span>${OptionAllowVideoPlaybackRemuxing}</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" id="chkForceRemoteSourceTranscoding" />
<span>${OptionForceRemoteSourceTranscoding}</span>
</label>
</div>
<div class="fieldDescription">${OptionAllowMediaPlaybackTranscodingHelp}</div>
</div>
<br />
<div class="verticalSection">
<div class="inputContainer">
<input is="emby-input" type="number" id="txtRemoteClientBitrateLimit" pattern="[0-9]*" min="0" step=".25" label="${LabelRemoteClientBitrateLimit}" />
<div class="fieldDescription">${LabelRemoteClientBitrateLimitHelp}</div>
<div class="fieldDescription">${LabelUserRemoteClientBitrateLimitHelp}</div>
</div>
</div>
<div class="verticalSection">
<div class="selectContainer fldSelectSyncPlayAccess">
<select class="selectSyncPlayAccess" is="emby-select" id="selectSyncPlayAccess" label="${LabelSyncPlayAccess}">
<option value="CreateAndJoinGroups">${LabelSyncPlayAccessCreateAndJoinGroups}</option>
<option value="JoinGroups">${LabelSyncPlayAccessJoinGroups}</option>
<option value="None">${LabelSyncPlayAccessNone}</option>
</select>
<div class="fieldDescription">${SyncPlayAccessHelp}</div>
</div>
</div>
<div class="verticalSection">
<h2 class="checkboxListLabel" style="margin-bottom:1em;">${HeaderAllowMediaDeletionFrom}</h2>
<div class="checkboxList paperList checkboxList-paperList">
<label class="checkboxContainer">
<input type="checkbox" is="emby-checkbox" id="chkEnableDeleteAllFolders" />
<span>${AllLibraries}</span>
</label>
<div class="deleteAccess">
</div>
</div>
</div>
<div class="verticalSection">
<h2 class="checkboxListLabel">${HeaderRemoteControl}</h2>
<div class="checkboxList paperList" style="padding:.5em 1em;">
<label>
<input type="checkbox" is="emby-checkbox" id="chkEnableRemoteControlOtherUsers" />
<span>${OptionAllowRemoteControlOthers}</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" id="chkRemoteControlSharedDevices" />
<span>${OptionAllowRemoteSharedDevices}</span>
</label>
</div>
<div class="fieldDescription">${OptionAllowRemoteSharedDevicesHelp}</div>
</div>
<div class="verticalSection">
<h2 class="checkboxListLabel">${HeaderDownloadSync}</h2>
<div class="checkboxList paperList" style="padding:.5em 1em;">
<label>
<input type="checkbox" is="emby-checkbox" id="chkEnableDownloading" />
<span>${OptionAllowContentDownloading}</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" id="chkEnableSyncTranscoding" />
<span>${OptionAllowSyncTranscoding}</span>
</label>
</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input type="checkbox" is="emby-checkbox" id="chkEnableConversion" />
<span>${AllowMediaConversion}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${AllowMediaConversionHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input type="checkbox" is="emby-checkbox" id="chkEnableSharing" />
<span>${OptionAllowLinkSharing}</span>
</label>
<div class="fieldDescription checkboxFieldDescription sharingHelp"></div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription" id="fldIsEnabled">
<label>
<input type="checkbox" is="emby-checkbox" id="chkDisabled" />
<span>${OptionDisableUser}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${OptionDisableUserHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription" id="fldIsHidden">
<label>
<input type="checkbox" is="emby-checkbox" id="chkIsHidden" />
<span>${OptionHideUser}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${OptionHideUserFromLoginHelp}</div>
</div>
<br/>
<div class=verticalSection>
<div class="inputContainer" id="fldLoginAttemptsBeforeLockout">
<input is="emby-input" type="number" id="txtLoginAttemptsBeforeLockout" min="-1" step="1" label="${LabelUserLoginAttemptsBeforeLockout}"/>
<div class="fieldDescription">${OptionLoginAttemptsBeforeLockout}</div>
<div class="fieldDescription">${OptionLoginAttemptsBeforeLockoutHelp}</div>
</div>
</div>
<br />
<div>
<button is="emby-button" type="submit" class="raised button-submit block">
<span>${Save}</span>
</button>
<button is="emby-button" type="button" class="raised button-cancel block btnCancel" onclick="history.back();">
<span>${ButtonCancel}</span>
</button>
</div>
</form>
</div>
</div>
</div>

View file

@ -14,13 +14,13 @@ import globalize from 'globalize';
let html = '';
for (const folder of mediaFolders) {
isChecked = user.Policy.EnableContentDeletion || -1 != user.Policy.EnableContentDeletionFromFolders.indexOf(folder.Id);
isChecked = user.Policy.EnableContentDeletion || user.Policy.EnableContentDeletionFromFolders.indexOf(folder.Id) != -1;
checkedAttribute = isChecked ? ' checked="checked"' : '';
html += '<label><input type="checkbox" is="emby-checkbox" class="chkFolder" data-id="' + folder.Id + '" ' + checkedAttribute + '><span>' + folder.Name + '</span></label>';
}
for (const folder of channelsResult.Items) {
isChecked = user.Policy.EnableContentDeletion || -1 != user.Policy.EnableContentDeletionFromFolders.indexOf(folder.Id);
isChecked = user.Policy.EnableContentDeletion || user.Policy.EnableContentDeletionFromFolders.indexOf(folder.Id) != -1;
checkedAttribute = isChecked ? ' checked="checked"' : '';
html += '<label><input type="checkbox" is="emby-checkbox" class="chkFolder" data-id="' + folder.Id + '" ' + checkedAttribute + '><span>' + folder.Name + '</span></label>';
}
@ -96,7 +96,7 @@ import globalize from 'globalize';
$('#chkEnableVideoPlaybackTranscoding', page).prop('checked', user.Policy.EnableVideoPlaybackTranscoding);
$('#chkEnableVideoPlaybackRemuxing', page).prop('checked', user.Policy.EnablePlaybackRemuxing);
$('#chkForceRemoteSourceTranscoding', page).prop('checked', user.Policy.ForceRemoteSourceTranscoding);
$('#chkRemoteAccess', page).prop('checked', null == user.Policy.EnableRemoteAccess || user.Policy.EnableRemoteAccess);
$('#chkRemoteAccess', page).prop('checked', user.Policy.EnableRemoteAccess == null || user.Policy.EnableRemoteAccess);
$('#chkEnableSyncTranscoding', page).prop('checked', user.Policy.EnableSyncTranscoding);
$('#chkEnableConversion', page).prop('checked', user.Policy.EnableMediaConversion || false);
$('#chkEnableSharing', page).prop('checked', user.Policy.EnablePublicSharing);

View file

@ -0,0 +1,68 @@
<div id="userLibraryAccessPage" data-role="page" class="page type-interior">
<div>
<div class="content-primary">
<div class="verticalSection">
<div class="sectionTitleContainer flex align-items-center">
<h2 class="sectionTitle username"></h2>
<a is="emby-linkbutton" rel="noopener noreferrer" class="raised button-alt headerHelpButton" target="_blank" href="https://docs.jellyfin.org/general/server/users/">${Help}</a>
</div>
</div>
<div data-role="controlgroup" data-type="horizontal" class="localnav" data-mini="true">
<a is="emby-linkbutton" href="#" data-role="button" onclick="Dashboard.navigate('useredit.html', true);">${TabProfile}</a>
<a is="emby-linkbutton" href="#" data-role="button" onclick="Dashboard.navigate('userlibraryaccess.html', true);" class="ui-btn-active">${TabAccess}</a>
<a is="emby-linkbutton" href="#" data-role="button" onclick="Dashboard.navigate('userparentalcontrol.html', true);">${TabParentalControl}</a>
<a is="emby-linkbutton" href="#" data-role="button" onclick="Dashboard.navigate('userpassword.html', true);">${HeaderPassword}</a>
</div>
<form class="userLibraryAccessForm">
<div class="folderAccessContainer">
<h2>${HeaderLibraryAccess}</h2>
<label class="checkboxContainer">
<input type="checkbox" is="emby-checkbox" id="chkEnableAllFolders" />
<span>${OptionEnableAccessToAllLibraries}</span>
</label>
<div class="folderAccessListContainer">
<div class="folderAccess">
</div>
<div class="fieldDescription">${LibraryAccessHelp}</div>
</div>
</div>
<div class="channelAccessContainer" style="display:none;">
<h2>${HeaderChannelAccess}</h2>
<label class="checkboxContainer">
<input type="checkbox" is="emby-checkbox" id="chkEnableAllChannels" />
<span>${OptionEnableAccessToAllChannels}</span>
</label>
<div class="channelAccessListContainer">
<div class="channelAccess">
</div>
<div class="fieldDescription">${ChannelAccessHelp}</div>
</div>
</div>
<br />
<div class="deviceAccessContainer hide">
<h2>${HeaderDeviceAccess}</h2>
<label class="checkboxContainer">
<input type="checkbox" is="emby-checkbox" id="chkEnableAllDevices" />
<span>${OptionEnableAccessFromAllDevices}</span>
</label>
<div class="deviceAccessListContainer">
<div class="deviceAccess">
</div>
<div class="fieldDescription">${DeviceAccessHelp}</div>
</div>
<br />
</div>
<br />
<div>
<button is="emby-button" type="submit" class="raised button-submit block">
<span>${Save}</span>
</button>
</div>
</form>
</div>
</div>
</div>

View file

@ -18,7 +18,7 @@ import globalize from 'globalize';
for (let i = 0, length = mediaFolders.length; i < length; i++) {
const folder = mediaFolders[i];
const isChecked = user.Policy.EnableAllFolders || -1 != user.Policy.EnabledFolders.indexOf(folder.Id);
const isChecked = user.Policy.EnableAllFolders || user.Policy.EnabledFolders.indexOf(folder.Id) != -1;
const checkedAttribute = isChecked ? ' checked="checked"' : '';
html += '<label><input type="checkbox" is="emby-checkbox" class="chkFolder" data-id="' + folder.Id + '" ' + checkedAttribute + '><span>' + folder.Name + '</span></label>';
}
@ -32,12 +32,12 @@ import globalize from 'globalize';
function loadChannels(page, user, channels) {
let html = '';
html += '<h3 class="checkboxListLabel">' + globalize.translate('HeaderChannels') + '</h3>';
html += '<h3 class="checkboxListLabel">' + globalize.translate('Channels') + '</h3>';
html += '<div class="checkboxList paperList checkboxList-paperList">';
for (let i = 0, length = channels.length; i < length; i++) {
const folder = channels[i];
const isChecked = user.Policy.EnableAllChannels || -1 != user.Policy.EnabledChannels.indexOf(folder.Id);
const isChecked = user.Policy.EnableAllChannels || user.Policy.EnabledChannels.indexOf(folder.Id) != -1;
const checkedAttribute = isChecked ? ' checked="checked"' : '';
html += '<label><input type="checkbox" is="emby-checkbox" class="chkChannel" data-id="' + folder.Id + '" ' + checkedAttribute + '><span>' + folder.Name + '</span></label>';
}
@ -61,7 +61,7 @@ import globalize from 'globalize';
for (let i = 0, length = devices.length; i < length; i++) {
const device = devices[i];
const checkedAttribute = user.Policy.EnableAllDevices || -1 != user.Policy.EnabledDevices.indexOf(device.Id) ? ' checked="checked"' : '';
const checkedAttribute = user.Policy.EnableAllDevices || user.Policy.EnabledDevices.indexOf(device.Id) != -1 ? ' checked="checked"' : '';
html += '<label><input type="checkbox" is="emby-checkbox" class="chkDevice" data-id="' + device.Id + '" ' + checkedAttribute + '><span>' + device.Name + ' - ' + device.AppName + '</span></label>';
}

View file

@ -0,0 +1,62 @@
<div id="newUserPage" data-role="page" class="page type-interior">
<div>
<div class="content-primary">
<form class="newUserProfileForm">
<div class="verticalSection">
<div class="sectionTitleContainer flex align-items-center">
<h2 class="sectionTitle">${HeaderAddUser}</h2>
<a is="emby-linkbutton" rel="noopener noreferrer" class="raised button-alt headerHelpButton" target="_blank" href="https://docs.jellyfin.org/general/server/users/">${Help}</a>
</div>
<div class="inputContainer">
<input is="emby-input" id="txtUsername" required type="text" label="${LabelName}" />
</div>
<div class="inputContainer">
<input is="emby-input" id="txtPassword" type="password" label="${LabelPassword}" />
</div>
</div>
<div class="folderAccessContainer verticalSection">
<h2 class="sectionTitle">${HeaderLibraryAccess}</h2>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input type="checkbox" is="emby-checkbox" id="chkEnableAllFolders" />
<span>${OptionEnableAccessToAllLibraries}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${LibraryAccessHelp}</div>
</div>
<div class="folderAccessListContainer">
<div class="folderAccess">
</div>
</div>
</div>
<div class="channelAccessContainer verticalSection verticalSection-extrabottompadding" style="display:none;">
<h2 class="sectionTitle">${HeaderChannelAccess}</h2>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input type="checkbox" is="emby-checkbox" id="chkEnableAllChannels" />
<span>${OptionEnableAccessToAllChannels}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${ChannelAccessHelp}</div>
</div>
<div class="channelAccessListContainer">
<div class="channelAccess">
</div>
</div>
</div>
<div>
<button is="emby-button" type="submit" class="raised button-submit block">
<span>${Save}</span>
</button>
<button is="emby-button" type="button" class="raised button-cancel block btnCancel" onclick="history.back();">
<span>${ButtonCancel}</span>
</button>
</div>
</form>
</div>
</div>
</div>

View file

@ -22,7 +22,7 @@ import 'emby-checkbox';
function loadChannels(page, channels) {
let html = '';
html += '<h3 class="checkboxListLabel">' + globalize.translate('HeaderChannels') + '</h3>';
html += '<h3 class="checkboxListLabel">' + globalize.translate('Channels') + '</h3>';
html += '<div class="checkboxList paperList" style="padding:.5em 1em;">';
for (let i = 0; i < channels.length; i++) {
@ -89,7 +89,7 @@ import 'emby-checkbox';
});
}, function (response) {
import('toast').then(({default: toast}) => {
toast(globalize.translate('DefaultErrorMessage'));
toast(globalize.translate('ErrorDefault'));
});
loading.hide();

View file

@ -0,0 +1,60 @@
<div id="userParentalControlPage" data-role="page" class="page type-interior">
<div>
<div class="content-primary">
<div class="verticalSection">
<div class="sectionTitleContainer flex align-items-center">
<h2 class="sectionTitle username"></h2>
<a is="emby-linkbutton" rel="noopener noreferrer" class="raised button-alt headerHelpButton" target="_blank" href="https://docs.jellyfin.org/general/server/users/">${Help}</a>
</div>
</div>
<div data-role="controlgroup" data-type="horizontal" class="localnav" data-mini="true">
<a is="emby-linkbutton" href="#" data-role="button" onclick="Dashboard.navigate('useredit.html', true);">${TabProfile}</a>
<a is="emby-linkbutton" href="#" data-role="button" onclick="Dashboard.navigate('userlibraryaccess.html', true);">${TabAccess}</a>
<a is="emby-linkbutton" href="#" data-role="button" onclick="Dashboard.navigate('userparentalcontrol.html', true);" class="ui-btn-active">${TabParentalControl}</a>
<a is="emby-linkbutton" href="#" data-role="button" onclick="Dashboard.navigate('userpassword.html', true);">${HeaderPassword}</a>
</div>
<form class="userParentalControlForm">
<div class="selectContainer">
<select is="emby-select" id="selectMaxParentalRating" label="${LabelMaxParentalRating}"></select>
<div class="fieldDescription">${MaxParentalRatingHelp}</div>
</div>
<div>
<div class="blockUnratedItems"></div>
</div>
<br />
<div class="verticalSection" style="margin-bottom:2em;">
<div class="detailSectionHeader sectionTitleContainer">
<h2 class="sectionTitle">${LabelBlockContentWithTags}</h2>
<button is="emby-button" type="button" class="fab btnAddBlockedTag submit" style="margin-left:1em;" title="${Add}">
<span class="material-icons add"></span>
</button>
</div>
<div class="blockedTags" style="margin-top:.5em;"></div>
</div>
<div class="accessScheduleSection verticalSection" style="margin-bottom:2em;">
<div class="sectionTitleContainer">
<h2 class="sectionTitle">${HeaderAccessSchedule}</h2>
<button is="emby-button" type="button" class="fab btnAddSchedule submit" style="margin-left:1em;" title="${Add}">
<span class="material-icons add"></span>
</button>
</div>
<p>${HeaderAccessScheduleHelp}</p>
<div class="accessScheduleList paperList"></div>
</div>
<div>
<button is="emby-button" type="submit" class="raised button-submit block">
<span>${Save}</span>
</button>
</div>
</form>
</div>
</div>
</div>

View file

@ -67,7 +67,7 @@ import 'paper-icon-button-light';
for (let i = 0, length = items.length; i < length; i++) {
const item = items[i];
const checkedAttribute = -1 != user.Policy.BlockUnratedItems.indexOf(item.value) ? ' checked="checked"' : '';
const checkedAttribute = user.Policy.BlockUnratedItems.indexOf(item.value) != -1 ? ' checked="checked"' : '';
html += '<label><input type="checkbox" is="emby-checkbox" class="chkUnratedItem" data-itemtype="' + item.value + '" type="checkbox"' + checkedAttribute + '><span>' + item.name + '</span></label>';
}
@ -201,7 +201,7 @@ import 'paper-icon-button-light';
}).then(function (updatedSchedule) {
const schedules = getSchedulesFromPage(page);
if (-1 == index) {
if (index == -1) {
index = schedules.length;
}
@ -234,7 +234,7 @@ import 'paper-icon-button-light';
}).then(function (value) {
const tags = getBlockedTagsFromPage(page);
if (-1 == tags.indexOf(value)) {
if (tags.indexOf(value) == -1) {
tags.push(value);
loadBlockedTags(page, tags);
}

View file

@ -0,0 +1,72 @@
<div id="userPasswordPage" data-role="page" class="page type-interior userPasswordPage">
<div>
<div class="content-primary">
<div class="verticalSection">
<div class="sectionTitleContainer flex align-items-center">
<h2 class="sectionTitle username"></h2>
<a is="emby-linkbutton" rel="noopener noreferrer" class="raised button-alt headerHelpButton" target="_blank" href="https://docs.jellyfin.org/general/server/users/">${Help}</a>
</div>
</div>
<div data-role="controlgroup" data-type="horizontal" class="localnav" data-mini="true">
<a is="emby-linkbutton" href="#" data-role="button" onclick="Dashboard.navigate('useredit.html', true);">${TabProfile}</a>
<a is="emby-linkbutton" href="#" data-role="button" onclick="Dashboard.navigate('userlibraryaccess.html', true);">${TabAccess}</a>
<a is="emby-linkbutton" href="#" data-role="button" onclick="Dashboard.navigate('userparentalcontrol.html', true);">${TabParentalControl}</a>
<a is="emby-linkbutton" href="#" data-role="button" onclick="Dashboard.navigate('userpassword.html', true);" class="ui-btn-active">${HeaderPassword}</a>
</div>
<div class="readOnlyContent">
<form class="updatePasswordForm passwordSection hide" style="margin: 0 auto 2em;">
<div class="detailSection">
<div id="fldCurrentPassword" class="inputContainer hide">
<input is="emby-input" type="password" id="txtCurrentPassword" label="${LabelCurrentPassword}" autocomplete="off" />
</div>
<div class="inputContainer">
<input is="emby-input" type="password" id="txtNewPassword" label="${LabelNewPassword}" autocomplete="off" />
</div>
<div class="inputContainer">
<input is="emby-input" type="password" id="txtNewPasswordConfirm" label="${LabelNewPasswordConfirm}" autocomplete="off" />
</div>
<br />
<div>
<button is="emby-button" type="submit" class="raised button-submit block"><span>${Save}</span></button>
<button is="emby-button" type="button" id="btnResetPassword" class="raised button-cancel block hide">
<span>${ButtonResetPassword}</span>
</button>
</div>
</div>
</form>
<br />
<form class="localAccessForm localAccessSection" style="margin: 0 auto;">
<div class="detailSection">
<div class="detailSectionHeader">
${HeaderEasyPinCode}
</div>
<br />
<div>${EasyPasswordHelp}</div>
<br />
<div class="inputContainer">
<input is="emby-input" type="number" id="txtEasyPassword" label="${LabelEasyPinCode}" autocomplete="off" pattern="[0-9]*" step="1" maxlength="5" />
</div>
<br />
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input type="checkbox" is="emby-checkbox" class="chkEnableLocalEasyPassword" />
<span>${LabelInNetworkSignInWithEasyPassword}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${LabelInNetworkSignInWithEasyPasswordHelp}</div>
</div>
<div>
<button is="emby-button" type="submit" class="raised button-submit block">
<span>${Save}</span>
</button>
<button is="emby-button" type="button" id="btnResetEasyPassword" class="raised button-cancel block hide">
<span>${ButtonResetEasyPassword}</span>
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>

View file

@ -14,7 +14,7 @@ import 'emby-button';
let showPasswordSection = true;
let showLocalAccessSection = false;
if ('Guest' == user.ConnectLinkType) {
if (user.ConnectLinkType == 'Guest') {
page.querySelector('.localAccessSection').classList.add('hide');
showPasswordSection = false;
} else if (user.HasConfiguredPassword) {
@ -145,14 +145,14 @@ import 'emby-button';
function resetPassword() {
const msg = globalize.translate('PasswordResetConfirmation');
import('confirm').then(({default: confirm}) => {
confirm(msg, globalize.translate('PasswordResetHeader')).then(function () {
confirm(msg, globalize.translate('HeaderResetPassword')).then(function () {
const userId = params.userId;
loading.show();
ApiClient.resetUserPassword(userId).then(function () {
loading.hide();
Dashboard.alert({
message: globalize.translate('PasswordResetComplete'),
title: globalize.translate('PasswordResetHeader')
title: globalize.translate('HeaderResetPassword')
});
loadUser(view, params);
});

View file

@ -0,0 +1,16 @@
<div id="userProfilesPage" data-role="page" class="page type-interior userProfilesPage fullWidthContent">
<div>
<div class="content-primary">
<div class="verticalSection verticalSection-extrabottompadding">
<div class="sectionTitleContainer sectionTitleContainer-cards">
<h2 class="sectionTitle sectionTitle-cards">${HeaderUsers}</h2>
<button is="emby-button" type="button" class="fab btnAddUser submit sectionTitleButton" style="margin-left:1em;" title="${ButtonAddUser}">
<span class="material-icons add"></span>
</button>
<a is="emby-linkbutton" rel="noopener noreferrer" style="margin-left:2em!important;" class="raised button-alt headerHelpButton" target="_blank" href="https://docs.jellyfin.org/general/server/users/adding-managing-users.html">${Help}</a>
</div>
<div class="localUsers itemsContainer vertical-wrap"></div>
</div>
</div>
</div>
</div>

View file

@ -18,7 +18,7 @@ import 'flexStyles';
confirm({
title: globalize.translate('DeleteUser'),
text: msg,
confirmText: globalize.translate('ButtonDelete'),
confirmText: globalize.translate('Delete'),
primary: 'delete'
}).then(function () {
loading.show();
@ -50,7 +50,7 @@ import 'flexStyles';
icon: 'person'
});
menuItems.push({
name: globalize.translate('ButtonDelete'),
name: globalize.translate('Delete'),
id: 'delete',
icon: 'delete'
});
@ -129,7 +129,7 @@ import 'flexStyles';
html += '</div>';
html += '<div class="cardText cardText-secondary">';
const lastSeen = getLastSeenText(user.LastActivityDate);
html += '' != lastSeen ? lastSeen : '&nbsp;';
html += lastSeen != '' ? lastSeen : '&nbsp;';
html += '</div>';
html += '</div>';
html += '</div>';

View file

@ -0,0 +1,17 @@
<div id="editItemMetadataPage" data-role="page" class="page libraryPage metadataEditorPage noSecondaryNavPage" data-title="${MetadataManager}">
<style>
#editItemMetadataPage .editMetadataForm {
max-width: 100%;
}
</style>
<div class="editPageSidebar" style="overflow:auto;">
<div class="libraryTree">
<ul></ul>
</div>
</div>
<div>
<div class="editPageInnerContent padded-top padded-bottom-page">
</div>
</div>
</div>

View file

@ -1,31 +1,30 @@
define(['loading', 'scripts/editorsidebar'], function (loading) {
'use strict';
import loading from 'loading';
import 'scripts/editorsidebar';
function reload(context, itemId) {
loading.show();
function reload(context, itemId) {
loading.show();
if (itemId) {
require(['metadataEditor'], function ({default: metadataEditor}) {
metadataEditor.embed(context.querySelector('.editPageInnerContent'), itemId, ApiClient.serverInfo().Id);
});
} else {
context.querySelector('.editPageInnerContent').innerHTML = '';
loading.hide();
}
if (itemId) {
import('metadataEditor').then(({ default: metadataEditor }) => {
metadataEditor.embed(context.querySelector('.editPageInnerContent'), itemId, ApiClient.serverInfo().Id);
});
} else {
context.querySelector('.editPageInnerContent').innerHTML = '';
loading.hide();
}
}
return function (view, params) {
view.addEventListener('viewshow', function () {
reload(this, MetadataEditor.getCurrentItemId());
});
MetadataEditor.setCurrentItemId(null);
view.querySelector('.libraryTree').addEventListener('itemclicked', function (event) {
var data = event.detail;
export default function (view, params) {
view.addEventListener('viewshow', function () {
reload(this, MetadataEditor.getCurrentItemId());
});
MetadataEditor.setCurrentItemId(null);
view.querySelector('.libraryTree').addEventListener('itemclicked', function (event) {
var data = event.detail;
if (data.id != MetadataEditor.getCurrentItemId()) {
MetadataEditor.setCurrentItemId(data.id);
reload(view, data.id);
}
});
};
});
if (data.id != MetadataEditor.getCurrentItemId()) {
MetadataEditor.setCurrentItemId(data.id);
reload(view, data.id);
}
});
}

View file

@ -1,5 +1,15 @@
define(['appRouter', 'cardBuilder', 'dom', 'globalize', 'connectionManager', 'apphost', 'layoutManager', 'focusManager', 'emby-itemscontainer', 'emby-scroller'], function (appRouter, cardBuilder, dom, globalize, connectionManager, appHost, layoutManager, focusManager) {
'use strict';
import appRouter from 'appRouter';
import cardBuilder from 'cardBuilder';
import dom from 'dom';
import globalize from 'globalize';
import connectionManager from 'connectionManager';
import appHost from 'apphost';
import layoutManager from 'layoutManager';
import focusManager from 'focusManager';
import 'emby-itemscontainer';
import 'emby-scroller';
/* eslint-disable indent */
function enableScrollX() {
return true;
@ -133,8 +143,8 @@ define(['appRouter', 'cardBuilder', 'dom', 'globalize', 'connectionManager', 'ap
function getFetchDataFn(section) {
return function () {
var apiClient = this.apiClient;
var options = {
const apiClient = this.apiClient;
const options = {
SortBy: (section.types, 'SeriesName,SortName'),
SortOrder: 'Ascending',
Filters: 'IsFavorite',
@ -145,13 +155,13 @@ define(['appRouter', 'cardBuilder', 'dom', 'globalize', 'connectionManager', 'ap
EnableTotalRecordCount: false
};
options.Limit = 20;
var userId = apiClient.getCurrentUserId();
const userId = apiClient.getCurrentUserId();
if ('MusicArtist' === section.types) {
if (section.types === 'MusicArtist') {
return apiClient.getArtists(userId, options);
}
if ('Person' === section.types) {
if (section.types === 'Person') {
return apiClient.getPeople(userId, options);
}
@ -170,16 +180,16 @@ define(['appRouter', 'cardBuilder', 'dom', 'globalize', 'connectionManager', 'ap
function getItemsHtmlFn(section) {
return function (items) {
var cardLayout = appHost.preferVisualCards && section.autoCardLayout && section.showTitle;
let cardLayout = appHost.preferVisualCards && section.autoCardLayout && section.showTitle;
cardLayout = false;
var serverId = this.apiClient.serverId();
var leadingButtons = layoutManager.tv ? [{
const serverId = this.apiClient.serverId();
const leadingButtons = layoutManager.tv ? [{
name: globalize.translate('All'),
id: 'more',
icon: 'favorite',
routeUrl: getRouteUrl(section, serverId)
}] : null;
var lines = 0;
let lines = 0;
if (section.showTitle) {
lines++;
@ -198,7 +208,7 @@ define(['appRouter', 'cardBuilder', 'dom', 'globalize', 'connectionManager', 'ap
preferThumb: section.preferThumb,
shape: section.shape,
centerText: section.centerText && !cardLayout,
overlayText: false !== section.overlayText,
overlayText: section.overlayText !== false,
showTitle: section.showTitle,
showYear: section.showYear,
showParentTitle: section.showParentTitle,
@ -215,23 +225,12 @@ define(['appRouter', 'cardBuilder', 'dom', 'globalize', 'connectionManager', 'ap
};
}
function FavoritesTab(view, params) {
this.view = view;
this.params = params;
this.apiClient = connectionManager.currentApiClient();
this.sectionsContainer = view.querySelector('.sections');
createSections(this, this.sectionsContainer, this.apiClient);
}
function createSections(instance, elem, apiClient) {
var i;
var length;
var sections = getSections();
var html = '';
const sections = getSections();
let html = '';
for (i = 0, length = sections.length; i < length; i++) {
var section = sections[i];
var sectionClass = 'verticalSection';
for (const section of sections) {
let sectionClass = 'verticalSection';
if (!section.showTitle) {
sectionClass += ' verticalSection-extrabottompadding';
@ -257,23 +256,32 @@ define(['appRouter', 'cardBuilder', 'dom', 'globalize', 'connectionManager', 'ap
}
elem.innerHTML = html;
var elems = elem.querySelectorAll('.itemsContainer');
const elems = elem.querySelectorAll('.itemsContainer');
for (i = 0, length = elems.length; i < length; i++) {
var itemsContainer = elems[i];
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');
}
}
FavoritesTab.prototype.onResume = function (options) {
var promises = (this.apiClient, []);
var view = this.view;
var elems = this.sectionsContainer.querySelectorAll('.itemsContainer');
class FavoritesTab {
constructor(view, params) {
this.view = view;
this.params = params;
this.apiClient = connectionManager.currentApiClient();
this.sectionsContainer = view.querySelector('.sections');
createSections(this, this.sectionsContainer, this.apiClient);
}
for (var i = 0, length = elems.length; i < length; i++) {
promises.push(elems[i].resume(options));
onResume(options) {
const promises = (this.apiClient, []);
const view = this.view;
const elems = this.sectionsContainer.querySelectorAll('.itemsContainer');
for (const elem of elems) {
promises.push(elem.resume(options));
}
Promise.all(promises).then(function () {
@ -281,30 +289,32 @@ define(['appRouter', 'cardBuilder', 'dom', 'globalize', 'connectionManager', 'ap
focusManager.autoFocus(view);
}
});
};
}
FavoritesTab.prototype.onPause = function () {
var elems = this.sectionsContainer.querySelectorAll('.itemsContainer');
onPause() {
const elems = this.sectionsContainer.querySelectorAll('.itemsContainer');
for (var i = 0, length = elems.length; i < length; i++) {
elems[i].pause();
for (const elem of elems) {
elem.pause();
}
};
}
FavoritesTab.prototype.destroy = function () {
destroy() {
this.view = null;
this.params = null;
this.apiClient = null;
var elems = this.sectionsContainer.querySelectorAll('.itemsContainer');
const elems = this.sectionsContainer.querySelectorAll('.itemsContainer');
for (var i = 0, length = elems.length; i < length; i++) {
elems[i].fetchData = null;
elems[i].getItemsHtml = null;
elems[i].parentContainer = null;
for (const elem of elems) {
elem.fetchData = null;
elem.getItemsHtml = null;
elem.parentContainer = null;
}
this.sectionsContainer = null;
};
}
}
return FavoritesTab;
});
export default FavoritesTab;
/* eslint-enable indent */

View file

@ -0,0 +1,9 @@
<div id="indexPage" style="outline: none;" data-role="page" data-dom-cache="true" class="page homePage libraryPage allLibraryPage backdropPage pageWithAbsoluteTabs withTabs" data-backdroptype="movie,series,book">
<div class="tabContent pageTabContent" id="homeTab" data-index="0">
<div class="sections"></div>
</div>
<div class="tabContent pageTabContent" id="favoritesTab" data-index="1">
<div class="sections"></div>
</div>
</div>

View file

@ -1,7 +1,33 @@
define(['tabbedView', 'globalize', 'require', 'emby-tabs', 'emby-button', 'emby-scroller'], function (TabbedView, globalize, require) {
'use strict';
import TabbedView from 'tabbedView';
import globalize from 'globalize';
import 'emby-tabs';
import 'emby-button';
import 'emby-scroller';
function getTabs() {
class HomeView extends TabbedView {
constructor(view, params) {
super(view, params);
}
setTitle() {
Emby.Page.setTitle(null);
}
onPause() {
super.onPause(this);
document.querySelector('.skinHeader').classList.remove('noHomeButtonHeader');
}
onResume(options) {
super.onResume(this, options);
document.querySelector('.skinHeader').classList.add('noHomeButtonHeader');
}
getDefaultTabIndex() {
return 0;
}
getTabs() {
return [{
name: globalize.translate('Home')
}, {
@ -9,35 +35,25 @@ define(['tabbedView', 'globalize', 'require', 'emby-tabs', 'emby-button', 'emby-
}];
}
function getDefaultTabIndex() {
return 0;
}
function getRequirePromise(deps) {
return new Promise(function (resolve, reject) {
require(deps, resolve);
});
}
function getTabController(index) {
if (null == index) {
getTabController(index) {
if (index == null) {
throw new Error('index cannot be null');
}
var depends = [];
let depends = '';
switch (index) {
case 0:
depends.push('controllers/hometab');
depends = 'controllers/hometab';
break;
case 1:
depends.push('controllers/favorites');
depends = 'controllers/favorites';
}
var instance = this;
return getRequirePromise(depends).then(function (controllerFactory) {
var controller = instance.tabControllers[index];
const instance = this;
return import(depends).then(({ default: controllerFactory }) => {
let controller = instance.tabControllers[index];
if (!controller) {
controller = new controllerFactory(instance.view.querySelector(".tabContent[data-index='" + index + "']"), instance.params);
@ -47,29 +63,6 @@ define(['tabbedView', 'globalize', 'require', 'emby-tabs', 'emby-button', 'emby-
return controller;
});
}
}
function HomeView(view, params) {
TabbedView.call(this, view, params);
}
Object.assign(HomeView.prototype, TabbedView.prototype);
HomeView.prototype.getTabs = getTabs;
HomeView.prototype.getDefaultTabIndex = getDefaultTabIndex;
HomeView.prototype.getTabController = getTabController;
HomeView.prototype.setTitle = function () {
Emby.Page.setTitle(null);
};
HomeView.prototype.onPause = function () {
TabbedView.prototype.onPause.call(this);
document.querySelector('.skinHeader').classList.remove('noHomeButtonHeader');
};
HomeView.prototype.onResume = function (options) {
TabbedView.prototype.onResume.call(this, options);
document.querySelector('.skinHeader').classList.add('noHomeButtonHeader');
};
return HomeView;
});
export default HomeView;

View file

@ -1,27 +1,21 @@
define(['userSettings', 'loading', 'connectionManager', 'apphost', 'layoutManager', 'focusManager', 'homeSections', 'emby-itemscontainer'], function (userSettings, loading, connectionManager, appHost, layoutManager, focusManager, homeSections) {
'use strict';
import * as userSettings from 'userSettings';
import loading from 'loading';
import connectionManager from 'connectionManager';
import focusManager from 'focusManager';
import homeSections from 'homeSections';
import 'emby-itemscontainer';
function HomeTab(view, params) {
class HomeTab {
constructor(view, params) {
this.view = view;
this.params = params;
this.apiClient = connectionManager.currentApiClient();
this.sectionsContainer = view.querySelector('.sections');
view.querySelector('.sections').addEventListener('settingschange', onHomeScreenSettingsChanged.bind(this));
}
function onHomeScreenSettingsChanged() {
this.sectionsRendered = false;
if (!this.paused) {
this.onResume({
refresh: true
});
}
}
HomeTab.prototype.onResume = function (options) {
onResume(options) {
if (this.sectionsRendered) {
var sectionsContainer = this.sectionsContainer;
const sectionsContainer = this.sectionsContainer;
if (sectionsContainer) {
return homeSections.resume(sectionsContainer, options);
@ -31,8 +25,8 @@ define(['userSettings', 'loading', 'connectionManager', 'apphost', 'layoutManage
}
loading.show();
var view = this.view;
var apiClient = this.apiClient;
const view = this.view;
const apiClient = this.apiClient;
this.destroyHomeSections();
this.sectionsRendered = true;
return apiClient.getCurrentUser().then(function (user) {
@ -44,31 +38,38 @@ define(['userSettings', 'loading', 'connectionManager', 'apphost', 'layoutManage
loading.hide();
});
});
};
HomeTab.prototype.onPause = function () {
var sectionsContainer = this.sectionsContainer;
}
onPause() {
const sectionsContainer = this.sectionsContainer;
if (sectionsContainer) {
homeSections.pause(sectionsContainer);
}
};
HomeTab.prototype.destroy = function () {
}
destroy() {
this.view = null;
this.params = null;
this.apiClient = null;
this.destroyHomeSections();
this.sectionsContainer = null;
};
HomeTab.prototype.destroyHomeSections = function () {
var sectionsContainer = this.sectionsContainer;
}
destroyHomeSections() {
const sectionsContainer = this.sectionsContainer;
if (sectionsContainer) {
homeSections.destroySections(sectionsContainer);
}
};
}
}
return HomeTab;
});
function onHomeScreenSettingsChanged() {
this.sectionsRendered = false;
if (!this.paused) {
this.onResume({
refresh: true
});
}
}
export default HomeTab;

View file

@ -24,7 +24,7 @@
</div>
</button>
<button is="emby-button" type="button" class="button-flat btnDownload hide detailButton" title="${ButtonDownload}">
<button is="emby-button" type="button" class="button-flat btnDownload hide detailButton" title="${Download}">
<div class="detailButton-content">
<span class="material-icons detailButton-icon get_app"></span>
</div>
@ -42,7 +42,7 @@
</div>
</button>
<button is="emby-button" type="button" class="button-flat btnShuffle hide detailButton" title="${ButtonShuffle}">
<button is="emby-button" type="button" class="button-flat btnShuffle hide detailButton" title="${Shuffle}">
<div class="detailButton-content">
<span class="material-icons detailButton-icon shuffle"></span>
</div>
@ -147,14 +147,14 @@
</div>
<div class="seriesTimerScheduleSection verticalSection detailVerticalSection hide" style="margin-top:-3em;">
<h2 class="sectionTitle">${HeaderSchedule}</h2>
<h2 class="sectionTitle">${Schedule}</h2>
<div class="seriesTimerSchedule padded-right"></div>
</div>
<div class="collectionItems hide"></div>
<div class="nextUpSection verticalSection detailVerticalSection hide">
<h2 class="sectionTitle sectionTitle-cards">${HeaderNextUp}</h2>
<h2 class="sectionTitle sectionTitle-cards">${NextUp}</h2>
<div is="emby-itemscontainer" class="nextUpItems vertical-wrap padded-right"></div>
</div>

File diff suppressed because it is too large Load diff

54
src/controllers/list.html Normal file
View file

@ -0,0 +1,54 @@
<div data-role="page" class="view flex flex-direction-column page libraryPage noSecondaryNavPage" data-backbutton="true">
<div class="alphaPicker alphaPicker-vertical alphaPicker-fixed focuscontainer-y hide">
</div>
<div class="flex-grow padded-left padded-right pageContainerTopPadding">
<div class="flex align-items-center focuscontainer-x itemsViewSettingsContainer padded-top padded-bottom flex-wrap-wrap">
<button is="emby-button" class="btnPlay button-flat hide listTextButton-autohide">
<span>${HeaderPlayAll}</span>
</button>
<button is="paper-icon-button-light" class="btnPlay listIconButton-autohide">
<span class="material-icons play_arrow"></span>
</button>
<button is="emby-button" class="btnQueue button-flat hide listTextButton-autohide">
<span>${Queue}</span>
</button>
<button is="paper-icon-button-light" class="btnQueue hide listIconButton-autohide">
<span class="material-icons playlist_add"></span>
</button>
<button is="emby-button" class="btnShuffle button-flat hide listTextButton-autohide">
<span>${Shuffle}</span>
</button>
<button is="paper-icon-button-light" class="btnShuffle hide listIconButton-autohide">
<span class="material-icons shuffle"></span>
</button>
<button is="emby-button" class="btnNewItem hide button-flat listTextButton-autohide">
<span>${New}</span>
</button>
<button is="paper-icon-button-light" class="btnNewItem hide listIconButton-autohide">
<span class="material-icons add"></span>
</button>
<button is="emby-button" class="btnSort hide button-flat listTextButton-autohide">
<span class="btnSortText"></span>
<span class="material-icons btnSortIcon arrow_upward"></span>
</button>
<button is="paper-icon-button-light" class="btnSort hide listIconButton-autohide">
<span class="material-icons sort"></span>
</button>
<button is="emby-button" class="btnFilter button-flat listTextButton-autohide">
<span>${Filter}</span>
</button>
<button is="paper-icon-button-light" class="btnFilter listIconButton-autohide" data-ripple="false" style="overflow:visible;">
<span class="material-icons filter_list"></span>
</button>
<button is="emby-button" class="btnViewSettings button-flat listTextButton-autohide">
<span class="material-icons more_vert"></span>
</button>
<button is="paper-icon-button-light" class="btnViewSettings listIconButton-autohide">
<span class="material-icons more_vert"></span>
</button>
</div>
<div is="emby-itemscontainer" class="vertical-wrap itemsContainer centered" style="padding: 0 0 10vh 0;">
</div>
</div>
</div>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,97 @@
<div id="liveTvSuggestedPage" data-dom-cache="true" data-role="page" class="page libraryPage liveTvPage pageWithAbsoluteTabs withTabs" data-title="${LiveTV}" data-backdroptype="series,movie">
<div class="liveTvContainer">
<div class="pageTabContent" id="suggestionsTab" data-index="0">
<div id="activePrograms" class="verticalSection">
<div class="sectionTitleContainer sectionTitleContainer-cards padded-left">
<a href="list.html?type=Programs&IsAiring=true" is="emby-linkbutton" class="button-flat button-flat-mini sectionTitleTextButton sectionTitleTextButton-programs">
<h2 class="sectionTitle sectionTitle-cards" style="display: inline-block; vertical-align: middle;">${HeaderOnNow}</h2>
<span class="material-icons chevron_right"></span>
</a>
</div>
<div is="emby-itemscontainer" class="activeProgramItems itemsContainer padded-left padded-right"></div>
</div>
<div id="upcomingEpisodes" class="verticalSection">
<div class="sectionTitleContainer sectionTitleContainer-cards padded-left">
<a href="list.html?type=Programs&IsSeries=true&IsMovie=false&IsNews=false" is="emby-linkbutton" class="button-flat button-flat-mini sectionTitleTextButton sectionTitleTextButton-programs">
<h2 class="sectionTitle sectionTitle-cards" style="display: inline-block; vertical-align: middle;">${Shows}</h2>
<span class="material-icons chevron_right"></span>
</a>
</div>
<div is="emby-itemscontainer" class="upcomingEpisodeItems itemsContainer padded-left padded-right"></div>
</div>
<div id="upcomingTvMovies" class="verticalSection">
<div class="sectionTitleContainer sectionTitleContainer-cards padded-left">
<a href="list.html?type=Programs&IsMovie=true" is="emby-linkbutton" class="button-flat button-flat-mini sectionTitleTextButton sectionTitleTextButton-programs">
<h2 class="sectionTitle sectionTitle-cards" style="display: inline-block; vertical-align: middle;">${Movies}</h2>
<span class="material-icons chevron_right"></span>
</a>
</div>
<div is="emby-itemscontainer" class="upcomingTvMovieItems itemsContainer padded-left padded-right"></div>
</div>
<div id="upcomingSports" class="verticalSection">
<div class="sectionTitleContainer sectionTitleContainer-cards padded-left">
<a href="list.html?type=Programs&IsSports=true" is="emby-linkbutton" class="button-flat button-flat-mini sectionTitleTextButton sectionTitleTextButton-programs">
<h2 class="sectionTitle sectionTitle-cards" style="display: inline-block; vertical-align: middle;">${Sports}</h2>
<span class="material-icons chevron_right"></span>
</a>
</div>
<div is="emby-itemscontainer" class="upcomingSportsItems itemsContainer padded-left padded-right"></div>
</div>
<div id="upcomingKids" class="verticalSection">
<div class="sectionTitleContainer sectionTitleContainer-cards padded-left">
<a href="list.html?type=Programs&IsKids=true" is="emby-linkbutton" class="button-flat button-flat-mini sectionTitleTextButton sectionTitleTextButton-programs">
<h2 class="sectionTitle sectionTitle-cards" style="display: inline-block; vertical-align: middle;">${HeaderForKids}</h2>
<span class="material-icons chevron_right"></span>
</a>
</div>
<div is="emby-itemscontainer" class="upcomingKidsItems itemsContainer padded-left padded-right"></div>
</div>
<div id="upcomingNews" class="verticalSection">
<div class="sectionTitleContainer sectionTitleContainer-cards padded-left">
<a href="list.html?type=Programs&IsNews=true" is="emby-linkbutton" class="button-flat button-flat-mini sectionTitleTextButton sectionTitleTextButton-programs">
<h2 class="sectionTitle sectionTitle-cards" style="display: inline-block; vertical-align: middle;">${News}</h2>
<span class="material-icons chevron_right"></span>
</a>
</div>
<div is="emby-itemscontainer" class="upcomingNewsItems itemsContainer padded-left padded-right"></div>
</div>
</div>
<div class="pageTabContent flexPageTabContent absolutePageTabContent" id="guideTab" data-index="1" style="width:auto;padding-top:0; padding-bottom: 0!important;">
</div>
<div class="pageTabContent" id="channelsTab" data-index="2">
<div class="flex align-items-center justify-content-center flex-wrap-wrap padded-top padded-left padded-right padded-bottom">
<div class="paging"></div>
<button is="paper-icon-button-light" class="btnFilter sectionTitleButton" title="${ButtonFilter}"><span class="material-icons filter_list"></span></button>
</div>
<div is="emby-itemscontainer" id="items" class="itemsContainer vertical-wrap padded-left padded-right"></div>
</div>
<div class="pageTabContent" id="recordingsTab" data-index="3">
<div id="latestRecordings" class="verticalSection hide">
<div class="sectionTitleContainer sectionTitleContainer-cards padded-left">
<button type="button" is="emby-button" class="button-flat button-flat-mini sectionTitleTextButton more" data-type="latest">
<h2 class="sectionTitle sectionTitle-cards">${HeaderLatestRecordings}</h2>
<span class="material-icons chevron_right"></span>
</button>
</div>
<div is="emby-itemscontainer" class="recordingItems itemsContainer padded-left padded-right"></div>
</div>
</div>
<div class="pageTabContent" id="scheduleTab" data-index="4">
<div id="activeRecordings" class="verticalSection hide">
<h2 class="sectionTitle sectionTitle-cards padded-left">${HeaderActiveRecordings}</h2>
<div is="emby-itemscontainer" class="recordingItems itemsContainer padded-left padded-right"></div>
</div>
<div id="upcomingRecordings" class="hide">
<div class="recordingItems"></div>
</div>
</div>
<div class="pageTabContent padded-top" id="seriesTab" data-index="5">
<div is="emby-itemscontainer" class="vertical-wrap itemsContainer centered padded-left padded-right" id="items"></div>
</div>
<div class="pageTabContent" data-index="6">
</div>
</div>
</div>

View file

@ -1,132 +1,134 @@
define(['cardBuilder', 'imageLoader', 'libraryBrowser', 'loading', 'events', 'userSettings', 'emby-itemscontainer'], function (cardBuilder, imageLoader, libraryBrowser, loading, events, userSettings) {
'use strict';
import cardBuilder from 'cardBuilder';
import imageLoader from 'imageLoader';
import libraryBrowser from 'libraryBrowser';
import loading from 'loading';
import events from 'events';
import * as userSettings from 'userSettings';
import 'emby-itemscontainer';
libraryBrowser = libraryBrowser.default || libraryBrowser;
export default function (view, params, tabContent) {
function getPageData() {
if (!pageData) {
pageData = {
query: {
StartIndex: 0,
Fields: 'PrimaryImageAspectRatio'
}
};
}
return function (view, params, tabContent) {
function getPageData() {
if (!pageData) {
pageData = {
query: {
StartIndex: 0,
Fields: 'PrimaryImageAspectRatio'
}
};
if (userSettings.libraryPageSize() > 0) {
pageData.query['Limit'] = userSettings.libraryPageSize();
}
return pageData;
}
function getQuery() {
return getPageData().query;
}
function getChannelsHtml(channels) {
return cardBuilder.getCardsHtml({
items: channels,
shape: 'square',
showTitle: true,
lazy: true,
cardLayout: true,
showDetailsMenu: true,
showCurrentProgram: true,
showCurrentProgramTime: true
});
}
function renderChannels(context, result) {
function onNextPageClick() {
if (isLoading) {
return;
}
if (userSettings.libraryPageSize() > 0) {
pageData.query['Limit'] = userSettings.libraryPageSize();
query.StartIndex += query.Limit;
}
reloadItems(context);
}
function onPreviousPageClick() {
if (isLoading) {
return;
}
return pageData;
}
function getQuery() {
return getPageData().query;
}
function getChannelsHtml(channels) {
return cardBuilder.getCardsHtml({
items: channels,
shape: 'square',
showTitle: true,
lazy: true,
cardLayout: true,
showDetailsMenu: true,
showCurrentProgram: true,
showCurrentProgramTime: true
});
}
function renderChannels(context, result) {
function onNextPageClick() {
if (isLoading) {
return;
}
if (userSettings.libraryPageSize() > 0) {
query.StartIndex += query.Limit;
}
reloadItems(context);
}
function onPreviousPageClick() {
if (isLoading) {
return;
}
if (userSettings.libraryPageSize() > 0) {
query.StartIndex = Math.max(0, query.StartIndex - query.Limit);
}
reloadItems(context);
}
var query = getQuery();
context.querySelector('.paging').innerHTML = libraryBrowser.getQueryPagingHtml({
startIndex: query.StartIndex,
limit: query.Limit,
totalRecordCount: result.TotalRecordCount,
showLimit: false,
updatePageSizeSetting: false,
filterButton: false
});
var html = getChannelsHtml(result.Items);
var elem = context.querySelector('#items');
elem.innerHTML = html;
imageLoader.lazyChildren(elem);
var i;
var length;
var elems;
for (elems = context.querySelectorAll('.btnNextPage'), i = 0, length = elems.length; i < length; i++) {
elems[i].addEventListener('click', onNextPageClick);
}
for (elems = context.querySelectorAll('.btnPreviousPage'), i = 0, length = elems.length; i < length; i++) {
elems[i].addEventListener('click', onPreviousPageClick);
if (userSettings.libraryPageSize() > 0) {
query.StartIndex = Math.max(0, query.StartIndex - query.Limit);
}
reloadItems(context);
}
function showFilterMenu(context) {
require(['components/filterdialog/filterdialog'], function ({default: filterDialogFactory}) {
var filterDialog = new filterDialogFactory({
query: getQuery(),
mode: 'livetvchannels',
serverId: ApiClient.serverId()
});
events.on(filterDialog, 'filterchange', function () {
reloadItems(context);
});
filterDialog.show();
});
}
function reloadItems(context, save) {
loading.show();
isLoading = true;
var query = getQuery();
var apiClient = ApiClient;
query.UserId = apiClient.getCurrentUserId();
apiClient.getLiveTvChannels(query).then(function (result) {
renderChannels(context, result);
loading.hide();
isLoading = false;
require(['autoFocuser'], function (autoFocuser) {
autoFocuser.autoFocus(view);
});
});
}
var pageData;
var self = this;
var isLoading = false;
tabContent.querySelector('.btnFilter').addEventListener('click', function () {
showFilterMenu(tabContent);
const query = getQuery();
context.querySelector('.paging').innerHTML = libraryBrowser.getQueryPagingHtml({
startIndex: query.StartIndex,
limit: query.Limit,
totalRecordCount: result.TotalRecordCount,
showLimit: false,
updatePageSizeSetting: false,
filterButton: false
});
const html = getChannelsHtml(result.Items);
const elem = context.querySelector('#items');
elem.innerHTML = html;
imageLoader.lazyChildren(elem);
let i;
let length;
let elems;
self.renderTab = function () {
reloadItems(tabContent);
};
for (elems = context.querySelectorAll('.btnNextPage'), i = 0, length = elems.length; i < length; i++) {
elems[i].addEventListener('click', onNextPageClick);
}
for (elems = context.querySelectorAll('.btnPreviousPage'), i = 0, length = elems.length; i < length; i++) {
elems[i].addEventListener('click', onPreviousPageClick);
}
}
function showFilterMenu(context) {
import(['components/filterdialog/filterdialog']).then(({default: FilterDialog}) => {
const filterDialog = new FilterDialog({
query: getQuery(),
mode: 'livetvchannels',
serverId: ApiClient.serverId()
});
events.on(filterDialog, 'filterchange', function () {
reloadItems(context);
});
filterDialog.show();
});
}
function reloadItems(context, save) {
loading.show();
isLoading = true;
const query = getQuery();
const apiClient = ApiClient;
query.UserId = apiClient.getCurrentUserId();
apiClient.getLiveTvChannels(query).then(function (result) {
renderChannels(context, result);
loading.hide();
isLoading = false;
import('autoFocuser').then(({default: autoFocuser}) => {
autoFocuser.autoFocus(view);
});
});
}
let pageData;
const self = this;
let isLoading = false;
tabContent.querySelector('.btnFilter').addEventListener('click', function () {
showFilterMenu(tabContent);
});
self.renderTab = function () {
reloadItems(tabContent);
};
});
}

View file

@ -1,29 +1,27 @@
define(['tvguide'], function (tvguide) {
'use strict';
import tvguide from 'tvguide';
return function (view, params, tabContent) {
var guideInstance;
var self = this;
export default function (view, params, tabContent) {
let guideInstance;
const self = this;
self.renderTab = function () {
if (!guideInstance) {
guideInstance = new tvguide({
element: tabContent,
serverId: ApiClient.serverId()
});
}
};
self.onShow = function () {
if (guideInstance) {
guideInstance.resume();
}
};
self.onHide = function () {
if (guideInstance) {
guideInstance.pause();
}
};
self.renderTab = function () {
if (!guideInstance) {
guideInstance = new tvguide({
element: tabContent,
serverId: ApiClient.serverId()
});
}
};
});
self.onShow = function () {
if (guideInstance) {
guideInstance.resume();
}
};
self.onHide = function () {
if (guideInstance) {
guideInstance.pause();
}
};
}

View file

@ -1,109 +1,112 @@
define(['layoutManager', 'loading', 'cardBuilder', 'apphost', 'imageLoader', 'scripts/livetvcomponents', 'listViewStyle', 'emby-itemscontainer'], function (layoutManager, loading, cardBuilder, appHost, imageLoader) {
'use strict';
import loading from 'loading';
import cardBuilder from 'cardBuilder';
import imageLoader from 'imageLoader';
import 'scripts/livetvcomponents';
import 'listViewStyle';
import 'emby-itemscontainer';
function renderRecordings(elem, recordings, cardOptions, scrollX) {
if (!elem) {
return;
}
if (recordings.length) {
elem.classList.remove('hide');
} else {
elem.classList.add('hide');
}
var recordingItems = elem.querySelector('.recordingItems');
if (scrollX) {
recordingItems.classList.add('scrollX');
recordingItems.classList.add('hiddenScrollX');
recordingItems.classList.remove('vertical-wrap');
} else {
recordingItems.classList.remove('scrollX');
recordingItems.classList.remove('hiddenScrollX');
recordingItems.classList.add('vertical-wrap');
}
recordingItems.innerHTML = cardBuilder.getCardsHtml(Object.assign({
items: recordings,
shape: scrollX ? 'autooverflow' : 'auto',
defaultShape: scrollX ? 'overflowBackdrop' : 'backdrop',
showTitle: true,
showParentTitle: true,
coverImage: true,
cardLayout: false,
centerText: true,
allowBottomPadding: !scrollX,
preferThumb: 'auto',
overlayText: false
}, cardOptions || {}));
imageLoader.lazyChildren(recordingItems);
function renderRecordings(elem, recordings, cardOptions, scrollX) {
if (!elem) {
return;
}
function renderLatestRecordings(context, promise) {
promise.then(function (result) {
renderRecordings(context.querySelector('#latestRecordings'), result.Items, {
showYear: true,
lines: 2
}, false);
loading.hide();
});
if (recordings.length) {
elem.classList.remove('hide');
} else {
elem.classList.add('hide');
}
function renderRecordingFolders(context, promise) {
promise.then(function (result) {
renderRecordings(context.querySelector('#recordingFolders'), result.Items, {
showYear: false,
showParentTitle: false
}, false);
});
const recordingItems = elem.querySelector('.recordingItems');
if (scrollX) {
recordingItems.classList.add('scrollX');
recordingItems.classList.add('hiddenScrollX');
recordingItems.classList.remove('vertical-wrap');
} else {
recordingItems.classList.remove('scrollX');
recordingItems.classList.remove('hiddenScrollX');
recordingItems.classList.add('vertical-wrap');
}
function onMoreClick(e) {
var type = this.getAttribute('data-type');
var serverId = ApiClient.serverId();
recordingItems.innerHTML = cardBuilder.getCardsHtml(Object.assign({
items: recordings,
shape: scrollX ? 'autooverflow' : 'auto',
defaultShape: scrollX ? 'overflowBackdrop' : 'backdrop',
showTitle: true,
showParentTitle: true,
coverImage: true,
cardLayout: false,
centerText: true,
allowBottomPadding: !scrollX,
preferThumb: 'auto',
overlayText: false
}, cardOptions || {}));
imageLoader.lazyChildren(recordingItems);
}
switch (type) {
case 'latest':
Dashboard.navigate('list.html?type=Recordings&serverId=' + serverId);
}
function renderLatestRecordings(context, promise) {
promise.then(function (result) {
renderRecordings(context.querySelector('#latestRecordings'), result.Items, {
showYear: true,
lines: 2
}, false);
loading.hide();
});
}
function renderRecordingFolders(context, promise) {
promise.then(function (result) {
renderRecordings(context.querySelector('#recordingFolders'), result.Items, {
showYear: false,
showParentTitle: false
}, false);
});
}
function onMoreClick(e) {
const type = this.getAttribute('data-type');
const serverId = ApiClient.serverId();
switch (type) {
case 'latest':
Dashboard.navigate('list.html?type=Recordings&serverId=' + serverId);
}
}
export default function (view, params, tabContent) {
function enableFullRender() {
return new Date().getTime() - lastFullRender > 300000;
}
return function (view, params, tabContent) {
function enableFullRender() {
return new Date().getTime() - lastFullRender > 300000;
let foldersPromise;
let latestPromise;
const self = this;
let lastFullRender = 0;
const moreButtons = tabContent.querySelectorAll('.more');
for (let i = 0, length = moreButtons.length; i < length; i++) {
moreButtons[i].addEventListener('click', onMoreClick);
}
self.preRender = function () {
if (enableFullRender()) {
latestPromise = ApiClient.getLiveTvRecordings({
UserId: Dashboard.getCurrentUserId(),
Limit: 12,
Fields: 'CanDelete,PrimaryImageAspectRatio,BasicSyncInfo',
EnableTotalRecordCount: false,
EnableImageTypes: 'Primary,Thumb,Backdrop'
});
foldersPromise = ApiClient.getRecordingFolders(Dashboard.getCurrentUserId());
}
var foldersPromise;
var latestPromise;
var self = this;
var lastFullRender = 0;
var moreButtons = tabContent.querySelectorAll('.more');
for (var i = 0, length = moreButtons.length; i < length; i++) {
moreButtons[i].addEventListener('click', onMoreClick);
}
self.preRender = function () {
if (enableFullRender()) {
latestPromise = ApiClient.getLiveTvRecordings({
UserId: Dashboard.getCurrentUserId(),
Limit: 12,
Fields: 'CanDelete,PrimaryImageAspectRatio,BasicSyncInfo',
EnableTotalRecordCount: false,
EnableImageTypes: 'Primary,Thumb,Backdrop'
});
foldersPromise = ApiClient.getRecordingFolders(Dashboard.getCurrentUserId());
}
};
self.renderTab = function () {
if (enableFullRender()) {
loading.show();
renderLatestRecordings(tabContent, latestPromise);
renderRecordingFolders(tabContent, foldersPromise);
lastFullRender = new Date().getTime();
}
};
};
});
self.renderTab = function () {
if (enableFullRender()) {
loading.show();
renderLatestRecordings(tabContent, latestPromise);
renderRecordingFolders(tabContent, foldersPromise);
lastFullRender = new Date().getTime();
}
};
}

View file

@ -1,120 +1,124 @@
define(['layoutManager', 'cardBuilder', 'apphost', 'imageLoader', 'loading', 'scripts/livetvcomponents', 'emby-button', 'emby-itemscontainer'], function (layoutManager, cardBuilder, appHost, imageLoader, loading) {
'use strict';
import layoutManager from 'layoutManager';
import cardBuilder from 'cardBuilder';
import imageLoader from 'imageLoader';
import loading from 'loading';
import 'scripts/livetvcomponents';
import 'emby-button';
import 'emby-itemscontainer';
function enableScrollX() {
return !layoutManager.desktop;
function enableScrollX() {
return !layoutManager.desktop;
}
function renderRecordings(elem, recordings, cardOptions) {
if (recordings.length) {
elem.classList.remove('hide');
} else {
elem.classList.add('hide');
}
function renderRecordings(elem, recordings, cardOptions) {
if (recordings.length) {
const recordingItems = elem.querySelector('.recordingItems');
if (enableScrollX()) {
recordingItems.classList.add('scrollX');
if (layoutManager.tv) {
recordingItems.classList.add('smoothScrollX');
}
recordingItems.classList.add('hiddenScrollX');
recordingItems.classList.remove('vertical-wrap');
} else {
recordingItems.classList.remove('scrollX');
recordingItems.classList.remove('smoothScrollX');
recordingItems.classList.remove('hiddenScrollX');
recordingItems.classList.add('vertical-wrap');
}
recordingItems.innerHTML = cardBuilder.getCardsHtml(Object.assign({
items: recordings,
shape: enableScrollX() ? 'autooverflow' : 'auto',
showTitle: true,
showParentTitle: true,
coverImage: true,
cardLayout: false,
centerText: true,
allowBottomPadding: !enableScrollX(),
preferThumb: 'auto'
}, cardOptions || {}));
imageLoader.lazyChildren(recordingItems);
}
function getBackdropShape() {
return enableScrollX() ? 'overflowBackdrop' : 'backdrop';
}
function renderActiveRecordings(context, promise) {
promise.then(function (result) {
renderRecordings(context.querySelector('#activeRecordings'), result.Items, {
shape: enableScrollX() ? 'autooverflow' : 'auto',
defaultShape: getBackdropShape(),
showParentTitle: false,
showParentTitleOrTitle: true,
showTitle: false,
showAirTime: true,
showAirEndTime: true,
showChannelName: true,
coverImage: true,
overlayText: false,
overlayMoreButton: true
});
});
}
function renderTimers(context, timers, options) {
LiveTvHelpers.getTimersHtml(timers, options).then(function (html) {
const elem = context;
if (html) {
elem.classList.remove('hide');
} else {
elem.classList.add('hide');
}
var recordingItems = elem.querySelector('.recordingItems');
elem.querySelector('.recordingItems').innerHTML = html;
imageLoader.lazyChildren(elem);
});
}
if (enableScrollX()) {
recordingItems.classList.add('scrollX');
function renderUpcomingRecordings(context, promise) {
promise.then(function (result) {
renderTimers(context.querySelector('#upcomingRecordings'), result.Items);
loading.hide();
});
}
if (layoutManager.tv) {
recordingItems.classList.add('smoothScrollX');
}
export default function (view, params, tabContent) {
let activeRecordingsPromise;
let upcomingRecordingsPromise;
const self = this;
tabContent.querySelector('#upcomingRecordings .recordingItems').addEventListener('timercancelled', function () {
self.preRender();
self.renderTab();
});
recordingItems.classList.add('hiddenScrollX');
recordingItems.classList.remove('vertical-wrap');
} else {
recordingItems.classList.remove('scrollX');
recordingItems.classList.remove('smoothScrollX');
recordingItems.classList.remove('hiddenScrollX');
recordingItems.classList.add('vertical-wrap');
}
recordingItems.innerHTML = cardBuilder.getCardsHtml(Object.assign({
items: recordings,
shape: enableScrollX() ? 'autooverflow' : 'auto',
showTitle: true,
showParentTitle: true,
coverImage: true,
cardLayout: false,
centerText: true,
allowBottomPadding: !enableScrollX(),
preferThumb: 'auto'
}, cardOptions || {}));
imageLoader.lazyChildren(recordingItems);
}
function getBackdropShape() {
return enableScrollX() ? 'overflowBackdrop' : 'backdrop';
}
function renderActiveRecordings(context, promise) {
promise.then(function (result) {
renderRecordings(context.querySelector('#activeRecordings'), result.Items, {
shape: enableScrollX() ? 'autooverflow' : 'auto',
defaultShape: getBackdropShape(),
showParentTitle: false,
showParentTitleOrTitle: true,
showTitle: false,
showAirTime: true,
showAirEndTime: true,
showChannelName: true,
coverImage: true,
overlayText: false,
overlayMoreButton: true
});
self.preRender = function () {
activeRecordingsPromise = ApiClient.getLiveTvRecordings({
UserId: Dashboard.getCurrentUserId(),
IsInProgress: true,
Fields: 'CanDelete,PrimaryImageAspectRatio,BasicSyncInfo',
EnableTotalRecordCount: false,
EnableImageTypes: 'Primary,Thumb,Backdrop'
});
}
function renderTimers(context, timers, options) {
LiveTvHelpers.getTimersHtml(timers, options).then(function (html) {
var elem = context;
if (html) {
elem.classList.remove('hide');
} else {
elem.classList.add('hide');
}
elem.querySelector('.recordingItems').innerHTML = html;
imageLoader.lazyChildren(elem);
upcomingRecordingsPromise = ApiClient.getLiveTvTimers({
IsActive: false,
IsScheduled: true
});
}
function renderUpcomingRecordings(context, promise) {
promise.then(function (result) {
renderTimers(context.querySelector('#upcomingRecordings'), result.Items);
loading.hide();
});
}
return function (view, params, tabContent) {
var activeRecordingsPromise;
var upcomingRecordingsPromise;
var self = this;
tabContent.querySelector('#upcomingRecordings .recordingItems').addEventListener('timercancelled', function () {
self.preRender();
self.renderTab();
});
self.preRender = function () {
activeRecordingsPromise = ApiClient.getLiveTvRecordings({
UserId: Dashboard.getCurrentUserId(),
IsInProgress: true,
Fields: 'CanDelete,PrimaryImageAspectRatio,BasicSyncInfo',
EnableTotalRecordCount: false,
EnableImageTypes: 'Primary,Thumb,Backdrop'
});
upcomingRecordingsPromise = ApiClient.getLiveTvTimers({
IsActive: false,
IsScheduled: true
});
};
self.renderTab = function () {
loading.show();
renderActiveRecordings(tabContent, activeRecordingsPromise);
renderUpcomingRecordings(tabContent, upcomingRecordingsPromise);
};
};
});
self.renderTab = function () {
loading.show();
renderActiveRecordings(tabContent, activeRecordingsPromise);
renderUpcomingRecordings(tabContent, upcomingRecordingsPromise);
};
}

View file

@ -1,50 +1,52 @@
define(['datetime', 'cardBuilder', 'imageLoader', 'apphost', 'loading', 'paper-icon-button-light', 'emby-button'], function (datetime, cardBuilder, imageLoader, appHost, loading) {
'use strict';
import cardBuilder from 'cardBuilder';
import imageLoader from 'imageLoader';
import loading from 'loading';
import 'paper-icon-button-light';
import 'emby-button';
function renderTimers(context, timers) {
var html = '';
html += cardBuilder.getCardsHtml({
items: timers,
shape: 'auto',
defaultShape: 'portrait',
showTitle: true,
cardLayout: false,
preferThumb: 'auto',
coverImage: true,
overlayText: false,
showSeriesTimerTime: true,
showSeriesTimerChannel: true,
centerText: true,
overlayMoreButton: true,
lines: 3
});
var elem = context.querySelector('#items');
elem.innerHTML = html;
imageLoader.lazyChildren(elem);
loading.hide();
}
function renderTimers(context, timers) {
const html = cardBuilder.getCardsHtml({
items: timers,
shape: 'auto',
defaultShape: 'portrait',
showTitle: true,
cardLayout: false,
preferThumb: 'auto',
coverImage: true,
overlayText: false,
showSeriesTimerTime: true,
showSeriesTimerChannel: true,
centerText: true,
overlayMoreButton: true,
lines: 3
});
const elem = context.querySelector('#items');
elem.innerHTML = html;
imageLoader.lazyChildren(elem);
loading.hide();
}
function reload(context, promise) {
loading.show();
promise.then(function (result) {
renderTimers(context, result.Items);
});
}
function reload(context, promise) {
loading.show();
promise.then(function (result) {
renderTimers(context, result.Items);
});
}
var query = {
SortBy: 'SortName',
SortOrder: 'Ascending'
const query = {
SortBy: 'SortName',
SortOrder: 'Ascending'
};
export default function (view, params, tabContent) {
let timersPromise;
const self = this;
self.preRender = function () {
timersPromise = ApiClient.getLiveTvSeriesTimers(query);
};
return function (view, params, tabContent) {
var timersPromise;
var self = this;
self.preRender = function () {
timersPromise = ApiClient.getLiveTvSeriesTimers(query);
};
self.renderTab = function () {
reload(tabContent, timersPromise);
};
self.renderTab = function () {
reload(tabContent, timersPromise);
};
});
}

View file

@ -1,393 +1,397 @@
define(['layoutManager', 'userSettings', 'inputManager', 'loading', 'globalize', 'libraryBrowser', 'mainTabsManager', 'cardBuilder', 'apphost', 'imageLoader', 'scrollStyles', 'emby-itemscontainer', 'emby-tabs', 'emby-button'], function (layoutManager, userSettings, inputManager, loading, globalize, libraryBrowser, mainTabsManager, cardBuilder, appHost, imageLoader) {
'use strict';
import layoutManager from 'layoutManager';
import * as userSettings from 'userSettings';
import inputManager from 'inputManager';
import loading from 'loading';
import globalize from 'globalize';
import * as mainTabsManager from 'mainTabsManager';
import cardBuilder from 'cardBuilder';
import imageLoader from 'imageLoader';
import 'scrollStyles';
import 'emby-itemscontainer';
import 'emby-tabs';
import 'emby-button';
function enableScrollX() {
return !layoutManager.desktop;
function enableScrollX() {
return !layoutManager.desktop;
}
function getBackdropShape() {
if (enableScrollX()) {
return 'overflowBackdrop';
}
return 'backdrop';
}
function getPortraitShape() {
if (enableScrollX()) {
return 'overflowPortrait';
}
return 'portrait';
}
function getLimit() {
if (enableScrollX()) {
return 12;
}
function getBackdropShape() {
if (enableScrollX()) {
return 'overflowBackdrop';
}
return 'backdrop';
return 9;
}
function loadRecommendedPrograms(page) {
loading.show();
let limit = getLimit();
if (enableScrollX()) {
limit *= 2;
}
function getPortraitShape() {
if (enableScrollX()) {
return 'overflowPortrait';
}
return 'portrait';
}
ApiClient.getLiveTvRecommendedPrograms({
userId: Dashboard.getCurrentUserId(),
IsAiring: true,
limit: limit,
ImageTypeLimit: 1,
EnableImageTypes: 'Primary,Thumb,Backdrop',
EnableTotalRecordCount: false,
Fields: 'ChannelInfo,PrimaryImageAspectRatio'
}).then(function (result) {
renderItems(page, result.Items, 'activeProgramItems', 'play', {
showAirDateTime: false,
showAirEndTime: true
});
loading.hide();
function getLimit() {
if (enableScrollX()) {
return 12;
}
import('autoFocuser').then(({default: autoFocuser}) => {
autoFocuser.autoFocus(page);
});
});
}
return 9;
}
function loadRecommendedPrograms(page) {
loading.show();
var limit = getLimit();
if (enableScrollX()) {
limit *= 2;
}
ApiClient.getLiveTvRecommendedPrograms({
function reload(page, enableFullRender) {
if (enableFullRender) {
loadRecommendedPrograms(page);
ApiClient.getLiveTvPrograms({
userId: Dashboard.getCurrentUserId(),
IsAiring: true,
limit: limit,
ImageTypeLimit: 1,
EnableImageTypes: 'Primary,Thumb,Backdrop',
HasAired: false,
limit: getLimit(),
IsMovie: false,
IsSports: false,
IsKids: false,
IsNews: false,
IsSeries: true,
EnableTotalRecordCount: false,
Fields: 'ChannelInfo,PrimaryImageAspectRatio'
Fields: 'ChannelInfo,PrimaryImageAspectRatio',
EnableImageTypes: 'Primary,Thumb'
}).then(function (result) {
renderItems(page, result.Items, 'activeProgramItems', 'play', {
showAirDateTime: false,
showAirEndTime: true
renderItems(page, result.Items, 'upcomingEpisodeItems');
});
ApiClient.getLiveTvPrograms({
userId: Dashboard.getCurrentUserId(),
HasAired: false,
limit: getLimit(),
IsMovie: true,
EnableTotalRecordCount: false,
Fields: 'ChannelInfo',
EnableImageTypes: 'Primary,Thumb'
}).then(function (result) {
renderItems(page, result.Items, 'upcomingTvMovieItems', null, {
shape: getPortraitShape(),
preferThumb: null,
showParentTitle: false
});
loading.hide();
require(['autoFocuser'], function (autoFocuser) {
autoFocuser.autoFocus(page);
});
ApiClient.getLiveTvPrograms({
userId: Dashboard.getCurrentUserId(),
HasAired: false,
limit: getLimit(),
IsSports: true,
EnableTotalRecordCount: false,
Fields: 'ChannelInfo,PrimaryImageAspectRatio',
EnableImageTypes: 'Primary,Thumb'
}).then(function (result) {
renderItems(page, result.Items, 'upcomingSportsItems');
});
ApiClient.getLiveTvPrograms({
userId: Dashboard.getCurrentUserId(),
HasAired: false,
limit: getLimit(),
IsKids: true,
EnableTotalRecordCount: false,
Fields: 'ChannelInfo,PrimaryImageAspectRatio',
EnableImageTypes: 'Primary,Thumb'
}).then(function (result) {
renderItems(page, result.Items, 'upcomingKidsItems');
});
ApiClient.getLiveTvPrograms({
userId: Dashboard.getCurrentUserId(),
HasAired: false,
limit: getLimit(),
IsNews: true,
EnableTotalRecordCount: false,
Fields: 'ChannelInfo,PrimaryImageAspectRatio',
EnableImageTypes: 'Primary,Thumb'
}).then(function (result) {
renderItems(page, result.Items, 'upcomingNewsItems', null, {
showParentTitleOrTitle: true,
showTitle: false,
showParentTitle: false
});
});
}
}
function reload(page, enableFullRender) {
if (enableFullRender) {
loadRecommendedPrograms(page);
ApiClient.getLiveTvPrograms({
userId: Dashboard.getCurrentUserId(),
HasAired: false,
limit: getLimit(),
IsMovie: false,
IsSports: false,
IsKids: false,
IsNews: false,
IsSeries: true,
EnableTotalRecordCount: false,
Fields: 'ChannelInfo,PrimaryImageAspectRatio',
EnableImageTypes: 'Primary,Thumb'
}).then(function (result) {
renderItems(page, result.Items, 'upcomingEpisodeItems');
});
ApiClient.getLiveTvPrograms({
userId: Dashboard.getCurrentUserId(),
HasAired: false,
limit: getLimit(),
IsMovie: true,
EnableTotalRecordCount: false,
Fields: 'ChannelInfo',
EnableImageTypes: 'Primary,Thumb'
}).then(function (result) {
renderItems(page, result.Items, 'upcomingTvMovieItems', null, {
shape: getPortraitShape(),
preferThumb: null,
showParentTitle: false
});
});
ApiClient.getLiveTvPrograms({
userId: Dashboard.getCurrentUserId(),
HasAired: false,
limit: getLimit(),
IsSports: true,
EnableTotalRecordCount: false,
Fields: 'ChannelInfo,PrimaryImageAspectRatio',
EnableImageTypes: 'Primary,Thumb'
}).then(function (result) {
renderItems(page, result.Items, 'upcomingSportsItems');
});
ApiClient.getLiveTvPrograms({
userId: Dashboard.getCurrentUserId(),
HasAired: false,
limit: getLimit(),
IsKids: true,
EnableTotalRecordCount: false,
Fields: 'ChannelInfo,PrimaryImageAspectRatio',
EnableImageTypes: 'Primary,Thumb'
}).then(function (result) {
renderItems(page, result.Items, 'upcomingKidsItems');
});
ApiClient.getLiveTvPrograms({
userId: Dashboard.getCurrentUserId(),
HasAired: false,
limit: getLimit(),
IsNews: true,
EnableTotalRecordCount: false,
Fields: 'ChannelInfo,PrimaryImageAspectRatio',
EnableImageTypes: 'Primary,Thumb'
}).then(function (result) {
renderItems(page, result.Items, 'upcomingNewsItems', null, {
showParentTitleOrTitle: true,
showTitle: false,
showParentTitle: false
});
});
function renderItems(page, items, sectionClass, overlayButton, cardOptions) {
const html = cardBuilder.getCardsHtml(Object.assign({
items: items,
preferThumb: 'auto',
inheritThumb: false,
shape: enableScrollX() ? 'autooverflow' : 'auto',
defaultShape: getBackdropShape(),
showParentTitle: true,
showTitle: true,
centerText: true,
coverImage: true,
overlayText: false,
lazy: true,
overlayPlayButton: overlayButton === 'play',
overlayMoreButton: overlayButton === 'more',
overlayInfoButton: overlayButton === 'info',
allowBottomPadding: !enableScrollX(),
showAirTime: true,
showAirDateTime: true
}, cardOptions || {}));
const elem = page.querySelector('.' + sectionClass);
elem.innerHTML = html;
imageLoader.lazyChildren(elem);
}
function getTabs() {
return [{
name: globalize.translate('Programs')
}, {
name: globalize.translate('Guide')
}, {
name: globalize.translate('Channels')
}, {
name: globalize.translate('Recordings')
}, {
name: globalize.translate('Schedule')
}, {
name: globalize.translate('Series')
}];
}
function setScrollClasses(elem, scrollX) {
if (scrollX) {
elem.classList.add('hiddenScrollX');
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 getDefaultTabIndex(folderId) {
if (userSettings.get('landing-' + folderId) === 'guide') {
return 1;
}
function renderItems(page, items, sectionClass, overlayButton, cardOptions) {
var html = cardBuilder.getCardsHtml(Object.assign({
items: items,
preferThumb: 'auto',
inheritThumb: false,
shape: enableScrollX() ? 'autooverflow' : 'auto',
defaultShape: getBackdropShape(),
showParentTitle: true,
showTitle: true,
centerText: true,
coverImage: true,
overlayText: false,
lazy: true,
overlayPlayButton: 'play' === overlayButton,
overlayMoreButton: 'more' === overlayButton,
overlayInfoButton: 'info' === overlayButton,
allowBottomPadding: !enableScrollX(),
showAirTime: true,
showAirDateTime: true
}, cardOptions || {}));
var elem = page.querySelector('.' + sectionClass);
elem.innerHTML = html;
imageLoader.lazyChildren(elem);
return 0;
}
export default function (view, params) {
function enableFullRender() {
return new Date().getTime() - lastFullRender > 3e5;
}
function getTabs() {
return [{
name: globalize.translate('Programs')
}, {
name: globalize.translate('TabGuide')
}, {
name: globalize.translate('TabChannels')
}, {
name: globalize.translate('TabRecordings')
}, {
name: globalize.translate('HeaderSchedule')
}, {
name: globalize.translate('TabSeries')
}, {
name: globalize.translate('ButtonSearch'),
cssClass: 'searchTabButton'
}];
function onBeforeTabChange(evt) {
preLoadTab(view, parseInt(evt.detail.selectedTabIndex));
}
function setScrollClasses(elem, scrollX) {
if (scrollX) {
elem.classList.add('hiddenScrollX');
function onTabChange(evt) {
const previousTabController = tabControllers[parseInt(evt.detail.previousIndex)];
if (layoutManager.tv) {
elem.classList.add('smoothScrollX');
if (previousTabController && previousTabController.onHide) {
previousTabController.onHide();
}
loadTab(view, parseInt(evt.detail.selectedTabIndex));
}
function getTabContainers() {
return view.querySelectorAll('.pageTabContent');
}
function initTabs() {
mainTabsManager.setTabs(view, currentTabIndex, getTabs, getTabContainers, onBeforeTabChange, onTabChange);
}
function getTabController(page, index, callback) {
let depends;
// TODO int is a little hard to read
switch (index) {
case 0:
depends = 'controllers/livetv/livetvsuggested';
break;
case 1:
depends = 'controllers/livetv/livetvguide';
break;
case 2:
depends = 'controllers/livetv/livetvchannels';
break;
case 3:
depends = 'controllers/livetv/livetvrecordings';
break;
case 4:
depends = 'controllers/livetv/livetvschedule';
break;
case 5:
depends = 'controllers/livetv/livetvseriestimers';
break;
}
import(depends).then(({default: controllerFactory}) => {
let tabContent;
if (index === 0) {
tabContent = view.querySelector(`.pageTabContent[data-index="${index}"]`);
self.tabContent = tabContent;
}
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');
}
}
let controller = tabControllers[index];
function getDefaultTabIndex(folderId) {
if (userSettings.get('landing-' + folderId) === 'guide') {
return 1;
}
if (!controller) {
tabContent = view.querySelector(`.pageTabContent[data-index="${index}"]`);
return 0;
}
return function (view, params) {
function enableFullRender() {
return new Date().getTime() - lastFullRender > 3e5;
}
function onBeforeTabChange(evt) {
preLoadTab(view, parseInt(evt.detail.selectedTabIndex));
}
function onTabChange(evt) {
var previousTabController = tabControllers[parseInt(evt.detail.previousIndex)];
if (previousTabController && previousTabController.onHide) {
previousTabController.onHide();
}
loadTab(view, parseInt(evt.detail.selectedTabIndex));
}
function getTabContainers() {
return view.querySelectorAll('.pageTabContent');
}
function initTabs() {
mainTabsManager.setTabs(view, currentTabIndex, getTabs, getTabContainers, onBeforeTabChange, onTabChange);
}
function getTabController(page, index, callback) {
var depends = [];
// TODO int is a little hard to read
switch (index) {
case 0:
break;
case 1:
depends.push('controllers/livetv/livetvguide');
break;
case 2:
depends.push('controllers/livetv/livetvchannels');
break;
case 3:
depends.push('controllers/livetv/livetvrecordings');
break;
case 4:
depends.push('controllers/livetv/livetvschedule');
break;
case 5:
depends.push('controllers/livetv/livetvseriestimers');
break;
case 6:
depends.push('scripts/searchtab');
}
require(depends, function (controllerFactory) {
var tabContent;
if (0 == index) {
tabContent = view.querySelector(".pageTabContent[data-index='" + index + "']");
self.tabContent = tabContent;
if (index === 0) {
controller = self;
} else if (index === 6) {
controller = new controllerFactory(view, tabContent, {
collectionType: 'livetv'
});
} else {
controller = new controllerFactory(view, params, tabContent);
}
var controller = tabControllers[index];
tabControllers[index] = controller;
if (!controller) {
tabContent = view.querySelector(".pageTabContent[data-index='" + index + "']");
if (controller.initTab) {
controller.initTab();
}
}
if (0 === index) {
controller = self;
} else if (6 === index) {
controller = new controllerFactory(view, tabContent, {
collectionType: 'livetv'
});
} else {
controller = new controllerFactory(view, params, tabContent);
}
callback(controller);
});
}
tabControllers[index] = controller;
function preLoadTab(page, index) {
getTabController(page, index, function (controller) {
if (renderedTabs.indexOf(index) === -1 && controller.preRender) {
controller.preRender();
}
});
}
if (controller.initTab) {
controller.initTab();
}
function loadTab(page, index) {
currentTabIndex = index;
getTabController(page, index, function (controller) {
initialTabIndex = null;
if (renderedTabs.indexOf(index) === -1) {
if (index === 1) {
renderedTabs.push(index);
}
callback(controller);
});
controller.renderTab();
} else if (controller.onShow) {
controller.onShow();
}
currentTabController = controller;
});
}
function onInputCommand(evt) {
if (evt.detail.command === 'search') {
evt.preventDefault();
Dashboard.navigate('search.html?collectionType=livetv');
}
}
function preLoadTab(page, index) {
getTabController(page, index, function (controller) {
if (renderedTabs.indexOf(index) === -1 && controller.preRender) {
controller.preRender();
}
});
let isViewRestored;
const self = this;
let currentTabIndex = parseInt(params.tab || getDefaultTabIndex('livetv'));
let initialTabIndex = currentTabIndex;
let lastFullRender = 0;
[].forEach.call(view.querySelectorAll('.sectionTitleTextButton-programs'), function (link) {
const href = link.href;
if (href) {
link.href = href + '&serverId=' + ApiClient.serverId();
}
});
function loadTab(page, index) {
currentTabIndex = index;
getTabController(page, index, function (controller) {
initialTabIndex = null;
self.initTab = function () {
const tabContent = view.querySelector('.pageTabContent[data-index="0"]');
const containers = tabContent.querySelectorAll('.itemsContainer');
if (-1 == renderedTabs.indexOf(index)) {
if (1 === index) {
renderedTabs.push(index);
}
controller.renderTab();
} else if (controller.onShow) {
controller.onShow();
}
currentTabController = controller;
});
for (let i = 0, length = containers.length; i < length; i++) {
setScrollClasses(containers[i], enableScrollX());
}
function onInputCommand(evt) {
if (evt.detail.command === 'search') {
evt.preventDefault();
Dashboard.navigate('search.html?collectionType=livetv');
}
}
var isViewRestored;
var self = this;
var currentTabIndex = parseInt(params.tab || getDefaultTabIndex('livetv'));
var initialTabIndex = currentTabIndex;
var lastFullRender = 0;
[].forEach.call(view.querySelectorAll('.sectionTitleTextButton-programs'), function (link) {
var href = link.href;
if (href) {
link.href = href + '&serverId=' + ApiClient.serverId();
}
});
self.initTab = function () {
var tabContent = view.querySelector(".pageTabContent[data-index='0']");
var containers = tabContent.querySelectorAll('.itemsContainer');
for (var i = 0, length = containers.length; i < length; i++) {
setScrollClasses(containers[i], enableScrollX());
}
};
self.renderTab = function () {
var tabContent = view.querySelector(".pageTabContent[data-index='0']");
if (enableFullRender()) {
reload(tabContent, true);
lastFullRender = new Date().getTime();
} else {
reload(tabContent);
}
};
var currentTabController;
var tabControllers = [];
var renderedTabs = [];
view.addEventListener('viewbeforeshow', function (evt) {
isViewRestored = evt.detail.isRestored;
initTabs();
});
view.addEventListener('viewshow', function (evt) {
isViewRestored = evt.detail.isRestored;
if (!isViewRestored) {
mainTabsManager.selectedTabIndex(initialTabIndex);
}
inputManager.on(window, onInputCommand);
});
view.addEventListener('viewbeforehide', function (e) {
if (currentTabController && currentTabController.onHide) {
currentTabController.onHide();
}
inputManager.off(window, onInputCommand);
});
view.addEventListener('viewdestroy', function (evt) {
tabControllers.forEach(function (tabController) {
if (tabController.destroy) {
tabController.destroy();
}
});
});
};
});
self.renderTab = function () {
const tabContent = view.querySelector('.pageTabContent[data-index="0"]');
if (enableFullRender()) {
reload(tabContent, true);
lastFullRender = new Date().getTime();
} else {
reload(tabContent);
}
};
let currentTabController;
const tabControllers = [];
const renderedTabs = [];
view.addEventListener('viewbeforeshow', function (evt) {
isViewRestored = evt.detail.isRestored;
initTabs();
});
view.addEventListener('viewshow', function (evt) {
isViewRestored = evt.detail.isRestored;
if (!isViewRestored) {
mainTabsManager.selectedTabIndex(initialTabIndex);
}
inputManager.on(window, onInputCommand);
});
view.addEventListener('viewbeforehide', function () {
if (currentTabController && currentTabController.onHide) {
currentTabController.onHide();
}
inputManager.off(window, onInputCommand);
});
view.addEventListener('viewdestroy', function () {
tabControllers.forEach(function (tabController) {
if (tabController.destroy) {
tabController.destroy();
}
});
});
}

View file

@ -0,0 +1,7 @@
<div id="liveTvGuideProviderPage" data-role="page" class="page type-interior liveTvSettingsPage">
<div>
<div class="content-primary">
<div class="readOnlyContent providerTemplate" style="margin-top: 2em;"></div>
</div>
</div>
</div>

View file

@ -1,30 +1,30 @@
define(['events', 'loading', 'globalize'], function (events, loading, globalize) {
'use strict';
import events from 'events';
import loading from 'loading';
import globalize from 'globalize';
function onListingsSubmitted() {
Dashboard.navigate('livetvstatus.html');
}
function onListingsSubmitted() {
Dashboard.navigate('livetvstatus.html');
}
function init(page, type, providerId) {
var url = 'components/tvproviders/' + type + '.js';
function init(page, type, providerId) {
const url = 'components/tvproviders/' + type + '.js';
require([url], function (factory) {
var instance = new factory(page, providerId, {});
events.on(instance, 'submitted', onListingsSubmitted);
instance.init();
});
}
function loadTemplate(page, type, providerId) {
require(['text!./components/tvproviders/' + type + '.template.html'], function (html) {
page.querySelector('.providerTemplate').innerHTML = globalize.translateHtml(html);
init(page, type, providerId);
});
}
pageIdOn('pageshow', 'liveTvGuideProviderPage', function () {
loading.show();
var providerId = getParameterByName('id');
loadTemplate(this, getParameterByName('type'), providerId);
import(url).then(({default: factory}) => {
const instance = new factory(page, providerId, {});
events.on(instance, 'submitted', onListingsSubmitted);
instance.init();
});
}
function loadTemplate(page, type, providerId) {
import('text!./../components/tvproviders/' + type + '.template.html').then(({default: html}) => {
page.querySelector('.providerTemplate').innerHTML = globalize.translateHtml(html);
init(page, type, providerId);
});
}
pageIdOn('pageshow', 'liveTvGuideProviderPage', function () {
loading.show();
const providerId = getParameterByName('id');
loadTemplate(this, getParameterByName('type'), providerId);
});

View file

@ -0,0 +1,102 @@
<div id="liveTvSettingsPage" data-role="page" class="page type-interior liveTvPage">
<div>
<div class="content-primary">
<div class="verticalSection">
<div class="sectionTitleContainer flex align-items-center">
<h2 class="sectionTitle">${HeaderDVR}</h2>
<a is="emby-linkbutton" rel="noopener noreferrer" class="raised button-alt headerHelpButton" target="_blank" href="https://docs.jellyfin.org/general/server/live-tv/index.html">${Help}</a>
</div>
</div>
<form class="liveTvSettingsForm">
<div class="selectContainer">
<select is="emby-select" id="selectGuideDays" label="${LabelNumberOfGuideDays}">
<option value="">${OptionAutomatic}</option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
<option value="7">7</option>
<option value="8">8</option>
<option value="9">9</option>
<option value="10">10</option>
<option value="11">11</option>
<option value="12">12</option>
<option value="13">13</option>
<option value="14">14</option>
</select>
<div class="fieldDescription">${LabelNumberOfGuideDaysHelp}</div>
</div>
<div class="inputContainer">
<div style="display: flex; align-items: center;">
<div style="flex-grow:1;">
<input is="emby-input" id="txtRecordingPath" label="${LabelRecordingPath}" autocomplete="off" />
</div>
<button type="button" is="paper-icon-button-light" id="btnSelectRecordingPath" title="${ButtonSelectDirectory}" class="emby-input-iconbutton"><span class="material-icons search"></span></button>
</div>
<div class="fieldDescription">${LabelRecordingPathHelp}</div>
</div>
<div class="inputContainer">
<div style="display: flex; align-items: center;">
<div style="flex-grow:1;">
<input is="emby-input" id="txtMovieRecordingPath" label="${LabelMovieRecordingPath}" autocomplete="off" />
</div>
<button type="button" is="paper-icon-button-light" id="btnSelectMovieRecordingPath" title="${ButtonSelectDirectory}" class="emby-input-iconbutton"><span class="material-icons search"></span></button>
</div>
</div>
<div class="inputContainer">
<div style="display: flex; align-items: center;">
<div style="flex-grow:1;">
<input is="emby-input" id="txtSeriesRecordingPath" label="${LabelSeriesRecordingPath}" autocomplete="off" />
</div>
<button type="button" is="paper-icon-button-light" id="btnSelectSeriesRecordingPath" title="${ButtonSelectDirectory}" class="emby-input-iconbutton"><span class="material-icons search"></span></button>
</div>
</div>
<div class="verticalSection">
<h2 class="sectionTitle">${HeaderDefaultRecordingSettings}</h2>
<div class="inputContainer">
<div style="display: flex; align-items: center;">
<div style="flex-grow: 1;">
<input is="emby-input" type="number" id="txtPrePaddingMinutes" pattern="[0-9]*" required="required" min="0" step="1" label="${LabelStartWhenPossible}" />
</div>
<div class="fieldDescription" style="margin-left:.5em;font-size:90%;margin-top:1.3em;">
${MinutesBefore}
</div>
</div>
</div>
<div class="inputContainer">
<div style="display: flex; align-items: center;">
<div style="flex-grow: 1;">
<input is="emby-input" type="number" id="txtPostPaddingMinutes" pattern="[0-9]*" required="required" min="0" step="1" label="${LabelStopWhenPossible}" />
</div>
<div class="fieldDescription" style="margin-left:.5em;font-size:90%;margin-top:1.3em;">
${MinutesAfter}
</div>
</div>
</div>
</div>
<div class="verticalSection">
<h2 class="sectionTitle">${HeaderRecordingPostProcessing}</h2>
<div class="inputContainer">
<div style="display: flex; align-items: center;">
<div style="flex-grow:1;">
<input is="emby-input" type="text" id="txtPostProcessor" label="${LabelPostProcessor}" />
</div>
<button type="button" is="paper-icon-button-light" id="btnSelectPostProcessorPath" class="emby-input-iconbutton"><span class="material-icons search"></span></button>
</div>
</div>
<div class="inputContainer">
<input is="emby-input" type="text" id="txtPostProcessorArguments" label="${LabelPostProcessorArguments}" />
<div class="fieldDescription">${LabelPostProcessorArgumentsHelp}</div>
</div>
</div>
<br />
<div>
<button is="emby-button" type="submit" class="raised button-submit block"><span>${Save}</span></button>
</div>
</form>
</div>
</div>
</div>

View file

@ -1,65 +1,68 @@
define(['jQuery', 'loading', 'globalize', 'emby-button'], function ($, loading, globalize) {
'use strict';
import $ from 'jQuery';
import loading from 'loading';
import globalize from 'globalize';
import 'emby-button';
function loadPage(page, config) {
$('.liveTvSettingsForm', page).show();
$('.noLiveTvServices', page).hide();
$('#selectGuideDays', page).val(config.GuideDays || '');
$('#txtPrePaddingMinutes', page).val(config.PrePaddingSeconds / 60);
$('#txtPostPaddingMinutes', page).val(config.PostPaddingSeconds / 60);
page.querySelector('#txtRecordingPath').value = config.RecordingPath || '';
page.querySelector('#txtMovieRecordingPath').value = config.MovieRecordingPath || '';
page.querySelector('#txtSeriesRecordingPath').value = config.SeriesRecordingPath || '';
page.querySelector('#txtPostProcessor').value = config.RecordingPostProcessor || '';
page.querySelector('#txtPostProcessorArguments').value = config.RecordingPostProcessorArguments || '';
loading.hide();
}
function loadPage(page, config) {
$('.liveTvSettingsForm', page).show();
$('.noLiveTvServices', page).hide();
$('#selectGuideDays', page).val(config.GuideDays || '');
$('#txtPrePaddingMinutes', page).val(config.PrePaddingSeconds / 60);
$('#txtPostPaddingMinutes', page).val(config.PostPaddingSeconds / 60);
page.querySelector('#txtRecordingPath').value = config.RecordingPath || '';
page.querySelector('#txtMovieRecordingPath').value = config.MovieRecordingPath || '';
page.querySelector('#txtSeriesRecordingPath').value = config.SeriesRecordingPath || '';
page.querySelector('#txtPostProcessor').value = config.RecordingPostProcessor || '';
page.querySelector('#txtPostProcessorArguments').value = config.RecordingPostProcessorArguments || '';
loading.hide();
}
function onSubmit() {
loading.show();
var form = this;
ApiClient.getNamedConfiguration('livetv').then(function (config) {
config.GuideDays = $('#selectGuideDays', form).val() || null;
var recordingPath = form.querySelector('#txtRecordingPath').value || null;
var movieRecordingPath = form.querySelector('#txtMovieRecordingPath').value || null;
var seriesRecordingPath = form.querySelector('#txtSeriesRecordingPath').value || null;
var recordingPathChanged = recordingPath != config.RecordingPath || movieRecordingPath != config.MovieRecordingPath || seriesRecordingPath != config.SeriesRecordingPath;
config.RecordingPath = recordingPath;
config.MovieRecordingPath = movieRecordingPath;
config.SeriesRecordingPath = seriesRecordingPath;
config.RecordingEncodingFormat = 'mkv';
config.PrePaddingSeconds = 60 * $('#txtPrePaddingMinutes', form).val();
config.PostPaddingSeconds = 60 * $('#txtPostPaddingMinutes', form).val();
config.RecordingPostProcessor = $('#txtPostProcessor', form).val();
config.RecordingPostProcessorArguments = $('#txtPostProcessorArguments', form).val();
ApiClient.updateNamedConfiguration('livetv', config).then(function () {
Dashboard.processServerConfigurationUpdateResult();
showSaveMessage(recordingPathChanged);
});
function onSubmit() {
loading.show();
const form = this;
ApiClient.getNamedConfiguration('livetv').then(function (config) {
config.GuideDays = $('#selectGuideDays', form).val() || null;
const recordingPath = form.querySelector('#txtRecordingPath').value || null;
const movieRecordingPath = form.querySelector('#txtMovieRecordingPath').value || null;
const seriesRecordingPath = form.querySelector('#txtSeriesRecordingPath').value || null;
const recordingPathChanged = recordingPath != config.RecordingPath || movieRecordingPath != config.MovieRecordingPath || seriesRecordingPath != config.SeriesRecordingPath;
config.RecordingPath = recordingPath;
config.MovieRecordingPath = movieRecordingPath;
config.SeriesRecordingPath = seriesRecordingPath;
config.RecordingEncodingFormat = 'mkv';
config.PrePaddingSeconds = 60 * $('#txtPrePaddingMinutes', form).val();
config.PostPaddingSeconds = 60 * $('#txtPostPaddingMinutes', form).val();
config.RecordingPostProcessor = $('#txtPostProcessor', form).val();
config.RecordingPostProcessorArguments = $('#txtPostProcessorArguments', form).val();
ApiClient.updateNamedConfiguration('livetv', config).then(function () {
Dashboard.processServerConfigurationUpdateResult();
showSaveMessage(recordingPathChanged);
});
return false;
});
return false;
}
function showSaveMessage(recordingPathChanged) {
let msg = '';
if (recordingPathChanged) {
msg += globalize.translate('MessageChangeRecordingPath');
}
function showSaveMessage(recordingPathChanged) {
var msg = '';
if (recordingPathChanged) {
msg += globalize.translate('RecordingPathChangeMessage');
}
if (msg) {
require(['alert'], function (alert) {
alert(msg);
});
}
if (msg) {
import('alert').then(({default: alert}) => {
alert(msg);
});
}
}
export default function () {
$(document).on('pageinit', '#liveTvSettingsPage', function () {
var page = this;
const page = this;
$('.liveTvSettingsForm').off('submit', onSubmit).on('submit', onSubmit);
$('#btnSelectRecordingPath', page).on('click.selectDirectory', function () {
require(['directorybrowser'], function (directoryBrowser) {
var picker = new directoryBrowser.default();
import('directorybrowser').then(({default: directoryBrowser}) => {
const picker = new directoryBrowser();
picker.show({
callback: function (path) {
if (path) {
@ -73,8 +76,8 @@ define(['jQuery', 'loading', 'globalize', 'emby-button'], function ($, loading,
});
});
$('#btnSelectMovieRecordingPath', page).on('click.selectDirectory', function () {
require(['directorybrowser'], function (directoryBrowser) {
var picker = new directoryBrowser.default();
import('directorybrowser').then(({default: directoryBrowser}) => {
const picker = new directoryBrowser();
picker.show({
callback: function (path) {
if (path) {
@ -88,8 +91,8 @@ define(['jQuery', 'loading', 'globalize', 'emby-button'], function ($, loading,
});
});
$('#btnSelectSeriesRecordingPath', page).on('click.selectDirectory', function () {
require(['directorybrowser'], function (directoryBrowser) {
var picker = new directoryBrowser.default();
import('directorybrowser').then(({default: directoryBrowser}) => {
const picker = new directoryBrowser();
picker.show({
callback: function (path) {
if (path) {
@ -103,8 +106,8 @@ define(['jQuery', 'loading', 'globalize', 'emby-button'], function ($, loading,
});
});
$('#btnSelectPostProcessorPath', page).on('click.selectDirectory', function () {
require(['directorybrowser'], function (directoryBrowser) {
var picker = new directoryBrowser.default();
import('directorybrowser').then(({default: directoryBrowser}) => {
const picker = new directoryBrowser();
picker.show({
includeFiles: true,
callback: function (path) {
@ -119,9 +122,9 @@ define(['jQuery', 'loading', 'globalize', 'emby-button'], function ($, loading,
});
}).on('pageshow', '#liveTvSettingsPage', function () {
loading.show();
var page = this;
const page = this;
ApiClient.getNamedConfiguration('livetv').then(function (config) {
loadPage(page, config);
});
});
});
}

View file

@ -0,0 +1,41 @@
<div id="liveTvStatusPage" data-role="page" class="page type-interior liveTvSettingsPage">
<div>
<div class="content-primary">
<div class="verticalSection verticalSection-extrabottompadding">
<div class="verticalSection verticalSection-extrabottompadding">
<div class="sectionTitleContainer sectionTitleContainer-cards">
<h2 class="sectionTitle sectionTitle-cards">
<span>${HeaderTunerDevices}</span>
</h2>
<button is="emby-button" type="button" class="fab btnAddDevice submit sectionTitleButton" style="margin-left:1em;" title="${Add}">
<span class="material-icons add"></span>
</button>
<a is="emby-linkbutton" rel="noopener noreferrer" style="margin-left:2em!important;" class="raised button-alt headerHelpButton" target="_blank" href="https://docs.jellyfin.org/general/server/live-tv/index.html">${Help}</a>
</div>
<div class="devicesList itemsContainer vertical-wrap" data-hovermenu="false" data-multiselect="false" style="margin-top: .5em;"></div>
</div>
</div>
<div class="readOnlyContent">
<div class="verticalSection">
<div class="sectionTitleContainer">
<h2 class="sectionTitle">${HeaderGuideProviders}</h2>
<button is="emby-button" type="button" class="fab btnAddProvider submit" style="margin-left:1em;" title="${Add}">
<span class="material-icons add"></span>
</button>
</div>
<div class="providerList">
</div>
<div>
<button is="emby-button" type="button" class="raised btnRefresh block button-cancel">
<span>${ButtonRefreshGuideData}</span>
</button>
<progress max="100" min="0" style="width: 100%;" class="refreshGuideProgress"></progress>
</div>
</div>
</div>
</div>
</div>
</div>

View file

@ -1,294 +1,303 @@
define(['jQuery', 'globalize', 'scripts/taskbutton', 'dom', 'libraryMenu', 'layoutManager', 'loading', 'browser', 'listViewStyle', 'flexStyles', 'emby-itemscontainer', 'cardStyle', 'material-icons', 'emby-button'], function ($, globalize, taskButton, dom, libraryMenu, layoutManager, loading, browser) {
'use strict';
import $ from 'jQuery';
import globalize from 'globalize';
import taskButton from 'scripts/taskbutton';
import dom from 'dom';
import layoutManager from 'layoutManager';
import loading from 'loading';
import browser from 'browser';
import 'listViewStyle';
import 'flexStyles';
import 'emby-itemscontainer';
import 'cardStyle';
import 'material-icons';
import 'emby-button';
var enableFocusTransform = !browser.slow && !browser.edge;
const enableFocusTransform = !browser.slow && !browser.edge;
function getDeviceHtml(device) {
var padderClass;
var html = '';
var cssClass = 'card scalableCard';
var cardBoxCssClass = 'cardBox visualCardBox';
cssClass += ' backdropCard backdropCard-scalable';
padderClass = 'cardPadder-backdrop';
function getDeviceHtml(device) {
const padderClass = 'cardPadder-backdrop';
let cssClass = 'card scalableCard backdropCard backdropCard-scalable';
const cardBoxCssClass = 'cardBox visualCardBox';
let html = '';
// TODO move card creation code to Card component
// TODO move card creation code to Card component
if (layoutManager.tv) {
cssClass += ' show-focus';
if (layoutManager.tv) {
cssClass += ' show-focus';
if (enableFocusTransform) {
cssClass += ' show-animation';
}
if (enableFocusTransform) {
cssClass += ' show-animation';
}
html += '<div type="button" class="' + cssClass + '" data-id="' + device.Id + '">';
html += '<div class="' + cardBoxCssClass + '">';
html += '<div class="cardScalable visualCardBox-cardScalable">';
html += '<div class="' + padderClass + '"></div>';
html += '<div class="cardContent searchImage">';
html += '<div class="cardImageContainer coveredImage"><span class="cardImageIcon material-icons dvr"></span></div>';
html += '</div>';
html += '</div>';
html += '<div class="cardFooter visualCardBox-cardFooter">';
html += '<button is="paper-icon-button-light" class="itemAction btnCardOptions autoSize" data-action="menu"><span class="material-icons more_vert"></span></button>';
html += '<div class="cardText">' + (device.FriendlyName || getTunerName(device.Type)) + '</div>';
html += '<div class="cardText cardText-secondary">';
html += device.Url || '&nbsp;';
html += '</div>';
html += '</div>';
html += '</div>';
return html += '</div>';
}
function renderDevices(page, devices) {
var html = devices.map(getDeviceHtml).join('');
page.querySelector('.devicesList').innerHTML = html;
}
html += '<div type="button" class="' + cssClass + '" data-id="' + device.Id + '">';
html += '<div class="' + cardBoxCssClass + '">';
html += '<div class="cardScalable visualCardBox-cardScalable">';
html += '<div class="' + padderClass + '"></div>';
html += '<div class="cardContent searchImage">';
html += '<div class="cardImageContainer coveredImage"><span class="cardImageIcon material-icons dvr"></span></div>';
html += '</div>';
html += '</div>';
html += '<div class="cardFooter visualCardBox-cardFooter">';
html += '<button is="paper-icon-button-light" class="itemAction btnCardOptions autoSize" data-action="menu"><span class="material-icons more_vert"></span></button>';
html += '<div class="cardText">' + (device.FriendlyName || getTunerName(device.Type)) + '</div>';
html += '<div class="cardText cardText-secondary">';
html += device.Url || '&nbsp;';
html += '</div>';
html += '</div>';
html += '</div>';
return html += '</div>';
}
function deleteDevice(page, id) {
var message = globalize.translate('MessageConfirmDeleteTunerDevice');
function renderDevices(page, devices) {
page.querySelector('.devicesList').innerHTML = devices.map(getDeviceHtml).join('');
}
require(['confirm'], function (confirm) {
confirm.default(message, globalize.translate('HeaderDeleteDevice')).then(function () {
loading.show();
ApiClient.ajax({
type: 'DELETE',
url: ApiClient.getUrl('LiveTv/TunerHosts', {
Id: id
})
}).then(function () {
reload(page);
});
function deleteDevice(page, id) {
const message = globalize.translate('MessageConfirmDeleteTunerDevice');
import('confirm').then(({default: confirm}) => {
confirm(message, globalize.translate('HeaderDeleteDevice')).then(function () {
loading.show();
ApiClient.ajax({
type: 'DELETE',
url: ApiClient.getUrl('LiveTv/TunerHosts', {
Id: id
})
}).then(function () {
reload(page);
});
});
}
});
}
function reload(page) {
loading.show();
ApiClient.getNamedConfiguration('livetv').then(function (config) {
renderDevices(page, config.TunerHosts);
renderProviders(page, config.ListingProviders);
function reload(page) {
loading.show();
ApiClient.getNamedConfiguration('livetv').then(function (config) {
renderDevices(page, config.TunerHosts);
renderProviders(page, config.ListingProviders);
});
loading.hide();
}
function submitAddDeviceForm(page) {
page.querySelector('.dlgAddDevice').close();
loading.show();
ApiClient.ajax({
type: 'POST',
url: ApiClient.getUrl('LiveTv/TunerHosts'),
data: JSON.stringify({
Type: $('#selectTunerDeviceType', page).val(),
Url: $('#txtDevicePath', page).val()
}),
contentType: 'application/json'
}).then(function () {
reload(page);
}, function () {
Dashboard.alert({
message: globalize.translate('ErrorAddingTunerDevice')
});
loading.hide();
}
});
}
function submitAddDeviceForm(page) {
page.querySelector('.dlgAddDevice').close();
loading.show();
ApiClient.ajax({
type: 'POST',
url: ApiClient.getUrl('LiveTv/TunerHosts'),
data: JSON.stringify({
Type: $('#selectTunerDeviceType', page).val(),
Url: $('#txtDevicePath', page).val()
}),
contentType: 'application/json'
}).then(function () {
reload(page);
}, function () {
Dashboard.alert({
message: globalize.translate('ErrorAddingTunerDevice')
});
});
}
function renderProviders(page, providers) {
let html = '';
function renderProviders(page, providers) {
var html = '';
if (providers.length) {
html += '<div class="paperList">';
for (var i = 0, length = providers.length; i < length; i++) {
var provider = providers[i];
html += '<div class="listItem">';
html += '<span class="listItemIcon material-icons dvr"></span>';
html += '<div class="listItemBody two-line">';
html += '<a is="emby-linkbutton" style="display:block;padding:0;margin:0;text-align:left;" class="clearLink" href="' + getProviderConfigurationUrl(provider.Type) + '&id=' + provider.Id + '">';
html += '<h3 class="listItemBodyText">';
html += getProviderName(provider.Type);
html += '</h3>';
html += '<div class="listItemBodyText secondary">';
html += provider.Path || provider.ListingsId || '';
html += '</div>';
html += '</a>';
html += '</div>';
html += '<button type="button" is="paper-icon-button-light" class="btnOptions" data-id="' + provider.Id + '"><span class="material-icons listItemAside more_vert"></span></button>';
html += '</div>';
}
if (providers.length) {
html += '<div class="paperList">';
for (let i = 0, length = providers.length; i < length; i++) {
const provider = providers[i];
html += '<div class="listItem">';
html += '<span class="listItemIcon material-icons dvr"></span>';
html += '<div class="listItemBody two-line">';
html += '<a is="emby-linkbutton" style="display:block;padding:0;margin:0;text-align:left;" class="clearLink" href="' + getProviderConfigurationUrl(provider.Type) + '&id=' + provider.Id + '">';
html += '<h3 class="listItemBodyText">';
html += getProviderName(provider.Type);
html += '</h3>';
html += '<div class="listItemBodyText secondary">';
html += provider.Path || provider.ListingsId || '';
html += '</div>';
html += '</a>';
html += '</div>';
html += '<button type="button" is="paper-icon-button-light" class="btnOptions" data-id="' + provider.Id + '"><span class="material-icons listItemAside more_vert"></span></button>';
html += '</div>';
}
var elem = $('.providerList', page).html(html);
$('.btnOptions', elem).on('click', function () {
var id = this.getAttribute('data-id');
showProviderOptions(page, id, this);
});
html += '</div>';
}
function showProviderOptions(page, providerId, button) {
var items = [];
items.push({
name: globalize.translate('ButtonDelete'),
id: 'delete'
});
items.push({
name: globalize.translate('MapChannels'),
id: 'map'
});
const elem = $('.providerList', page).html(html);
$('.btnOptions', elem).on('click', function () {
const id = this.getAttribute('data-id');
showProviderOptions(page, id, this);
});
}
require(['actionsheet'], function (actionsheet) {
actionsheet.show({
items: items,
positionTo: button
}).then(function (id) {
switch (id) {
case 'delete':
deleteProvider(page, providerId);
break;
function showProviderOptions(page, providerId, button) {
const items = [];
items.push({
name: globalize.translate('Delete'),
id: 'delete'
});
items.push({
name: globalize.translate('MapChannels'),
id: 'map'
});
case 'map':
mapChannels(page, providerId);
}
});
});
}
import('actionsheet').then(({default: actionsheet}) => {
actionsheet.show({
items: items,
positionTo: button
}).then(function (id) {
switch (id) {
case 'delete':
deleteProvider(page, providerId);
break;
function mapChannels(page, providerId) {
require(['components/channelMapper/channelMapper'], function (channelMapper) {
new channelMapper.default({
serverId: ApiClient.serverInfo().Id,
providerId: providerId
}).show();
});
}
function deleteProvider(page, id) {
var message = globalize.translate('MessageConfirmDeleteGuideProvider');
require(['confirm'], function (confirm) {
confirm.default(message, globalize.translate('HeaderDeleteProvider')).then(function () {
loading.show();
ApiClient.ajax({
type: 'DELETE',
url: ApiClient.getUrl('LiveTv/ListingProviders', {
Id: id
})
}).then(function () {
reload(page);
}, function () {
reload(page);
});
});
});
}
function getTunerName(providerId) {
switch (providerId = providerId.toLowerCase()) {
case 'm3u':
return 'M3U';
case 'hdhomerun':
return 'HDHomeRun';
case 'hauppauge':
return 'Hauppauge';
case 'satip':
return 'DVB';
default:
return 'Unknown';
}
}
function getProviderName(providerId) {
switch (providerId = providerId.toLowerCase()) {
case 'schedulesdirect':
return 'Schedules Direct';
case 'xmltv':
return 'XMLTV';
default:
return 'Unknown';
}
}
function getProviderConfigurationUrl(providerId) {
switch (providerId = providerId.toLowerCase()) {
case 'xmltv':
return 'livetvguideprovider.html?type=xmltv';
case 'schedulesdirect':
return 'livetvguideprovider.html?type=schedulesdirect';
}
}
function addProvider(button) {
var menuItems = [];
menuItems.push({
name: 'Schedules Direct',
id: 'SchedulesDirect'
});
menuItems.push({
name: 'XMLTV',
id: 'xmltv'
});
require(['actionsheet'], function (actionsheet) {
actionsheet.show({
items: menuItems,
positionTo: button,
callback: function (id) {
Dashboard.navigate(getProviderConfigurationUrl(id));
}
});
});
}
function addDevice(button) {
Dashboard.navigate('livetvtuner.html');
}
function showDeviceMenu(button, tunerDeviceId) {
var items = [];
items.push({
name: globalize.translate('ButtonDelete'),
id: 'delete'
});
items.push({
name: globalize.translate('ButtonEdit'),
id: 'edit'
});
require(['actionsheet'], function (actionsheet) {
actionsheet.show({
items: items,
positionTo: button
}).then(function (id) {
switch (id) {
case 'delete':
deleteDevice(dom.parentWithClass(button, 'page'), tunerDeviceId);
break;
case 'edit':
Dashboard.navigate('livetvtuner.html?id=' + tunerDeviceId);
}
});
});
}
function onDevicesListClick(e) {
var card = dom.parentWithClass(e.target, 'card');
if (card) {
var id = card.getAttribute('data-id');
var btnCardOptions = dom.parentWithClass(e.target, 'btnCardOptions');
if (btnCardOptions) {
showDeviceMenu(btnCardOptions, id);
} else {
Dashboard.navigate('livetvtuner.html?id=' + id);
case 'map':
mapChannels(page, providerId);
}
});
});
}
function mapChannels(page, providerId) {
import('components/channelMapper/channelMapper').then(({default: channelMapper}) => {
new channelMapper({
serverId: ApiClient.serverInfo().Id,
providerId: providerId
}).show();
});
}
function deleteProvider(page, id) {
const message = globalize.translate('MessageConfirmDeleteGuideProvider');
import('confirm').then(({default: confirm}) => {
confirm(message, globalize.translate('HeaderDeleteProvider')).then(function () {
loading.show();
ApiClient.ajax({
type: 'DELETE',
url: ApiClient.getUrl('LiveTv/ListingProviders', {
Id: id
})
}).then(function () {
reload(page);
}, function () {
reload(page);
});
});
});
}
function getTunerName(providerId) {
switch (providerId = providerId.toLowerCase()) {
case 'm3u':
return 'M3U';
case 'hdhomerun':
return 'HDHomeRun';
case 'hauppauge':
return 'Hauppauge';
case 'satip':
return 'DVB';
default:
return 'Unknown';
}
}
function getProviderName(providerId) {
switch (providerId = providerId.toLowerCase()) {
case 'schedulesdirect':
return 'Schedules Direct';
case 'xmltv':
return 'XMLTV';
default:
return 'Unknown';
}
}
function getProviderConfigurationUrl(providerId) {
switch (providerId = providerId.toLowerCase()) {
case 'xmltv':
return 'livetvguideprovider.html?type=xmltv';
case 'schedulesdirect':
return 'livetvguideprovider.html?type=schedulesdirect';
}
}
function addProvider(button) {
const menuItems = [];
menuItems.push({
name: 'Schedules Direct',
id: 'SchedulesDirect'
});
menuItems.push({
name: 'XMLTV',
id: 'xmltv'
});
import('actionsheet').then(({default: actionsheet}) => {
actionsheet.show({
items: menuItems,
positionTo: button,
callback: function (id) {
Dashboard.navigate(getProviderConfigurationUrl(id));
}
});
});
}
function addDevice(button) {
Dashboard.navigate('livetvtuner.html');
}
function showDeviceMenu(button, tunerDeviceId) {
const items = [];
items.push({
name: globalize.translate('Delete'),
id: 'delete'
});
items.push({
name: globalize.translate('ButtonEdit'),
id: 'edit'
});
import('actionsheet').then(({default: actionsheet}) => {
actionsheet.show({
items: items,
positionTo: button
}).then(function (id) {
switch (id) {
case 'delete':
deleteDevice(dom.parentWithClass(button, 'page'), tunerDeviceId);
break;
case 'edit':
Dashboard.navigate('livetvtuner.html?id=' + tunerDeviceId);
}
});
});
}
function onDevicesListClick(e) {
const card = dom.parentWithClass(e.target, 'card');
if (card) {
const id = card.getAttribute('data-id');
const btnCardOptions = dom.parentWithClass(e.target, 'btnCardOptions');
if (btnCardOptions) {
showDeviceMenu(btnCardOptions, id);
} else {
Dashboard.navigate('livetvtuner.html?id=' + id);
}
}
}
export default function () {
$(document).on('pageinit', '#liveTvStatusPage', function () {
var page = this;
const page = this;
$('.btnAddDevice', page).on('click', function () {
addDevice(this);
});
@ -301,21 +310,21 @@ define(['jQuery', 'globalize', 'scripts/taskbutton', 'dom', 'libraryMenu', 'layo
});
page.querySelector('.devicesList').addEventListener('click', onDevicesListClick);
}).on('pageshow', '#liveTvStatusPage', function () {
var page = this;
const page = this;
reload(page);
taskButton.default({
taskButton({
mode: 'on',
progressElem: page.querySelector('.refreshGuideProgress'),
taskKey: 'RefreshGuide',
button: page.querySelector('.btnRefresh')
});
}).on('pagehide', '#liveTvStatusPage', function () {
var page = this;
taskButton.default({
const page = this;
taskButton({
mode: 'off',
progressElem: page.querySelector('.refreshGuideProgress'),
taskKey: 'RefreshGuide',
button: page.querySelector('.btnRefresh')
});
});
});
}

View file

@ -0,0 +1,79 @@
<div id="liveTvTunerPage" data-role="page" class="page type-interior liveTvSettingsPage">
<div>
<div class="content-primary">
<form>
<div class="verticalSection">
<div class="sectionTitleContainer flex align-items-center">
<h1 class="sectionTitle">${HeaderLiveTvTunerSetup}</h1>
<a is="emby-linkbutton" rel="noopener noreferrer" class="raised button-alt headerHelpButton" target="_blank" href="https://docs.jellyfin.org/general/server/live-tv/index.html">${Help}</a>
</div>
</div>
<div class="selectContainer">
<select is="emby-select" class="selectType" label="${LabelTunerType}" required="required"></select>
</div>
<button is="emby-button" type="button" class="raised button-cancel block btnDetect hide" style="margin-bottom:3em;">${HeaderDetectMyDevices}</button>
<div class="inputContainer fldFriendlyName hide">
<input is="emby-input" type="text" class="txtFriendlyName" label="${LabelFriendlyName}" autocomplete="off" />
</div>
<div class="inputContainer fldPath hide">
<div style="display: flex; align-items: center;">
<div style="flex-grow: 1;">
<input is="emby-input" type="text" class="txtDevicePath" label="${LabelFileOrUrl}" required="required" autocomplete="off" />
</div>
<button type="button" is="paper-icon-button-light" class="btnSelectPath hide emby-input-iconbutton"><span class="material-icons search"></span></button>
</div>
</div>
<div class="inputContainer fldUserAgent hide">
<input is="emby-input" type="text" class="txtUserAgent" label="${LabelUserAgent}" autocomplete="off" />
<div class="fieldDescription">${UserAgentHelp}</div>
</div>
<div class="inputContainer fldTunerCount hide">
<input is="emby-input" type="number" pattern="[0-9]*" required="required" min="0" step="1" class="txtTunerCount" label="${LabelSimultaneousConnectionLimit}" autocomplete="off" value="0" />
<div class="fieldDescription">${SimultaneousConnectionLimitHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription fldFavorites hide">
<label>
<input type="checkbox" is="emby-checkbox" class="chkFavorite" />
<span>${LabelImportOnlyFavoriteChannels}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${ImportFavoriteChannelsHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription fldTranscode hide">
<label>
<input type="checkbox" is="emby-checkbox" class="chkTranscode" />
<span>${LabelAllowHWTranscoding}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${AllowHWTranscodingHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription fldStreamLoop hide">
<label>
<input type="checkbox" is="emby-checkbox" class="chkStreamLoop" />
<span>${EnableStreamLooping}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${EnableStreamLoopingHelp}</div>
</div>
<p class="drmMessage hide">${DrmChannelsNotImported}</p>
<br />
<input type="hidden" class="fldDeviceId" />
<div>
<button is="emby-button" type="submit" class="raised button-submit block">
<span>${Save}</span>
</button>
<button is="emby-button" type="button" class="raised button-cancel block btnCancel" onclick="history.back();">
<span>${ButtonCancel}</span>
</button>
</div>
</form>
</div>
</div>
</div>

View file

@ -1,232 +1,228 @@
define(['globalize', 'loading', 'libraryMenu', 'dom', 'emby-input', 'emby-button', 'emby-checkbox', 'emby-select'], function (globalize, loading, libraryMenu, dom) {
'use strict';
import globalize from 'globalize';
import loading from 'loading';
import dom from 'dom';
import 'emby-input';
import 'emby-button';
import 'emby-checkbox';
import 'emby-select';
function isM3uVariant(type) {
return ['nextpvr'].indexOf(type || '') !== -1;
}
function isM3uVariant(type) {
return ['nextpvr'].indexOf(type || '') !== -1;
}
function fillTypes(view, currentId) {
return ApiClient.getJSON(ApiClient.getUrl('LiveTv/TunerHosts/Types')).then(function (types) {
var selectType = view.querySelector('.selectType');
var html = '';
html += types.map(function (tuner) {
return '<option value="' + tuner.Id + '">' + tuner.Name + '</option>';
}).join('');
html += '<option value="other">';
html += globalize.translate('TabOther');
html += '</option>';
selectType.innerHTML = html;
selectType.disabled = null != currentId;
selectType.value = '';
onTypeChange.call(selectType);
});
}
function reload(view, providerId) {
view.querySelector('.txtDevicePath').value = '';
view.querySelector('.chkFavorite').checked = false;
view.querySelector('.txtDevicePath').value = '';
if (providerId) {
ApiClient.getNamedConfiguration('livetv').then(function (config) {
var info = config.TunerHosts.filter(function (i) {
return i.Id === providerId;
})[0];
fillTunerHostInfo(view, info);
});
}
}
function fillTunerHostInfo(view, info) {
var selectType = view.querySelector('.selectType');
var type = info.Type || '';
if (info.Source && isM3uVariant(info.Source)) {
type = info.Source;
}
selectType.value = type;
function fillTypes(view, currentId) {
return ApiClient.getJSON(ApiClient.getUrl('LiveTv/TunerHosts/Types')).then(function (types) {
const selectType = view.querySelector('.selectType');
let html = '';
html += types.map(function (tuner) {
return '<option value="' + tuner.Id + '">' + tuner.Name + '</option>';
}).join('');
html += '<option value="other">';
html += globalize.translate('TabOther');
html += '</option>';
selectType.innerHTML = html;
selectType.disabled = currentId != null;
selectType.value = '';
onTypeChange.call(selectType);
view.querySelector('.txtDevicePath').value = info.Url || '';
view.querySelector('.txtFriendlyName').value = info.FriendlyName || '';
view.querySelector('.txtUserAgent').value = info.UserAgent || '';
view.querySelector('.fldDeviceId').value = info.DeviceId || '';
view.querySelector('.chkFavorite').checked = info.ImportFavoritesOnly;
view.querySelector('.chkTranscode').checked = info.AllowHWTranscoding;
view.querySelector('.chkStreamLoop').checked = info.EnableStreamLooping;
view.querySelector('.txtTunerCount').value = info.TunerCount || '0';
}
});
}
function submitForm(page) {
loading.show();
var info = {
Type: page.querySelector('.selectType').value,
Url: page.querySelector('.txtDevicePath').value || null,
UserAgent: page.querySelector('.txtUserAgent').value || null,
FriendlyName: page.querySelector('.txtFriendlyName').value || null,
DeviceId: page.querySelector('.fldDeviceId').value || null,
TunerCount: page.querySelector('.txtTunerCount').value || 0,
ImportFavoritesOnly: page.querySelector('.chkFavorite').checked,
AllowHWTranscoding: page.querySelector('.chkTranscode').checked,
EnableStreamLooping: page.querySelector('.chkStreamLoop').checked
};
function reload(view, providerId) {
view.querySelector('.txtDevicePath').value = '';
view.querySelector('.chkFavorite').checked = false;
view.querySelector('.txtDevicePath').value = '';
if (isM3uVariant(info.Type)) {
info.Source = info.Type;
info.Type = 'm3u';
}
var id = getParameterByName('id');
if (id) {
info.Id = id;
}
ApiClient.ajax({
type: 'POST',
url: ApiClient.getUrl('LiveTv/TunerHosts'),
data: JSON.stringify(info),
contentType: 'application/json'
}).then(function (result) {
Dashboard.processServerConfigurationUpdateResult();
Dashboard.navigate('livetvstatus.html');
}, function () {
loading.hide();
Dashboard.alert({
message: globalize.translate('ErrorSavingTvProvider')
});
if (providerId) {
ApiClient.getNamedConfiguration('livetv').then(function (config) {
const info = config.TunerHosts.filter(function (i) {
return i.Id === providerId;
})[0];
fillTunerHostInfo(view, info);
});
}
}
function getRequirePromise(deps) {
return new Promise(function (resolve, reject) {
require(deps, resolve);
});
function fillTunerHostInfo(view, info) {
const selectType = view.querySelector('.selectType');
let type = info.Type || '';
if (info.Source && isM3uVariant(info.Source)) {
type = info.Source;
}
function getDetectedDevice() {
return getRequirePromise(['tunerPicker']).then(function (tunerPicker) {
return new tunerPicker().show({
serverId: ApiClient.serverId()
});
});
}
selectType.value = type;
onTypeChange.call(selectType);
view.querySelector('.txtDevicePath').value = info.Url || '';
view.querySelector('.txtFriendlyName').value = info.FriendlyName || '';
view.querySelector('.txtUserAgent').value = info.UserAgent || '';
view.querySelector('.fldDeviceId').value = info.DeviceId || '';
view.querySelector('.chkFavorite').checked = info.ImportFavoritesOnly;
view.querySelector('.chkTranscode').checked = info.AllowHWTranscoding;
view.querySelector('.chkStreamLoop').checked = info.EnableStreamLooping;
view.querySelector('.txtTunerCount').value = info.TunerCount || '0';
}
function onTypeChange() {
var value = this.value;
var view = dom.parentWithClass(this, 'page');
var mayIncludeUnsupportedDrmChannels = 'hdhomerun' === value;
var supportsTranscoding = 'hdhomerun' === value;
var supportsFavorites = 'hdhomerun' === value;
var supportsTunerIpAddress = 'hdhomerun' === value;
var supportsTunerFileOrUrl = 'm3u' === value;
var supportsStreamLooping = 'm3u' === value;
var supportsTunerCount = 'm3u' === value;
var supportsUserAgent = 'm3u' === value;
var suppportsSubmit = 'other' !== value;
var supportsSelectablePath = supportsTunerFileOrUrl;
var txtDevicePath = view.querySelector('.txtDevicePath');
if (supportsTunerIpAddress) {
txtDevicePath.label(globalize.translate('LabelTunerIpAddress'));
view.querySelector('.fldPath').classList.remove('hide');
} else if (supportsTunerFileOrUrl) {
txtDevicePath.label(globalize.translate('LabelFileOrUrl'));
view.querySelector('.fldPath').classList.remove('hide');
} else {
view.querySelector('.fldPath').classList.add('hide');
}
if (supportsSelectablePath) {
view.querySelector('.btnSelectPath').classList.remove('hide');
view.querySelector('.txtDevicePath').setAttribute('required', 'required');
} else {
view.querySelector('.btnSelectPath').classList.add('hide');
view.querySelector('.txtDevicePath').removeAttribute('required');
}
if (supportsUserAgent) {
view.querySelector('.fldUserAgent').classList.remove('hide');
} else {
view.querySelector('.fldUserAgent').classList.add('hide');
}
if (supportsFavorites) {
view.querySelector('.fldFavorites').classList.remove('hide');
} else {
view.querySelector('.fldFavorites').classList.add('hide');
}
if (supportsTranscoding) {
view.querySelector('.fldTranscode').classList.remove('hide');
} else {
view.querySelector('.fldTranscode').classList.add('hide');
}
if (supportsStreamLooping) {
view.querySelector('.fldStreamLoop').classList.remove('hide');
} else {
view.querySelector('.fldStreamLoop').classList.add('hide');
}
if (supportsTunerCount) {
view.querySelector('.fldTunerCount').classList.remove('hide');
view.querySelector('.txtTunerCount').setAttribute('required', 'required');
} else {
view.querySelector('.fldTunerCount').classList.add('hide');
view.querySelector('.txtTunerCount').removeAttribute('required');
}
if (mayIncludeUnsupportedDrmChannels) {
view.querySelector('.drmMessage').classList.remove('hide');
} else {
view.querySelector('.drmMessage').classList.add('hide');
}
if (suppportsSubmit) {
view.querySelector('.button-submit').classList.remove('hide');
} else {
view.querySelector('.button-submit').classList.add('hide');
}
}
return function (view, params) {
if (!params.id) {
view.querySelector('.btnDetect').classList.remove('hide');
}
view.addEventListener('viewshow', function () {
var currentId = params.id;
fillTypes(view, currentId).then(function () {
reload(view, currentId);
});
});
view.querySelector('form').addEventListener('submit', function (e) {
submitForm(view);
e.preventDefault();
e.stopPropagation();
return false;
});
view.querySelector('.selectType').addEventListener('change', onTypeChange);
view.querySelector('.btnDetect').addEventListener('click', function () {
getDetectedDevice().then(function (info) {
fillTunerHostInfo(view, info);
});
});
view.querySelector('.btnSelectPath').addEventListener('click', function () {
require(['directorybrowser'], function (directoryBrowser) {
var picker = new directoryBrowser.default();
picker.show({
includeFiles: true,
callback: function (path) {
if (path) {
view.querySelector('.txtDevicePath').value = path;
}
picker.close();
}
});
});
});
function submitForm(page) {
loading.show();
const info = {
Type: page.querySelector('.selectType').value,
Url: page.querySelector('.txtDevicePath').value || null,
UserAgent: page.querySelector('.txtUserAgent').value || null,
FriendlyName: page.querySelector('.txtFriendlyName').value || null,
DeviceId: page.querySelector('.fldDeviceId').value || null,
TunerCount: page.querySelector('.txtTunerCount').value || 0,
ImportFavoritesOnly: page.querySelector('.chkFavorite').checked,
AllowHWTranscoding: page.querySelector('.chkTranscode').checked,
EnableStreamLooping: page.querySelector('.chkStreamLoop').checked
};
});
if (isM3uVariant(info.Type)) {
info.Source = info.Type;
info.Type = 'm3u';
}
if (getParameterByName('id')) {
info.Id = getParameterByName('id');
}
ApiClient.ajax({
type: 'POST',
url: ApiClient.getUrl('LiveTv/TunerHosts'),
data: JSON.stringify(info),
contentType: 'application/json'
}).then(function (result) {
Dashboard.processServerConfigurationUpdateResult();
Dashboard.navigate('livetvstatus.html');
}, function () {
loading.hide();
Dashboard.alert({
message: globalize.translate('ErrorSavingTvProvider')
});
});
}
function getDetectedDevice() {
return import('tunerPicker').then(({default: tunerPicker}) => {
return new tunerPicker().show({
serverId: ApiClient.serverId()
});
});
}
function onTypeChange() {
const value = this.value;
const view = dom.parentWithClass(this, 'page');
const mayIncludeUnsupportedDrmChannels = value === 'hdhomerun';
const supportsTranscoding = value === 'hdhomerun';
const supportsFavorites = value === 'hdhomerun';
const supportsTunerIpAddress = value === 'hdhomerun';
const supportsTunerFileOrUrl = value === 'm3u';
const supportsStreamLooping = value === 'm3u';
const supportsTunerCount = value === 'm3u';
const supportsUserAgent = value === 'm3u';
const suppportsSubmit = value !== 'other';
const supportsSelectablePath = supportsTunerFileOrUrl;
const txtDevicePath = view.querySelector('.txtDevicePath');
if (supportsTunerIpAddress) {
txtDevicePath.label(globalize.translate('LabelTunerIpAddress'));
view.querySelector('.fldPath').classList.remove('hide');
} else if (supportsTunerFileOrUrl) {
txtDevicePath.label(globalize.translate('LabelFileOrUrl'));
view.querySelector('.fldPath').classList.remove('hide');
} else {
view.querySelector('.fldPath').classList.add('hide');
}
if (supportsSelectablePath) {
view.querySelector('.btnSelectPath').classList.remove('hide');
view.querySelector('.txtDevicePath').setAttribute('required', 'required');
} else {
view.querySelector('.btnSelectPath').classList.add('hide');
view.querySelector('.txtDevicePath').removeAttribute('required');
}
if (supportsUserAgent) {
view.querySelector('.fldUserAgent').classList.remove('hide');
} else {
view.querySelector('.fldUserAgent').classList.add('hide');
}
if (supportsFavorites) {
view.querySelector('.fldFavorites').classList.remove('hide');
} else {
view.querySelector('.fldFavorites').classList.add('hide');
}
if (supportsTranscoding) {
view.querySelector('.fldTranscode').classList.remove('hide');
} else {
view.querySelector('.fldTranscode').classList.add('hide');
}
if (supportsStreamLooping) {
view.querySelector('.fldStreamLoop').classList.remove('hide');
} else {
view.querySelector('.fldStreamLoop').classList.add('hide');
}
if (supportsTunerCount) {
view.querySelector('.fldTunerCount').classList.remove('hide');
view.querySelector('.txtTunerCount').setAttribute('required', 'required');
} else {
view.querySelector('.fldTunerCount').classList.add('hide');
view.querySelector('.txtTunerCount').removeAttribute('required');
}
if (mayIncludeUnsupportedDrmChannels) {
view.querySelector('.drmMessage').classList.remove('hide');
} else {
view.querySelector('.drmMessage').classList.add('hide');
}
if (suppportsSubmit) {
view.querySelector('.button-submit').classList.remove('hide');
} else {
view.querySelector('.button-submit').classList.add('hide');
}
}
export default function (view, params) {
if (!params.id) {
view.querySelector('.btnDetect').classList.remove('hide');
}
view.addEventListener('viewshow', function () {
const currentId = params.id;
fillTypes(view, currentId).then(function () {
reload(view, currentId);
});
});
view.querySelector('form').addEventListener('submit', function (e) {
submitForm(view);
e.preventDefault();
e.stopPropagation();
return false;
});
view.querySelector('.selectType').addEventListener('change', onTypeChange);
view.querySelector('.btnDetect').addEventListener('click', function () {
getDetectedDevice().then(function (info) {
fillTunerHostInfo(view, info);
});
});
view.querySelector('.btnSelectPath').addEventListener('click', function () {
import('directorybrowser').then(({default: directorybrowser}) => {
const picker = new directorybrowser();
picker.show({
includeFiles: true,
callback: function (path) {
if (path) {
view.querySelector('.txtDevicePath').value = path;
}
picker.close();
}
});
});
});
}

View file

@ -1,12 +1,18 @@
define(['loading', 'events', 'libraryBrowser', 'imageLoader', 'listView', 'cardBuilder', 'userSettings', 'globalize', 'emby-itemscontainer'], function (loading, events, libraryBrowser, imageLoader, listView, cardBuilder, userSettings, globalize) {
'use strict';
import loading from 'loading';
import libraryBrowser from 'libraryBrowser';
import imageLoader from 'imageLoader';
import listView from 'listView';
import cardBuilder from 'cardBuilder';
import * as userSettings from 'userSettings';
import globalize from 'globalize';
import 'emby-itemscontainer';
libraryBrowser = libraryBrowser.default || libraryBrowser;
/* eslint-disable indent */
return function (view, params, tabContent) {
export default function (view, params, tabContent) {
function getPageData(context) {
var key = getSavedQueryKey(context);
var pageData = data[key];
const key = getSavedQueryKey(context);
let pageData = data[key];
if (!pageData) {
pageData = data[key] = {
@ -46,11 +52,11 @@ define(['loading', 'events', 'libraryBrowser', 'imageLoader', 'listView', 'cardB
return context.savedQueryKey;
}
function onViewStyleChange() {
var viewStyle = self.getCurrentViewStyle();
var itemsContainer = tabContent.querySelector('.itemsContainer');
const onViewStyleChange = () => {
const viewStyle = this.getCurrentViewStyle();
const itemsContainer = tabContent.querySelector('.itemsContainer');
if ('List' == viewStyle) {
if (viewStyle == 'List') {
itemsContainer.classList.add('vertical-list');
itemsContainer.classList.remove('vertical-wrap');
} else {
@ -59,13 +65,13 @@ define(['loading', 'events', 'libraryBrowser', 'imageLoader', 'listView', 'cardB
}
itemsContainer.innerHTML = '';
}
};
function reloadItems(page) {
const reloadItems = (page) => {
loading.show();
isLoading = true;
var query = getQuery(page);
ApiClient.getItems(ApiClient.getCurrentUserId(), query).then(function (result) {
const query = getQuery(page);
ApiClient.getItems(ApiClient.getCurrentUserId(), query).then((result) => {
function onNextPageClick() {
if (isLoading) {
return;
@ -89,8 +95,8 @@ define(['loading', 'events', 'libraryBrowser', 'imageLoader', 'listView', 'cardB
}
window.scrollTo(0, 0);
var html;
var pagingHtml = libraryBrowser.getQueryPagingHtml({
let html;
const pagingHtml = libraryBrowser.getQueryPagingHtml({
startIndex: query.StartIndex,
limit: query.Limit,
totalRecordCount: result.TotalRecordCount,
@ -100,7 +106,7 @@ define(['loading', 'events', 'libraryBrowser', 'imageLoader', 'listView', 'cardB
sortButton: false,
filterButton: false
});
var viewStyle = self.getCurrentViewStyle();
const viewStyle = this.getCurrentViewStyle();
if (viewStyle == 'Thumb') {
html = cardBuilder.getCardsHtml({
items: result.Items,
@ -154,22 +160,21 @@ define(['loading', 'events', 'libraryBrowser', 'imageLoader', 'listView', 'cardB
showTitle: true
});
}
var i;
var length;
var elems = tabContent.querySelectorAll('.paging');
for (i = 0, length = elems.length; i < length; i++) {
elems[i].innerHTML = pagingHtml;
let elems = tabContent.querySelectorAll('.paging');
for (const elem of elems) {
elem.innerHTML = pagingHtml;
}
elems = tabContent.querySelectorAll('.btnNextPage');
for (i = 0, length = elems.length; i < length; i++) {
elems[i].addEventListener('click', onNextPageClick);
for (const elem of elems) {
elem.addEventListener('click', onNextPageClick);
}
elems = tabContent.querySelectorAll('.btnPreviousPage');
for (i = 0, length = elems.length; i < length; i++) {
elems[i].addEventListener('click', onPreviousPageClick);
for (const elem of elems) {
elem.addEventListener('click', onPreviousPageClick);
}
if (!result.Items.length) {
@ -181,28 +186,27 @@ define(['loading', 'events', 'libraryBrowser', 'imageLoader', 'listView', 'cardB
html += '</div>';
}
var itemsContainer = tabContent.querySelector('.itemsContainer');
const itemsContainer = tabContent.querySelector('.itemsContainer');
itemsContainer.innerHTML = html;
imageLoader.lazyChildren(itemsContainer);
libraryBrowser.saveQueryValues(getSavedQueryKey(page), query);
loading.hide();
isLoading = false;
require(['autoFocuser'], function (autoFocuser) {
import('autoFocuser').then(({default: autoFocuser}) => {
autoFocuser.autoFocus(page);
});
});
}
};
var self = this;
var data = {};
var isLoading = false;
const data = {};
let isLoading = false;
self.getCurrentViewStyle = function () {
this.getCurrentViewStyle = function () {
return getPageData(tabContent).view;
};
function initPage(tabContent) {
const initPage = (tabContent) => {
tabContent.querySelector('.btnSort').addEventListener('click', function (e) {
libraryBrowser.showSortMenu({
items: [{
@ -229,36 +233,37 @@ define(['loading', 'events', 'libraryBrowser', 'imageLoader', 'listView', 'cardB
button: e.target
});
});
var btnSelectView = tabContent.querySelector('.btnSelectView');
const btnSelectView = tabContent.querySelector('.btnSelectView');
btnSelectView.addEventListener('click', function (e) {
libraryBrowser.showLayoutMenu(e.target, self.getCurrentViewStyle(), 'List,Poster,PosterCard,Thumb,ThumbCard'.split(','));
libraryBrowser.showLayoutMenu(e.target, this.getCurrentViewStyle(), 'List,Poster,PosterCard,Thumb,ThumbCard'.split(','));
});
btnSelectView.addEventListener('layoutchange', function (e) {
var viewStyle = e.detail.viewStyle;
const viewStyle = e.detail.viewStyle;
getPageData(tabContent).view = viewStyle;
libraryBrowser.saveViewSetting(getSavedQueryKey(tabContent), viewStyle);
getQuery(tabContent).StartIndex = 0;
onViewStyleChange();
reloadItems(tabContent);
});
tabContent.querySelector('.btnNewCollection').addEventListener('click', function () {
require(['collectionEditor'], function (collectionEditor) {
var serverId = ApiClient.serverInfo().Id;
tabContent.querySelector('.btnNewCollection').addEventListener('click', () => {
import('collectionEditor').then(({default: collectionEditor}) => {
const serverId = ApiClient.serverInfo().Id;
new collectionEditor.showEditor({
items: [],
serverId: serverId
});
});
});
}
};
initPage(tabContent);
onViewStyleChange();
self.renderTab = function () {
this.renderTab = function () {
reloadItems(tabContent);
};
self.destroy = function () {};
};
});
this.destroy = function () {};
}
/* eslint-enable indent */

View file

@ -1,12 +1,18 @@
define(['layoutManager', 'loading', 'libraryBrowser', 'cardBuilder', 'lazyLoader', 'apphost', 'globalize', 'appRouter', 'dom', 'emby-button'], function (layoutManager, loading, libraryBrowser, cardBuilder, lazyLoader, appHost, globalize, appRouter, dom) {
'use strict';
import layoutManager from 'layoutManager';
import loading from 'loading';
import libraryBrowser from 'libraryBrowser';
import cardBuilder from 'cardBuilder';
import lazyLoader from 'lazyLoader';
import globalize from 'globalize';
import appRouter from 'appRouter';
import 'emby-button';
libraryBrowser = libraryBrowser.default || libraryBrowser;
/* eslint-disable indent */
return function (view, params, tabContent) {
export default function (view, params, tabContent) {
function getPageData() {
var key = getSavedQueryKey();
var pageData = data[key];
const key = getSavedQueryKey();
let pageData = data[key];
if (!pageData) {
pageData = data[key] = {
@ -36,7 +42,7 @@ define(['layoutManager', 'loading', 'libraryBrowser', 'cardBuilder', 'lazyLoader
function getPromise() {
loading.show();
var query = getQuery();
const query = getQuery();
return ApiClient.getGenres(ApiClient.getCurrentUserId(), query);
}
@ -52,18 +58,18 @@ define(['layoutManager', 'loading', 'libraryBrowser', 'cardBuilder', 'lazyLoader
return enableScrollX() ? 'overflowPortrait' : 'portrait';
}
function fillItemsContainer(entry) {
var elem = entry.target;
var id = elem.getAttribute('data-id');
var viewStyle = self.getCurrentViewStyle();
var limit = 'Thumb' == viewStyle || 'ThumbCard' == viewStyle ? 5 : 9;
const fillItemsContainer = (entry) => {
const elem = entry.target;
const id = elem.getAttribute('data-id');
const viewStyle = this.getCurrentViewStyle();
let limit = viewStyle == 'Thumb' || viewStyle == 'ThumbCard' ? 5 : 9;
if (enableScrollX()) {
limit = 10;
}
var enableImageTypes = 'Thumb' == viewStyle || 'ThumbCard' == viewStyle ? 'Primary,Backdrop,Thumb' : 'Primary';
var query = {
const enableImageTypes = viewStyle == 'Thumb' || viewStyle == 'ThumbCard' ? 'Primary,Backdrop,Thumb' : 'Primary';
const query = {
SortBy: 'SortName',
SortOrder: 'Ascending',
IncludeItemTypes: 'Movie',
@ -125,17 +131,17 @@ define(['layoutManager', 'loading', 'libraryBrowser', 'cardBuilder', 'lazyLoader
tabContent.querySelector('.btnMoreFromGenre' + id + ' .material-icons').classList.remove('hide');
}
});
}
};
function reloadItems(context, promise) {
var query = getQuery();
const query = getQuery();
promise.then(function (result) {
var elem = context.querySelector('#items');
var html = '';
var items = result.Items;
const elem = context.querySelector('#items');
let html = '';
const items = result.Items;
for (var i = 0, length = items.length; i < length; i++) {
var item = items[i];
for (let i = 0, length = items.length; i < length; i++) {
const item = items[i];
html += '<div class="verticalSection">';
html += '<div class="sectionTitleContainer sectionTitleContainer-cards padded-left">';
@ -150,7 +156,7 @@ define(['layoutManager', 'loading', 'libraryBrowser', 'cardBuilder', 'lazyLoader
html += '</a>';
html += '</div>';
if (enableScrollX()) {
var scrollXClass = 'scrollX hiddenScrollX';
let scrollXClass = 'scrollX hiddenScrollX';
if (layoutManager.tv) {
scrollXClass += 'smoothScrollX padded-top-focusscale padded-bottom-focusscale';
@ -181,37 +187,37 @@ define(['layoutManager', 'loading', 'libraryBrowser', 'cardBuilder', 'lazyLoader
});
}
function fullyReload() {
self.preRender();
self.renderTab();
}
const fullyReload = () => {
this.preRender();
this.renderTab();
};
var self = this;
var data = {};
const data = {};
self.getViewStyles = function () {
this.getViewStyles = function () {
return 'Poster,PosterCard,Thumb,ThumbCard'.split(',');
};
self.getCurrentViewStyle = function () {
this.getCurrentViewStyle = function () {
return getPageData().view;
};
self.setCurrentViewStyle = function (viewStyle) {
this.setCurrentViewStyle = function (viewStyle) {
getPageData().view = viewStyle;
libraryBrowser.saveViewSetting(getSavedQueryKey(), viewStyle);
fullyReload();
};
self.enableViewSelection = true;
var promise;
this.enableViewSelection = true;
let promise;
self.preRender = function () {
this.preRender = function () {
promise = getPromise();
};
self.renderTab = function () {
this.renderTab = function () {
reloadItems(tabContent, promise);
};
};
});
}
/* eslint-enable indent */

View file

@ -0,0 +1,93 @@
<div id="moviesPage" data-role="page" data-dom-cache="true" class="page libraryPage backdropPage collectionEditorPage pageWithAbsoluteTabs withTabs" data-backdroptype="movie">
<div class="pageTabContent" data-index="0">
<div class="flex align-items-center justify-content-center flex-wrap-wrap padded-top padded-left padded-right padded-bottom">
<div class="paging"></div>
<button is="paper-icon-button-light" class="btnSelectView autoSize" title="${ButtonSelectView}"><span class="material-icons view_comfy"></span></button>
<button is="paper-icon-button-light" class="btnSort autoSize" title="${Sort}"><span class="material-icons sort_by_alpha"></span></button>
<button is="paper-icon-button-light" class="btnFilter autoSize" title="${ButtonFilter}"><span class="material-icons filter_list"></span></button>
</div>
<div class="alphaPicker alphaPicker-fixed alphaPicker-vertical">
</div>
<div is="emby-itemscontainer" class="itemsContainer padded-left padded-right">
</div>
<div class="flex align-items-center justify-content-center flex-wrap-wrap padded-top padded-left padded-right padded-bottom">
<div class="paging"></div>
</div>
</div>
<div class="pageTabContent" data-index="1">
<div id="resumableSection" class="verticalSection hide">
<div class="sectionTitleContainer sectionTitleContainer-cards">
<h2 class="sectionTitle sectionTitle-cards padded-left">${HeaderContinueWatching}</h2>
</div>
<div is="emby-itemscontainer" id="resumableItems" class="itemsContainer padded-left padded-right">
</div>
</div>
<div class="verticalSection">
<div class="sectionTitleContainer sectionTitleContainer-cards">
<h2 class="sectionTitle sectionTitle-cards padded-left">${HeaderLatestMovies}</h2>
</div>
<div is="emby-itemscontainer" id="recentlyAddedItems" class="itemsContainer padded-left padded-right">
</div>
</div>
<div class="recommendations">
</div>
<div class="noItemsMessage hide padded-left padded-right">
<br />
<p>${MessageNoMovieSuggestionsAvailable}</p>
</div>
</div>
<div class="pageTabContent" data-index="2">
<div class="flex align-items-center justify-content-center flex-wrap-wrap padded-top padded-left padded-right padded-bottom">
<div class="paging"></div>
<button is="paper-icon-button-light" class="btnSort autoSize" title="${Sort}"><span class="material-icons sort_by_alpha"></span></button>
<button is="paper-icon-button-light" class="btnFilter autoSize" title="${ButtonFilter}"><span class="material-icons filter_list"></span></button>
</div>
<div class="alphaPicker alphaPicker-fixed alphaPicker-fixed-right alphaPicker-vertical">
</div>
<div is="emby-itemscontainer" class="itemsContainer vertical-wrap padded-left padded-right">
</div>
<div class="flex align-items-center justify-content-center flex-wrap-wrap padded-top padded-left padded-right padded-bottom">
<div class="paging"></div>
</div>
</div>
<div class="pageTabContent" data-index="3">
<div class="flex align-items-center justify-content-center flex-wrap-wrap padded-top padded-left padded-right padded-bottom">
<div class="paging"></div>
<button is="paper-icon-button-light" class="btnSelectView autoSize" title="${ButtonSelectView}"><span class="material-icons view_comfy"></span></button>
</div>
<div is="emby-itemscontainer" class="itemsContainer padded-left padded-right">
</div>
<div class="flex align-items-center justify-content-center flex-wrap-wrap padded-top padded-left padded-right padded-bottom">
<div class="paging"></div>
</div>
</div>
<div class="pageTabContent" data-index="4">
<div class="flex align-items-center justify-content-center flex-wrap-wrap padded-top padded-left padded-right padded-bottom">
<div class="paging"></div>
<button is="paper-icon-button-light" class="btnSelectView autoSize" title="${ButtonSelectView}"><span class="material-icons view_comfy"></span></button>
<button is="paper-icon-button-light" class="btnSort autoSize" title="${Sort}"><span class="material-icons sort_by_alpha"></span></button>
<button type="button" is="paper-icon-button-light" class="btnNewCollection autoSize"><span class="material-icons add"></span></button>
</div>
<div is="emby-itemscontainer" class="itemsContainer vertical-wrap centered padded-left padded-right" style="text-align:center;">
</div>
<div class="flex align-items-center justify-content-center flex-wrap-wrap padded-top padded-left padded-right padded-bottom">
<div class="paging"></div>
</div>
</div>
<div class="pageTabContent" data-index="5">
<div id="items"></div>
</div>
<div class="pageTabContent" data-index="6">
</div>
</div>

View file

@ -1,11 +1,18 @@
define(['loading', 'layoutManager', 'userSettings', 'events', 'libraryBrowser', 'alphaPicker', 'listView', 'cardBuilder', 'globalize', 'emby-itemscontainer'], function (loading, layoutManager, userSettings, events, libraryBrowser, AlphaPicker, listView, cardBuilder, globalize) {
'use strict';
import loading from 'loading';
import * as userSettings from 'userSettings';
import events from 'events';
import libraryBrowser from 'libraryBrowser';
import AlphaPicker from 'alphaPicker';
import listView from 'listView';
import cardBuilder from 'cardBuilder';
import globalize from 'globalize';
import 'emby-itemscontainer';
libraryBrowser = libraryBrowser.default || libraryBrowser;
/* eslint-disable indent */
return function (view, params, tabContent, options) {
function onViewStyleChange() {
if (self.getCurrentViewStyle() == 'List') {
export default function (view, params, tabContent, options) {
const onViewStyleChange = () => {
if (this.getCurrentViewStyle() == 'List') {
itemsContainer.classList.add('vertical-list');
itemsContainer.classList.remove('vertical-wrap');
} else {
@ -14,13 +21,13 @@ define(['loading', 'layoutManager', 'userSettings', 'events', 'libraryBrowser',
}
itemsContainer.innerHTML = '';
}
};
function updateFilterControls() {
if (self.alphaPicker) {
self.alphaPicker.value(query.NameStartsWithOrGreater);
const updateFilterControls = () => {
if (this.alphaPicker) {
this.alphaPicker.value(query.NameStartsWithOrGreater);
}
}
};
function fetchData() {
isLoading = true;
@ -53,7 +60,7 @@ define(['loading', 'layoutManager', 'userSettings', 'events', 'libraryBrowser',
window.scrollTo(0, 0);
updateFilterControls();
var pagingHtml = libraryBrowser.getQueryPagingHtml({
const pagingHtml = libraryBrowser.getQueryPagingHtml({
startIndex: query.StartIndex,
limit: query.Limit,
totalRecordCount: result.TotalRecordCount,
@ -63,35 +70,30 @@ define(['loading', 'layoutManager', 'userSettings', 'events', 'libraryBrowser',
sortButton: false,
filterButton: false
});
var i;
var length;
var elems = tabContent.querySelectorAll('.paging');
for (i = 0, length = elems.length; i < length; i++) {
elems[i].innerHTML = pagingHtml;
for (const elem of tabContent.querySelectorAll('.paging')) {
elem.innerHTML = pagingHtml;
}
elems = tabContent.querySelectorAll('.btnNextPage');
for (i = 0, length = elems.length; i < length; i++) {
elems[i].addEventListener('click', onNextPageClick);
for (const elem of tabContent.querySelectorAll('.btnNextPage')) {
elem.addEventListener('click', onNextPageClick);
}
elems = tabContent.querySelectorAll('.btnPreviousPage');
for (i = 0, length = elems.length; i < length; i++) {
elems[i].addEventListener('click', onPreviousPageClick);
for (const elem of tabContent.querySelectorAll('.btnPreviousPage')) {
elem.addEventListener('click', onPreviousPageClick);
}
isLoading = false;
loading.hide();
require(['autoFocuser'], function (autoFocuser) {
import('autoFocuser').then(({default: autoFocuser}) => {
autoFocuser.autoFocus(tabContent);
});
}
function getItemsHtml(items) {
var html;
var viewStyle = self.getCurrentViewStyle();
const getItemsHtml = (items) => {
let html;
const viewStyle = this.getCurrentViewStyle();
if (viewStyle == 'Thumb') {
html = cardBuilder.getCardsHtml({
@ -155,22 +157,22 @@ define(['loading', 'layoutManager', 'userSettings', 'events', 'libraryBrowser',
}
return html;
}
};
function initPage(tabContent) {
const initPage = (tabContent) => {
itemsContainer.fetchData = fetchData;
itemsContainer.getItemsHtml = getItemsHtml;
itemsContainer.afterRefresh = afterRefresh;
var alphaPickerElement = tabContent.querySelector('.alphaPicker');
let alphaPickerElement = tabContent.querySelector('.alphaPicker');
if (alphaPickerElement) {
alphaPickerElement.addEventListener('alphavaluechanged', function (e) {
var newValue = e.detail.value;
let newValue = e.detail.value;
query.NameStartsWithOrGreater = newValue;
query.StartIndex = 0;
itemsContainer.refreshItems();
});
self.alphaPicker = new AlphaPicker.default({
this.alphaPicker = new AlphaPicker({
element: alphaPickerElement,
valueChangeEvent: 'click'
});
@ -180,14 +182,14 @@ define(['loading', 'layoutManager', 'userSettings', 'events', 'libraryBrowser',
itemsContainer.classList.add('padded-right-withalphapicker');
}
var btnFilter = tabContent.querySelector('.btnFilter');
const btnFilter = tabContent.querySelector('.btnFilter');
if (btnFilter) {
btnFilter.addEventListener('click', function () {
self.showFilterMenu();
btnFilter.addEventListener('click', () => {
this.showFilterMenu();
});
}
var btnSort = tabContent.querySelector('.btnSort');
const btnSort = tabContent.querySelector('.btnSort');
if (btnSort) {
btnSort.addEventListener('click', function (e) {
@ -230,24 +232,23 @@ define(['loading', 'layoutManager', 'userSettings', 'events', 'libraryBrowser',
});
});
}
var btnSelectView = tabContent.querySelector('.btnSelectView');
const btnSelectView = tabContent.querySelector('.btnSelectView');
btnSelectView.addEventListener('click', function (e) {
libraryBrowser.showLayoutMenu(e.target, self.getCurrentViewStyle(), 'Banner,List,Poster,PosterCard,Thumb,ThumbCard'.split(','));
libraryBrowser.showLayoutMenu(e.target, this.getCurrentViewStyle, 'Banner,List,Poster,PosterCard,Thumb,ThumbCard'.split(','));
});
btnSelectView.addEventListener('layoutchange', function (e) {
var viewStyle = e.detail.viewStyle;
let viewStyle = e.detail.viewStyle;
userSettings.set(savedViewKey, viewStyle);
query.StartIndex = 0;
onViewStyleChange();
itemsContainer.refreshItems();
});
}
};
var self = this;
var itemsContainer = tabContent.querySelector('.itemsContainer');
var savedQueryKey = params.topParentId + '-' + options.mode;
var savedViewKey = savedQueryKey + '-view';
var query = {
let itemsContainer = tabContent.querySelector('.itemsContainer');
const savedQueryKey = params.topParentId + '-' + options.mode;
const savedViewKey = savedQueryKey + '-view';
let query = {
SortBy: 'SortName,ProductionYear',
SortOrder: 'Ascending',
IncludeItemTypes: 'Movie',
@ -263,7 +264,7 @@ define(['loading', 'layoutManager', 'userSettings', 'events', 'libraryBrowser',
query['Limit'] = userSettings.libraryPageSize();
}
var isLoading = false;
let isLoading = false;
if (options.mode === 'favorites') {
query.IsFavorite = true;
@ -271,14 +272,14 @@ define(['loading', 'layoutManager', 'userSettings', 'events', 'libraryBrowser',
query = userSettings.loadQuerySettings(savedQueryKey, query);
self.showFilterMenu = function () {
require(['components/filterdialog/filterdialog'], function ({default: filterDialogFactory}) {
var filterDialog = new filterDialogFactory({
this.showFilterMenu = function () {
import('components/filterdialog/filterdialog').then(({default: filterDialogFactory}) => {
let filterDialog = new filterDialogFactory({
query: query,
mode: 'movies',
serverId: ApiClient.serverId()
});
events.on(filterDialog, 'filterchange', function () {
events.on(filterDialog, 'filterchange', () => {
query.StartIndex = 0;
itemsContainer.refreshItems();
});
@ -286,22 +287,23 @@ define(['loading', 'layoutManager', 'userSettings', 'events', 'libraryBrowser',
});
};
self.getCurrentViewStyle = function () {
this.getCurrentViewStyle = function () {
return userSettings.get(savedViewKey) || 'Poster';
};
self.initTab = function () {
this.initTab = function () {
initPage(tabContent);
onViewStyleChange();
};
self.renderTab = function () {
this.renderTab = function () {
itemsContainer.refreshItems();
updateFilterControls();
};
self.destroy = function () {
this.destroy = function () {
itemsContainer = null;
};
};
});
}
/* eslint-enable indent */

View file

@ -1,5 +1,20 @@
define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu', 'mainTabsManager', 'cardBuilder', 'dom', 'imageLoader', 'playbackManager', 'globalize', 'emby-scroller', 'emby-itemscontainer', 'emby-tabs', 'emby-button'], function (events, layoutManager, inputManager, userSettings, libraryMenu, mainTabsManager, cardBuilder, dom, imageLoader, playbackManager, globalize) {
'use strict';
import events from 'events';
import layoutManager from 'layoutManager';
import inputManager from 'inputManager';
import * as userSettings from 'userSettings';
import libraryMenu from 'libraryMenu';
import * as mainTabsManager from 'mainTabsManager';
import cardBuilder from 'cardBuilder';
import dom from 'dom';
import imageLoader from 'imageLoader';
import playbackManager from 'playbackManager';
import globalize from 'globalize';
import 'emby-scroller';
import 'emby-itemscontainer';
import 'emby-tabs';
import 'emby-button';
/* eslint-disable indent */
function enableScrollX() {
return !layoutManager.desktop;
@ -14,7 +29,7 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu'
}
function loadLatest(page, userId, parentId) {
var options = {
const options = {
IncludeItemTypes: 'Movie',
Limit: 18,
Fields: 'PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo',
@ -24,8 +39,8 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu'
EnableTotalRecordCount: false
};
ApiClient.getJSON(ApiClient.getUrl('Users/' + userId + '/Items/Latest', options)).then(function (items) {
var allowBottomPadding = !enableScrollX();
var container = page.querySelector('#recentlyAddedItems');
const allowBottomPadding = !enableScrollX();
const container = page.querySelector('#recentlyAddedItems');
cardBuilder.buildCards(items, {
itemsContainer: container,
shape: getPortraitShape(),
@ -43,8 +58,8 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu'
}
function loadResume(page, userId, parentId) {
var screenWidth = dom.getWindowSize().innerWidth;
var options = {
let screenWidth = dom.getWindowSize().innerWidth;
const options = {
SortBy: 'DatePlayed',
SortOrder: 'Descending',
IncludeItemTypes: 'Movie',
@ -65,8 +80,8 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu'
page.querySelector('#resumableSection').classList.add('hide');
}
var allowBottomPadding = !enableScrollX();
var container = page.querySelector('#resumableItems');
const allowBottomPadding = !enableScrollX();
const container = page.querySelector('#resumableItems');
cardBuilder.buildCards(result.Items, {
itemsContainer: container,
preferThumb: true,
@ -86,8 +101,8 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu'
}
function getRecommendationHtml(recommendation) {
var html = '';
var title = '';
let html = '';
let title = '';
switch (recommendation.RecommendationType) {
case 'SimilarToRecentlyPlayed':
@ -111,7 +126,7 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu'
html += '<div class="verticalSection">';
html += '<h2 class="sectionTitle sectionTitle-cards padded-left">' + title + '</h2>';
var allowBottomPadding = true;
const allowBottomPadding = true;
if (enableScrollX()) {
html += '<div is="emby-scroller" class="padded-top-focusscale padded-bottom-focusscale" data-mousewheel="false" data-centerfocus="true">';
@ -139,8 +154,8 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu'
}
function loadSuggestions(page, userId, parentId) {
var screenWidth = dom.getWindowSize().innerWidth;
var url = ApiClient.getUrl('Movies/Recommendations', {
let screenWidth = dom.getWindowSize().innerWidth;
let url = ApiClient.getUrl('Movies/Recommendations', {
userId: userId,
categoryLimit: 6,
ItemLimit: screenWidth >= 1920 ? 8 : screenWidth >= 1600 ? 8 : screenWidth >= 1200 ? 6 : 5,
@ -155,9 +170,9 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu'
return;
}
var html = recommendations.map(getRecommendationHtml).join('');
const html = recommendations.map(getRecommendationHtml).join('');
page.querySelector('.noItemsMessage').classList.add('hide');
var recs = page.querySelector('.recommendations');
let recs = page.querySelector('.recommendations');
recs.innerHTML = html;
imageLoader.lazyChildren(recs);
@ -167,7 +182,7 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu'
}
function autoFocus(page) {
require(['autoFocuser'], function (autoFocuser) {
import('autoFocuser').then(({default: autoFocuser}) => {
autoFocuser.autoFocus(page);
});
}
@ -193,17 +208,16 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu'
}
function initSuggestedTab(page, tabContent) {
var containers = tabContent.querySelectorAll('.itemsContainer');
const containers = tabContent.querySelectorAll('.itemsContainer');
for (var i = 0, length = containers.length; i < length; i++) {
setScrollClasses(containers[i], enableScrollX());
for (const container of containers) {
setScrollClasses(container, enableScrollX());
}
}
function loadSuggestionsTab(view, params, tabContent) {
var parentId = params.topParentId;
var userId = ApiClient.getCurrentUserId();
console.debug('loadSuggestionsTab');
const parentId = params.topParentId;
const userId = ApiClient.getCurrentUserId();
loadResume(tabContent, userId, parentId);
loadLatest(tabContent, userId, parentId);
loadSuggestions(tabContent, userId, parentId);
@ -213,18 +227,15 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu'
return [{
name: globalize.translate('Movies')
}, {
name: globalize.translate('TabSuggestions')
name: globalize.translate('Suggestions')
}, {
name: globalize.translate('TabTrailers')
}, {
name: globalize.translate('TabFavorites')
name: globalize.translate('Favorites')
}, {
name: globalize.translate('TabCollections')
name: globalize.translate('Collections')
}, {
name: globalize.translate('TabGenres')
}, {
name: globalize.translate('ButtonSearch'),
cssClass: 'searchTabButton'
name: globalize.translate('Genres')
}];
}
@ -247,13 +258,13 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu'
}
}
return function (view, params) {
export default function (view, params) {
function onBeforeTabChange(e) {
preLoadTab(view, parseInt(e.detail.selectedTabIndex));
}
function onTabChange(e) {
var newIndex = parseInt(e.detail.selectedTabIndex);
const newIndex = parseInt(e.detail.selectedTabIndex);
loadTab(view, newIndex);
}
@ -265,52 +276,50 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu'
mainTabsManager.setTabs(view, currentTabIndex, getTabs, getTabContainers, onBeforeTabChange, onTabChange);
}
function getTabController(page, index, callback) {
var depends = [];
const getTabController = (page, index, callback) => {
let depends = '';
switch (index) {
case 0:
depends.push('controllers/movies/movies');
depends = 'controllers/movies/movies';
break;
case 1:
depends = 'controllers/movies/moviesrecommended.js';
break;
case 2:
depends.push('controllers/movies/movietrailers');
depends = 'controllers/movies/movietrailers';
break;
case 3:
depends.push('controllers/movies/movies');
depends = 'controllers/movies/movies';
break;
case 4:
depends.push('controllers/movies/moviecollections');
depends = 'controllers/movies/moviecollections';
break;
case 5:
depends.push('controllers/movies/moviegenres');
depends = 'controllers/movies/moviegenres';
break;
case 6:
depends.push('scripts/searchtab');
}
require(depends, function (controllerFactory) {
var tabContent;
import(depends).then(({default: controllerFactory}) => {
let tabContent;
if (index === suggestionsTabIndex) {
tabContent = view.querySelector(".pageTabContent[data-index='" + index + "']");
self.tabContent = tabContent;
this.tabContent = tabContent;
}
var controller = tabControllers[index];
let controller = tabControllers[index];
if (!controller) {
tabContent = view.querySelector(".pageTabContent[data-index='" + index + "']");
if (index === suggestionsTabIndex) {
controller = self;
controller = this;
} else if (index === 6) {
controller = new controllerFactory(view, tabContent, {
collectionType: 'movies',
@ -333,7 +342,7 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu'
callback(controller);
});
}
};
function preLoadTab(page, index) {
getTabController(page, index, function (controller) {
@ -345,12 +354,12 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu'
function loadTab(page, index) {
currentTabIndex = index;
getTabController(page, index, function (controller) {
getTabController(page, index, ((controller) => {
if (renderedTabs.indexOf(index) == -1) {
renderedTabs.push(index);
controller.renderTab();
}
});
}));
}
function onPlaybackStop(e, state) {
@ -368,22 +377,21 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu'
}
}
var self = this;
var currentTabIndex = parseInt(params.tab || getDefaultTabIndex(params.topParentId));
var suggestionsTabIndex = 1;
let currentTabIndex = parseInt(params.tab || getDefaultTabIndex(params.topParentId));
const suggestionsTabIndex = 1;
self.initTab = function () {
var tabContent = view.querySelector(".pageTabContent[data-index='" + suggestionsTabIndex + "']");
this.initTab = function () {
let tabContent = view.querySelector(".pageTabContent[data-index='" + suggestionsTabIndex + "']");
initSuggestedTab(view, tabContent);
};
self.renderTab = function () {
var tabContent = view.querySelector(".pageTabContent[data-index='" + suggestionsTabIndex + "']");
this.renderTab = function () {
let tabContent = view.querySelector(".pageTabContent[data-index='" + suggestionsTabIndex + "']");
loadSuggestionsTab(view, params, tabContent);
};
var tabControllers = [];
var renderedTabs = [];
let tabControllers = [];
let renderedTabs = [];
view.addEventListener('viewshow', function (e) {
initTabs();
if (!view.getAttribute('data-title')) {
@ -395,23 +403,22 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu'
libraryMenu.setTitle(item.Name);
});
} else {
view.setAttribute('data-title', globalize.translate('TabMovies'));
libraryMenu.setTitle(globalize.translate('TabMovies'));
view.setAttribute('data-title', globalize.translate('Movies'));
libraryMenu.setTitle(globalize.translate('Movies'));
}
}
events.on(playbackManager, 'playbackstop', onPlaybackStop);
inputManager.on(window, onInputCommand);
});
view.addEventListener('viewbeforehide', function (e) {
view.addEventListener('viewbeforehide', function () {
inputManager.off(window, onInputCommand);
});
view.addEventListener('viewdestroy', function (e) {
tabControllers.forEach(function (t) {
if (t.destroy) {
t.destroy();
}
});
});
};
});
for (const tabController of tabControllers) {
if (tabController.destroy) {
tabController.destroy();
}
}
}
/* eslint-enable indent */

View file

@ -1,12 +1,20 @@
define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', 'alphaPicker', 'listView', 'cardBuilder', 'userSettings', 'globalize', 'emby-itemscontainer'], function (layoutManager, loading, events, libraryBrowser, imageLoader, AlphaPicker, listView, cardBuilder, userSettings, globalize) {
'use strict';
import loading from 'loading';
import events from 'events';
import libraryBrowser from 'libraryBrowser';
import imageLoader from 'imageLoader';
import AlphaPicker from 'alphaPicker';
import listView from 'listView';
import cardBuilder from 'cardBuilder';
import * as userSettings from 'userSettings';
import globalize from 'globalize';
import 'emby-itemscontainer';
libraryBrowser = libraryBrowser.default || libraryBrowser;
/* eslint-disable indent */
return function (view, params, tabContent) {
export default function (view, params, tabContent) {
function getPageData(context) {
var key = getSavedQueryKey(context);
var pageData = data[key];
const key = getSavedQueryKey(context);
let pageData = data[key];
if (!pageData) {
pageData = data[key] = {
@ -45,11 +53,11 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', '
return context.savedQueryKey;
}
function reloadItems() {
const reloadItems = () => {
loading.show();
isLoading = true;
var query = getQuery(tabContent);
ApiClient.getItems(ApiClient.getCurrentUserId(), query).then(function (result) {
const query = getQuery(tabContent);
ApiClient.getItems(ApiClient.getCurrentUserId(), query).then((result) => {
function onNextPageClick() {
if (isLoading) {
return;
@ -74,7 +82,7 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', '
window.scrollTo(0, 0);
updateFilterControls(tabContent);
var pagingHtml = libraryBrowser.getQueryPagingHtml({
const pagingHtml = libraryBrowser.getQueryPagingHtml({
startIndex: query.StartIndex,
limit: query.Limit,
totalRecordCount: result.TotalRecordCount,
@ -84,8 +92,8 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', '
sortButton: false,
filterButton: false
});
var html;
var viewStyle = self.getCurrentViewStyle();
let html;
const viewStyle = this.getCurrentViewStyle();
if (viewStyle == 'Thumb') {
html = cardBuilder.getCardsHtml({
@ -141,22 +149,20 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', '
});
}
var i;
var length;
var elems = tabContent.querySelectorAll('.paging');
let elems = tabContent.querySelectorAll('.paging');
for (i = 0, length = elems.length; i < length; i++) {
elems[i].innerHTML = pagingHtml;
for (const elem of elems) {
elem.innerHTML = pagingHtml;
}
elems = tabContent.querySelectorAll('.btnNextPage');
for (i = 0, length = elems.length; i < length; i++) {
elems[i].addEventListener('click', onNextPageClick);
for (const elem of elems) {
elem.addEventListener('click', onNextPageClick);
}
elems = tabContent.querySelectorAll('.btnPreviousPage');
for (i = 0, length = elems.length; i < length; i++) {
elems[i].addEventListener('click', onPreviousPageClick);
for (const elem of elems) {
elem.addEventListener('click', onPreviousPageClick);
}
if (!result.Items.length) {
@ -168,27 +174,26 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', '
html += '</div>';
}
var itemsContainer = tabContent.querySelector('.itemsContainer');
const itemsContainer = tabContent.querySelector('.itemsContainer');
itemsContainer.innerHTML = html;
imageLoader.lazyChildren(itemsContainer);
libraryBrowser.saveQueryValues(getSavedQueryKey(tabContent), query);
loading.hide();
isLoading = false;
});
}
};
function updateFilterControls(tabContent) {
var query = getQuery(tabContent);
self.alphaPicker.value(query.NameStartsWithOrGreater);
}
const updateFilterControls = (tabContent) => {
const query = getQuery(tabContent);
this.alphaPicker.value(query.NameStartsWithOrGreater);
};
var self = this;
var data = {};
var isLoading = false;
const data = {};
let isLoading = false;
self.showFilterMenu = function () {
require(['components/filterdialog/filterdialog'], function ({default: filterDialogFactory}) {
var filterDialog = new filterDialogFactory({
this.showFilterMenu = function () {
import('components/filterdialog/filterdialog').then(({default: filterDialogFactory}) => {
const filterDialog = new filterDialogFactory({
query: getQuery(tabContent),
mode: 'movies',
serverId: ApiClient.serverId()
@ -201,21 +206,21 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', '
});
};
self.getCurrentViewStyle = function () {
this.getCurrentViewStyle = function () {
return getPageData(tabContent).view;
};
function initPage(tabContent) {
var alphaPickerElement = tabContent.querySelector('.alphaPicker');
var itemsContainer = tabContent.querySelector('.itemsContainer');
const initPage = (tabContent) => {
const alphaPickerElement = tabContent.querySelector('.alphaPicker');
const itemsContainer = tabContent.querySelector('.itemsContainer');
alphaPickerElement.addEventListener('alphavaluechanged', function (e) {
var newValue = e.detail.value;
var query = getQuery(tabContent);
const newValue = e.detail.value;
const query = getQuery(tabContent);
query.NameStartsWithOrGreater = newValue;
query.StartIndex = 0;
reloadItems();
});
self.alphaPicker = new AlphaPicker.default({
this.alphaPicker = new AlphaPicker({
element: alphaPickerElement,
valueChangeEvent: 'click'
});
@ -225,7 +230,7 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', '
itemsContainer.classList.add('padded-right-withalphapicker');
tabContent.querySelector('.btnFilter').addEventListener('click', function () {
self.showFilterMenu();
this.showFilterMenu();
});
tabContent.querySelector('.btnSort').addEventListener('click', function (e) {
libraryBrowser.showSortMenu({
@ -259,15 +264,16 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', '
button: e.target
});
});
}
};
initPage(tabContent);
self.renderTab = function () {
this.renderTab = function () {
reloadItems();
updateFilterControls(tabContent);
};
self.destroy = function () {};
};
});
this.destroy = function () {};
}
/* eslint-enable indent */

View file

@ -0,0 +1,110 @@
<div id="musicRecommendedPage" data-dom-cache="true" data-role="page" class="page libraryPage backdropPage pageWithAbsoluteTabs withTabs" data-backdroptype="musicartist">
<style>
@media all and (max-width: 32em) {
.paging {
width: 100%;
}
}
</style>
<div class="pageTabContent pageTabContent" id="suggestionsTab" data-index="0">
<div class="verticalSection">
<h2 class="sectionTitle sectionTitle-cards padded-left flex-grow">${HeaderLatestMusic}</h2>
<div is="emby-itemscontainer" id="recentlyAddedSongs" class="itemsContainer padded-left padded-right" style="text-align:left;">
</div>
</div>
<div id="recentlyPlayed" class="verticalSection hide">
<h2 class="sectionTitle sectionTitle-cards padded-left">${HeaderRecentlyPlayed}</h2>
<div is="emby-itemscontainer" id="recentlyPlayedSongs" class="itemsContainer padded-left padded-right" style="text-align:left;">
</div>
</div>
<div id="topPlayed" class="verticalSection hide">
<h2 class="sectionTitle sectionTitle-cards padded-left">${HeaderFrequentlyPlayed}</h2>
<div is="emby-itemscontainer" id="topPlayedSongs" class="itemsContainer padded-left padded-right" style="text-align: left;">
</div>
</div>
<div class="favoriteSections verticalSection"></div>
</div>
<div class="pageTabContent pageTabContent" id="albumsTab" data-index="1">
<div class="flex align-items-center justify-content-center flex-wrap-wrap padded-top padded-left padded-right padded-bottom">
<div class="paging"></div>
<button is="paper-icon-button-light" class="btnPlayAll musicglobalButton" title="${HeaderPlayAll}"><span class="material-icons play_arrow"></span></button>
<button is="paper-icon-button-light" class="btnShuffle musicglobalButton" title="${Shuffle}"><span class="material-icons shuffle"></span></button>
<button is="paper-icon-button-light" class="btnSelectView autoSize" title="${ButtonSelectView}"><span class="material-icons view_comfy"></span></button>
<button is="paper-icon-button-light" class="btnSort autoSize" title="${Sort}"><span class="material-icons sort_by_alpha"></span></button>
<button is="paper-icon-button-light" class="btnFilter autoSize" title="${ButtonFilter}"><span class="material-icons filter_list"></span></button>
</div>
<div class="alphaPicker alphaPicker-fixed alphaPicker-vertical">
</div>
<div is="emby-itemscontainer" class="itemsContainer padded-left padded-right">
</div>
<div class="flex align-items-center justify-content-center flex-wrap-wrap padded-top padded-left padded-right padded-bottom">
<div class="paging"></div>
</div>
</div>
<div class="pageTabContent" id="albumArtistsTab" data-index="2">
<div class="flex align-items-center justify-content-center flex-wrap-wrap padded-top padded-left padded-right padded-bottom">
<div class="paging"></div>
<button is="paper-icon-button-light" class="btnSelectView autoSize" title="${ButtonSelectView}"><span class="material-icons view_comfy"></span></button>
<button is="paper-icon-button-light" class="btnFilter autoSize" title="${ButtonFilter}"><span class="material-icons filter_list"></span></button>
</div>
<div class="alphaPicker alphaPicker-fixed alphaPicker-vertical">
</div>
<div is="emby-itemscontainer" class="itemsContainer padded-left padded-right">
</div>
<div class="flex align-items-center justify-content-center flex-wrap-wrap padded-top padded-left padded-right padded-bottom">
<div class="paging"></div>
</div>
</div>
<div class="pageTabContent" id="artistsTab" data-index="3">
<div class="flex align-items-center justify-content-center flex-wrap-wrap padded-top padded-left padded-right padded-bottom">
<div class="paging"></div>
<button is="paper-icon-button-light" class="btnSelectView autoSize" title="${ButtonSelectView}"><span class="material-icons view_comfy"></span></button>
<button is="paper-icon-button-light" class="btnFilter autoSize" title="${ButtonFilter}"><span class="material-icons filter_list"></span></button>
</div>
<div class="alphaPicker alphaPicker-fixed alphaPicker-vertical">
</div>
<div is="emby-itemscontainer" class="itemsContainer padded-left padded-right">
</div>
<div class="flex align-items-center justify-content-center flex-wrap-wrap padded-top padded-left padded-right padded-bottom">
<div class="paging"></div>
</div>
</div>
<div class="pageTabContent" data-index="4">
<div is="emby-itemscontainer" id="items" class="itemsContainer padded-left padded-right padded-top vertical-wrap centered"></div>
</div>
<div class="pageTabContent" id="songsTab" data-index="5">
<div class="flex align-items-center justify-content-center flex-wrap-wrap padded-top padded-left padded-right padded-bottom">
<div class="paging"></div>
<button is="paper-icon-button-light" class="btnSort autoSize" title="${Sort}"><span class="material-icons sort_by_alpha"></span></button>
<button is="paper-icon-button-light" class="btnFilter autoSize" title="${ButtonFilter}"><span class="material-icons filter_list"></span></button>
</div>
<div is="emby-itemscontainer" id="items" class="itemsContainer vertical-list" style="max-width:67.5em;margin: 0 auto;"></div>
<div class="flex align-items-center justify-content-center flex-wrap-wrap padded-top padded-left padded-right padded-bottom">
<div class="paging"></div>
</div>
</div>
<div class="pageTabContent" id="genresTab" data-index="6">
<div is="emby-itemscontainer" id="items" class="itemsContainer padded-left padded-right padded-top vertical-wrap"></div>
</div>
<div class="pageTabContent" data-index="7">
</div>
</div>

View file

@ -73,7 +73,7 @@ import 'emby-itemscontainer';
const viewStyle = self.getCurrentViewStyle();
const itemsContainer = tabContent.querySelector('.itemsContainer');
if ('List' == viewStyle) {
if (viewStyle == 'List') {
itemsContainer.classList.add('vertical-list');
itemsContainer.classList.remove('vertical-wrap');
} else {

View file

@ -57,7 +57,7 @@ import 'emby-itemscontainer';
const viewStyle = self.getCurrentViewStyle();
const itemsContainer = tabContent.querySelector('.itemsContainer');
if ('List' == viewStyle) {
if (viewStyle == 'List') {
itemsContainer.classList.add('vertical-list');
itemsContainer.classList.remove('vertical-wrap');
} else {

View file

@ -104,7 +104,6 @@ import 'flexStyles';
}
var itemsContainer = elem.querySelector('.itemsContainer');
itemsContainer.innerHTML = cardBuilder.getCardsHtml({
items: result.Items,
showUnplayedIndicator: false,
@ -178,22 +177,19 @@ import 'flexStyles';
function getTabs() {
return [{
name: globalize.translate('TabSuggestions')
name: globalize.translate('Suggestions')
}, {
name: globalize.translate('TabAlbums')
name: globalize.translate('Albums')
}, {
name: globalize.translate('TabAlbumArtists')
}, {
name: globalize.translate('TabArtists')
name: globalize.translate('Artists')
}, {
name: globalize.translate('TabPlaylists')
name: globalize.translate('Playlists')
}, {
name: globalize.translate('TabSongs')
name: globalize.translate('Songs')
}, {
name: globalize.translate('TabGenres')
}, {
name: globalize.translate('ButtonSearch'),
cssClass: 'searchTabButton'
name: globalize.translate('Genres')
}];
}
@ -295,16 +291,12 @@ import 'flexStyles';
case 6:
depends = 'controllers/music/musicgenres';
break;
case 7:
depends = 'scripts/searchtab';
break;
}
import(depends).then(({default: controllerFactory}) => {
let tabContent;
if (0 == index) {
if (index == 0) {
tabContent = view.querySelector(".pageTabContent[data-index='" + index + "']");
self.tabContent = tabContent;
}

View file

@ -30,7 +30,7 @@
<div class="nowPlayingInfoButtons">
<button is="paper-icon-button-light" class="btnCommand btnRepeat repeatToggleButton autoSize" title="${ButtonRepeat}"
<button is="paper-icon-button-light" class="btnCommand btnRepeat repeatToggleButton autoSize" title="${Repeat}"
data-command="SetRepeatMode">
<span class="material-icons repeat"></span>
</button>
@ -59,7 +59,7 @@
<span class="material-icons forward_30"></span>
</button>
<button is="paper-icon-button-light" class="btnShuffleQueue autoSize" title="${ButtonShuffle}">
<button is="paper-icon-button-light" class="btnShuffleQueue autoSize" title="${Shuffle}">
<span class="material-icons shuffle"></span>
</button>
@ -81,11 +81,11 @@
<span class="material-icons fullscreen"></span>
</button>
<button is="paper-icon-button-light" class="btnShuffleQueue autoSize" title="${ButtonShuffle}">
<button is="paper-icon-button-light" class="btnShuffleQueue autoSize" title="${Shuffle}">
<span class="material-icons shuffle"></span>
</button>
<button is="paper-icon-button-light" class="btnCommand btnRepeat repeatToggleButton autoSize" title="${ButtonRepeat}"
<button is="paper-icon-button-light" class="btnCommand btnRepeat repeatToggleButton autoSize" title="${Repeat}"
data-command="SetRepeatMode">
<span class="material-icons repeat"></span>
</button>
@ -99,7 +99,7 @@
<div is="emby-collapse" title="${HeaderNavigation}">
<div class="collapseContent">
<div>
<button is="paper-icon-button-light" class="btnArrowUp btnCommand autoSize button-submit" title="${ButtonArrowUp}" data-command="MoveUp">
<button is="paper-icon-button-light" class="btnArrowUp btnCommand autoSize button-submit" title="${Up}" data-command="MoveUp">
<span class="material-icons keyboard_arrow_up"></span>
</button>
</div>
@ -120,7 +120,7 @@
<button is="paper-icon-button-light" class="btnBack btnCommand autoSize" title="${ButtonBack}" data-command="Back">
<span class="material-icons arrow_back"></span>
</button>
<button is="paper-icon-button-light" class="btnArrowDown btnCommand autoSize button-submit" title="${ButtonArrowDown}" data-command="MoveDown">
<button is="paper-icon-button-light" class="btnArrowDown btnCommand autoSize button-submit" title="${Down}" data-command="MoveDown">
<span class="material-icons keyboard_arrow_down"></span>
</button>
<button is="paper-icon-button-light" class="btnContextMenu btnCommand autoSize" title="${ButtonInfo}" data-command="ToggleContextMenu">
@ -132,7 +132,7 @@
<button is="paper-icon-button-light" class="btnGoHome btnCommand autoSize" title="${ButtonHome}" data-command="GoHome">
<span class="material-icons home"></span>
</button>
<button is="paper-icon-button-light" class="btnShowSearch btnCommand autoSize" title="${ButtonSearch}" data-command="GoToSearch">
<button is="paper-icon-button-light" class="btnShowSearch btnCommand autoSize" title="${Search}" data-command="GoToSearch">
<span class="material-icons search"></span>
</button>
<button is="paper-icon-button-light" class="bthShowSettings btnCommand autoSize" title="${ButtonSettings}" data-command="GoToSettings">
@ -180,10 +180,10 @@
<button id="togglePlaylist" is="paper-icon-button-light" class="btnTogglePlaylist hide" title="${ButtonTogglePlaylist}">
<span class="material-icons queue_music"></span>
</button>
<button is="paper-icon-button-light" class="btnSavePlaylist hide" title="${ButtonSave}">
<button is="paper-icon-button-light" class="btnSavePlaylist hide" title="${Save}">
<span class="material-icons save"></span>
</button>
<button id="toggleContextMenu" is="paper-icon-button-light" class="btnToggleContextMenu" title="${ButtonToggleContextMenu}">
<button id="toggleContextMenu" is="paper-icon-button-light" class="btnToggleContextMenu" title="${ButtonMore}">
<span class="material-icons more_vert"></span>
</button>
</div>

View file

@ -1,5 +1,4 @@
<div id="videoOsdPage" data-role="page" class="page libraryPage" data-backbutton="true">
<div class="pageContainer flex"></div>
<div class="upNextContainer hide"></div>
<div class="videoOsdBottom videoOsdBottom-maincontrols">
<div class="osdPoster"></div>
@ -28,7 +27,7 @@
<span class="xlargePaperIconButton material-icons fiber_manual_record"></span>
</button>
<button is="paper-icon-button-light" class="btnPreviousTrack autoSize hide">
<button is="paper-icon-button-light" class="btnPreviousTrack autoSize hide" title="${PreviousTrack}">
<span class="xlargePaperIconButton material-icons skip_previous"></span>
</button>
@ -44,7 +43,7 @@
<span class="xlargePaperIconButton material-icons fast_forward"></span>
</button>
<button is="paper-icon-button-light" class="btnNextTrack autoSize hide">
<button is="paper-icon-button-light" class="btnNextTrack autoSize hide" title="${NextTrack}">
<span class="xlargePaperIconButton material-icons skip_next"></span>
</button>

View file

@ -1,6 +1,7 @@
import playbackManager from 'playbackManager';
import dom from 'dom';
import inputManager from 'inputManager';
import mouseManager from 'mouseManager';
import datetime from 'datetime';
import itemHelper from 'itemHelper';
import mediaInfo from 'mediaInfo';
@ -21,18 +22,18 @@ import 'css!assets/css/videoosd';
/* eslint-disable indent */
function seriesImageUrl(item, options) {
if ('Episode' !== item.Type) {
if (item.Type !== 'Episode') {
return null;
}
options = options || {};
options.type = options.type || 'Primary';
if ('Primary' === options.type && item.SeriesPrimaryImageTag) {
if (options.type === 'Primary' && item.SeriesPrimaryImageTag) {
options.tag = item.SeriesPrimaryImageTag;
return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options);
}
if ('Thumb' === options.type) {
if (options.type === 'Thumb') {
if (item.SeriesThumbImageTag) {
options.tag = item.SeriesThumbImageTag;
return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options);
@ -56,7 +57,7 @@ import 'css!assets/css/videoosd';
return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.PrimaryImageItemId || item.Id, options);
}
if ('Primary' === options.type && item.AlbumId && item.AlbumPrimaryImageTag) {
if (options.type === 'Primary' && item.AlbumId && item.AlbumPrimaryImageTag) {
options.tag = item.AlbumPrimaryImageTag;
return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.AlbumId, options);
}
@ -103,7 +104,7 @@ import 'css!assets/css/videoosd';
function onDoubleClick(e) {
const clientX = e.clientX;
if (null != clientX) {
if (clientX != null) {
if (clientX < dom.getWindowSize().innerWidth / 2) {
playbackManager.rewind(currentPlayer);
} else {
@ -116,7 +117,7 @@ import 'css!assets/css/videoosd';
}
function getDisplayItem(item) {
if ('TvChannel' === item.Type) {
if (item.Type === 'TvChannel') {
const apiClient = connectionManager.getApiClient(item.ServerId);
return apiClient.getItem(apiClient.getCurrentUserId(), item.Id).then(function (refreshedItem) {
return {
@ -132,7 +133,7 @@ import 'css!assets/css/videoosd';
}
function updateRecordingButton(item) {
if (!item || 'Program' !== item.Type) {
if (!item || item.Type !== 'Program') {
if (recordingButtonManager) {
recordingButtonManager.destroy();
recordingButtonManager = null;
@ -171,12 +172,10 @@ import 'css!assets/css/videoosd';
}
setTitle(displayItem, parentName);
let titleElement;
const osdTitle = view.querySelector('.osdTitle');
titleElement = osdTitle;
const titleElement = view.querySelector('.osdTitle');
let displayName = itemHelper.getDisplayName(displayItem, {
includeParentInfo: 'Program' !== displayItem.Type,
includeIndexNumber: 'Program' !== displayItem.Type
includeParentInfo: displayItem.Type !== 'Program',
includeIndexNumber: displayItem.Type !== 'Program'
});
if (!displayName) {
@ -197,8 +196,8 @@ import 'css!assets/css/videoosd';
tomatoes: false,
endsAt: false,
episodeTitle: false,
originalAirDate: 'Program' !== displayItem.Type,
episodeTitleIndexNumber: 'Program' !== displayItem.Type,
originalAirDate: displayItem.Type !== 'Program',
episodeTitleIndexNumber: displayItem.Type !== 'Program',
programIndicator: false
});
const osdMediaInfo = view.querySelector('.osdMediaInfo');
@ -270,7 +269,7 @@ import 'css!assets/css/videoosd';
}
function shouldEnableProgressByTimeOfDay(item) {
return !('TvChannel' !== item.Type || !item.CurrentProgram);
return !(item.Type !== 'TvChannel' || !item.CurrentProgram);
}
function updateNowPlayingInfo(player, state) {
@ -329,24 +328,24 @@ import 'css!assets/css/videoosd';
if (item) {
let imgUrl = seriesImageUrl(item, {
maxWidth: osdPoster.clientWidth * 2,
maxWidth: osdPoster.clientWidth,
type: 'Primary'
}) || seriesImageUrl(item, {
maxWidth: osdPoster.clientWidth * 2,
maxWidth: osdPoster.clientWidth,
type: 'Thumb'
}) || imageUrl(item, {
maxWidth: osdPoster.clientWidth * 2,
maxWidth: osdPoster.clientWidth,
type: 'Primary'
});
if (!imgUrl && secondaryItem && (imgUrl = seriesImageUrl(secondaryItem, {
maxWidth: osdPoster.clientWidth * 2,
maxWidth: osdPoster.clientWidth,
type: 'Primary'
}) || seriesImageUrl(secondaryItem, {
maxWidth: osdPoster.clientWidth * 2,
maxWidth: osdPoster.clientWidth,
type: 'Thumb'
}) || imageUrl(secondaryItem, {
maxWidth: osdPoster.clientWidth * 2,
maxWidth: osdPoster.clientWidth,
type: 'Primary'
})), imgUrl) {
return void (osdPoster.innerHTML = '<img src="' + imgUrl + '" />');
@ -367,10 +366,11 @@ import 'css!assets/css/videoosd';
function hideOsd() {
slideUpToHide(headerElement);
hideMainOsdControls();
mouseManager.hideCursor();
}
function toggleOsd() {
if ('osd' === currentVisibleMenu) {
if (currentVisibleMenu === 'osd') {
hideOsd();
} else if (!currentVisibleMenu) {
showOsd();
@ -431,10 +431,11 @@ import 'css!assets/css/videoosd';
}
function hideMainOsdControls() {
if ('osd' === currentVisibleMenu) {
if (currentVisibleMenu === 'osd') {
const elem = osdBottomElement;
clearHideAnimationEventListeners(elem);
elem.classList.add('videoOsdBottom-hidden');
dom.addEventListener(elem, transitionEndEventName, onHideAnimationComplete, {
once: true
});
@ -460,7 +461,7 @@ import 'css!assets/css/videoosd';
}
function onPointerMove(e) {
if ('mouse' === (e.pointerType || (layoutManager.mobile ? 'touch' : 'mouse'))) {
if ((e.pointerType || (layoutManager.mobile ? 'touch' : 'mouse')) === 'mouse') {
const eventX = e.screenX || 0;
const eventY = e.screenY || 0;
const obj = lastPointerMoveData;
@ -488,7 +489,7 @@ import 'css!assets/css/videoosd';
switch (e.detail.command) {
case 'left':
if ('osd' === currentVisibleMenu) {
if (currentVisibleMenu === 'osd') {
showOsd();
} else {
if (!currentVisibleMenu) {
@ -500,7 +501,7 @@ import 'css!assets/css/videoosd';
break;
case 'right':
if ('osd' === currentVisibleMenu) {
if (currentVisibleMenu === 'osd') {
showOsd();
} else if (!currentVisibleMenu) {
e.preventDefault();
@ -615,7 +616,7 @@ import 'css!assets/css/videoosd';
resetUpNextDialog();
console.debug('nowplaying event: ' + e.type);
if ('Video' !== state.NextMediaType) {
if (state.NextMediaType !== 'Video') {
view.removeEventListener('viewbeforehide', onViewHideStopPlayback);
Emby.Page.back();
}
@ -702,7 +703,7 @@ import 'css!assets/css/videoosd';
}
function showComingUpNextIfNeeded(player, currentItem, currentTimeTicks, runtimeTicks) {
if (runtimeTicks && currentTimeTicks && !comingUpNextDisplayed && !currentVisibleMenu && 'Episode' === currentItem.Type && userSettings.enableNextVideoInfoOverlay()) {
if (runtimeTicks && currentTimeTicks && !comingUpNextDisplayed && !currentVisibleMenu && currentItem.Type === 'Episode' && userSettings.enableNextVideoInfoOverlay()) {
const showAtSecondsLeft = runtimeTicks >= 3e10 ? 40 : runtimeTicks >= 24e9 ? 35 : 30;
const showAtTicks = runtimeTicks - 1e3 * showAtSecondsLeft * 1e4;
const timeRemainingTicks = runtimeTicks - currentTimeTicks;
@ -714,7 +715,7 @@ import 'css!assets/css/videoosd';
}
function onUpNextHidden() {
if ('upnext' === currentVisibleMenu) {
if (currentVisibleMenu === 'upnext') {
currentVisibleMenu = null;
}
}
@ -737,7 +738,7 @@ import 'css!assets/css/videoosd';
}
function refreshProgramInfoIfNeeded(player, item) {
if ('TvChannel' === item.Type) {
if (item.Type === 'TvChannel') {
const program = item.CurrentProgram;
if (program && program.EndDate) {
@ -778,7 +779,7 @@ import 'css!assets/css/videoosd';
updatePlayPauseState(playState.IsPaused);
const supportedCommands = playbackManager.getSupportedCommands(player);
currentPlayerSupportedCommands = supportedCommands;
supportsBrightnessChange = -1 !== supportedCommands.indexOf('SetBrightness');
supportsBrightnessChange = supportedCommands.indexOf('SetBrightness') !== -1;
updatePlayerVolumeState(player, playState.IsMuted, playState.VolumeLevel);
if (nowPlayingPositionSlider && !nowPlayingPositionSlider.dragging) {
@ -792,13 +793,13 @@ import 'css!assets/css/videoosd';
updateTimeDisplay(playState.PositionTicks, nowPlayingItem.RunTimeTicks, playState.PlaybackStartTimeTicks, playState.BufferedRanges || []);
updateNowPlayingInfo(player, state);
if (state.MediaSource && state.MediaSource.SupportsTranscoding && -1 !== supportedCommands.indexOf('SetMaxStreamingBitrate')) {
if (state.MediaSource && state.MediaSource.SupportsTranscoding && supportedCommands.indexOf('SetMaxStreamingBitrate') !== -1) {
view.querySelector('.btnVideoOsdSettings').classList.remove('hide');
} else {
view.querySelector('.btnVideoOsdSettings').classList.add('hide');
}
const isProgressClear = state.MediaSource && null == state.MediaSource.RunTimeTicks;
const isProgressClear = state.MediaSource && state.MediaSource.RunTimeTicks == null;
nowPlayingPositionSlider.setIsClear(isProgressClear);
if (nowPlayingItem.RunTimeTicks) {
@ -806,19 +807,19 @@ import 'css!assets/css/videoosd';
userSettings.skipForwardLength() * 1000000 / nowPlayingItem.RunTimeTicks);
}
if (-1 === supportedCommands.indexOf('ToggleFullscreen') || player.isLocalPlayer && layoutManager.tv && playbackManager.isFullscreen(player)) {
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 (-1 === supportedCommands.indexOf('PictureInPicture')) {
if (supportedCommands.indexOf('PictureInPicture') === -1) {
view.querySelector('.btnPip').classList.add('hide');
} else {
view.querySelector('.btnPip').classList.remove('hide');
}
if (-1 === supportedCommands.indexOf('AirPlay')) {
if (supportedCommands.indexOf('AirPlay') === -1) {
view.querySelector('.btnAirPlay').classList.add('hide');
} else {
view.querySelector('.btnAirPlay').classList.remove('hide');
@ -866,7 +867,7 @@ import 'css!assets/css/videoosd';
nowPlayingPositionSlider.value = 0;
}
if (runtimeTicks && null != positionTicks && currentRuntimeTicks && !enableProgressByTimeOfDay && currentItem.RunTimeTicks && 'Recording' !== currentItem.Type) {
if (runtimeTicks && positionTicks != null && currentRuntimeTicks && !enableProgressByTimeOfDay && currentItem.RunTimeTicks && currentItem.Type !== 'Recording') {
endsAtText.innerHTML = '&nbsp;&nbsp;-&nbsp;&nbsp;' + mediaInfo.getEndsAtFromPosition(runtimeTicks, positionTicks, true);
} else {
endsAtText.innerHTML = '';
@ -887,11 +888,11 @@ import 'css!assets/css/videoosd';
let showMuteButton = true;
let showVolumeSlider = true;
if (-1 === supportedCommands.indexOf('Mute')) {
if (supportedCommands.indexOf('Mute') === -1) {
showMuteButton = false;
}
if (-1 === supportedCommands.indexOf('SetVolume')) {
if (supportedCommands.indexOf('SetVolume') === -1) {
showVolumeSlider = false;
}
@ -942,7 +943,7 @@ import 'css!assets/css/videoosd';
}
function updateTimeText(elem, ticks, divider) {
if (null == ticks) {
if (ticks == null) {
elem.innerHTML = '';
return;
}
@ -984,9 +985,9 @@ import 'css!assets/css/videoosd';
}
function onSettingsOption(selectedOption) {
if ('stats' === selectedOption) {
if (selectedOption === 'stats') {
toggleStats();
} else if ('suboffset' === selectedOption) {
} else if (selectedOption === 'suboffset') {
const player = currentPlayer;
if (player) {
playbackManager.enableShowingSubtitleOffset(player);
@ -1060,7 +1061,7 @@ import 'css!assets/css/videoosd';
const streams = playbackManager.subtitleTracks(player);
let currentIndex = playbackManager.getSubtitleStreamIndex(player);
if (null == currentIndex) {
if (currentIndex == null) {
currentIndex = -1;
}
@ -1131,8 +1132,9 @@ import 'css!assets/css/videoosd';
clickedElement = e.target;
const key = keyboardnavigation.getKeyName(e);
const isKeyModified = e.ctrlKey || e.altKey || e.metaKey;
if (!currentVisibleMenu && 32 === e.keyCode) {
if (!currentVisibleMenu && e.keyCode === 32) {
playbackManager.playPause(currentPlayer);
showOsd();
return;
@ -1235,10 +1237,18 @@ import 'css!assets/css/videoosd';
case '7':
case '8':
case '9': {
const percent = parseInt(key, 10) * 10;
playbackManager.seekPercent(percent, currentPlayer);
if (!isKeyModified) {
const percent = parseInt(key, 10) * 10;
playbackManager.seekPercent(percent, currentPlayer);
}
break;
}
case '>':
playbackManager.increasePlaybackRate(currentPlayer);
break;
case '<':
playbackManager.decreasePlaybackRate(currentPlayer);
break;
}
}
@ -1613,7 +1623,7 @@ import 'css!assets/css/videoosd';
const item = currentItem;
if (item && item.Chapters && item.Chapters.length && item.Chapters[0].ImageTag) {
let html = getChapterBubbleHtml(connectionManager.getApiClient(item.ServerId), item, item.Chapters, ticks);
const html = getChapterBubbleHtml(connectionManager.getApiClient(item.ServerId), item, item.Chapters, ticks);
if (html) {
return html;

View file

@ -0,0 +1,4 @@
<div id="searchPage" data-role="page" class="page libraryPage allLibraryPage noSecondaryNavPage" data-title="${Search}" data-backbutton="true">
<div class="padded-left padded-right searchFields"></div>
<div class="searchResults padded-bottom-page padded-top"></div>
</div>

View file

@ -8,7 +8,7 @@
</div>
<br />
<button is="emby-button" type="submit" class="raised button-submit block">
<span>${ButtonConnect}</span>
<span>${Connect}</span>
</button>
<button is="emby-button" type="button" class="raised button-cancel block btnCancel">
<span>${ButtonCancel}</span>

View file

@ -3,21 +3,21 @@ import globalize from 'globalize';
/* eslint-disable indent */
function processForgotPasswordResult(result) {
if ('ContactAdmin' == result.Action) {
if (result.Action == 'ContactAdmin') {
return void Dashboard.alert({
message: globalize.translate('MessageContactAdminToResetPassword'),
title: globalize.translate('HeaderForgotPassword')
});
}
if ('InNetworkRequired' == result.Action) {
if (result.Action == 'InNetworkRequired') {
return void Dashboard.alert({
message: globalize.translate('MessageForgotPasswordInNetworkRequired'),
title: globalize.translate('HeaderForgotPassword')
});
}
if ('PinCode' == result.Action) {
if (result.Action == 'PinCode') {
let msg = globalize.translate('MessageForgotPasswordFileCreated');
msg += '<br/>';
msg += '<br/>';
@ -41,9 +41,9 @@ import globalize from 'globalize';
type: 'POST',
url: ApiClient.getUrl('Users/ForgotPassword'),
dataType: 'json',
data: {
data: JSON.stringify({
EnteredUsername: view.querySelector('#txtName').value
}
})
}).then(processForgotPasswordResult);
e.preventDefault();
return false;

View file

@ -29,9 +29,9 @@ import globalize from 'globalize';
type: 'POST',
url: ApiClient.getUrl('Users/ForgotPassword/Pin'),
dataType: 'json',
data: {
data: JSON.stringify({
Pin: view.querySelector('#txtPin').value
}
})
}).then(processForgotPasswordResult);
e.preventDefault();
return false;

View file

@ -58,7 +58,7 @@ import 'emby-itemscontainer';
const viewStyle = self.getCurrentViewStyle();
const itemsContainer = tabContent.querySelector('.itemsContainer');
if ('List' == viewStyle) {
if (viewStyle == 'List') {
itemsContainer.classList.add('vertical-list');
itemsContainer.classList.remove('vertical-wrap');
} else {

View file

@ -62,13 +62,13 @@ import 'emby-button';
const elem = entry.target;
const id = elem.getAttribute('data-id');
const viewStyle = self.getCurrentViewStyle();
let limit = 'Thumb' == viewStyle || 'ThumbCard' == viewStyle ? 5 : 9;
let limit = viewStyle == 'Thumb' || viewStyle == 'ThumbCard' ? 5 : 9;
if (enableScrollX()) {
limit = 10;
}
const enableImageTypes = 'Thumb' == viewStyle || 'ThumbCard' == viewStyle ? 'Primary,Backdrop,Thumb' : 'Primary';
const enableImageTypes = viewStyle == 'Thumb' || viewStyle == 'ThumbCard' ? 'Primary,Backdrop,Thumb' : 'Primary';
const query = {
SortBy: 'SortName',
SortOrder: 'Ascending',

View file

@ -0,0 +1,74 @@
<div id="tvRecommendedPage" data-dom-cache="true" data-role="page" class="page libraryPage backdropPage pageWithAbsoluteTabs withTabs" data-backdroptype="series">
<div class="pageTabContent" id="seriesTab" data-index="0">
<div class="flex align-items-center justify-content-center flex-wrap-wrap padded-top padded-left padded-right padded-bottom">
<div class="paging"></div>
<button is="paper-icon-button-light" class="btnSelectView autoSize" title="${ButtonSelectView}"><span class="material-icons view_comfy"></span></button>
<button is="paper-icon-button-light" class="btnSort autoSize" title="${Sort}"><span class="material-icons sort_by_alpha"></span></button>
<button is="paper-icon-button-light" class="btnFilter autoSize" title="${ButtonFilter}"><span class="material-icons filter_list"></span></button>
</div>
<div is="emby-itemscontainer" class="itemsContainer padded-left padded-right"></div>
<div class="alphaPicker alphaPicker-fixed alphaPicker-vertical"></div>
<div class="flex align-items-center justify-content-center flex-wrap-wrap padded-top padded-left padded-right padded-bottom">
<div class="paging"></div>
</div>
</div>
<div class="pageTabContent" id="suggestionsTab" data-index="1">
<div id="resumableSection" class="verticalSection">
<div class="sectionTitleContainer sectionTitleContainer-cards">
<h2 class="sectionTitle sectionTitle-cards padded-left">${HeaderContinueWatching}</h2>
</div>
<div is="emby-itemscontainer" id="resumableItems" class="itemsContainer padded-left padded-right"></div>
</div>
<div class="verticalSection">
<div class="sectionTitleContainer sectionTitleContainer-cards">
<h2 class="sectionTitle sectionTitle-cards padded-left nextUpHeader">${NextUp}</h2>
</div>
<div is="emby-itemscontainer" id="nextUpItems" class="itemsContainer vertical-wrap padded-left padded-right">
</div>
</div>
<p class="noNextUpItems" style="display: none;">${MessageNoNextUpItems}</p>
</div>
<div class="pageTabContent" id="latestTab" data-index="2">
<div class="verticalSection">
<div class="sectionTitleContainer sectionTitleContainer-cards">
<h2 class="sectionTitle sectionTitle-cards padded-left">${HeaderLatestEpisodes}</h2>
</div>
<div is="emby-itemscontainer" id="latestEpisodes" class="itemsContainer vertical-wrap padded-left padded-right">
</div>
</div>
</div>
<div class="pageTabContent" id="upcomingTab" data-index="3">
<div id="upcomingItems">
</div>
<div class="noItemsMessage centerMessage" style="display: none;">
<h1>${MessageNothingHere}</h1>
<p>${MessagePleaseEnsureInternetMetadata}</p>
</div>
</div>
<div class="pageTabContent" id="genresTab" data-index="4">
<div id="items"></div>
</div>
<div class="pageTabContent" id="studiosTab" data-index="5">
<div is="emby-itemscontainer" id="items" class="itemsContainer padded-left padded-right padded-top vertical-wrap" style="text-align: center;"></div>
</div>
<div class="pageTabContent" data-index="6">
<div class="flex align-items-center justify-content-center flex-wrap-wrap padded-top padded-left padded-right padded-bottom">
<div class="paging"></div>
<button is="paper-icon-button-light" class="btnSelectView autoSize" title="${ButtonSelectView}"><span class="material-icons view_comfy"></span></button>
<button is="paper-icon-button-light" class="btnSort autoSize" title="${Sort}"><span class="material-icons sort_by_alpha"></span></button>
<button is="paper-icon-button-light" class="btnFilter autoSize" title="${ButtonFilter}"><span class="material-icons filter_list"></span></button>
</div>
<div is="emby-itemscontainer" class="itemsContainer vertical-wrap padded-left padded-right">
</div>
<div class="flex align-items-center justify-content-center flex-wrap-wrap padded-top padded-left padded-right padded-bottom">
<div class="paging"></div>
</div>
</div>
<div class="pageTabContent" data-index="7">
</div>
</div>

View file

@ -17,22 +17,19 @@ import 'emby-button';
function getTabs() {
return [{
name: globalize.translate('TabShows')
name: globalize.translate('Shows')
}, {
name: globalize.translate('TabSuggestions')
name: globalize.translate('Suggestions')
}, {
name: globalize.translate('TabLatest')
}, {
name: globalize.translate('TabUpcoming')
}, {
name: globalize.translate('TabGenres')
name: globalize.translate('Genres')
}, {
name: globalize.translate('TabNetworks')
}, {
name: globalize.translate('TabEpisodes')
}, {
name: globalize.translate('ButtonSearch'),
cssClass: 'searchTabButton'
}];
}
@ -217,10 +214,6 @@ import 'emby-button';
case 6:
depends = 'controllers/shows/episodes';
break;
case 7:
depends = 'scripts/searchtab';
break;
}
import(depends).then(({default: controllerFactory}) => {
@ -325,8 +318,8 @@ import 'emby-button';
libraryMenu.setTitle(item.Name);
});
} else {
view.setAttribute('data-title', globalize.translate('TabShows'));
libraryMenu.setTitle(globalize.translate('TabShows'));
view.setAttribute('data-title', globalize.translate('Shows'));
libraryMenu.setTitle(globalize.translate('Shows'));
}
}

View file

@ -58,7 +58,7 @@ import 'emby-itemscontainer';
const viewStyle = self.getCurrentViewStyle();
const itemsContainer = tabContent.querySelector('.itemsContainer');
if ('List' == viewStyle) {
if (viewStyle == 'List') {
itemsContainer.classList.add('vertical-list');
itemsContainer.classList.remove('vertical-wrap');
} else {

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