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 deinterlace-improvements

This commit is contained in:
Orry Verducci 2020-08-10 22:11:25 +01:00
commit 077c02282f
221 changed files with 13279 additions and 13706 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="${ButtonAdd}">
<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

@ -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>${ButtonSave}</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">${TabDevices}</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

@ -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">${TabTranscoding}</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="${ButtonAdd}">
<span class="material-icons add"></span>
</button>
</div>
<div class="httpHeaderIdentificationList"></div>
</div>
</div>
<div is="emby-collapse" title="${HeaderDisplay}">
<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>${ButtonAdd}</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="${ButtonAdd}">
<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">${ButtonNew}</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">${ButtonNew}</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">${ButtonNew}</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">${ButtonNew}</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">${ButtonNew}</button>
<br />
<div class="mediaProfiles"></div>
</div>
<br />
<div>
<button is="emby-button" type="submit" class="raised button-submit block">
<span>${ButtonSave}</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

@ -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

@ -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>${ButtonSave}</span>
</button>
</div>
</form>
</div>
</div>
</div>

View file

@ -0,0 +1,193 @@
<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">${TabTranscoding}</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">
<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="bwdif">${Bwdif}</option>
</select>
<div class="fieldDescription">${DeinterlaceMethodHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="chkDoubleRateDeinterlacing" />
<span>${UseDoubleRateDeinterlacing}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${UseDoubleRateDeinterlacingHelp}</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>${ButtonSave}</span>
</button>
</div>
</form>
</div>
</div>
</div>

View file

@ -83,7 +83,7 @@ import libraryMenu from 'libraryMenu';
updateEncoder(form);
}, function () {
import('alert').then(({default: alert}) => {
alert(globalize.translate('DefaultErrorMessage'));
alert(globalize.translate('ErrorDefault'));
});
Dashboard.processServerConfigurationUpdateResult();

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">${LabelReadHowYouCanContribute}</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>${ButtonSave}</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

@ -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>${ButtonSave}</span>
</button>
</form>
</div>
</div>
</div>

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

@ -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>${ButtonSave}</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>${ButtonSave}</span></button></div>
</form>
</div>
</div>
</div>

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>${ButtonSave}</span>
</button>
</div>
</form>
</div>
</div>
</div>

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 (this.value == 'Custom') {
$('.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>${ButtonSave}</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++) {
let 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 = 'HeaderLiveTV';
} else if (category === 'ScreenSaver') {
category = 'HeaderScreenSavers';
}
function getHeaderText(category) {
category = category.replace(' ', '');
if (category === 'Channel') {
category = 'Channels';
} else if (category === 'Theme') {
category = 'Themes';
} else if (category === 'LiveTV') {
category = 'HeaderLiveTV';
} else if (category === 'ScreenSaver') {
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

@ -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">${OptionOnAppStartup}</option>
</select>
</div>
<div id="fldDayOfWeek" class="selectContainer">
<select is="emby-select" id="selectDayOfWeek" name="selectDayOfWeek" label="${LabelDay}">
<option value="Sunday">${OptionSunday}</option>
<option value="Monday">${OptionMonday}</option>
<option value="Tuesday">${OptionTuesday}</option>
<option value="Wednesday">${OptionWednesday}</option>
<option value="Thursday">${OptionThursday}</option>
<option value="Friday">${OptionFriday}</option>
<option value="Saturday">${OptionSaturday}</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>${ButtonAdd}</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

@ -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

@ -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>${ButtonSave}</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);">${TabPassword}</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>${ButtonSave}</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

@ -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);">${TabPassword}</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>${ButtonSave}</span>
</button>
</div>
</form>
</div>
</div>
</div>

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>${ButtonSave}</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

@ -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);">${TabPassword}</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="${ButtonAdd}">
<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="${ButtonAdd}">
<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>${ButtonSave}</span>
</button>
</div>
</form>
</div>
</div>
</div>

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">${TabPassword}</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>${ButtonSave}</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>${ButtonSave}</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

@ -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

@ -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,7 +155,7 @@ define(['appRouter', 'cardBuilder', 'dom', 'globalize', 'connectionManager', 'ap
EnableTotalRecordCount: false
};
options.Limit = 20;
var userId = apiClient.getCurrentUserId();
const userId = apiClient.getCurrentUserId();
if (section.types === 'MusicArtist') {
return apiClient.getArtists(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++;
@ -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) {
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

@ -1,3 +1,4 @@
import appHost from 'apphost';
import loading from 'loading';
import appRouter from 'appRouter';
import layoutManager from 'layoutManager';
@ -657,7 +658,7 @@ import 'emby-select';
setPeopleHeader(page, item);
loading.hide();
if (item.Type === 'Book') {
if (item.Type === 'Book' && item.CanDownload && appHost.supports('filedownload')) {
hideAll(page, 'btnDownload', true);
}

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>

View file

@ -1,8 +1,21 @@
define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager', 'cardBuilder', 'loading', 'connectionManager', 'alphaNumericShortcuts', 'scroller', 'playbackManager', 'alphaPicker', 'emby-itemscontainer', 'emby-scroller'], function (globalize, listView, layoutManager, userSettings, focusManager, cardBuilder, loading, connectionManager, AlphaNumericShortcuts, scroller, playbackManager, AlphaPicker) {
'use strict';
import globalize from 'globalize';
import listView from 'listView';
import layoutManager from 'layoutManager';
import * as userSettings from 'userSettings';
import focusManager from 'focusManager';
import cardBuilder from 'cardBuilder';
import loading from 'loading';
import connectionManager from 'connectionManager';
import AlphaNumericShortcuts from 'alphaNumericShortcuts';
import playbackManager from 'playbackManager';
import AlphaPicker from 'alphaPicker';
import 'emby-itemscontainer';
import 'emby-scroller';
/* eslint-disable indent */
function getInitialLiveTvQuery(instance, params) {
var query = {
const query = {
UserId: connectionManager.getApiClient(params.serverId).getCurrentUserId(),
StartIndex: 0,
Fields: 'ChannelInfo,PrimaryImageAspectRatio',
@ -59,7 +72,7 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
}
function modifyQueryWithFilters(instance, query) {
var sortValues = instance.getSortValues();
const sortValues = instance.getSortValues();
if (!query.SortBy) {
query.SortBy = sortValues.sortBy;
@ -68,9 +81,9 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
query.Fields = query.Fields ? query.Fields + ',PrimaryImageAspectRatio' : 'PrimaryImageAspectRatio';
query.ImageTypeLimit = 1;
var hasFilters;
var queryFilters = [];
var filters = instance.getFilters();
let hasFilters;
const queryFilters = [];
const filters = instance.getFilters();
if (filters.IsPlayed) {
queryFilters.push('IsPlayed');
@ -164,21 +177,21 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
}
function updateSortText(instance) {
var btnSortText = instance.btnSortText;
const btnSortText = instance.btnSortText;
if (btnSortText) {
var options = instance.getSortMenuOptions();
var values = instance.getSortValues();
var sortBy = values.sortBy;
const options = instance.getSortMenuOptions();
const values = instance.getSortValues();
const sortBy = values.sortBy;
for (var i = 0, length = options.length; i < length; i++) {
if (sortBy === options[i].value) {
btnSortText.innerHTML = globalize.translate('SortByValue', options[i].name);
for (const option of options) {
if (sortBy === option.value) {
btnSortText.innerHTML = globalize.translate('SortByValue', option.name);
break;
}
}
var btnSortIcon = instance.btnSortIcon;
const btnSortIcon = instance.btnSortIcon;
if (btnSortIcon) {
setSortButtonIcon(btnSortIcon, values.sortOrder === 'Descending' ? 'arrow_downward' : 'arrow_upward');
@ -198,10 +211,10 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
function updateAlphaPickerState(instance, numItems) {
if (instance.alphaPicker) {
var alphaPicker = instance.alphaPickerElement;
const alphaPicker = instance.alphaPickerElement;
if (alphaPicker) {
var values = instance.getSortValues();
const values = instance.getSortValues();
if (numItems == null) {
numItems = 100;
@ -219,7 +232,7 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
}
function getItems(instance, params, item, sortBy, startIndex, limit) {
var apiClient = connectionManager.getApiClient(params.serverId);
const apiClient = connectionManager.getApiClient(params.serverId);
instance.queryRecursive = false;
if (params.type === 'Recordings') {
@ -248,7 +261,7 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
if (!item) {
instance.queryRecursive = true;
var method = 'getItems';
let method = 'getItems';
if (params.type === 'MusicArtist') {
method = 'getArtists';
@ -271,7 +284,7 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
if (item.Type === 'Genre' || item.Type === 'MusicGenre' || item.Type === 'Studio' || item.Type === 'Person') {
instance.queryRecursive = true;
var query = {
const query = {
StartIndex: startIndex,
Limit: limit,
Fields: 'PrimaryImageAspectRatio,SortName',
@ -320,8 +333,8 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
return Promise.resolve(null);
}
var apiClient = connectionManager.getApiClient(params.serverId);
var itemId = params.genreId || params.musicGenreId || params.studioId || params.personId || params.parentId;
const apiClient = connectionManager.getApiClient(params.serverId);
const itemId = params.genreId || params.musicGenreId || params.studioId || params.personId || params.parentId;
if (itemId) {
return apiClient.getItem(apiClient.getCurrentUserId(), itemId);
@ -331,9 +344,9 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
}
function showViewSettingsMenu() {
var instance = this;
const instance = this;
require(['viewSettings'], function (ViewSettings) {
import('viewSettings').then(({default: ViewSettings}) => {
new ViewSettings().show({
settingsKey: instance.getSettingsKey(),
settings: instance.getViewSettings(),
@ -346,9 +359,9 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
}
function showFilterMenu() {
var instance = this;
const instance = this;
require(['filterMenu'], function (FilterMenu) {
import('filterMenu').then(({default: FilterMenu}) => {
new FilterMenu().show({
settingsKey: instance.getSettingsKey(),
settings: instance.getFilters(),
@ -365,9 +378,9 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
}
function showSortMenu() {
var instance = this;
const instance = this;
require(['sortMenu'], function (SortMenu) {
import('sortMenu').then(({default: SortMenu}) => {
new SortMenu().show({
settingsKey: instance.getSettingsKey(),
settings: instance.getSortValues(),
@ -383,10 +396,10 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
}
function onNewItemClick() {
var instance = this;
const instance = this;
require(['playlistEditor'], function (playlistEditor) {
new playlistEditor.showEditor({
import('playlistEditor').then(({default: playlistEditor}) => {
new playlistEditor({
items: [],
serverId: instance.params.serverId
});
@ -394,22 +407,23 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
}
function hideOrShowAll(elems, hide) {
for (var i = 0, length = elems.length; i < length; i++) {
for (const elem of elems) {
if (hide) {
elems[i].classList.add('hide');
elem.classList.add('hide');
} else {
elems[i].classList.remove('hide');
elem.classList.remove('hide');
}
}
}
function bindAll(elems, eventName, fn) {
for (var i = 0, length = elems.length; i < length; i++) {
elems[i].addEventListener(eventName, fn);
for (const elem of elems) {
elem.addEventListener(eventName, fn);
}
}
function ItemsView(view, params) {
class ItemsView {
constructor(view, params) {
function fetchData() {
return getItems(self, params, self.currentItem).then(function (result) {
if (self.totalItemCount == null) {
@ -422,7 +436,7 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
}
function getItemsHtml(items) {
var settings = self.getViewSettings();
const settings = self.getViewSettings();
if (settings.imageType === 'list') {
return listView.getListViewHtml({
@ -430,13 +444,13 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
});
}
var shape;
var preferThumb;
var preferDisc;
var preferLogo;
var defaultShape;
var item = self.currentItem;
var lines = settings.showTitle ? 2 : 0;
let shape;
let preferThumb;
let preferDisc;
let preferLogo;
let defaultShape;
const item = self.currentItem;
let lines = settings.showTitle ? 2 : 0;
if (settings.imageType === 'banner') {
shape = 'banner';
@ -460,7 +474,7 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
shape = 'autoVertical';
}
var posterOptions = {
let posterOptions = {
shape: shape,
showTitle: settings.showTitle,
showYear: settings.showTitle,
@ -493,19 +507,19 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
lines = 1;
} else if (params.type === 'Programs') {
lines = settings.showTitle ? 1 : 0;
var showParentTitle = settings.showTitle && params.IsMovie !== 'true';
const showParentTitle = settings.showTitle && params.IsMovie !== 'true';
if (showParentTitle) {
lines++;
}
var showAirTime = settings.showTitle && params.type !== 'Recordings';
const showAirTime = settings.showTitle && params.type !== 'Recordings';
if (showAirTime) {
lines++;
}
var showYear = settings.showTitle && params.IsMovie === 'true' && params.type === 'Recordings';
const showYear = settings.showTitle && params.IsMovie === 'true' && params.type === 'Recordings';
if (showYear) {
lines++;
@ -538,13 +552,13 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
function initAlphaPicker() {
self.scroller = view.querySelector('.scrollFrameY');
var alphaPickerElement = self.alphaPickerElement;
const alphaPickerElement = self.alphaPickerElement;
alphaPickerElement.classList.add('alphaPicker-fixed-right');
alphaPickerElement.classList.add('focuscontainer-right');
self.itemsContainer.parentNode.classList.add('padded-right-withalphapicker');
self.alphaPicker = new AlphaPicker.default({
self.alphaPicker = new AlphaPicker({
element: alphaPickerElement,
itemsContainer: layoutManager.tv ? self.itemsContainer : null,
itemClass: 'card',
@ -649,7 +663,7 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
}
function play() {
var currentItem = self.currentItem;
const currentItem = self.currentItem;
if (currentItem && !self.hasFilters) {
playbackManager.play({
@ -665,7 +679,7 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
}
function queue() {
var currentItem = self.currentItem;
const currentItem = self.currentItem;
if (currentItem && !self.hasFilters) {
playbackManager.queue({
@ -681,7 +695,7 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
}
function shuffle() {
var currentItem = self.currentItem;
const currentItem = self.currentItem;
if (currentItem && !self.hasFilters) {
playbackManager.shuffle(currentItem);
@ -694,7 +708,7 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
}
}
var self = this;
const self = this;
self.params = params;
this.itemsContainer = view.querySelector('.itemsContainer');
@ -708,20 +722,17 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
this.itemsContainer.setAttribute('data-refreshinterval', '300000');
}
var i;
var length;
var btnViewSettings = view.querySelectorAll('.btnViewSettings');
const btnViewSettings = view.querySelectorAll('.btnViewSettings');
for (i = 0, length = btnViewSettings.length; i < length; i++) {
btnViewSettings[i].addEventListener('click', showViewSettingsMenu.bind(this));
for (const btnViewSetting of btnViewSettings) {
btnViewSetting.addEventListener('click', showViewSettingsMenu.bind(this));
}
var filterButtons = view.querySelectorAll('.btnFilter');
const filterButtons = view.querySelectorAll('.btnFilter');
this.filterButtons = filterButtons;
var hasVisibleFilters = this.getVisibleFilters().length;
const hasVisibleFilters = this.getVisibleFilters().length;
for (i = 0, length = filterButtons.length; i < length; i++) {
var btnFilter = filterButtons[i];
for (const btnFilter of filterButtons) {
btnFilter.addEventListener('click', showFilterMenu.bind(this));
if (hasVisibleFilters) {
@ -731,10 +742,10 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
}
}
var sortButtons = view.querySelectorAll('.btnSort');
const sortButtons = view.querySelectorAll('.btnSort');
for (this.sortButtons = sortButtons, i = 0, length = sortButtons.length; i < length; i++) {
var sortButton = sortButtons[i];
this.sortButtons = sortButtons;
for (const sortButton of sortButtons) {
sortButton.addEventListener('click', showSortMenu.bind(this));
if (params.type !== 'nextup') {
@ -749,7 +760,7 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
self.itemsContainer.fetchData = fetchData;
self.itemsContainer.getItemsHtml = getItemsHtml;
view.addEventListener('viewshow', function (e) {
var isRestored = e.detail.isRestored;
const isRestored = e.detail.isRestored;
if (!isRestored) {
loading.show();
@ -761,7 +772,7 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
getItem(params).then(function (item) {
setTitle(item);
self.currentItem = item;
var refresh = !isRestored;
const refresh = !isRestored;
self.itemsContainer.resume({
refresh: refresh
}).then(function () {
@ -776,7 +787,7 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
initAlphaPicker();
}
var itemType = item ? item.Type : null;
const itemType = item ? item.Type : null;
if (itemType === 'MusicGenre' || params.type !== 'Programs' && itemType !== 'Channel') {
hideOrShowAll(view.querySelectorAll('.btnPlay'), false);
@ -803,18 +814,18 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
bindAll(view.querySelectorAll('.btnShuffle'), 'click', shuffle);
}
self.alphaNumericShortcuts = new AlphaNumericShortcuts.default({
self.alphaNumericShortcuts = new AlphaNumericShortcuts({
itemsContainer: self.itemsContainer
});
});
view.addEventListener('viewhide', function (e) {
var itemsContainer = self.itemsContainer;
const itemsContainer = self.itemsContainer;
if (itemsContainer) {
itemsContainer.pause();
}
var alphaNumericShortcuts = self.alphaNumericShortcuts;
const alphaNumericShortcuts = self.alphaNumericShortcuts;
if (alphaNumericShortcuts) {
alphaNumericShortcuts.destroy();
@ -842,8 +853,8 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
});
}
ItemsView.prototype.getFilters = function () {
var basekey = this.getSettingsKey();
getFilters() {
const basekey = this.getSettingsKey();
return {
IsPlayed: userSettings.getFilter(basekey + '-filter-IsPlayed') === 'true',
IsUnplayed: userSettings.getFilter(basekey + '-filter-IsUnplayed') === 'true',
@ -862,39 +873,37 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
HasThemeVideo: userSettings.getFilter(basekey + '-filter-HasThemeVideo'),
GenreIds: userSettings.getFilter(basekey + '-filter-GenreIds')
};
};
}
ItemsView.prototype.getSortValues = function () {
var basekey = this.getSettingsKey();
getSortValues() {
const basekey = this.getSettingsKey();
return {
sortBy: userSettings.getFilter(basekey + '-sortby') || this.getDefaultSortBy(),
sortOrder: userSettings.getFilter(basekey + '-sortorder') === 'Descending' ? 'Descending' : 'Ascending'
};
};
}
ItemsView.prototype.getDefaultSortBy = function () {
var params = this.params;
var sortNameOption = this.getNameSortOption(params);
getDefaultSortBy() {
const sortNameOption = this.getNameSortOption(this.params);
if (params.type) {
if (this.params.type) {
return sortNameOption.value;
}
return 'IsFolder,' + sortNameOption.value;
};
}
ItemsView.prototype.getSortMenuOptions = function () {
var sortBy = [];
var params = this.params;
getSortMenuOptions() {
const sortBy = [];
if (params.type === 'Programs') {
if (this.params.type === 'Programs') {
sortBy.push({
name: globalize.translate('AirDate'),
value: 'StartDate,SortName'
});
}
var option = this.getNameSortOption(params);
let option = this.getNameSortOption(this.params);
if (option) {
sortBy.push(option);
@ -912,7 +921,7 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
sortBy.push(option);
}
if (params.type !== 'Programs') {
if (this.params.type !== 'Programs') {
sortBy.push({
name: globalize.translate('DateAdded'),
value: 'DateCreated,SortName'
@ -925,8 +934,8 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
sortBy.push(option);
}
if (!params.type) {
option = this.getNameSortOption(params);
if (!this.params.type) {
option = this.getNameSortOption(this.params);
sortBy.push({
name: globalize.translate('Folders'),
value: 'IsFolder,' + option.value
@ -952,9 +961,9 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
value: 'Runtime,SortName'
});
return sortBy;
};
}
ItemsView.prototype.getNameSortOption = function (params) {
getNameSortOption(params) {
if (params.type === 'Episode') {
return {
name: globalize.translate('Name'),
@ -966,9 +975,9 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
name: globalize.translate('Name'),
value: 'SortName'
};
};
}
ItemsView.prototype.getPlayCountSortOption = function () {
getPlayCountSortOption() {
if (this.params.type === 'Programs') {
return null;
}
@ -977,9 +986,9 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
name: globalize.translate('PlayCount'),
value: 'PlayCount,SortName'
};
};
}
ItemsView.prototype.getDatePlayedSortOption = function () {
getDatePlayedSortOption() {
if (this.params.type === 'Programs') {
return null;
}
@ -988,9 +997,9 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
name: globalize.translate('DatePlayed'),
value: 'DatePlayed,SortName'
};
};
}
ItemsView.prototype.getCriticRatingSortOption = function () {
getCriticRatingSortOption() {
if (this.params.type === 'Programs') {
return null;
}
@ -999,18 +1008,18 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
name: globalize.translate('CriticRating'),
value: 'CriticRating,SortName'
};
};
}
ItemsView.prototype.getCommunityRatingSortOption = function () {
getCommunityRatingSortOption() {
return {
name: globalize.translate('CommunityRating'),
value: 'CommunityRating,SortName'
};
};
}
ItemsView.prototype.getVisibleFilters = function () {
var filters = [];
var params = this.params;
getVisibleFilters() {
const filters = [];
const params = this.params;
if (!(params.type === 'nextup')) {
if (params.type === 'Programs') {
@ -1034,16 +1043,15 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
}
return filters;
};
}
ItemsView.prototype.setFilterStatus = function (hasFilters) {
setFilterStatus(hasFilters) {
this.hasFilters = hasFilters;
var filterButtons = this.filterButtons;
const filterButtons = this.filterButtons;
if (filterButtons.length) {
for (var i = 0, length = filterButtons.length; i < length; i++) {
var btnFilter = filterButtons[i];
var bubble = btnFilter.querySelector('.filterButtonBubble');
for (const btnFilter of filterButtons) {
let bubble = btnFilter.querySelector('.filterButtonBubble');
if (!bubble) {
if (!hasFilters) {
@ -1062,10 +1070,10 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
}
}
}
};
}
ItemsView.prototype.getFilterMenuOptions = function () {
var params = this.params;
getFilterMenuOptions() {
const params = this.params;
return {
IsAiring: params.IsAiring,
IsMovie: params.IsMovie,
@ -1075,11 +1083,11 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
IsSeries: params.IsSeries,
Recursive: this.queryRecursive
};
};
}
ItemsView.prototype.getVisibleViewSettings = function () {
var item = (this.params, this.currentItem);
var fields = ['showTitle'];
getVisibleViewSettings() {
const item = (this.params, this.currentItem);
const fields = ['showTitle'];
if (!item || item.Type !== 'PhotoAlbum' && item.Type !== 'ChannelFolderItem') {
fields.push('imageType');
@ -1087,13 +1095,13 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
fields.push('viewType');
return fields;
};
}
ItemsView.prototype.getViewSettings = function () {
var basekey = this.getSettingsKey();
var params = this.params;
var item = this.currentItem;
var showTitle = userSettings.get(basekey + '-showTitle');
getViewSettings() {
const basekey = this.getSettingsKey();
const params = this.params;
const item = this.currentItem;
let showTitle = userSettings.get(basekey + '-showTitle');
if (showTitle === 'true') {
showTitle = true;
@ -1105,7 +1113,7 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
showTitle = true;
}
var imageType = userSettings.get(basekey + '-imageType');
let imageType = userSettings.get(basekey + '-imageType');
if (!imageType && params.type === 'nextup') {
imageType = 'thumb';
@ -1117,10 +1125,10 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
imageType: imageType || 'primary',
viewType: userSettings.get(basekey + '-viewType') || 'images'
};
};
}
ItemsView.prototype.getItemTypes = function () {
var params = this.params;
getItemTypes() {
const params = this.params;
if (params.type === 'nextup') {
return ['Episode'];
@ -1131,12 +1139,12 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
}
return [];
};
}
ItemsView.prototype.getSettingsKey = function () {
var values = [];
getSettingsKey() {
const values = [];
values.push('items');
var params = this.params;
const params = this.params;
if (params.type) {
values.push(params.type);
@ -1193,7 +1201,9 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager'
}
return values.join('-');
};
}
}
return ItemsView;
});
export default ItemsView;
/* eslint-enable indent */

View file

@ -0,0 +1,97 @@
<div id="liveTvSuggestedPage" data-dom-cache="true" data-role="page" class="page libraryPage liveTvPage pageWithAbsoluteTabs withTabs" data-title="${HeaderLiveTv}" 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;">${TabShows}</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;">${HeaderMovies}</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,6 +1,9 @@
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';
loading = loading.default || loading;
layoutManager = layoutManager.default || layoutManager;
function enableScrollX() {
return !layoutManager.desktop;
}
@ -167,9 +170,6 @@ define(['layoutManager', 'userSettings', 'inputManager', 'loading', 'globalize',
name: globalize.translate('HeaderSchedule')
}, {
name: globalize.translate('TabSeries')
}, {
name: globalize.translate('ButtonSearch'),
cssClass: 'searchTabButton'
}];
}
@ -253,12 +253,11 @@ define(['layoutManager', 'userSettings', 'inputManager', 'loading', 'globalize',
case 5:
depends.push('controllers/livetv/livetvseriestimers');
break;
case 6:
depends.push('scripts/searchtab');
}
require(depends, function (controllerFactory) {
controllerFactory = controllerFactory.default || controllerFactory;
var tabContent;
if (index == 0) {

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>${ButtonSave}</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="${ButtonAdd}">
<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="${ButtonAdd}">
<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,305 @@
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) {
let padderClass;
let html = '';
let cssClass = 'card scalableCard';
let cardBoxCssClass = 'cardBox visualCardBox';
cssClass += ' backdropCard backdropCard-scalable';
padderClass = 'cardPadder-backdrop';
// 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('ButtonDelete'),
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('ButtonDelete'),
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 +312,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>${ButtonSave}</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,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,9 +52,9 @@ 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 (viewStyle == 'List') {
itemsContainer.classList.add('vertical-list');
@ -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 = viewStyle == 'Thumb' || viewStyle == 'ThumbCard' ? 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 = viewStyle == 'Thumb' || viewStyle == 'ThumbCard' ? '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="${ButtonSort}"><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="${ButtonSort}"><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="${ButtonSort}"><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);
@ -222,9 +236,6 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu'
name: globalize.translate('TabCollections')
}, {
name: globalize.translate('TabGenres')
}, {
name: globalize.translate('ButtonSearch'),
cssClass: 'searchTabButton'
}];
}
@ -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')) {
@ -403,15 +411,14 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu'
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="${ButtonShuffle}"><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="${ButtonSort}"><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="${ButtonSort}"><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

@ -191,9 +191,6 @@ import 'flexStyles';
name: globalize.translate('TabSongs')
}, {
name: globalize.translate('TabGenres')
}, {
name: globalize.translate('ButtonSearch'),
cssClass: 'searchTabButton'
}];
}
@ -295,10 +292,6 @@ import 'flexStyles';
case 6:
depends = 'controllers/music/musicgenres';
break;
case 7:
depends = 'scripts/searchtab';
break;
}
import(depends).then(({default: controllerFactory}) => {

View file

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

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="${ButtonSort}"><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">${HeaderNextUp}</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="${ButtonSort}"><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

@ -30,9 +30,6 @@ import 'emby-button';
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}) => {

View file

@ -0,0 +1,24 @@
<div id="wizardLibraryPage" data-role="page" class="page standalonePage wizardPage mediaLibraryPage">
<div class="padded-left padded-right padded-top">
<div class="ui-corner-all ui-shadow wizardContent">
<div>
<h1 style="display:inline-block;">${HeaderSetupLibrary}</h1>
<a href="https://docs.jellyfin.org/general/server/libraries.html" rel="noopener noreferrer" target="_blank" class="clearLink" style="margin-top:-10px;display:inline-block;vertical-align:middle;margin-left:2em;"><button is="emby-button" type="button" class="raised"><span class="material-icons info"></span><span>${ButtonHelp}</span></button></a>
</div>
<br />
<div id="divVirtualFolders"></div>
<br />
<br />
<div class="wizardNavigation">
<button is="emby-button" type="button" class="raised button-cancel" onclick="history.back();">
<span class="material-icons arrow_back"></span>
<span>${LabelPrevious}</span>
</button>
<button is="emby-button" type="button" class="raised button-submit" onclick="WizardLibraryPage.next();">
<span>${LabelNext}</span>
<span class="material-icons arrow_forward"></span>
</button>
</div>
</div>
</div>
</div>