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

First separation commit.

Added LICENSE, README.md, CONTRIBUTORS.md
This commit is contained in:
Erwin de Haan 2019-01-09 12:36:54 +01:00
parent 09513af31b
commit 4678528d00
657 changed files with 422 additions and 0 deletions

View file

@ -0,0 +1,51 @@
define(["dialogHelper", "datetime", "emby-select", "paper-icon-button-light", "formDialogStyle"], function(dialogHelper, datetime) {
"use strict";
function getDisplayTime(hours) {
var minutes = 0,
pct = hours % 1;
return pct && (minutes = parseInt(60 * pct)), datetime.getDisplayTime(new Date(2e3, 1, 1, hours, minutes, 0, 0))
}
function populateHours(context) {
for (var html = "", i = 0; i < 24; i++) html += '<option value="' + i + '">' + getDisplayTime(i) + "</option>";
html += '<option value="24">' + getDisplayTime(0) + "</option>", context.querySelector("#selectStart").innerHTML = html, context.querySelector("#selectEnd").innerHTML = html
}
function loadSchedule(context, schedule) {
context.querySelector("#selectDay").value = schedule.DayOfWeek || "Sunday", context.querySelector("#selectStart").value = schedule.StartHour || 0, context.querySelector("#selectEnd").value = schedule.EndHour || 0
}
function submitSchedule(context, options) {
var updatedSchedule = {
DayOfWeek: context.querySelector("#selectDay").value,
StartHour: context.querySelector("#selectStart").value,
EndHour: context.querySelector("#selectEnd").value
};
if (parseFloat(updatedSchedule.StartHour) >= parseFloat(updatedSchedule.EndHour)) return void alert(Globalize.translate("ErrorMessageStartHourGreaterThanEnd"));
context.submitted = !0, options.schedule = Object.assign(options.schedule, updatedSchedule), dialogHelper.close(context)
}
return {
show: function(options) {
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest;
xhr.open("GET", "components/accessschedule/accessschedule.template.html", !0), xhr.onload = function(e) {
var template = this.response,
dlg = dialogHelper.createDialog({
removeOnClose: !0,
size: "small"
});
dlg.classList.add("formDialog");
var html = "";
html += Globalize.translateDocument(template), dlg.innerHTML = html, populateHours(dlg), loadSchedule(dlg, options.schedule), dialogHelper.open(dlg), dlg.addEventListener("close", function() {
dlg.submitted ? resolve(options.schedule) : reject()
}), dlg.querySelector(".btnCancel").addEventListener("click", function(e) {
dialogHelper.close(dlg)
}), dlg.querySelector("form").addEventListener("submit", function(e) {
return submitSchedule(dlg, options), e.preventDefault(), !1
})
}, xhr.send()
})
}
}
});

View file

@ -0,0 +1,39 @@
<div class="formDialogHeader">
<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><i class="md-icon">&#xE5C4;</i></button>
<h3 class="formDialogHeaderTitle">
${HeaderAccessSchedule}
</h3>
</div>
<div class="formDialogContent scrollY" style="padding-top:2em;">
<div class="dialogContentInner dialog-content-centered">
<form class="scheduleForm" style="margin:auto;">
<div class="selectContainer">
<select is="emby-select" id="selectDay" label="${LabelAccessDay}">
<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>
<option value="Everyday">${OptionEveryday}</option>
<option value="Weekday">${OptionWeekdays}</option>
<option value="Weekend">${OptionWeekends}</option>
</select>
</div>
<div class="selectContainer">
<select is="emby-select" class="selectHour" id="selectStart" label="${LabelAccessStart}"></select>
</div>
<div class="selectContainer">
<select is="emby-select" class="selectHour" id="selectEnd" label="${LabelAccessEnd}"></select>
</div>
<div class="formDialogFooter">
<button is="emby-button" type="submit" class="raised button-submit block formDialogFooterItem">
<span>${ButtonAdd}</span>
</button>
</div>
</form>
</div>
</div>

View file

@ -0,0 +1,89 @@
define(["events", "globalize", "dom", "datetime", "userSettings", "serverNotifications", "connectionManager", "emby-button", "listViewStyle"], function(events, globalize, dom, datetime, userSettings, serverNotifications, connectionManager) {
"use strict";
function getEntryHtml(entry, apiClient) {
var html = "";
html += '<div class="listItem listItem-border">';
var color = "Error" == entry.Severity || "Fatal" == entry.Severity || "Warn" == entry.Severity ? "#cc0000" : "#00a4dc";
if (entry.UserId && entry.UserPrimaryImageTag) {
html += '<i class="listItemIcon md-icon" style="width:2em!important;height:2em!important;padding:0;color:transparent;background-color:' + color + ";background-image:url('" + apiClient.getUserImageUrl(entry.UserId, {
type: "Primary",
tag: entry.UserPrimaryImageTag,
height: 40
}) + "');background-repeat:no-repeat;background-position:center center;background-size: cover;\">dvr</i>"
} else html += '<i class="listItemIcon md-icon" style="background-color:' + color + '">dvr</i>';
html += '<div class="listItemBody three-line">', html += '<div class="listItemBodyText">', html += entry.Name, html += "</div>", html += '<div class="listItemBodyText secondary">';
var date = datetime.parseISO8601Date(entry.Date, !0);
return html += datetime.toLocaleString(date).toLowerCase(), html += "</div>", html += '<div class="listItemBodyText secondary listItemBodyText-nowrap">', html += entry.ShortOverview || "", html += "</div>", html += "</div>", entry.Overview && (html += '<button type="button" is="paper-icon-button-light" class="btnEntryInfo" data-id="' + entry.Id + '" title="' + globalize.translate("Info") + '"><i class="md-icon">info</i></button>'), html += "</div>"
}
function renderList(elem, apiClient, result, startIndex, limit) {
elem.innerHTML = result.Items.map(function(i) {
return getEntryHtml(i, apiClient)
}).join("")
}
function reloadData(instance, elem, apiClient, startIndex, limit) {
null == startIndex && (startIndex = parseInt(elem.getAttribute("data-activitystartindex") || "0")), limit = limit || parseInt(elem.getAttribute("data-activitylimit") || "7");
var minDate = new Date,
hasUserId = "false" !== elem.getAttribute("data-useractivity");
hasUserId ? minDate.setTime(minDate.getTime() - 864e5) : minDate.setTime(minDate.getTime() - 6048e5), ApiClient.getJSON(ApiClient.getUrl("System/ActivityLog/Entries", {
startIndex: startIndex,
limit: limit,
minDate: minDate.toISOString(),
hasUserId: hasUserId
})).then(function(result) {
if (elem.setAttribute("data-activitystartindex", startIndex), elem.setAttribute("data-activitylimit", limit), !startIndex) {
var activityContainer = dom.parentWithClass(elem, "activityContainer");
activityContainer && (result.Items.length ? activityContainer.classList.remove("hide") : activityContainer.classList.add("hide"))
}
instance.items = result.Items, renderList(elem, apiClient, result, startIndex, limit)
})
}
function onActivityLogUpdate(e, apiClient, data) {
var options = this.options;
options && options.serverId === apiClient.serverId() && reloadData(this, options.element, apiClient)
}
function onListClick(e) {
var btnEntryInfo = dom.parentWithClass(e.target, "btnEntryInfo");
if (btnEntryInfo) {
var id = btnEntryInfo.getAttribute("data-id"),
items = this.items;
if (items) {
var item = items.filter(function(i) {
return i.Id.toString() === id
})[0];
item && showItemOverview(item)
}
}
}
function showItemOverview(item) {
require(["alert"], function(alert) {
alert({
text: item.Overview
})
})
}
function ActivityLog(options) {
this.options = options;
var element = options.element;
element.classList.add("activityLogListWidget"), element.addEventListener("click", onListClick.bind(this));
var apiClient = connectionManager.getApiClient(options.serverId);
reloadData(this, element, apiClient);
var onUpdate = onActivityLogUpdate.bind(this);
this.updateFn = onUpdate, events.on(serverNotifications, "ActivityLogEntry", onUpdate), apiClient.sendMessage("ActivityLogEntryStart", "0,1500")
}
return ActivityLog.prototype.destroy = function() {
var options = this.options;
if (options) {
options.element.classList.remove("activityLogListWidget");
connectionManager.getApiClient(options.serverId).sendMessage("ActivityLogEntryStop", "0,1500")
}
var onUpdate = this.updateFn;
onUpdate && events.off(serverNotifications, "ActivityLogEntry", onUpdate), this.items = null, this.options = null
}, ActivityLog
});

191
src/components/apphost.js Normal file
View file

@ -0,0 +1,191 @@
define(["appSettings", "browser", "events", "htmlMediaHelper"], function(appSettings, browser, events, htmlMediaHelper) {
"use strict";
function getBaseProfileOptions(item) {
var disableHlsVideoAudioCodecs = [];
return item && htmlMediaHelper.enableHlsJsPlayer(item.RunTimeTicks, item.MediaType) && ((browser.edge || browser.msie) && disableHlsVideoAudioCodecs.push("mp3"), disableHlsVideoAudioCodecs.push("ac3"), disableHlsVideoAudioCodecs.push("eac3"), disableHlsVideoAudioCodecs.push("opus")), {
enableMkvProgressive: !1,
disableHlsVideoAudioCodecs: disableHlsVideoAudioCodecs
}
}
function getDeviceProfileForWindowsUwp(item) {
return new Promise(function(resolve, reject) {
require(["browserdeviceprofile", "environments/windows-uwp/mediacaps"], function(profileBuilder, uwpMediaCaps) {
var profileOptions = getBaseProfileOptions(item);
profileOptions.supportsDts = uwpMediaCaps.supportsDTS(), profileOptions.supportsTrueHd = uwpMediaCaps.supportsDolby(), profileOptions.audioChannels = uwpMediaCaps.getAudioChannels(), resolve(profileBuilder(profileOptions))
})
})
}
function getDeviceProfile(item, options) {
return options = options || {}, self.Windows ? getDeviceProfileForWindowsUwp(item) : new Promise(function(resolve, reject) {
require(["browserdeviceprofile"], function(profileBuilder) {
var profile = profileBuilder(getBaseProfileOptions(item));
item && !options.isRetry && "allcomplexformats" !== appSettings.get("subtitleburnin") && (browser.orsay || browser.tizen || (profile.SubtitleProfiles.push({
Format: "ass",
Method: "External"
}), profile.SubtitleProfiles.push({
Format: "ssa",
Method: "External"
}))), resolve(profile)
})
})
}
function escapeRegExp(str) {
return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1")
}
function replaceAll(originalString, strReplace, strWith) {
var strReplace2 = escapeRegExp(strReplace),
reg = new RegExp(strReplace2, "ig");
return originalString.replace(reg, strWith)
}
function generateDeviceId() {
var keys = [];
if (keys.push(navigator.userAgent), keys.push((new Date).getTime()), self.btoa) {
var result = replaceAll(btoa(keys.join("|")), "=", "1");
return Promise.resolve(result)
}
return Promise.resolve((new Date).getTime())
}
function getDeviceId() {
var key = "_deviceId2",
deviceId = appSettings.get(key);
return deviceId ? Promise.resolve(deviceId) : generateDeviceId().then(function(deviceId) {
return appSettings.set(key, deviceId), deviceId
})
}
function getDeviceName() {
var deviceName;
return deviceName = browser.tizen ? "Samsung Smart TV" : browser.web0s ? "LG Smart TV" : browser.operaTv ? "Opera TV" : browser.xboxOne ? "Xbox One" : browser.ps4 ? "Sony PS4" : browser.chrome ? "Chrome" : browser.edge ? "Edge" : browser.firefox ? "Firefox" : browser.msie ? "Internet Explorer" : browser.opera ? "Opera" : "Web Browser", browser.ipad ? deviceName += " Ipad" : browser.iphone ? deviceName += " Iphone" : browser.android && (deviceName += " Android"), deviceName
}
function supportsVoiceInput() {
return !browser.tv && (window.SpeechRecognition || window.webkitSpeechRecognition || window.mozSpeechRecognition || window.oSpeechRecognition || window.msSpeechRecognition)
}
function supportsFullscreen() {
if (browser.tv) return !1;
var element = document.documentElement;
return !!(element.requestFullscreen || element.mozRequestFullScreen || element.webkitRequestFullscreen || element.msRequestFullscreen) || !!document.createElement("video").webkitEnterFullscreen
}
function getSyncProfile() {
return new Promise(function(resolve, reject) {
require(["browserdeviceprofile", "appSettings"], function(profileBuilder, appSettings) {
var profile = profileBuilder();
profile.MaxStaticMusicBitrate = appSettings.maxStaticMusicBitrate(), resolve(profile)
})
})
}
function getDefaultLayout() {
return "desktop"
}
function supportsHtmlMediaAutoplay() {
if (browser.edgeUwp || browser.tizen || browser.web0s || browser.orsay || browser.operaTv || browser.ps4 || browser.xboxOne) return !0;
if (browser.mobile) return !1;
var savedResult = appSettings.get(htmlMediaAutoplayAppStorageKey);
return "true" === savedResult || "false" !== savedResult && null
}
function cueSupported() {
try {
var video = document.createElement("video"),
style = document.createElement("style");
style.textContent = "video::cue {background: inherit}", document.body.appendChild(style), document.body.appendChild(video);
var cue = window.getComputedStyle(video, "::cue").background;
return document.body.removeChild(style), document.body.removeChild(video), !!cue.length
} catch (err) {
return console.log("Error detecting cue support:" + err), !1
}
}
function onAppVisible() {
_isHidden && (_isHidden = !1, console.log("triggering app resume event"), events.trigger(appHost, "resume"))
}
function onAppHidden() {
_isHidden || (_isHidden = !0, console.log("app is hidden"))
}
var htmlMediaAutoplayAppStorageKey = "supportshtmlmediaautoplay0",
supportedFeatures = function() {
var features = [];
return navigator.share && features.push("sharing"), browser.edgeUwp || browser.tv || browser.xboxOne || browser.ps4 || features.push("filedownload"), browser.operaTv || browser.tizen || browser.orsay || browser.web0s ? features.push("exit") : (features.push("exitmenu"), features.push("plugins")), browser.operaTv || browser.tizen || browser.orsay || browser.web0s || browser.ps4 || (features.push("externallinks"), features.push("externalpremium")), browser.operaTv || features.push("externallinkdisplay"), supportsVoiceInput() && features.push("voiceinput"), !browser.tv && !browser.xboxOne && browser.ps4, supportsHtmlMediaAutoplay() && (features.push("htmlaudioautoplay"), features.push("htmlvideoautoplay")), browser.edgeUwp && features.push("sync"), supportsFullscreen() && features.push("fullscreenchange"), (browser.chrome || browser.edge && !browser.slow) && (browser.noAnimation || browser.edgeUwp || browser.xboxOne || features.push("imageanalysis")), AppInfo.isNativeApp && features.push("multiserver"), (browser.tv || browser.xboxOne || browser.ps4 || browser.mobile) && features.push("physicalvolumecontrol"), browser.tv || browser.xboxOne || browser.ps4 || features.push("remotecontrol"), browser.operaTv || browser.tizen || browser.orsay || browser.web0s || browser.edgeUwp || features.push("remotevideo"), features.push("otherapppromotions"), features.push("targetblank"), browser.orsay || browser.tizen || browser.msie || !(browser.firefox || browser.ps4 || browser.edge || cueSupported()) || features.push("subtitleappearancesettings"), browser.orsay || browser.tizen || features.push("subtitleburnsettings"), browser.tv || browser.ps4 || browser.xboxOne || features.push("fileinput"), AppInfo.isNativeApp && features.push("displaylanguage"), browser.chrome && features.push("chromecast"), features
}(); - 1 === supportedFeatures.indexOf("htmlvideoautoplay") && !1 !== supportsHtmlMediaAutoplay() && require(["autoPlayDetect"], function(autoPlayDetect) {
autoPlayDetect.supportsHtmlMediaAutoplay().then(function() {
appSettings.set(htmlMediaAutoplayAppStorageKey, "true"), supportedFeatures.push("htmlvideoautoplay"), supportedFeatures.push("htmlaudioautoplay")
}, function() {
appSettings.set(htmlMediaAutoplayAppStorageKey, "false")
})
});
var deviceId, deviceName, visibilityChange, visibilityState, appVersion = window.dashboardVersion || "3.0",
appHost = {
getWindowState: function() {
return document.windowState || "Normal"
},
setWindowState: function(state) {
alert("setWindowState is not supported and should not be called")
},
exit: function() {
if (browser.tizen) try {
tizen.application.getCurrentApplication().exit()
} catch (err) {
console.log("error closing application: " + err)
} else window.close()
},
supports: function(command) {
return -1 !== supportedFeatures.indexOf(command.toLowerCase())
},
preferVisualCards: browser.android || browser.chrome,
moreIcon: browser.android ? "dots-vert" : "dots-horiz",
getSyncProfile: getSyncProfile,
getDefaultLayout: getDefaultLayout,
getDeviceProfile: getDeviceProfile,
init: function() {
return deviceName = getDeviceName(), getDeviceId().then(function(resolvedDeviceId) {
deviceId = resolvedDeviceId
})
},
deviceName: function() {
return deviceName
},
deviceId: function() {
return deviceId
},
appName: function() {
return "Jellyfin Web"
},
appVersion: function() {
return appVersion
},
getPushTokenInfo: function() {
return {}
},
setThemeColor: function(color) {
var metaThemeColor = document.querySelector("meta[name=theme-color]");
metaThemeColor && metaThemeColor.setAttribute("content", color)
},
setUserScalable: function(scalable) {
if (!browser.tv) {
var att = scalable ? "width=device-width, initial-scale=1, minimum-scale=1, user-scalable=yes" : "width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no";
document.querySelector("meta[name=viewport]").setAttribute("content", att)
}
},
deviceIconUrl: function() {
return browser.edgeUwp, browser.edgeUwp ? "https://github.com/MediaBrowser/Emby.Resources/raw/master/images/devices/windowsrt.png" : browser.opera || browser.operaTv ? "https://github.com/MediaBrowser/Emby.Resources/raw/master/images/devices/opera.png" : browser.orsay || browser.tizen ? "https://github.com/MediaBrowser/Emby.Resources/raw/master/images/devices/samsungtv.png" : browser.web0s ? "https://github.com/MediaBrowser/Emby.Resources/raw/master/images/devices/lgtv.png" : browser.ps4 ? "https://github.com/MediaBrowser/Emby.Resources/raw/master/images/devices/ps4.png" : browser.chromecast ? "https://github.com/MediaBrowser/Emby.Resources/raw/master/images/devices/chromecast.png" : browser.chrome ? "https://github.com/MediaBrowser/Emby.Resources/raw/master/images/devices/chrome.png" : browser.edge ? "https://github.com/MediaBrowser/Emby.Resources/raw/master/images/devices/edge.png" : browser.firefox ? "https://github.com/MediaBrowser/Emby.Resources/raw/master/images/devices/firefox.png" : browser.msie ? "https://github.com/MediaBrowser/Emby.Resources/raw/master/images/devices/internetexplorer.png" : browser.safari ? "https://github.com/MediaBrowser/Emby.Resources/raw/master/images/devices/safari.png" : "https://github.com/MediaBrowser/Emby.Resources/raw/master/images/devices/html5.png"
}
},
doc = self.document;
doc && (void 0 !== doc.visibilityState ? (visibilityChange = "visibilitychange", visibilityState = "hidden") : void 0 !== doc.mozHidden ? (visibilityChange = "mozvisibilitychange", visibilityState = "mozVisibilityState") : void 0 !== doc.msHidden ? (visibilityChange = "msvisibilitychange", visibilityState = "msVisibilityState") : void 0 !== doc.webkitHidden && (visibilityChange = "webkitvisibilitychange", visibilityState = "webkitVisibilityState"));
var _isHidden = !1;
return doc && doc.addEventListener(visibilityChange, function() {
document[visibilityState] ? onAppHidden() : onAppVisible()
}), self.addEventListener && (self.addEventListener("focus", onAppVisible), self.addEventListener("blur", onAppHidden)), appHost
});

View file

@ -0,0 +1,31 @@
define(["itemHelper", "libraryMenu", "apphost"], function(itemHelper, libraryMenu, appHost) {
"use strict";
function initSyncButtons(view) {
var apiClient = window.ApiClient;
apiClient && apiClient.getCurrentUserId() && apiClient.getCurrentUser().then(function(user) {
for (var item = {
SupportsSync: !0
}, categorySyncButtons = view.querySelectorAll(".categorySyncButton"), i = 0, length = categorySyncButtons.length; i < length; i++) categorySyncButtons[i].addEventListener("click", onCategorySyncButtonClick), itemHelper.canSync(user, item) ? categorySyncButtons[i].classList.remove("hide") : categorySyncButtons[i].classList.add("hide")
})
}
function onCategorySyncButtonClick(e) {
var button = this,
category = button.getAttribute("data-category"),
parentId = libraryMenu.getTopParentId();
require(["syncDialog"], function(syncDialog) {
syncDialog.showMenu({
ParentId: parentId,
Category: category,
serverId: ApiClient.serverId(),
mode: appHost.supports("sync") ? "download" : "sync"
})
})
}
return {
init: function(view) {
initSyncButtons(view)
}
}
});

View file

@ -0,0 +1,96 @@
define(["dialogHelper", "loading", "connectionManager", "globalize", "actionsheet", "emby-input", "paper-icon-button-light", "emby-button", "listViewStyle", "material-icons", "formDialogStyle"], function(dialogHelper, loading, connectionManager, globalize, actionsheet) {
"use strict";
return function(options) {
function parentWithClass(elem, className) {
for (; !elem.classList || !elem.classList.contains(className);)
if (!(elem = elem.parentNode)) return null;
return elem
}
function mapChannel(button, channelId, providerChannelId) {
loading.show();
var providerId = options.providerId;
connectionManager.getApiClient(options.serverId).ajax({
type: "POST",
url: ApiClient.getUrl("LiveTv/ChannelMappings"),
data: {
providerId: providerId,
tunerChannelId: channelId,
providerChannelId: providerChannelId
},
dataType: "json"
}).then(function(mapping) {
var listItem = parentWithClass(button, "listItem");
button.setAttribute("data-providerid", mapping.ProviderChannelId), listItem.querySelector(".secondary").innerHTML = getMappingSecondaryName(mapping, currentMappingOptions.ProviderName), loading.hide()
})
}
function onChannelsElementClick(e) {
var btnMap = parentWithClass(e.target, "btnMap");
if (btnMap) {
var channelId = btnMap.getAttribute("data-id"),
providerChannelId = btnMap.getAttribute("data-providerid"),
menuItems = currentMappingOptions.ProviderChannels.map(function(m) {
return {
name: m.Name,
id: m.Id,
selected: m.Id.toLowerCase() === providerChannelId.toLowerCase()
}
});
actionsheet.show({
positionTo: btnMap,
items: menuItems
}).then(function(newChannelId) {
mapChannel(btnMap, channelId, newChannelId)
})
}
}
function getChannelMappingOptions(serverId, providerId) {
var apiClient = connectionManager.getApiClient(serverId);
return apiClient.getJSON(apiClient.getUrl("LiveTv/ChannelMappingOptions", {
providerId: providerId
}))
}
function getMappingSecondaryName(mapping, providerName) {
return (mapping.ProviderChannelName || "") + " - " + providerName
}
function getTunerChannelHtml(channel, providerName) {
var html = "";
return html += '<div class="listItem">', html += '<i class="md-icon listItemIcon">dvr</i>', html += '<div class="listItemBody two-line">', html += '<h3 class="listItemBodyText">', html += channel.Name, html += "</h3>", html += '<div class="secondary listItemBodyText">', channel.ProviderChannelName && (html += getMappingSecondaryName(channel, providerName)), html += "</div>", html += "</div>", html += '<button class="btnMap autoSize" is="paper-icon-button-light" type="button" data-id="' + channel.Id + '" data-providerid="' + channel.ProviderChannelId + '"><i class="md-icon">mode_edit</i></button>', html += "</div>"
}
function getEditorHtml() {
var html = "";
return html += '<div class="formDialogContent">', html += '<div class="dialogContentInner dialog-content-centered">', html += '<form style="margin:auto;">', html += "<h1>" + globalize.translate("HeaderChannels") + "</h1>", html += '<div class="channels paperList">', html += "</div>", html += "</form>", html += "</div>", html += "</div>"
}
function initEditor(dlg, options) {
getChannelMappingOptions(options.serverId, options.providerId).then(function(result) {
currentMappingOptions = result;
var channelsElement = dlg.querySelector(".channels");
channelsElement.innerHTML = result.TunerChannels.map(function(channel) {
return getTunerChannelHtml(channel, result.ProviderName)
}).join(""), channelsElement.addEventListener("click", onChannelsElementClick)
})
}
var currentMappingOptions, self = this;
self.show = function() {
var dialogOptions = {
removeOnClose: !0
};
dialogOptions.size = "small";
var dlg = dialogHelper.createDialog(dialogOptions);
dlg.classList.add("formDialog"), dlg.classList.add("ui-body-a"), dlg.classList.add("background-theme-a");
var html = "",
title = globalize.translate("MapChannels");
return html += '<div class="formDialogHeader">', html += '<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><i class="md-icon">&#xE5C4;</i></button>', html += '<h3 class="formDialogHeaderTitle">', html += title, html += "</h3>", html += "</div>", html += getEditorHtml(), dlg.innerHTML = html, initEditor(dlg, options), dlg.querySelector(".btnCancel").addEventListener("click", function() {
dialogHelper.close(dlg)
}), new Promise(function(resolve, reject) {
dlg.addEventListener("close", resolve), dialogHelper.open(dlg)
})
}
}
});

View file

@ -0,0 +1,8 @@
#ulDirectoryPickerList a {
padding-top: .4em;
padding-bottom: .4em
}
.lblDirectoryPickerPath {
white-space: nowrap
}

View file

@ -0,0 +1,145 @@
define(["loading", "dialogHelper", "dom", "listViewStyle", "emby-input", "emby-button", "paper-icon-button-light", "css!./directorybrowser", "formDialogStyle", "emby-linkbutton"], function(loading, dialogHelper, dom) {
"use strict";
function getSystemInfo() {
return systemInfo ? Promise.resolve(systemInfo) : ApiClient.getPublicSystemInfo().then(function(info) {
return systemInfo = info, info
})
}
function onDialogClosed() {
loading.hide()
}
function refreshDirectoryBrowser(page, path, fileOptions, updatePathOnError) {
if (path && "string" != typeof path) throw new Error("invalid path");
loading.show();
var promises = [];
"Network" === path ? promises.push(ApiClient.getNetworkDevices()) : path ? (promises.push(ApiClient.getDirectoryContents(path, fileOptions)), promises.push(ApiClient.getParentPath(path))) : promises.push(ApiClient.getDrives()), Promise.all(promises).then(function(responses) {
var folders = responses[0],
parentPath = responses[1] || "";
page.querySelector("#txtDirectoryPickerPath").value = path || "";
var html = "";
path && (html += getItem("lnkPath lnkDirectory", "", parentPath, "..."));
for (var i = 0, length = folders.length; i < length; i++) {
var folder = folders[i];
html += getItem("File" === folder.Type ? "lnkPath lnkFile" : "lnkPath lnkDirectory", folder.Type, folder.Path, folder.Name)
}
path || (html += getItem("lnkPath lnkDirectory", "", "Network", Globalize.translate("ButtonNetwork"))), page.querySelector(".results").innerHTML = html, loading.hide()
}, function() {
updatePathOnError && (page.querySelector("#txtDirectoryPickerPath").value = ""), page.querySelector(".results").innerHTML = "", loading.hide()
})
}
function getItem(cssClass, type, path, name) {
var html = "";
return html += '<div class="listItem listItem-border ' + cssClass + '" data-type="' + type + '" data-path="' + path + '">', html += '<div class="listItemBody" style="padding-left:0;padding-top:.5em;padding-bottom:.5em;">', html += '<div class="listItemBodyText">', html += name, html += "</div>", html += "</div>", html += '<i class="md-icon" style="font-size:inherit;">arrow_forward</i>', html += "</div>"
}
function getEditorHtml(options, systemInfo) {
var html = "";
if (html += '<div class="formDialogContent scrollY">', html += '<div class="dialogContentInner dialog-content-centered" style="padding-top:2em;">', !options.pathReadOnly) {
var instruction = options.instruction ? options.instruction + "<br/><br/>" : "";
html += '<div class="infoBanner" style="margin-bottom:1.5em;">', html += instruction, html += Globalize.translate("MessageDirectoryPickerInstruction").replace("{0}", "<b>\\\\server</b>").replace("{1}", "<b>\\\\192.168.1.101</b>"), "synology" === (systemInfo.PackageName || "").toLowerCase() ? (html += "<br/>", html += "<br/>", html += '<a is="emby-linkbutton" class="button-link" href="https://web.archive.org/web/20181216120305/https://github.com/MediaBrowser/Wiki/wiki/Synology-:-Setting-Up-Your-Media-Library-Share" target="_blank">' + Globalize.translate("LearnHowToCreateSynologyShares") + "</a>") : "bsd" === systemInfo.OperatingSystem.toLowerCase() ? (html += "<br/>", html += "<br/>", html += Globalize.translate("MessageDirectoryPickerBSDInstruction"), html += "<br/>", html += '<a is="emby-linkbutton" class="button-link" href="http://doc.freenas.org/9.3/freenas_jails.html#add-storage" target="_blank">' + Globalize.translate("ButtonMoreInformation") + "</a>") : "linux" === systemInfo.OperatingSystem.toLowerCase() && (html += "<br/>", html += "<br/>", html += Globalize.translate("MessageDirectoryPickerLinuxInstruction"), html += "<br/>"), html += "</div>"
}
html += '<form style="margin:auto;">', html += '<div class="inputContainer" style="display: flex; align-items: center;">', html += '<div style="flex-grow:1;">';
var labelKey = !0 !== options.includeFiles ? "LabelFolder" : "LabelPath",
readOnlyAttribute = options.pathReadOnly ? " readonly" : "";
return html += '<input is="emby-input" id="txtDirectoryPickerPath" type="text" required="required" ' + readOnlyAttribute + ' label="' + Globalize.translate(labelKey) + '"/>', html += "</div>", readOnlyAttribute || (html += '<button type="button" is="paper-icon-button-light" class="btnRefreshDirectories emby-input-iconbutton" title="' + Globalize.translate("ButtonRefresh") + '"><i class="md-icon">search</i></button>'), html += "</div>", readOnlyAttribute || (html += '<div class="results paperList" style="max-height: 200px; overflow-y: auto;"></div>'), options.enableNetworkSharePath && (html += '<div class="inputContainer" style="margin-top:2em;">', html += '<input is="emby-input" id="txtNetworkPath" type="text" label="' + Globalize.translate("LabelOptionalNetworkPath") + '"/>', html += '<div class="fieldDescription">', html += Globalize.translate("LabelOptionalNetworkPathHelp"), html += "</div>", html += "</div>"), html += '<div class="formDialogFooter">', html += '<button is="emby-button" type="submit" class="raised button-submit block formDialogFooterItem">' + Globalize.translate("ButtonOk") + "</button>", html += "</div>", html += "</form>", html += "</div>", html += "</div>", html += "</div>"
}
function alertText(text) {
alertTextWithOptions({
text: text
})
}
function alertTextWithOptions(options) {
require(["alert"], function(alert) {
alert(options)
})
}
function validatePath(path, validateWriteable, apiClient) {
return apiClient.ajax({
type: "POST",
url: apiClient.getUrl("Environment/ValidatePath"),
data: {
ValidateWriteable: validateWriteable,
Path: path
}
}).catch(function(response) {
if (response) {
if (404 === response.status) return alertText("The path could not be found. Please ensure the path is valid and try again."), Promise.reject();
if (500 === response.status) return alertText(validateWriteable ? "Jellyfin Server requires write access to this folder. Please ensure write access and try again." : "The path could not be found. Please ensure the path is valid and try again."), Promise.reject()
}
return Promise.resolve()
})
}
function initEditor(content, options, fileOptions) {
content.addEventListener("click", function(e) {
var lnkPath = dom.parentWithClass(e.target, "lnkPath");
if (lnkPath) {
var path = lnkPath.getAttribute("data-path");
lnkPath.classList.contains("lnkFile") ? content.querySelector("#txtDirectoryPickerPath").value = path : refreshDirectoryBrowser(content, path, fileOptions, !0)
}
}), content.addEventListener("click", function(e) {
if (dom.parentWithClass(e.target, "btnRefreshDirectories")) {
var path = content.querySelector("#txtDirectoryPickerPath").value;
refreshDirectoryBrowser(content, path, fileOptions)
}
}), content.addEventListener("change", function(e) {
var txtDirectoryPickerPath = dom.parentWithTag(e.target, "INPUT");
txtDirectoryPickerPath && "txtDirectoryPickerPath" === txtDirectoryPickerPath.id && refreshDirectoryBrowser(content, txtDirectoryPickerPath.value, fileOptions)
}), content.querySelector("form").addEventListener("submit", function(e) {
if (options.callback) {
var networkSharePath = this.querySelector("#txtNetworkPath");
networkSharePath = networkSharePath ? networkSharePath.value : null;
var path = this.querySelector("#txtDirectoryPickerPath").value;
validatePath(path, options.validateWriteable, ApiClient).then(function() {
options.callback(path, networkSharePath)
})
}
return e.preventDefault(), e.stopPropagation(), !1
})
}
function getDefaultPath(options) {
return options.path ? Promise.resolve(options.path) : ApiClient.getJSON(ApiClient.getUrl("Environment/DefaultDirectoryBrowser")).then(function(result) {
return result.Path || ""
}, function() {
return ""
})
}
function directoryBrowser() {
var currentDialog, self = this;
self.show = function(options) {
options = options || {};
var fileOptions = {
includeDirectories: !0
};
null != options.includeDirectories && (fileOptions.includeDirectories = options.includeDirectories), null != options.includeFiles && (fileOptions.includeFiles = options.includeFiles), Promise.all([getSystemInfo(), getDefaultPath(options)]).then(function(responses) {
var systemInfo = responses[0],
initialPath = responses[1],
dlg = dialogHelper.createDialog({
size: "medium-tall",
removeOnClose: !0,
scrollY: !1
});
dlg.classList.add("ui-body-a"), dlg.classList.add("background-theme-a"), dlg.classList.add("directoryPicker"), dlg.classList.add("formDialog");
var html = "";
html += '<div class="formDialogHeader">', html += '<button is="paper-icon-button-light" class="btnCloseDialog autoSize" tabindex="-1"><i class="md-icon">&#xE5C4;</i></button>', html += '<h3 class="formDialogHeaderTitle">', html += options.header || Globalize.translate("HeaderSelectPath"), html += "</h3>", html += "</div>", html += getEditorHtml(options, systemInfo), dlg.innerHTML = html, initEditor(dlg, options, fileOptions), dlg.addEventListener("close", onDialogClosed), dialogHelper.open(dlg), dlg.querySelector(".btnCloseDialog").addEventListener("click", function() {
dialogHelper.close(dlg)
}), currentDialog = dlg, dlg.querySelector("#txtDirectoryPickerPath").value = initialPath;
var txtNetworkPath = dlg.querySelector("#txtNetworkPath");
txtNetworkPath && (txtNetworkPath.value = options.networkSharePath || ""), options.pathReadOnly || refreshDirectoryBrowser(dlg, initialPath, fileOptions, !0)
})
}, self.close = function() {
currentDialog && dialogHelper.close(currentDialog)
}
}
var systemInfo;
return directoryBrowser
});

View file

@ -0,0 +1,177 @@
define(["loading", "libraryBrowser", "cardBuilder", "dom", "apphost", "imageLoader", "globalize", "layoutManager", "scrollStyles", "emby-itemscontainer"], function(loading, libraryBrowser, cardBuilder, dom, appHost, imageLoader, globalize, layoutManager) {
"use strict";
function enableScrollX() {
return !layoutManager.desktop
}
function getThumbShape() {
return enableScrollX() ? "overflowBackdrop" : "backdrop"
}
function getPosterShape() {
return enableScrollX() ? "overflowPortrait" : "portrait"
}
function getSquareShape() {
return enableScrollX() ? "overflowSquare" : "square"
}
function getSections() {
return [{
name: "HeaderFavoriteMovies",
types: "Movie",
id: "favoriteMovies",
shape: getPosterShape(),
showTitle: !1,
overlayPlayButton: !0
}, {
name: "HeaderFavoriteShows",
types: "Series",
id: "favoriteShows",
shape: getPosterShape(),
showTitle: !1,
overlayPlayButton: !0
}, {
name: "HeaderFavoriteEpisodes",
types: "Episode",
id: "favoriteEpisode",
shape: getThumbShape(),
preferThumb: !1,
showTitle: !0,
showParentTitle: !0,
overlayPlayButton: !0,
overlayText: !1,
centerText: !0
}, {
name: "HeaderFavoriteVideos",
types: "Video,MusicVideo",
id: "favoriteVideos",
shape: getThumbShape(),
preferThumb: !0,
showTitle: !0,
overlayPlayButton: !0,
overlayText: !1,
centerText: !0
}, {
name: "HeaderFavoriteGames",
types: "Game",
id: "favoriteGames",
shape: getSquareShape(),
preferThumb: !1,
showTitle: !0
}, {
name: "HeaderFavoriteArtists",
types: "MusicArtist",
id: "favoriteArtists",
shape: getSquareShape(),
preferThumb: !1,
showTitle: !0,
overlayText: !1,
showParentTitle: !1,
centerText: !0,
overlayPlayButton: !0,
coverImage: !0
}, {
name: "HeaderFavoriteAlbums",
types: "MusicAlbum",
id: "favoriteAlbums",
shape: getSquareShape(),
preferThumb: !1,
showTitle: !0,
overlayText: !1,
showParentTitle: !0,
centerText: !0,
overlayPlayButton: !0,
coverImage: !0
}, {
name: "HeaderFavoriteSongs",
types: "Audio",
id: "favoriteSongs",
shape: getSquareShape(),
preferThumb: !1,
showTitle: !0,
overlayText: !1,
showParentTitle: !0,
centerText: !0,
overlayMoreButton: !0,
action: "instantmix",
coverImage: !0
}]
}
function loadSection(elem, userId, topParentId, section, isSingleSection) {
var screenWidth = dom.getWindowSize().innerWidth,
options = {
SortBy: "SortName",
SortOrder: "Ascending",
Filters: "IsFavorite",
Recursive: !0,
Fields: "PrimaryImageAspectRatio,BasicSyncInfo",
CollapseBoxSetItems: !1,
ExcludeLocationTypes: "Virtual",
EnableTotalRecordCount: !1
};
topParentId && (options.ParentId = topParentId), isSingleSection || (options.Limit = screenWidth >= 1920 ? 10 : screenWidth >= 1440 ? 8 : 6, enableScrollX() && (options.Limit = 20));
var promise;
return "MusicArtist" === section.types ? promise = ApiClient.getArtists(userId, options) : (options.IncludeItemTypes = section.types, promise = ApiClient.getItems(userId, options)), promise.then(function(result) {
var html = "";
if (result.Items.length) {
if (html += '<div class="sectionTitleContainer sectionTitleContainer-cards padded-left">', !layoutManager.tv && options.Limit && result.Items.length >= options.Limit) {
html += '<a is="emby-linkbutton" href="' + ("list/list.html?serverId=" + ApiClient.serverId() + "&type=" + section.types + "&IsFavorite=true") + '" class="more button-flat button-flat-mini sectionTitleTextButton">', html += '<h2 class="sectionTitle sectionTitle-cards">', html += globalize.translate(section.name), html += "</h2>", html += '<i class="md-icon">&#xE5CC;</i>', html += "</a>"
} else html += '<h2 class="sectionTitle sectionTitle-cards">' + globalize.translate(section.name) + "</h2>";
if (html += "</div>", enableScrollX()) {
var scrollXClass = "scrollX hiddenScrollX";
layoutManager.tv && (scrollXClass += " smoothScrollX"), html += '<div is="emby-itemscontainer" class="itemsContainer ' + scrollXClass + ' padded-left padded-right">'
} else html += '<div is="emby-itemscontainer" class="itemsContainer vertical-wrap padded-left padded-right">';
var supportsImageAnalysis = appHost.supports("imageanalysis"),
cardLayout = (appHost.preferVisualCards || supportsImageAnalysis) && section.autoCardLayout && section.showTitle;
cardLayout = !1, html += cardBuilder.getCardsHtml(result.Items, {
preferThumb: section.preferThumb,
shape: section.shape,
centerText: section.centerText && !cardLayout,
overlayText: !1 !== section.overlayText,
showTitle: section.showTitle,
showParentTitle: section.showParentTitle,
scalable: !0,
coverImage: section.coverImage,
overlayPlayButton: section.overlayPlayButton,
overlayMoreButton: section.overlayMoreButton && !cardLayout,
action: section.action,
allowBottomPadding: !enableScrollX(),
cardLayout: cardLayout,
vibrant: supportsImageAnalysis && cardLayout
}), html += "</div>"
}
elem.innerHTML = html, imageLoader.lazyChildren(elem)
})
}
function loadSections(page, userId, topParentId, types) {
loading.show();
var sections = getSections(),
sectionid = getParameterByName("sectionid");
sectionid && (sections = sections.filter(function(s) {
return s.id === sectionid
})), types && (sections = sections.filter(function(s) {
return -1 !== types.indexOf(s.id)
}));
var i, length, elem = page.querySelector(".favoriteSections");
if (!elem.innerHTML) {
var html = "";
for (i = 0, length = sections.length; i < length; i++) html += '<div class="verticalSection section' + sections[i].id + '"></div>';
elem.innerHTML = html
}
var promises = [];
for (i = 0, length = sections.length; i < length; i++) {
var section = sections[i];
elem = page.querySelector(".section" + section.id), promises.push(loadSection(elem, userId, topParentId, section, 1 === sections.length))
}
Promise.all(promises).then(function() {
loading.hide()
})
}
return {
render: loadSections
}
});

View file

@ -0,0 +1,198 @@
define(["dialogHelper", "globalize", "connectionManager", "events", "browser", "require", "emby-checkbox", "emby-collapse", "css!./style"], function(dialogHelper, globalize, connectionManager, events, browser, require) {
"use strict";
function renderOptions(context, selector, cssClass, items, isCheckedFn) {
var elem = context.querySelector(selector);
items.length ? elem.classList.remove("hide") : elem.classList.add("hide");
var html = "";
html += '<div class="checkboxList">', html += items.map(function(filter) {
var itemHtml = "",
checkedHtml = isCheckedFn(filter) ? " checked" : "";
return itemHtml += "<label>", itemHtml += '<input is="emby-checkbox" type="checkbox"' + checkedHtml + ' data-filter="' + filter + '" class="' + cssClass + '"/>', itemHtml += "<span>" + filter + "</span>", itemHtml += "</label>"
}).join(""), html += "</div>", elem.querySelector(".filterOptions").innerHTML = html
}
function renderFilters(context, result, query) {
result.Tags && (result.Tags.length = Math.min(result.Tags.length, 50)), renderOptions(context, ".genreFilters", "chkGenreFilter", result.Genres, function(i) {
return -1 != ("|" + (query.Genres || "") + "|").indexOf("|" + i + "|")
}), renderOptions(context, ".officialRatingFilters", "chkOfficialRatingFilter", result.OfficialRatings, function(i) {
return -1 != ("|" + (query.OfficialRatings || "") + "|").indexOf("|" + i + "|")
}), renderOptions(context, ".tagFilters", "chkTagFilter", result.Tags, function(i) {
return -1 != ("|" + (query.Tags || "") + "|").indexOf("|" + i + "|")
}), renderOptions(context, ".yearFilters", "chkYearFilter", result.Years, function(i) {
return -1 != ("," + (query.Years || "") + ",").indexOf("," + i + ",")
})
}
function loadDynamicFilters(context, apiClient, userId, itemQuery) {
return apiClient.getJSON(apiClient.getUrl("Items/Filters", {
UserId: userId,
ParentId: itemQuery.ParentId,
IncludeItemTypes: itemQuery.IncludeItemTypes
})).then(function(result) {
renderFilters(context, result, itemQuery)
})
}
function updateFilterControls(context, options) {
var elems, i, length, query = options.query;
if ("livetvchannels" == options.mode) context.querySelector(".chkFavorite").checked = 1 == query.IsFavorite, context.querySelector(".chkLikes").checked = 1 == query.IsLiked, context.querySelector(".chkDislikes").checked = 1 == query.IsDisliked;
else
for (elems = context.querySelectorAll(".chkStandardFilter"), i = 0, length = elems.length; i < length; i++) {
var chkStandardFilter = elems[i],
filters = "," + (query.Filters || ""),
filterName = chkStandardFilter.getAttribute("data-filter");
chkStandardFilter.checked = -1 != filters.indexOf("," + filterName)
}
for (elems = context.querySelectorAll(".chkVideoTypeFilter"), i = 0, length = elems.length; i < length; i++) {
var chkVideoTypeFilter = elems[i],
filters = "," + (query.VideoTypes || ""),
filterName = chkVideoTypeFilter.getAttribute("data-filter");
chkVideoTypeFilter.checked = -1 != filters.indexOf("," + filterName)
}
for (context.querySelector(".chk3DFilter").checked = 1 == query.Is3D, context.querySelector(".chkHDFilter").checked = 1 == query.IsHD, context.querySelector(".chk4KFilter").checked = 1 == query.Is4K, context.querySelector(".chkSDFilter").checked = 1 == query.IsHD, context.querySelector("#chkSubtitle").checked = 1 == query.HasSubtitles, context.querySelector("#chkTrailer").checked = 1 == query.HasTrailer, context.querySelector("#chkThemeSong").checked = 1 == query.HasThemeSong, context.querySelector("#chkThemeVideo").checked = 1 == query.HasThemeVideo, context.querySelector("#chkSpecialFeature").checked = 1 == query.HasSpecialFeature, context.querySelector("#chkSpecialEpisode").checked = 0 == query.ParentIndexNumber, context.querySelector("#chkMissingEpisode").checked = 1 == query.IsMissing, context.querySelector("#chkFutureEpisode").checked = 1 == query.IsUnaired, i = 0, length = elems.length; i < length; i++) {
var chkStatus = elems[i],
filters = "," + (query.SeriesStatus || ""),
filterName = chkStatus.getAttribute("data-filter");
chkStatus.checked = -1 != filters.indexOf("," + filterName)
}
}
function triggerChange(instance) {
events.trigger(instance, "filterchange")
}
function parentWithClass(elem, className) {
for (; !elem.classList || !elem.classList.contains(className);)
if (!(elem = elem.parentNode)) return null;
return elem
}
function setVisibility(context, options) {
"livetvchannels" != options.mode && "albums" != options.mode && "artists" != options.mode && "albumartists" != options.mode && "songs" != options.mode || hideByClass(context, "videoStandard"), enableDynamicFilters(options.mode) && (context.querySelector(".genreFilters").classList.remove("hide"), context.querySelector(".officialRatingFilters").classList.remove("hide"), context.querySelector(".tagFilters").classList.remove("hide"), context.querySelector(".yearFilters").classList.remove("hide")), "movies" != options.mode && "episodes" != options.mode || context.querySelector(".videoTypeFilters").classList.remove("hide"), options.mode, "movies" != options.mode && "series" != options.mode && "games" != options.mode && "episodes" != options.mode || context.querySelector(".features").classList.remove("hide"), "series" == options.mode && context.querySelector(".seriesStatus").classList.remove("hide"), "episodes" == options.mode && showByClass(context, "episodeFilter")
}
function showByClass(context, className) {
for (var elems = context.querySelectorAll("." + className), i = 0, length = elems.length; i < length; i++) elems[i].classList.remove("hide")
}
function hideByClass(context, className) {
for (var elems = context.querySelectorAll("." + className), i = 0, length = elems.length; i < length; i++) elems[i].classList.add("hide")
}
function enableDynamicFilters(mode) {
return "movies" == mode || "games" == mode || "series" == mode || "albums" == mode || "albumartists" == mode || "artists" == mode || "songs" == mode || "episodes" == mode
}
return function(options) {
function onFavoriteChange() {
var query = options.query;
query.StartIndex = 0, query.IsFavorite = !!this.checked || null, triggerChange(self)
}
function onStandardFilterChange() {
var query = options.query,
filterName = this.getAttribute("data-filter"),
filters = query.Filters || "";
filters = ("," + filters).replace("," + filterName, "").substring(1), this.checked && (filters = filters ? filters + "," + filterName : filterName), query.StartIndex = 0, query.Filters = filters, triggerChange(self)
}
function onVideoTypeFilterChange() {
var query = options.query,
filterName = this.getAttribute("data-filter"),
filters = query.VideoTypes || "";
filters = ("," + filters).replace("," + filterName, "").substring(1), this.checked && (filters = filters ? filters + "," + filterName : filterName), query.StartIndex = 0, query.VideoTypes = filters, triggerChange(self)
}
function onStatusChange() {
var query = options.query,
filterName = this.getAttribute("data-filter"),
filters = query.SeriesStatus || "";
filters = ("," + filters).replace("," + filterName, "").substring(1), this.checked && (filters = filters ? filters + "," + filterName : filterName), query.SeriesStatus = filters, query.StartIndex = 0, triggerChange(self)
}
function bindEvents(context) {
var elems, i, length, query = options.query;
if ("livetvchannels" == options.mode) {
for (elems = context.querySelectorAll(".chkFavorite"), i = 0, length = elems.length; i < length; i++) elems[i].addEventListener("change", onFavoriteChange);
context.querySelector(".chkLikes").addEventListener("change", function() {
query.StartIndex = 0, query.IsLiked = !!this.checked || null, triggerChange(self)
}), context.querySelector(".chkDislikes").addEventListener("change", function() {
query.StartIndex = 0, query.IsDisliked = !!this.checked || null, triggerChange(self)
})
} else
for (elems = context.querySelectorAll(".chkStandardFilter"), i = 0, length = elems.length; i < length; i++) elems[i].addEventListener("change", onStandardFilterChange);
for (elems = context.querySelectorAll(".chkVideoTypeFilter"), i = 0, length = elems.length; i < length; i++) elems[i].addEventListener("change", onVideoTypeFilterChange);
for (context.querySelector(".chk3DFilter").addEventListener("change", function() {
query.StartIndex = 0, query.Is3D = !!this.checked || null, triggerChange(self)
}), context.querySelector(".chk4KFilter").addEventListener("change", function() {
query.StartIndex = 0, query.Is4K = !!this.checked || null, triggerChange(self)
}), context.querySelector(".chkHDFilter").addEventListener("change", function() {
query.StartIndex = 0, query.IsHD = !!this.checked || null, triggerChange(self)
}), context.querySelector(".chkSDFilter").addEventListener("change", function() {
query.StartIndex = 0, query.IsHD = !this.checked && null, triggerChange(self)
}), elems = context.querySelectorAll(".chkStatus"), i = 0, length = elems.length; i < length; i++) elems[i].addEventListener("change", onStatusChange);
context.querySelector("#chkTrailer").addEventListener("change", function() {
query.StartIndex = 0, query.HasTrailer = !!this.checked || null, triggerChange(self)
}), context.querySelector("#chkThemeSong").addEventListener("change", function() {
query.StartIndex = 0, query.HasThemeSong = !!this.checked || null, triggerChange(self)
}), context.querySelector("#chkSpecialFeature").addEventListener("change", function() {
query.StartIndex = 0, query.HasSpecialFeature = !!this.checked || null, triggerChange(self)
}), context.querySelector("#chkThemeVideo").addEventListener("change", function() {
query.StartIndex = 0, query.HasThemeVideo = !!this.checked || null, triggerChange(self)
}), context.querySelector("#chkMissingEpisode").addEventListener("change", function() {
query.StartIndex = 0, this.checked ? (query.IsMissing = !0, query.IsUnaired = !1) : (query.IsMissing = !1, query.IsUnaired = context.querySelector("#chkFutureEpisode").checked || null), triggerChange(self)
}), context.querySelector("#chkSpecialEpisode").addEventListener("change", function() {
query.StartIndex = 0, query.ParentIndexNumber = this.checked ? 0 : null, triggerChange(self)
}), context.querySelector("#chkFutureEpisode").addEventListener("change", function() {
query.StartIndex = 0, this.checked ? (query.IsUnaired = !0, query.IsMissing = null) : (query.IsUnaired = null, query.IsMissing = context.querySelector("#chkMissingEpisode").checked), triggerChange(self)
}), context.querySelector("#chkSubtitle").addEventListener("change", function() {
query.StartIndex = 0, query.HasSubtitles = !!this.checked || null, triggerChange(self)
}), context.addEventListener("change", function(e) {
var chkGenreFilter = parentWithClass(e.target, "chkGenreFilter");
if (chkGenreFilter) {
var filterName = chkGenreFilter.getAttribute("data-filter"),
filters = query.Genres || "",
delimiter = "|";
return filters = (delimiter + filters).replace(delimiter + filterName, "").substring(1), chkGenreFilter.checked && (filters = filters ? filters + delimiter + filterName : filterName), query.StartIndex = 0, query.Genres = filters, void triggerChange(self)
}
var chkTagFilter = parentWithClass(e.target, "chkTagFilter");
if (chkTagFilter) {
var filterName = chkTagFilter.getAttribute("data-filter"),
filters = query.Tags || "",
delimiter = "|";
return filters = (delimiter + filters).replace(delimiter + filterName, "").substring(1), chkTagFilter.checked && (filters = filters ? filters + delimiter + filterName : filterName), query.StartIndex = 0, query.Tags = filters, void triggerChange(self)
}
var chkYearFilter = parentWithClass(e.target, "chkYearFilter");
if (chkYearFilter) {
var filterName = chkYearFilter.getAttribute("data-filter"),
filters = query.Years || "",
delimiter = ",";
return filters = (delimiter + filters).replace(delimiter + filterName, "").substring(1), chkYearFilter.checked && (filters = filters ? filters + delimiter + filterName : filterName), query.StartIndex = 0, query.Years = filters, void triggerChange(self)
}
var chkOfficialRatingFilter = parentWithClass(e.target, "chkOfficialRatingFilter");
if (chkOfficialRatingFilter) {
var filterName = chkOfficialRatingFilter.getAttribute("data-filter"),
filters = query.OfficialRatings || "",
delimiter = "|";
return filters = (delimiter + filters).replace(delimiter + filterName, "").substring(1), chkOfficialRatingFilter.checked && (filters = filters ? filters + delimiter + filterName : filterName), query.StartIndex = 0, query.OfficialRatings = filters, void triggerChange(self)
}
})
}
var self = this;
self.show = function() {
return new Promise(function(resolve, reject) {
require(["text!./filterdialog.template.html"], function(template) {
var dlg = dialogHelper.createDialog({
removeOnClose: !0,
modal: !1
});
if (dlg.classList.add("ui-body-a"), dlg.classList.add("background-theme-a"), dlg.classList.add("formDialog"), dlg.classList.add("filterDialog"), dlg.innerHTML = globalize.translateDocument(template), setVisibility(dlg, options), dialogHelper.open(dlg), dlg.addEventListener("close", resolve), updateFilterControls(dlg, options), bindEvents(dlg), enableDynamicFilters(options.mode)) {
dlg.classList.add("dynamicFilterDialog");
var apiClient = connectionManager.getApiClient(options.serverId);
loadDynamicFilters(dlg, apiClient, apiClient.getCurrentUserId(), options.query)
}
})
})
}
}
});

View file

@ -0,0 +1,154 @@
<div style="margin: 0;padding:1.5em 2em;" class="filterDialogContent">
<h2 style="margin-bottom: .25em;">
${HeaderFilters}
</h2>
<div class="checkboxList">
<label>
<input type="checkbox" is="emby-checkbox" class="chkStandardFilter videoStandard" data-filter="IsPlayed" />
<span>${OptionPlayed}</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="chkStandardFilter videoStandard" data-filter="IsUnPlayed" />
<span>${OptionUnplayed}</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="chkStandardFilter videoStandard" data-filter="IsResumable" />
<span>${OptionResumable}</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="chkStandardFilter chkFavorite" data-filter="IsFavorite" />
<span>${OptionFavorite}</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="chkStandardFilter chkLikes" data-filter="Likes" />
<span>${OptionLikes}</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="chkStandardFilter chkDislikes" data-filter="Dislikes" />
<span>${OptionDislikes}</span>
</label>
<label class="episodeFilter hide">
<input type="checkbox" is="emby-checkbox" id="chkSpecialEpisode" />
<span>${OptionSpecialEpisode}</span>
</label>
<label class="episodeFilter hide">
<input type="checkbox" is="emby-checkbox" id="chkMissingEpisode" />
<span>${OptionMissingEpisode}</span>
</label>
<label class="episodeFilter hide">
<input type="checkbox" is="emby-checkbox" id="chkFutureEpisode" />
<span>${OptionUnairedEpisode}</span>
</label>
</div>
<div class="seriesStatus hide">
<br />
<h2 style="margin-bottom: .25em;">
${HeaderStatus}
</h2>
<div class="checkboxList">
<label>
<input type="checkbox" is="emby-checkbox" class="chkStatus" data-filter="Continuing" />
<span>${OptionContinuing}</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="chkStatus" data-filter="Ended" />
<span>${OptionEnded}</span>
</label>
</div>
</div>
<br />
<div is="emby-collapse" title="${HeaderFeatures}" class="features hide">
<div class="collapseContent">
<div class="checkboxList">
<label>
<input type="checkbox" is="emby-checkbox" class="chkFeatureFilter" id="chkSubtitle" />
<span>${OptionHasSubtitles}</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="chkFeatureFilter" id="chkTrailer" />
<span>${OptionHasTrailer}</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="chkFeatureFilter" id="chkSpecialFeature" />
<span>${OptionHasSpecialFeatures}</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="chkFeatureFilter" id="chkThemeSong" />
<span>${OptionHasThemeSong}</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="chkFeatureFilter" id="chkThemeVideo" />
<span>${OptionHasThemeVideo}</span>
</label>
</div>
</div>
</div>
<!--<div class="players hide">
<h2 style="margin-bottom: .25em;">
${HeaderNumberOfPlayers}
</h2>
<paper-radio-group class="playersRadioGroup" selected="all">
<paper-radio-button class="radioPlayers" name="all">${OptionAnyNumberOfPlayers}</paper-radio-button>
<paper-radio-button class="radioPlayers" name="2">${Option2Player}</paper-radio-button>
<paper-radio-button class="radioPlayers" name="3">${Option3Player}</paper-radio-button>
<paper-radio-button class="radioPlayers" name="4">${Option4Player}</paper-radio-button>
</paper-radio-group>
</div>-->
<div is="emby-collapse" title="${HeaderGenres}" class="genreFilters hide">
<div class="collapseContent filterOptions">
</div>
</div>
<div is="emby-collapse" title="${HeaderParentalRatings}" class="officialRatingFilters hide">
<div class="collapseContent filterOptions">
</div>
</div>
<div is="emby-collapse" title="${HeaderTags}" class="tagFilters hide">
<div class="collapseContent filterOptions">
</div>
</div>
<div is="emby-collapse" title="${HeaderVideoTypes}" class="videoTypeFilters hide">
<div class="collapseContent">
<div class="checkboxList">
<label>
<input type="checkbox" is="emby-checkbox" class="chkVideoTypeFilter chkBluray" data-filter="Bluray" />
<span>${OptionBluray}</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="chkVideoTypeFilter chkDvd" data-filter="Dvd" />
<span>${OptionDvd}</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="chkHDFilter IsHD" />
<span>${OptionIsHD}</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="chk4KFilter Is4K" />
<span>4K</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="chkSDFilter IsHD" />
<span>${OptionIsSD}</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="chk3DFilter chk3D" />
<span>${Option3D}</span>
</label>
</div>
</div>
</div>
<div is="emby-collapse" title="${HeaderYears}" class="yearFilters hide">
<div class="collapseContent filterOptions">
</div>
</div>
</div>

View file

@ -0,0 +1,36 @@
.dynamicFilterDialog {
position: fixed !important;
top: 5% !important;
bottom: 5% !important;
margin-top: 0 !important;
margin-bottom: 0 !important;
margin-right: 0 !important;
-webkit-border-radius: 0 !important;
border-radius: 0 !important;
max-height: none !important;
max-width: none !important
}
@media all and (min-height:600px) {
.dynamicFilterDialog {
top: 10% !important;
bottom: 10% !important
}
}
@media all and (max-width:400px) {
.dynamicFilterDialog {
width: auto;
left: 10vw !important;
right: 10vw !important;
margin-left: 0 !important
}
}
@media all and (min-width:400px) {
.dynamicFilterDialog {
width: 300px;
margin-left: -150px !important;
left: 50% !important
}
}

View file

@ -0,0 +1,32 @@
define(["dom", "appRouter", "connectionManager"], function(dom, appRouter, connectionManager) {
"use strict";
function onGroupedCardClick(e, card) {
var itemId = card.getAttribute("data-id"),
serverId = card.getAttribute("data-serverid"),
apiClient = connectionManager.getApiClient(serverId),
userId = apiClient.getCurrentUserId(),
playedIndicator = card.querySelector(".playedIndicator"),
playedIndicatorHtml = playedIndicator ? playedIndicator.innerHTML : null,
options = {
Limit: parseInt(playedIndicatorHtml || "10"),
Fields: "PrimaryImageAspectRatio,DateCreated",
ParentId: itemId,
GroupItems: !1
},
actionableParent = dom.parentWithTag(e.target, ["A", "BUTTON", "INPUT"]);
if (!actionableParent || actionableParent.classList.contains("cardContent")) return apiClient.getJSON(apiClient.getUrl("Users/" + userId + "/Items/Latest", options)).then(function(items) {
if (1 === items.length) return void appRouter.showItem(items[0]);
var url = "itemdetails.html?id=" + itemId + "&serverId=" + serverId;
Dashboard.navigate(url)
}), e.stopPropagation(), e.preventDefault(), !1
}
function onItemsContainerClick(e) {
var groupedCard = dom.parentWithClass(e.target, "groupedCard");
groupedCard && onGroupedCardClick(e, groupedCard)
}
return {
onItemsContainerClick: onItemsContainerClick
}
});

View file

@ -0,0 +1,58 @@
define(["dialogHelper", "loading", "require", "emby-input", "emby-button", "emby-checkbox", "paper-icon-button-light", "formDialogStyle", "emby-linkbutton"], function(dialogHelper, loading, require) {
"use strict";
function renderLibrarySharingList(context, result) {
var folderHtml = "";
folderHtml += result.Items.map(function(i) {
var currentHtml = "";
return currentHtml += '<label><input is="emby-checkbox" class="chkShareFolder" type="checkbox" data-folderid="' + i.Id + '" checked="checked"/><span>' + i.Name + "</span></label>"
}).join(""), context.querySelector(".librarySharingList").innerHTML = folderHtml
}
function inviteUser(dlg) {
loading.show();
var shareExcludes = Array.prototype.filter.call(dlg.querySelectorAll(".chkShareFolder"), function(i) {
return i.checked
}).map(function(i) {
return i.getAttribute("data-folderid")
});
require(["connectHelper"], function(connectHelper) {
connectHelper.inviteGuest({
apiClient: ApiClient,
guestOptions: {
ConnectUsername: dlg.querySelector("#txtConnectUsername").value,
EnabledLibraries: shareExcludes.join(","),
SendingUserId: Dashboard.getCurrentUserId(),
EnableLiveTv: !1
}
}).then(function() {
loading.hide(), dlg.submitted = !0, dialogHelper.close(dlg)
})
})
}
return {
show: function() {
return new Promise(function(resolve, reject) {
require(["text!./guestinviter.template.html"], function(template) {
var dlg = dialogHelper.createDialog({
removeOnClose: !0,
size: "small"
});
dlg.classList.add("ui-body-a"), dlg.classList.add("background-theme-a"), dlg.classList.add("formDialog");
var html = "";
html += Globalize.translateDocument(template), dlg.innerHTML = html, dialogHelper.open(dlg), dlg.addEventListener("close", function() {
dlg.submitted ? resolve() : reject()
}), dlg.querySelector(".btnCancel").addEventListener("click", function(e) {
dialogHelper.close(dlg)
}), dlg.querySelector("form").addEventListener("submit", function(e) {
return inviteUser(dlg), e.preventDefault(), !1
}), ApiClient.getJSON(ApiClient.getUrl("Library/MediaFolders", {
IsHidden: !1
})).then(function(result) {
renderLibrarySharingList(dlg, result)
})
})
})
}
}
});

View file

@ -0,0 +1,36 @@
<div class="formDialogHeader">
<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1">
<i class="md-icon">&#xE5C4;</i>
</button>
<h3 class="formDialogHeaderTitle">
${HeaderInviteUser}
</h3>
</div>
<div class="formDialogContent scrollY">
<div class="dialogContentInner dialog-content-centered">
<form class="addUserForm" style="max-width: none;margin:1em 0 0;">
<div class="inputContainer">
<input is="emby-input" type="text" id="txtConnectUsername" value="" label="${LabelConnectGuestUserName}" required />
<div class="fieldDescription">
<div>${LabelConnectGuestUserNameHelp}</div>
<div style="margin-top: .25em;"><a is="emby-linkbutton" class="button-link" href="https://github.com/jellyfin/jellyfin" target="_blank">${ButtonLearnMoreAboutEmbyConnect}</a></div>
</div>
</div>
<h3 class="checkboxListLabel">${HeaderShareMediaFolders}</h3>
<div class="librarySharingList checkboxList paperList checkboxList-paperList">
</div>
<p class="fieldDescription" style="margin-top:.5em;">${MessageGuestSharingPermissionsHelp}</p>
<br />
<div class="formDialogFooter">
<button is="emby-button" type="submit" class="raised button-submit block formDialogFooterItem">
<span>${ButtonSendInvitation}</span>
</button>
</div>
</form>
</div>
</div>

View file

@ -0,0 +1,28 @@
define(["datetime"], function(datetime) {
"use strict";
function humane_date(date_str) {
var format, time_formats = [
[90, "a minute"],
[3600, "minutes", 60],
[5400, "an hour"],
[86400, "hours", 3600],
[129600, "a day"],
[604800, "days", 86400],
[907200, "a week"],
[2628e3, "weeks", 604800],
[3942e3, "a month"],
[31536e3, "months", 2628e3],
[47304e3, "a year"],
[31536e5, "years", 31536e3]
],
dt = new Date,
date = datetime.parseISO8601Date(date_str, !0),
seconds = (dt - date) / 1e3,
i = 0;
for (seconds < 0 && (seconds = Math.abs(seconds)); format = time_formats[i++];)
if (seconds < format[0]) return 2 == format.length ? format[1] + " ago" : Math.round(seconds / format[2]) + " " + format[1] + " ago";
return seconds > 47304e5 ? Math.round(seconds / 47304e5) + " centuries ago" : date_str
}
return window.humane_date = humane_date, humane_date
});

57
src/components/iap.js Normal file
View file

@ -0,0 +1,57 @@
define(["globalize", "shell", "browser", "apphost"], function(globalize, shell, browser, appHost) {
"use strict";
function getProductInfo(feature) {
return null
}
function getPremiumInfoUrl() {
return "https://github.com/jellyfin/jellyfin"
}
function beginPurchase(feature, email) {
appHost.supports("externalpremium") ? shell.openUrl(getPremiumInfoUrl()) : require(["alert"], function(alert) {
alert("Please visit " + getPremiumInfoUrl())
})
}
function restorePurchase(id) {
return Promise.reject()
}
function getSubscriptionOptions() {
var options = [];
return options.push({
id: "embypremiere",
title: globalize.translate("sharedcomponents#HeaderBecomeProjectSupporter"),
requiresEmail: !1
}), Promise.resolve(options)
}
function isUnlockedByDefault(feature, options) {
return "playback" === feature || "livetv" === feature ? Promise.resolve() : Promise.reject()
}
function getAdminFeatureName(feature) {
return feature
}
function getRestoreButtonText() {
return globalize.translate("sharedcomponents#HeaderAlreadyPaid")
}
function getPeriodicMessageIntervalMs(feature) {
return 0
}
return {
getProductInfo: getProductInfo,
beginPurchase: beginPurchase,
restorePurchase: restorePurchase,
getSubscriptionOptions: getSubscriptionOptions,
isUnlockedByDefault: isUnlockedByDefault,
getAdminFeatureName: getAdminFeatureName,
getRestoreButtonText: getRestoreButtonText,
getPeriodicMessageIntervalMs: getPeriodicMessageIntervalMs,
getPremiumInfoUrl: getPremiumInfoUrl
}
});

View file

@ -0,0 +1,77 @@
define(["globalize", "dom", "dialogHelper", "emby-checkbox", "emby-select", "emby-input"], function(globalize, dom, dialogHelper) {
"use strict";
function getDefaultImageConfig(itemType, type) {
return {
Type: type,
MinWidth: 0,
Limit: "Primary" === type ? 1 : 0
}
}
function findImageOptions(imageOptions, type) {
return imageOptions.filter(function(i) {
return i.Type == type
})[0]
}
function getImageConfig(options, availableOptions, imageType, itemType) {
return findImageOptions(options.ImageOptions || [], imageType) || findImageOptions(availableOptions.DefaultImageOptions || [], imageType) || getDefaultImageConfig(itemType, imageType)
}
function setVisibilityOfBackdrops(elem, visible) {
visible ? (elem.classList.remove("hide"), elem.querySelector("input").setAttribute("required", "required")) : (elem.classList.add("hide"), elem.querySelector("input").setAttribute("required", ""), elem.querySelector("input").removeAttribute("required"))
}
function loadValues(context, itemType, options, availableOptions) {
var supportedImageTypes = availableOptions.SupportedImageTypes || [];
setVisibilityOfBackdrops(context.querySelector(".backdropFields"), -1 != supportedImageTypes.indexOf("Backdrop")), setVisibilityOfBackdrops(context.querySelector(".screenshotFields"), -1 != supportedImageTypes.indexOf("Screenshot")), Array.prototype.forEach.call(context.querySelectorAll(".imageType"), function(i) {
var imageType = i.getAttribute("data-imagetype"),
container = dom.parentWithTag(i, "LABEL"); - 1 == supportedImageTypes.indexOf(imageType) ? container.classList.add("hide") : container.classList.remove("hide"), getImageConfig(options, availableOptions, imageType, itemType).Limit ? i.checked = !0 : i.checked = !1
});
var backdropConfig = getImageConfig(options, availableOptions, "Backdrop", itemType);
context.querySelector("#txtMaxBackdrops").value = backdropConfig.Limit, context.querySelector("#txtMinBackdropDownloadWidth").value = backdropConfig.MinWidth;
var screenshotConfig = getImageConfig(options, availableOptions, "Screenshot", itemType);
context.querySelector("#txtMaxScreenshots").value = screenshotConfig.Limit, context.querySelector("#txtMinScreenshotDownloadWidth").value = screenshotConfig.MinWidth
}
function saveValues(context, options) {
options.ImageOptions = Array.prototype.map.call(context.querySelectorAll(".imageType:not(.hide)"), function(c) {
return {
Type: c.getAttribute("data-imagetype"),
Limit: c.checked ? 1 : 0,
MinWidth: 0
}
}), options.ImageOptions.push({
Type: "Backdrop",
Limit: context.querySelector("#txtMaxBackdrops").value,
MinWidth: context.querySelector("#txtMinBackdropDownloadWidth").value
}), options.ImageOptions.push({
Type: "Screenshot",
Limit: context.querySelector("#txtMaxScreenshots").value,
MinWidth: context.querySelector("#txtMinScreenshotDownloadWidth").value
})
}
function editor() {
this.show = function(itemType, options, availableOptions) {
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest;
xhr.open("GET", "components/imageoptionseditor/imageoptionseditor.template.html", !0), xhr.onload = function(e) {
var template = this.response,
dlg = dialogHelper.createDialog({
size: "medium-tall",
removeOnClose: !0,
scrollY: !1
});
dlg.classList.add("formDialog"), dlg.innerHTML = globalize.translateDocument(template), dlg.addEventListener("close", function() {
saveValues(dlg, options)
}), loadValues(dlg, itemType, options, availableOptions), dialogHelper.open(dlg).then(resolve, resolve), dlg.querySelector(".btnCancel").addEventListener("click", function() {
dialogHelper.close(dlg)
})
}, xhr.send()
})
}
}
return editor
});

View file

@ -0,0 +1,78 @@
<div class="formDialogHeader">
<button type="button" is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><i class="md-icon">&#xE5C4;</i></button>
<h3 class="formDialogHeaderTitle">
${HeaderImageOptions}
</h3>
</div>
<div class="formDialogContent scrollY">
<div class="dialogContentInner dialog-content-centered">
<form style="margin:1.5em auto 0;">
<div class="verticalSection">
<h3 class="checkboxListLabel">${HeaderFetchImages}</h3>
<div class="imageSelections checkboxList">
<label>
<input type="checkbox" is="emby-checkbox" class="imageType" data-imagetype="Primary" />
<span>${OptionDownloadPrimaryImage}</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="imageType" data-imagetype="Art" />
<span>${OptionDownloadArtImage}</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="imageType" data-imagetype="BoxRear" />
<span>${OptionDownloadBackImage}</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="imageType" data-imagetype="Banner" />
<span>${OptionDownloadBannerImage}</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="imageType" data-imagetype="Box" />
<span>${OptionDownloadBoxImage}</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="imageType" data-imagetype="Disc" />
<span>${OptionDownloadDiscImage}</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="imageType" data-imagetype="Logo" />
<span>${OptionDownloadLogoImage}</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="imageType" data-imagetype="Menu" />
<span>${OptionDownloadMenuImage}</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="imageType" data-imagetype="Thumb" />
<span>${OptionDownloadThumbImage}</span>
</label>
</div>
</div>
<div class="backdropFields">
<div class="inputContainer">
<input is="emby-input" type="number" id="txtMaxBackdrops" pattern="[0-9]*" required="required" min="0" label="${LabelMaxBackdropsPerItem}" />
</div>
<div class="inputContainer">
<input is="emby-input" type="number" id="txtMinBackdropDownloadWidth" pattern="[0-9]*" required="required" min="0" label="${LabelMinBackdropDownloadWidth}" />
</div>
</div>
<div class="screenshotFields">
<div class="inputContainer">
<input is="emby-input" type="number" id="txtMaxScreenshots" pattern="[0-9]*" required="required" min="0" label="${LabelMaxScreenshotsPerItem}" />
</div>
<div class="inputContainer">
<input is="emby-input" type="number" id="txtMinScreenshotDownloadWidth" pattern="[0-9]*" required="required" min="0" label="${LabelMinScreenshotDownloadWidth}" />
</div>
</div>
</form>
</div>
</div>

View file

@ -0,0 +1,344 @@
define(["globalize", "dom", "emby-checkbox", "emby-select", "emby-input"], function(globalize, dom) {
"use strict";
function populateLanguages(parent) {
return ApiClient.getCultures().then(function(languages) {
populateLanguagesIntoSelect(parent.querySelector("#selectLanguage"), languages), populateLanguagesIntoList(parent.querySelector(".subtitleDownloadLanguages"), languages)
})
}
function populateLanguagesIntoSelect(select, languages) {
var html = "";
html += "<option value=''></option>";
for (var i = 0, length = languages.length; i < length; i++) {
var culture = languages[i];
html += "<option value='" + culture.TwoLetterISOLanguageName + "'>" + culture.DisplayName + "</option>"
}
select.innerHTML = html
}
function populateLanguagesIntoList(element, languages) {
for (var html = "", i = 0, length = languages.length; i < length; i++) {
var culture = languages[i];
html += '<label><input type="checkbox" is="emby-checkbox" class="chkSubtitleLanguage" data-lang="' + culture.ThreeLetterISOLanguageName.toLowerCase() + '" /><span>' + culture.DisplayName + "</span></label>"
}
element.innerHTML = html
}
function populateCountries(select) {
return ApiClient.getCountries().then(function(allCountries) {
var html = "";
html += "<option value=''></option>";
for (var i = 0, length = allCountries.length; i < length; i++) {
var culture = allCountries[i];
html += "<option value='" + culture.TwoLetterISORegionName + "'>" + culture.DisplayName + "</option>"
}
select.innerHTML = html
})
}
function populateRefreshInterval(select) {
var html = "";
html += "<option value='0'>" + globalize.translate("Never") + "</option>", html += [30, 60, 90].map(function(val) {
return "<option value='" + val + "'>" + globalize.translate("EveryNDays", val) + "</option>"
}).join(""), select.innerHTML = html
}
function renderMetadataReaders(page, plugins) {
var html = "",
elem = page.querySelector(".metadataReaders");
if (plugins.length < 1) return elem.innerHTML = "", elem.classList.add("hide"), !1;
html += '<h3 class="checkboxListLabel">' + globalize.translate("LabelMetadataReaders") + "</h3>", html += '<div class="checkboxList paperList checkboxList-paperList">';
for (var i = 0, length = plugins.length; i < length; i++) {
var plugin = plugins[i];
html += '<div class="listItem localReaderOption sortableOption" data-pluginname="' + plugin.Name + '">', html += '<i class="listItemIcon md-icon">live_tv</i>', html += '<div class="listItemBody">', html += '<h3 class="listItemBodyText">', html += plugin.Name, html += "</h3>", html += "</div>", i > 0 ? html += '<button type="button" is="paper-icon-button-light" title="' + globalize.translate("ButtonUp") + '" class="btnSortableMoveUp btnSortable" data-pluginindex="' + i + '"><i class="md-icon">keyboard_arrow_up</i></button>' : plugins.length > 1 && (html += '<button type="button" is="paper-icon-button-light" title="' + globalize.translate("ButtonDown") + '" class="btnSortableMoveDown btnSortable" data-pluginindex="' + i + '"><i class="md-icon">keyboard_arrow_down</i></button>'), html += "</div>"
}
return html += "</div>", html += '<div class="fieldDescription">' + globalize.translate("LabelMetadataReadersHelp") + "</div>", plugins.length < 2 ? elem.classList.add("hide") : elem.classList.remove("hide"), elem.innerHTML = html, !0
}
function renderMetadataSavers(page, metadataSavers) {
var html = "",
elem = page.querySelector(".metadataSavers");
if (!metadataSavers.length) return elem.innerHTML = "", elem.classList.add("hide"), !1;
html += '<h3 class="checkboxListLabel">' + globalize.translate("LabelMetadataSavers") + "</h3>", html += '<div class="checkboxList paperList checkboxList-paperList">';
for (var i = 0, length = metadataSavers.length; i < length; i++) {
var plugin = metadataSavers[i];
html += '<label><input type="checkbox" data-defaultenabled="' + plugin.DefaultEnabled + '" is="emby-checkbox" class="chkMetadataSaver" data-pluginname="' + plugin.Name + '" ' + !1 + "><span>" + plugin.Name + "</span></label>"
}
return html += "</div>", html += '<div class="fieldDescription" style="margin-top:.25em;">' + globalize.translate("LabelMetadataSaversHelp") + "</div>", elem.innerHTML = html, elem.classList.remove("hide"), !0
}
function getMetadataFetchersForTypeHtml(availableTypeOptions, libraryOptionsForType) {
var html = "",
plugins = availableTypeOptions.MetadataFetchers;
if (plugins = getOrderedPlugins(plugins, libraryOptionsForType.MetadataFetcherOrder || []), !plugins.length) return html;
html += '<div class="metadataFetcher" data-type="' + availableTypeOptions.Type + '">', html += '<h3 class="checkboxListLabel">' + globalize.translate("LabelTypeMetadataDownloaders", availableTypeOptions.Type) + "</h3>", html += '<div class="checkboxList paperList checkboxList-paperList">';
for (var i = 0, length = plugins.length; i < length; i++) {
var plugin = plugins[i];
html += '<div class="listItem metadataFetcherItem sortableOption" data-pluginname="' + plugin.Name + '">';
var isChecked = libraryOptionsForType.MetadataFetchers ? -1 !== libraryOptionsForType.MetadataFetchers.indexOf(plugin.Name) : plugin.DefaultEnabled,
checkedHtml = isChecked ? ' checked="checked"' : "";
html += '<label class="listItemCheckboxContainer"><input type="checkbox" is="emby-checkbox" class="chkMetadataFetcher" data-pluginname="' + plugin.Name + '" ' + checkedHtml + "><span></span></label>", html += '<div class="listItemBody">', html += '<h3 class="listItemBodyText">', html += plugin.Name, html += "</h3>", html += "</div>", i > 0 ? html += '<button type="button" is="paper-icon-button-light" title="' + globalize.translate("ButtonUp") + '" class="btnSortableMoveUp btnSortable" data-pluginindex="' + i + '"><i class="md-icon">keyboard_arrow_up</i></button>' : plugins.length > 1 && (html += '<button type="button" is="paper-icon-button-light" title="' + globalize.translate("ButtonDown") + '" class="btnSortableMoveDown btnSortable" data-pluginindex="' + i + '"><i class="md-icon">keyboard_arrow_down</i></button>'), html += "</div>"
}
return html += "</div>", html += '<div class="fieldDescription">' + globalize.translate("LabelMetadataDownloadersHelp") + "</div>", html += "</div>"
}
function getTypeOptions(allOptions, type) {
for (var allTypeOptions = allOptions.TypeOptions || [], i = 0, length = allTypeOptions.length; i < length; i++) {
var typeOptions = allTypeOptions[i];
if (typeOptions.Type === type) return typeOptions
}
return null
}
function renderMetadataFetchers(page, availableOptions, libraryOptions) {
for (var html = "", elem = page.querySelector(".metadataFetchers"), i = 0, length = availableOptions.TypeOptions.length; i < length; i++) {
var availableTypeOptions = availableOptions.TypeOptions[i];
html += getMetadataFetchersForTypeHtml(availableTypeOptions, getTypeOptions(libraryOptions, availableTypeOptions.Type) || {})
}
return elem.innerHTML = html, html ? (elem.classList.remove("hide"), page.querySelector(".fldAutoRefreshInterval").classList.remove("hide"), page.querySelector(".fldMetadataLanguage").classList.remove("hide"), page.querySelector(".fldMetadataCountry").classList.remove("hide")) : (elem.classList.add("hide"), page.querySelector(".fldAutoRefreshInterval").classList.add("hide"), page.querySelector(".fldMetadataLanguage").classList.add("hide"), page.querySelector(".fldMetadataCountry").classList.add("hide")), !0
}
function renderSubtitleFetchers(page, availableOptions, libraryOptions) {
try {
var html = "",
elem = page.querySelector(".subtitleFetchers"),
html = "",
plugins = availableOptions.SubtitleFetchers;
if (plugins = getOrderedPlugins(plugins, libraryOptions.SubtitleFetcherOrder || []), !plugins.length) return html;
html += '<h3 class="checkboxListLabel">' + globalize.translate("LabelSubtitleDownloaders") + "</h3>", html += '<div class="checkboxList paperList checkboxList-paperList">';
for (var i = 0, length = plugins.length; i < length; i++) {
var plugin = plugins[i];
html += '<div class="listItem subtitleFetcherItem sortableOption" data-pluginname="' + plugin.Name + '">';
var isChecked = libraryOptions.DisabledSubtitleFetchers ? -1 === libraryOptions.DisabledSubtitleFetchers.indexOf(plugin.Name) : plugin.DefaultEnabled,
checkedHtml = isChecked ? ' checked="checked"' : "";
html += '<label class="listItemCheckboxContainer"><input type="checkbox" is="emby-checkbox" class="chkSubtitleFetcher" data-pluginname="' + plugin.Name + '" ' + checkedHtml + "><span></span></label>", html += '<div class="listItemBody">', html += '<h3 class="listItemBodyText">', html += plugin.Name, html += "</h3>", "Open Subtitles" === plugin.Name && (html += '<div class="listItemBodyText secondary">', html += globalize.translate("OpenSubtitleInstructions"), html += "</div>"), html += "</div>", i > 0 ? html += '<button type="button" is="paper-icon-button-light" title="' + globalize.translate("ButtonUp") + '" class="btnSortableMoveUp btnSortable" data-pluginindex="' + i + '"><i class="md-icon">keyboard_arrow_up</i></button>' : plugins.length > 1 && (html += '<button type="button" is="paper-icon-button-light" title="' + globalize.translate("ButtonDown") + '" class="btnSortableMoveDown btnSortable" data-pluginindex="' + i + '"><i class="md-icon">keyboard_arrow_down</i></button>'), html += "</div>"
}
html += "</div>", html += '<div class="fieldDescription">' + globalize.translate("SubtitleDownloadersHelp") + "</div>", elem.innerHTML = html
} catch (err) {
alert(err)
}
}
function getImageFetchersForTypeHtml(availableTypeOptions, libraryOptionsForType) {
var html = "",
plugins = availableTypeOptions.ImageFetchers;
if (plugins = getOrderedPlugins(plugins, libraryOptionsForType.ImageFetcherOrder || []), !plugins.length) return html;
html += '<div class="imageFetcher" data-type="' + availableTypeOptions.Type + '">', html += '<div class="flex align-items-center" style="margin:1.5em 0 .5em;">', html += '<h3 class="checkboxListLabel" style="margin:0;">' + globalize.translate("HeaderTypeImageFetchers", availableTypeOptions.Type) + "</h3>";
var supportedImageTypes = availableTypeOptions.SupportedImageTypes || [];
(supportedImageTypes.length > 1 || 1 === supportedImageTypes.length && "Primary" !== supportedImageTypes[0]) && (html += '<button is="emby-button" class="raised btnImageOptionsForType" type="button" style="margin-left:1.5em;font-size:90%;"><span>' + globalize.translate("HeaderFetcherSettings") + "</span></button>"), html += "</div>", html += '<div class="checkboxList paperList checkboxList-paperList">';
for (var i = 0, length = plugins.length; i < length; i++) {
var plugin = plugins[i];
html += '<div class="listItem imageFetcherItem sortableOption" data-pluginname="' + plugin.Name + '">';
var isChecked = libraryOptionsForType.ImageFetchers ? -1 !== libraryOptionsForType.ImageFetchers.indexOf(plugin.Name) : plugin.DefaultEnabled,
checkedHtml = isChecked ? ' checked="checked"' : "";
html += '<label class="listItemCheckboxContainer"><input type="checkbox" is="emby-checkbox" class="chkImageFetcher" data-pluginname="' + plugin.Name + '" ' + checkedHtml + "><span></span></label>", html += '<div class="listItemBody">', html += '<h3 class="listItemBodyText">', html += plugin.Name, html += "</h3>", html += "</div>", i > 0 ? html += '<button type="button" is="paper-icon-button-light" title="' + globalize.translate("ButtonUp") + '" class="btnSortableMoveUp btnSortable" data-pluginindex="' + i + '"><i class="md-icon">keyboard_arrow_up</i></button>' : plugins.length > 1 && (html += '<button type="button" is="paper-icon-button-light" title="' + globalize.translate("ButtonDown") + '" class="btnSortableMoveDown btnSortable" data-pluginindex="' + i + '"><i class="md-icon">keyboard_arrow_down</i></button>'), html += "</div>"
}
return html += "</div>", html += '<div class="fieldDescription">' + globalize.translate("LabelImageFetchersHelp") + "</div>", html += "</div>"
}
function renderImageFetchers(page, availableOptions, libraryOptions) {
for (var html = "", elem = page.querySelector(".imageFetchers"), i = 0, length = availableOptions.TypeOptions.length; i < length; i++) {
var availableTypeOptions = availableOptions.TypeOptions[i];
html += getImageFetchersForTypeHtml(availableTypeOptions, getTypeOptions(libraryOptions, availableTypeOptions.Type) || {})
}
return elem.innerHTML = html, html ? (elem.classList.remove("hide"), page.querySelector(".chkDownloadImagesInAdvanceContainer").classList.remove("hide"), page.querySelector(".chkSaveLocalContainer").classList.remove("hide")) : (elem.classList.add("hide"), page.querySelector(".chkDownloadImagesInAdvanceContainer").classList.add("hide"), page.querySelector(".chkSaveLocalContainer").classList.add("hide")), !0
}
function populateMetadataSettings(parent, contentType, isNewLibrary) {
var isNewLibrary = parent.classList.contains("newlibrary");
return ApiClient.getJSON(ApiClient.getUrl("Libraries/AvailableOptions", {
LibraryContentType: contentType,
IsNewLibrary: isNewLibrary
})).then(function(availableOptions) {
currentAvailableOptions = availableOptions, parent.availableOptions = availableOptions, renderMetadataSavers(parent, availableOptions.MetadataSavers), renderMetadataReaders(parent, availableOptions.MetadataReaders), renderMetadataFetchers(parent, availableOptions, {}), renderSubtitleFetchers(parent, availableOptions, {}), renderImageFetchers(parent, availableOptions, {}), availableOptions.SubtitleFetchers.length ? parent.querySelector(".subtitleDownloadSettings").classList.remove("hide") : parent.querySelector(".subtitleDownloadSettings").classList.add("hide")
}).catch(function() {
return Promise.resolve()
})
}
function adjustSortableListElement(elem) {
var btnSortable = elem.querySelector(".btnSortable");
elem.previousSibling ? (btnSortable.classList.add("btnSortableMoveUp"), btnSortable.classList.remove("btnSortableMoveDown"), btnSortable.querySelector("i").innerHTML = "keyboard_arrow_up") : (btnSortable.classList.remove("btnSortableMoveUp"), btnSortable.classList.add("btnSortableMoveDown"), btnSortable.querySelector("i").innerHTML = "keyboard_arrow_down")
}
function showImageOptionsForType(type) {
require(["imageoptionseditor"], function(ImageOptionsEditor) {
var typeOptions = getTypeOptions(currentLibraryOptions, type);
typeOptions || (typeOptions = {
Type: type
}, currentLibraryOptions.TypeOptions.push(typeOptions));
var availableOptions = getTypeOptions(currentAvailableOptions || {}, type);
(new ImageOptionsEditor).show(type, typeOptions, availableOptions)
})
}
function onImageFetchersContainerClick(e) {
var btnImageOptionsForType = dom.parentWithClass(e.target, "btnImageOptionsForType");
if (btnImageOptionsForType) {
return void showImageOptionsForType(dom.parentWithClass(btnImageOptionsForType, "imageFetcher").getAttribute("data-type"))
}
onSortableContainerClick.call(this, e)
}
function onSortableContainerClick(e) {
var btnSortable = dom.parentWithClass(e.target, "btnSortable");
if (btnSortable) {
var li = dom.parentWithClass(btnSortable, "sortableOption"),
list = dom.parentWithClass(li, "paperList");
if (btnSortable.classList.contains("btnSortableMoveDown")) {
var next = li.nextSibling;
next && (li.parentNode.removeChild(li), next.parentNode.insertBefore(li, next.nextSibling))
} else {
var prev = li.previousSibling;
prev && (li.parentNode.removeChild(li), prev.parentNode.insertBefore(li, prev))
}
Array.prototype.forEach.call(list.querySelectorAll(".sortableOption"), adjustSortableListElement)
}
}
function bindEvents(parent) {
parent.querySelector(".metadataReaders").addEventListener("click", onSortableContainerClick), parent.querySelector(".subtitleFetchers").addEventListener("click", onSortableContainerClick), parent.querySelector(".metadataFetchers").addEventListener("click", onSortableContainerClick), parent.querySelector(".imageFetchers").addEventListener("click", onImageFetchersContainerClick)
}
function embed(parent, contentType, libraryOptions) {
currentLibraryOptions = {
TypeOptions: []
}, currentAvailableOptions = null;
var isNewLibrary = null == libraryOptions;
return isNewLibrary && parent.classList.add("newlibrary"), new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest;
xhr.open("GET", "components/libraryoptionseditor/libraryoptionseditor.template.html", !0), xhr.onload = function(e) {
var template = this.response;
parent.innerHTML = globalize.translateDocument(template), populateRefreshInterval(parent.querySelector("#selectAutoRefreshInterval"));
var promises = [populateLanguages(parent), populateCountries(parent.querySelector("#selectCountry"))];
Promise.all(promises).then(function() {
return setContentType(parent, contentType).then(function() {
libraryOptions && setLibraryOptions(parent, libraryOptions), bindEvents(parent), resolve()
})
})
}, xhr.send()
})
}
function setAdvancedVisible(parent, visible) {
for (var elems = parent.querySelectorAll(".advanced"), i = 0, length = elems.length; i < length; i++) visible ? elems[i].classList.remove("advancedHide") : elems[i].classList.add("advancedHide")
}
function setContentType(parent, contentType) {
return "homevideos" === contentType || "photos" === contentType ? parent.querySelector(".chkEnablePhotosContainer").classList.remove("hide") : parent.querySelector(".chkEnablePhotosContainer").classList.add("hide"), "tvshows" !== contentType && "movies" !== contentType && "homevideos" !== contentType && "musicvideos" !== contentType && "mixed" !== contentType && contentType ? parent.querySelector(".chapterSettingsSection").classList.add("hide") : parent.querySelector(".chapterSettingsSection").classList.remove("hide"), "tvshows" === contentType ? (parent.querySelector(".chkImportMissingEpisodesContainer").classList.remove("hide"), parent.querySelector(".chkAutomaticallyGroupSeriesContainer").classList.remove("hide"), parent.querySelector(".fldSeasonZeroDisplayName").classList.remove("hide"), parent.querySelector("#txtSeasonZeroName").setAttribute("required", "required")) : (parent.querySelector(".chkImportMissingEpisodesContainer").classList.add("hide"), parent.querySelector(".chkAutomaticallyGroupSeriesContainer").classList.add("hide"), parent.querySelector(".fldSeasonZeroDisplayName").classList.add("hide"), parent.querySelector("#txtSeasonZeroName").removeAttribute("required")), "games" === contentType || "books" === contentType || "boxsets" === contentType || "playlists" === contentType || "music" === contentType ? parent.querySelector(".chkEnableEmbeddedTitlesContainer").classList.add("hide") : parent.querySelector(".chkEnableEmbeddedTitlesContainer").classList.remove("hide"), populateMetadataSettings(parent, contentType)
}
function setSubtitleFetchersIntoOptions(parent, options) {
options.DisabledSubtitleFetchers = Array.prototype.map.call(Array.prototype.filter.call(parent.querySelectorAll(".chkSubtitleFetcher"), function(elem) {
return !elem.checked
}), function(elem) {
return elem.getAttribute("data-pluginname")
}), options.SubtitleFetcherOrder = Array.prototype.map.call(parent.querySelectorAll(".subtitleFetcherItem"), function(elem) {
return elem.getAttribute("data-pluginname")
})
}
function setMetadataFetchersIntoOptions(parent, options) {
for (var sections = parent.querySelectorAll(".metadataFetcher"), i = 0, length = sections.length; i < length; i++) {
var section = sections[i],
type = section.getAttribute("data-type"),
typeOptions = getTypeOptions(options, type);
typeOptions || (typeOptions = {
Type: type
}, options.TypeOptions.push(typeOptions)), typeOptions.MetadataFetchers = Array.prototype.map.call(Array.prototype.filter.call(section.querySelectorAll(".chkMetadataFetcher"), function(elem) {
return elem.checked
}), function(elem) {
return elem.getAttribute("data-pluginname")
}), typeOptions.MetadataFetcherOrder = Array.prototype.map.call(section.querySelectorAll(".metadataFetcherItem"), function(elem) {
return elem.getAttribute("data-pluginname")
})
}
}
function setImageFetchersIntoOptions(parent, options) {
for (var sections = parent.querySelectorAll(".imageFetcher"), i = 0, length = sections.length; i < length; i++) {
var section = sections[i],
type = section.getAttribute("data-type"),
typeOptions = getTypeOptions(options, type);
typeOptions || (typeOptions = {
Type: type
}, options.TypeOptions.push(typeOptions)), typeOptions.ImageFetchers = Array.prototype.map.call(Array.prototype.filter.call(section.querySelectorAll(".chkImageFetcher"), function(elem) {
return elem.checked
}), function(elem) {
return elem.getAttribute("data-pluginname")
}), typeOptions.ImageFetcherOrder = Array.prototype.map.call(section.querySelectorAll(".imageFetcherItem"), function(elem) {
return elem.getAttribute("data-pluginname")
})
}
}
function setImageOptionsIntoOptions(parent, options) {
for (var originalTypeOptions = (currentLibraryOptions || {}).TypeOptions || [], i = 0, length = originalTypeOptions.length; i < length; i++) {
var originalTypeOption = originalTypeOptions[i],
typeOptions = getTypeOptions(options, originalTypeOption.Type);
typeOptions || (typeOptions = {
Type: type
}, options.TypeOptions.push(typeOptions)), originalTypeOption.ImageOptions && (typeOptions.ImageOptions = originalTypeOption.ImageOptions)
}
}
function getLibraryOptions(parent) {
var options = {
EnableArchiveMediaFiles: !1,
EnablePhotos: parent.querySelector(".chkEnablePhotos").checked,
EnableRealtimeMonitor: parent.querySelector(".chkEnableRealtimeMonitor").checked,
ExtractChapterImagesDuringLibraryScan: parent.querySelector(".chkExtractChaptersDuringLibraryScan").checked,
EnableChapterImageExtraction: parent.querySelector(".chkExtractChapterImages").checked,
DownloadImagesInAdvance: parent.querySelector("#chkDownloadImagesInAdvance").checked,
EnableInternetProviders: !0,
ImportMissingEpisodes: parent.querySelector("#chkImportMissingEpisodes").checked,
SaveLocalMetadata: parent.querySelector("#chkSaveLocal").checked,
EnableAutomaticSeriesGrouping: parent.querySelector(".chkAutomaticallyGroupSeries").checked,
PreferredMetadataLanguage: parent.querySelector("#selectLanguage").value,
MetadataCountryCode: parent.querySelector("#selectCountry").value,
SeasonZeroDisplayName: parent.querySelector("#txtSeasonZeroName").value,
AutomaticRefreshIntervalDays: parseInt(parent.querySelector("#selectAutoRefreshInterval").value),
EnableEmbeddedTitles: parent.querySelector("#chkEnableEmbeddedTitles").checked,
SkipSubtitlesIfEmbeddedSubtitlesPresent: parent.querySelector("#chkSkipIfGraphicalSubsPresent").checked,
SkipSubtitlesIfAudioTrackMatches: parent.querySelector("#chkSkipIfAudioTrackPresent").checked,
SaveSubtitlesWithMedia: parent.querySelector("#chkSaveSubtitlesLocally").checked,
RequirePerfectSubtitleMatch: parent.querySelector("#chkRequirePerfectMatch").checked,
MetadataSavers: Array.prototype.map.call(Array.prototype.filter.call(parent.querySelectorAll(".chkMetadataSaver"), function(elem) {
return elem.checked
}), function(elem) {
return elem.getAttribute("data-pluginname")
}),
TypeOptions: []
};
return options.LocalMetadataReaderOrder = Array.prototype.map.call(parent.querySelectorAll(".localReaderOption"), function(elem) {
return elem.getAttribute("data-pluginname")
}), options.SubtitleDownloadLanguages = Array.prototype.map.call(Array.prototype.filter.call(parent.querySelectorAll(".chkSubtitleLanguage"), function(elem) {
return elem.checked
}), function(elem) {
return elem.getAttribute("data-lang")
}), setSubtitleFetchersIntoOptions(parent, options), setMetadataFetchersIntoOptions(parent, options), setImageFetchersIntoOptions(parent, options), setImageOptionsIntoOptions(parent, options), options
}
function getOrderedPlugins(plugins, configuredOrder) {
return plugins = plugins.slice(0), plugins.sort(function(a, b) {
return a = configuredOrder.indexOf(a.Name), b = configuredOrder.indexOf(b.Name), a < b ? -1 : a > b ? 1 : 0
}), plugins
}
function setLibraryOptions(parent, options) {
currentLibraryOptions = options, currentAvailableOptions = parent.availableOptions, parent.querySelector("#selectLanguage").value = options.PreferredMetadataLanguage || "", parent.querySelector("#selectCountry").value = options.MetadataCountryCode || "", parent.querySelector("#selectAutoRefreshInterval").value = options.AutomaticRefreshIntervalDays || "0", parent.querySelector("#txtSeasonZeroName").value = options.SeasonZeroDisplayName || "Specials", parent.querySelector(".chkEnablePhotos").checked = options.EnablePhotos, parent.querySelector(".chkEnableRealtimeMonitor").checked = options.EnableRealtimeMonitor, parent.querySelector(".chkExtractChaptersDuringLibraryScan").checked = options.ExtractChapterImagesDuringLibraryScan, parent.querySelector(".chkExtractChapterImages").checked = options.EnableChapterImageExtraction, parent.querySelector("#chkDownloadImagesInAdvance").checked = options.DownloadImagesInAdvance, parent.querySelector("#chkSaveLocal").checked = options.SaveLocalMetadata, parent.querySelector("#chkImportMissingEpisodes").checked = options.ImportMissingEpisodes, parent.querySelector(".chkAutomaticallyGroupSeries").checked = options.EnableAutomaticSeriesGrouping, parent.querySelector("#chkEnableEmbeddedTitles").checked = options.EnableEmbeddedTitles, parent.querySelector("#chkSkipIfGraphicalSubsPresent").checked = options.SkipSubtitlesIfEmbeddedSubtitlesPresent, parent.querySelector("#chkSaveSubtitlesLocally").checked = options.SaveSubtitlesWithMedia, parent.querySelector("#chkSkipIfAudioTrackPresent").checked = options.SkipSubtitlesIfAudioTrackMatches, parent.querySelector("#chkRequirePerfectMatch").checked = options.RequirePerfectSubtitleMatch, Array.prototype.forEach.call(parent.querySelectorAll(".chkMetadataSaver"), function(elem) {
elem.checked = options.MetadataSavers ? -1 !== options.MetadataSavers.indexOf(elem.getAttribute("data-pluginname")) : "true" === elem.getAttribute("data-defaultenabled")
}), Array.prototype.forEach.call(parent.querySelectorAll(".chkSubtitleLanguage"), function(elem) {
elem.checked = !!options.SubtitleDownloadLanguages && -1 !== options.SubtitleDownloadLanguages.indexOf(elem.getAttribute("data-lang"))
}), renderMetadataReaders(parent, getOrderedPlugins(parent.availableOptions.MetadataReaders, options.LocalMetadataReaderOrder || [])), renderMetadataFetchers(parent, parent.availableOptions, options), renderImageFetchers(parent, parent.availableOptions, options), renderSubtitleFetchers(parent, parent.availableOptions, options)
}
var currentLibraryOptions, currentAvailableOptions;
return {
embed: embed,
setContentType: setContentType,
getLibraryOptions: getLibraryOptions,
setLibraryOptions: setLibraryOptions,
setAdvancedVisible: setAdvancedVisible
}
});

View file

@ -0,0 +1,145 @@
<style>
.advancedHide {
display: none !important;
}
</style>
<h2>${HeaderLibrarySettings}</h2>
<div class="selectContainer fldMetadataLanguage hide">
<select is="emby-select" id="selectLanguage" label="${LabelMetadataDownloadLanguage}"></select>
</div>
<div class="selectContainer fldMetadataCountry hide">
<select is="emby-select" id="selectCountry" label="${LabelCountry}"></select>
</div>
<div class="checkboxContainer checkboxContainer-withDescription chkEnablePhotosContainer">
<label>
<input type="checkbox" is="emby-checkbox" class="chkEnablePhotos" checked />
<span>${EnablePhotos}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${EnablePhotosHelp}</div>
</div>
<div class="inputContainer fldSeasonZeroDisplayName hide advanced">
<input is="emby-input" type="text" id="txtSeasonZeroName" label="${LabelSpecialSeasonsDisplayName}" value="Specials" required />
</div>
<div class="checkboxContainer checkboxContainer-withDescription chkEnableEmbeddedTitlesContainer hide advanced">
<label>
<input is="emby-checkbox" type="checkbox" id="chkEnableEmbeddedTitles" />
<span>${PreferEmbeddedTitlesOverFileNames}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${PreferEmbeddedTitlesOverFileNamesHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription advanced">
<label>
<input type="checkbox" is="emby-checkbox" class="chkEnableRealtimeMonitor" checked />
<span>${LabelEnableRealtimeMonitor}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${LabelEnableRealtimeMonitorHelp}</div>
</div>
<div class="metadataReaders hide advanced" style="margin-bottom: 2em;">
</div>
<div class="metadataFetchers hide" style="margin-bottom: 2em;">
</div>
<div class="selectContainer fldAutoRefreshInterval hide advanced" style="margin: 2em 0;">
<select is="emby-select" id="selectAutoRefreshInterval" label="${LabelAutomaticallyRefreshInternetMetadataEvery}"></select>
<div class="fieldDescription">${MessageEnablingOptionLongerScans}</div>
</div>
<div class="metadataSavers hide" style="margin-bottom: 2em;">
</div>
<div class="imageFetchers hide advanced" style="margin-bottom: 2em;">
</div>
<div class="checkboxContainer checkboxContainer-withDescription chkSaveLocalContainer hide">
<label>
<input is="emby-checkbox" type="checkbox" id="chkSaveLocal" />
<span>${LabelSaveLocalMetadata}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${LabelSaveLocalMetadataHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription hide chkDownloadImagesInAdvanceContainer advanced">
<label>
<input is="emby-checkbox" type="checkbox" id="chkDownloadImagesInAdvance" />
<span>${OptionDownloadImagesInAdvance}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${OptionDownloadImagesInAdvanceHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription chkAutomaticallyGroupSeriesContainer hide advanced">
<label>
<input type="checkbox" is="emby-checkbox" class="chkAutomaticallyGroupSeries" checked />
<span>${OptionAutomaticallyGroupSeries}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${OptionAutomaticallyGroupSeriesHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription hide chkImportMissingEpisodesContainer advanced">
<label>
<input is="emby-checkbox" type="checkbox" id="chkImportMissingEpisodes" />
<span>${LabelDisplayMissingEpisodesWithinSeasons}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${ImportMissingEpisodesHelp}</div>
</div>
<div class="chapterSettingsSection hide">
<h2>${HeaderChapterImages}</h2>
<div class="checkboxContainer checkboxContainer-withDescription fldExtractChapterImages">
<label>
<input type="checkbox" is="emby-checkbox" class="chkExtractChapterImages" />
<span>${OptionExtractChapterImage}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${ExtractChapterImagesHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription fldExtractChaptersDuringLibraryScan advanced">
<label>
<input type="checkbox" is="emby-checkbox" class="chkExtractChaptersDuringLibraryScan" />
<span>${LabelExtractChaptersDuringLibraryScan}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${LabelExtractChaptersDuringLibraryScanHelp}</div>
</div>
</div>
<div class="subtitleDownloadSettings hide">
<h2>${HeaderSubtitleDownloads}</h2>
<div>
<h3 class="checkboxListLabel">${LabelDownloadLanguages}</h3>
<div class="subtitleDownloadLanguages paperList checkboxList" style="max-height: 10.5em; overflow-y: auto; padding: .5em 1em;">
</div>
</div>
<br />
<div class="subtitleFetchers advanced" style="margin-bottom: 2em;">
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="chkRequirePerfectMatch" checked />
<span>${OptionRequirePerfectSubtitleMatch}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${OptionRequirePerfectSubtitleMatchHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription advanced">
<label>
<input is="emby-checkbox" type="checkbox" id="chkSkipIfAudioTrackPresent" />
<span>${LabelSkipIfAudioTrackPresent}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${LabelSkipIfAudioTrackPresentHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription advanced">
<label>
<input is="emby-checkbox" type="checkbox" id="chkSkipIfGraphicalSubsPresent" />
<span>${LabelSkipIfGraphicalSubsPresent}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${LabelSkipIfGraphicalSubsPresentHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription advanced">
<label>
<input type="checkbox" is="emby-checkbox" id="chkSaveSubtitlesLocally" checked />
<span>${SaveSubtitlesIntoMediaFolders}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${SaveSubtitlesIntoMediaFoldersHelp}</div>
</div>
</div>

View file

@ -0,0 +1,135 @@
define(["loading", "dialogHelper", "dom", "jQuery", "components/libraryoptionseditor/libraryoptionseditor", "emby-toggle", "emby-input", "emby-select", "paper-icon-button-light", "listViewStyle", "formDialogStyle", "emby-linkbutton", "flexStyles"], function(loading, dialogHelper, dom, $, libraryoptionseditor) {
"use strict";
function onSubmit(e) {
if (e.preventDefault(), e.stopPropagation(), 0 == pathInfos.length) return require(["alert"], function(alert) {
alert({
text: Globalize.translate("PleaseAddAtLeastOneFolder"),
type: "error"
})
}), !1;
var form = this,
dlg = $(form).parents(".dialog")[0],
name = $("#txtValue", form).val(),
type = $("#selectCollectionType", form).val();
"mixed" == type && (type = null);
var libraryOptions = libraryoptionseditor.getLibraryOptions(dlg.querySelector(".libraryOptions"));
return libraryOptions.PathInfos = pathInfos, ApiClient.addVirtualFolder(name, type, currentOptions.refresh, libraryOptions).then(function() {
hasChanges = !0, dialogHelper.close(dlg)
}, function() {
require(["toast"], function(toast) {
toast(Globalize.translate("ErrorAddingMediaPathToVirtualFolder"))
})
}), !1
}
function getCollectionTypeOptionsHtml(collectionTypeOptions) {
return collectionTypeOptions.filter(function(i) {
return !1 !== i.isSelectable
}).map(function(i) {
return '<option value="' + i.value + '">' + i.name + "</option>"
}).join("")
}
function initEditor(page, collectionTypeOptions) {
$("#selectCollectionType", page).html(getCollectionTypeOptionsHtml(collectionTypeOptions)).val("").on("change", function() {
var value = this.value,
dlg = $(this).parents(".dialog")[0];
if (libraryoptionseditor.setContentType(dlg.querySelector(".libraryOptions"), "mixed" == value ? "" : value), value ? dlg.querySelector(".libraryOptions").classList.remove("hide") : dlg.querySelector(".libraryOptions").classList.add("hide"), "mixed" != value) {
var index = this.selectedIndex;
if (-1 != index) {
var name = this.options[index].innerHTML.replace("*", "").replace("&amp;", "&");
$("#txtValue", dlg).val(name);
var folderOption = collectionTypeOptions.filter(function(i) {
return i.value == value
})[0];
$(".collectionTypeFieldDescription", dlg).html(folderOption.message || "")
}
}
}), page.querySelector(".btnAddFolder").addEventListener("click", onAddButtonClick), page.querySelector("form").addEventListener("submit", onSubmit), page.querySelector(".folderList").addEventListener("click", onRemoveClick), page.querySelector(".chkAdvanced").addEventListener("change", onToggleAdvancedChange)
}
function onToggleAdvancedChange() {
var dlg = dom.parentWithClass(this, "dlg-librarycreator");
libraryoptionseditor.setAdvancedVisible(dlg.querySelector(".libraryOptions"), this.checked)
}
function onAddButtonClick() {
var page = dom.parentWithClass(this, "dlg-librarycreator");
require(["directorybrowser"], function(directoryBrowser) {
var picker = new directoryBrowser;
picker.show({
enableNetworkSharePath: !0,
callback: function(path, networkSharePath) {
path && addMediaLocation(page, path, networkSharePath), picker.close()
}
})
})
}
function getFolderHtml(pathInfo, index) {
var html = "";
return html += '<div class="listItem listItem-border lnkPath" style="padding-left:.5em;">', html += '<div class="' + (pathInfo.NetworkPath ? "listItemBody two-line" : "listItemBody") + '">', html += '<div class="listItemBodyText">' + pathInfo.Path + "</div>", pathInfo.NetworkPath && (html += '<div class="listItemBodyText secondary">' + pathInfo.NetworkPath + "</div>"), html += "</div>", html += '<button type="button" is="paper-icon-button-light"" class="listItemButton btnRemovePath" data-index="' + index + '"><i class="md-icon">remove_circle</i></button>', html += "</div>"
}
function renderPaths(page) {
var foldersHtml = pathInfos.map(getFolderHtml).join(""),
folderList = page.querySelector(".folderList");
folderList.innerHTML = foldersHtml, foldersHtml ? folderList.classList.remove("hide") : folderList.classList.add("hide")
}
function addMediaLocation(page, path, networkSharePath) {
var pathLower = path.toLowerCase();
if (0 == pathInfos.filter(function(p) {
return p.Path.toLowerCase() == pathLower
}).length) {
var pathInfo = {
Path: path
};
networkSharePath && (pathInfo.NetworkPath = networkSharePath), pathInfos.push(pathInfo), renderPaths(page)
}
}
function onRemoveClick(e) {
var button = dom.parentWithClass(e.target, "btnRemovePath"),
index = parseInt(button.getAttribute("data-index")),
location = pathInfos[index].Path,
locationLower = location.toLowerCase();
pathInfos = pathInfos.filter(function(p) {
return p.Path.toLowerCase() != locationLower
}), renderPaths(dom.parentWithClass(button, "dlg-librarycreator"))
}
function onDialogClosed() {
loading.hide(), currentResolve(hasChanges)
}
function initLibraryOptions(dlg) {
libraryoptionseditor.embed(dlg.querySelector(".libraryOptions")).then(function() {
$("#selectCollectionType", dlg).trigger("change"), onToggleAdvancedChange.call(dlg.querySelector(".chkAdvanced"))
})
}
function editor() {
this.show = function(options) {
return new Promise(function(resolve, reject) {
currentOptions = options, currentResolve = resolve, hasChanges = !1;
var xhr = new XMLHttpRequest;
xhr.open("GET", "components/medialibrarycreator/medialibrarycreator.template.html", !0), xhr.onload = function(e) {
var template = this.response,
dlg = dialogHelper.createDialog({
size: "medium-tall",
modal: !1,
removeOnClose: !0,
scrollY: !1
});
dlg.classList.add("ui-body-a"), dlg.classList.add("background-theme-a"), dlg.classList.add("dlg-librarycreator"), dlg.classList.add("formDialog"), dlg.innerHTML = Globalize.translateDocument(template), initEditor(dlg, options.collectionTypeOptions), dlg.addEventListener("close", onDialogClosed), dialogHelper.open(dlg), dlg.querySelector(".btnCancel").addEventListener("click", function() {
dialogHelper.close(dlg)
}), pathInfos = [], renderPaths(dlg), initLibraryOptions(dlg)
}, xhr.send()
})
}
}
var currentResolve, hasChanges, currentOptions, pathInfos = [];
return editor
});

View file

@ -0,0 +1,45 @@
<div class="formDialogHeader">
<button type="button" is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><i class="md-icon">&#xE5C4;</i></button>
<h3 class="formDialogHeaderTitle">
${ButtonAddMediaLibrary}
</h3>
</div>
<div class="formDialogContent scrollY" style="padding-top:2em;">
<div class="dialogContentInner dialog-content-centered">
<form style="margin:auto;">
<div class="inputContainer" style="text-align:right;">
<label style="width:auto;">
<input is="emby-toggle" type="checkbox" class="chkAdvanced noautofocus" />
<span>${ShowAdvancedSettings}</span>
</label>
</div>
<div id="fldCollectionType" class="selectContainer">
<select is="emby-select" id="selectCollectionType" data-mini="true" required="required" label="${LabelContentType}"></select>
<div class="collectionTypeFieldDescription fieldDescription">
</div>
</div>
<div class="inputContainer">
<input is="emby-input" type="text" id="txtValue" required="required" label="${LabelDisplayName}" />
</div>
<div style="display: flex; align-items: center;">
<h1 style="margin: .5em 0;">${HeadersFolders}</h1>
<button is="emby-button" type="button" class="fab btnAddFolder submit" style="margin-left:1em;" title="${ButtonAdd}">
<i class="md-icon">add</i>
</button>
</div>
<div class="paperList folderList hide" style="margin-bottom:2em;"></div>
<div class="libraryOptions"></div>
<br />
<div class="formDialogFooter">
<button is="emby-button" type="submit" class="raised button-submit block formDialogFooterItem">
<span>${ButtonOk}</span>
</button>
</div>
</form>
</div>
</div>

View file

@ -0,0 +1,149 @@
define(["loading", "dialogHelper", "dom", "components/libraryoptionseditor/libraryoptionseditor", "emby-button", "listViewStyle", "paper-icon-button-light", "formDialogStyle", "emby-toggle", "flexStyles"], function(loading, dialogHelper, dom, libraryoptionseditor) {
"use strict";
function addMediaLocation(page, path, networkSharePath) {
var virtualFolder = currentOptions.library,
refreshAfterChange = currentOptions.refresh;
ApiClient.addMediaPath(virtualFolder.Name, path, networkSharePath, refreshAfterChange).then(function() {
hasChanges = !0, refreshLibraryFromServer(page)
}, function() {
require(["toast"], function(toast) {
toast(Globalize.translate("ErrorAddingMediaPathToVirtualFolder"))
})
})
}
function updateMediaLocation(page, path, networkSharePath) {
var virtualFolder = currentOptions.library;
ApiClient.updateMediaPath(virtualFolder.Name, {
Path: path,
NetworkPath: networkSharePath
}).then(function() {
hasChanges = !0, refreshLibraryFromServer(page)
}, function() {
require(["toast"], function(toast) {
toast(Globalize.translate("ErrorAddingMediaPathToVirtualFolder"))
})
})
}
function onRemoveClick(btnRemovePath, location) {
var button = btnRemovePath,
virtualFolder = currentOptions.library;
require(["confirm"], function(confirm) {
confirm({
title: Globalize.translate("HeaderRemoveMediaLocation"),
text: Globalize.translate("MessageConfirmRemoveMediaLocation"),
confirmText: Globalize.translate("ButtonDelete"),
primary: "cancel"
}).then(function() {
var refreshAfterChange = currentOptions.refresh;
ApiClient.removeMediaPath(virtualFolder.Name, location, refreshAfterChange).then(function() {
hasChanges = !0, refreshLibraryFromServer(dom.parentWithClass(button, "dlg-libraryeditor"))
}, function() {
require(["toast"], function(toast) {
toast(Globalize.translate("DefaultErrorMessage"))
})
})
})
})
}
function onListItemClick(e) {
var listItem = dom.parentWithClass(e.target, "listItem");
if (listItem) {
var index = parseInt(listItem.getAttribute("data-index")),
pathInfos = (currentOptions.library.LibraryOptions || {}).PathInfos || [],
pathInfo = null == index ? {} : pathInfos[index] || {},
originalPath = pathInfo.Path || (null == index ? null : currentOptions.library.Locations[index]),
btnRemovePath = dom.parentWithClass(e.target, "btnRemovePath");
if (btnRemovePath) return void onRemoveClick(btnRemovePath, originalPath);
showDirectoryBrowser(dom.parentWithClass(listItem, "dlg-libraryeditor"), originalPath, pathInfo.NetworkPath)
}
}
function getFolderHtml(pathInfo, index) {
var html = "";
return html += '<div class="listItem listItem-border lnkPath" data-index="' + index + '" style="padding-left:.5em;">', html += '<div class="' + (pathInfo.NetworkPath ? "listItemBody two-line" : "listItemBody") + '">', html += '<h3 class="listItemBodyText">', html += pathInfo.Path, html += "</h3>", pathInfo.NetworkPath && (html += '<div class="listItemBodyText secondary">' + pathInfo.NetworkPath + "</div>"), html += "</div>", html += '<button type="button" is="paper-icon-button-light" class="listItemButton btnRemovePath" data-index="' + index + '"><i class="md-icon">remove_circle</i></button>', html += "</div>"
}
function refreshLibraryFromServer(page) {
ApiClient.getVirtualFolders().then(function(result) {
var library = result.filter(function(f) {
return f.Name === currentOptions.library.Name
})[0];
library && (currentOptions.library = library, renderLibrary(page, currentOptions))
})
}
function renderLibrary(page, options) {
var pathInfos = (options.library.LibraryOptions || {}).PathInfos || [];
pathInfos.length || (pathInfos = options.library.Locations.map(function(p) {
return {
Path: p
}
})), "boxsets" === options.library.CollectionType ? page.querySelector(".folders").classList.add("hide") : page.querySelector(".folders").classList.remove("hide"), page.querySelector(".folderList").innerHTML = pathInfos.map(getFolderHtml).join("")
}
function onAddButtonClick() {
showDirectoryBrowser(dom.parentWithClass(this, "dlg-libraryeditor"))
}
function showDirectoryBrowser(context, originalPath, networkPath) {
require(["directorybrowser"], function(directoryBrowser) {
var picker = new directoryBrowser;
picker.show({
enableNetworkSharePath: !0,
pathReadOnly: null != originalPath,
path: originalPath,
networkSharePath: networkPath,
callback: function(path, networkSharePath) {
path && (originalPath ? updateMediaLocation(context, originalPath, networkSharePath) : addMediaLocation(context, path, networkSharePath)), picker.close()
}
})
})
}
function onToggleAdvancedChange() {
var dlg = dom.parentWithClass(this, "dlg-libraryeditor");
libraryoptionseditor.setAdvancedVisible(dlg.querySelector(".libraryOptions"), this.checked)
}
function initEditor(dlg, options) {
renderLibrary(dlg, options), dlg.querySelector(".btnAddFolder").addEventListener("click", onAddButtonClick), dlg.querySelector(".folderList").addEventListener("click", onListItemClick), dlg.querySelector(".chkAdvanced").addEventListener("change", onToggleAdvancedChange), libraryoptionseditor.embed(dlg.querySelector(".libraryOptions"), options.library.CollectionType, options.library.LibraryOptions).then(function() {
onToggleAdvancedChange.call(dlg.querySelector(".chkAdvanced"))
})
}
function onDialogClosing() {
var dlg = this,
libraryOptions = libraryoptionseditor.getLibraryOptions(dlg.querySelector(".libraryOptions"));
libraryOptions = Object.assign(currentOptions.library.LibraryOptions || {}, libraryOptions), ApiClient.updateVirtualFolderOptions(currentOptions.library.ItemId, libraryOptions)
}
function onDialogClosed() {
loading.hide(), hasChanges = !0, currentDeferred.resolveWith(null, [hasChanges])
}
function editor() {
this.show = function(options) {
var deferred = jQuery.Deferred();
currentOptions = options, currentDeferred = deferred, hasChanges = !1;
var xhr = new XMLHttpRequest;
return xhr.open("GET", "components/medialibraryeditor/medialibraryeditor.template.html", !0), xhr.onload = function(e) {
var template = this.response,
dlg = dialogHelper.createDialog({
size: "medium-tall",
modal: !1,
removeOnClose: !0,
scrollY: !1
});
dlg.classList.add("dlg-libraryeditor"), dlg.classList.add("ui-body-a"), dlg.classList.add("background-theme-a"), dlg.classList.add("formDialog"), dlg.innerHTML = Globalize.translateDocument(template), dlg.querySelector(".formDialogHeaderTitle").innerHTML = options.library.Name, initEditor(dlg, options), dlg.addEventListener("closing", onDialogClosing), dlg.addEventListener("close", onDialogClosed), dialogHelper.open(dlg), dlg.querySelector(".btnCancel").addEventListener("click", function() {
dialogHelper.close(dlg)
}), refreshLibraryFromServer(dlg)
}, xhr.send(), deferred.promise()
}
}
var currentDeferred, hasChanges, currentOptions;
return editor
});

View file

@ -0,0 +1,31 @@
<div class="formDialogHeader">
<button type="button" is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><i class="md-icon">&#xE5C4;</i></button>
<h3 class="formDialogHeaderTitle"></h3>
</div>
<div class="formDialogContent scrollY" style="padding-top:2em;">
<div class="dialogContentInner dialog-content-centered">
<div class="infoBanner" style="margin-bottom:1.8em;">
${ChangingMetadataImageSettingsNewContent}
</div>
<div class="inputContainer" style="text-align:right;">
<label style="width:auto;">
<input is="emby-toggle" type="checkbox" class="chkAdvanced noautofocus" />
<span>${ShowAdvancedSettings}</span>
</label>
</div>
<div class="folders hide">
<div style="display: flex; align-items: center;">
<h1 style="margin: .5em 0;">${HeadersFolders}</h1>
<button is="emby-button" type="button" class="fab btnAddFolder submit" style="margin-left:1em;" title="${ButtonAdd}">
<i class="md-icon">add</i>
</button>
</div>
<div class="paperList folderList" style="margin-bottom:2em;"></div>
</div>
<div class="libraryOptions"></div>
</div>
</div>

View file

@ -0,0 +1,53 @@
.tmla-mask,
.touch-menu-la {
position: fixed;
top: 0;
bottom: 0;
contain: strict
}
.touch-menu-la {
background-color: #FFF;
will-change: transform;
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-transition: -webkit-transform ease-out 40ms, left ease-out 260ms;
-o-transition: transform ease-out 40ms, left ease-out 260ms;
transition: transform ease-out 40ms, left ease-out 260ms;
z-index: 1099
}
.touch-menu-la.transition {
-webkit-transition: -webkit-transform ease-out 240ms, left ease-out 260ms;
-o-transition: transform ease-out 240ms, left ease-out 260ms;
transition: transform ease-out 240ms, left ease-out 260ms
}
.drawer-open {
-webkit-box-shadow: 2px 0 12px rgba(0, 0, 0, .4);
box-shadow: 2px 0 12px rgba(0, 0, 0, .4)
}
.scrollContainer {
-webkit-box-flex: 1;
-webkit-flex-grow: 1;
flex-grow: 1
}
.tmla-mask {
left: 0;
right: 0;
background-color: #000;
opacity: 0;
z-index: 1098;
-webkit-transition: opacity ease-in-out .38s, visibility ease-in-out .38s;
-o-transition: opacity ease-in-out .38s, visibility ease-in-out .38s;
transition: opacity ease-in-out .38s, visibility ease-in-out .38s;
will-change: opacity;
background-color: rgba(0, 0, 0, .3)
}
.tmla-mask.backdrop {
opacity: 1
}

View file

@ -0,0 +1,179 @@
define(["browser", "dom", "css!./navdrawer", "scrollStyles"], function(browser, dom) {
"use strict";
return function(options) {
function getTouches(e) {
return e.changedTouches || e.targetTouches || e.touches
}
function onMenuTouchStart(e) {
options.target.classList.remove("transition");
var touches = getTouches(e),
touch = touches[0] || {};
menuTouchStartX = touch.clientX, menuTouchStartY = touch.clientY, menuTouchStartTime = (new Date).getTime()
}
function setVelocity(deltaX) {
var time = (new Date).getTime() - (menuTouchStartTime || 0);
velocity = Math.abs(deltaX) / time
}
function onMenuTouchMove(e) {
var isOpen = self.visible,
touches = getTouches(e),
touch = touches[0] || {},
endX = touch.clientX || 0,
endY = touch.clientY || 0,
deltaX = endX - (menuTouchStartX || 0),
deltaY = endY - (menuTouchStartY || 0);
setVelocity(deltaX), isOpen && 1 !== dragMode && deltaX > 0 && (dragMode = 2), 0 === dragMode && (!isOpen || Math.abs(deltaX) >= 10) && Math.abs(deltaY) < 5 ? (dragMode = 1, scrollContainer.addEventListener("scroll", disableEvent), self.showMask()) : 0 === dragMode && Math.abs(deltaY) >= 5 && (dragMode = 2), 1 === dragMode && (newPos = currentPos + deltaX, self.changeMenuPos())
}
function onMenuTouchEnd(e) {
options.target.classList.add("transition"), scrollContainer.removeEventListener("scroll", disableEvent), dragMode = 0;
var touches = getTouches(e),
touch = touches[0] || {},
endX = touch.clientX || 0,
endY = touch.clientY || 0,
deltaX = endX - (menuTouchStartX || 0),
deltaY = endY - (menuTouchStartY || 0);
currentPos = deltaX, self.checkMenuState(deltaX, deltaY)
}
function onEdgeTouchStart(e) {
if (isPeeking) onMenuTouchMove(e);
else {
((getTouches(e)[0] || {}).clientX || 0) <= options.handleSize && (isPeeking = !0, "touchstart" === e.type && (dom.removeEventListener(edgeContainer, "touchmove", onEdgeTouchMove, {}), dom.addEventListener(edgeContainer, "touchmove", onEdgeTouchMove, {})), onMenuTouchStart(e))
}
}
function onEdgeTouchMove(e) {
onEdgeTouchStart(e), e.preventDefault(), e.stopPropagation()
}
function onEdgeTouchEnd(e) {
isPeeking && (isPeeking = !1, dom.removeEventListener(edgeContainer, "touchmove", onEdgeTouchMove, {}), onMenuTouchEnd(e))
}
function disableEvent(e) {
e.preventDefault(), e.stopPropagation()
}
function onBackgroundTouchStart(e) {
var touches = getTouches(e),
touch = touches[0] || {};
backgroundTouchStartX = touch.clientX, backgroundTouchStartTime = (new Date).getTime()
}
function onBackgroundTouchMove(e) {
var touches = getTouches(e),
touch = touches[0] || {},
endX = touch.clientX || 0;
if (endX <= options.width && self.isVisible) {
countStart++;
var deltaX = endX - (backgroundTouchStartX || 0);
if (1 === countStart && (startPoint = deltaX), deltaX < 0 && 2 !== dragMode) {
dragMode = 1, newPos = deltaX - startPoint + options.width, self.changeMenuPos();
var time = (new Date).getTime() - (backgroundTouchStartTime || 0);
velocity = Math.abs(deltaX) / time
}
}
e.preventDefault(), e.stopPropagation()
}
function onBackgroundTouchEnd(e) {
var touches = getTouches(e),
touch = touches[0] || {},
endX = touch.clientX || 0,
deltaX = endX - (backgroundTouchStartX || 0);
self.checkMenuState(deltaX), countStart = 0
}
function onMaskTransitionEnd() {
var classList = mask.classList;
classList.contains("backdrop") || classList.add("hide")
}
var self, defaults, mask, newPos = 0,
currentPos = 0,
startPoint = 0,
countStart = 0,
velocity = 0;
options.target.classList.add("transition");
var dragMode = 0,
scrollContainer = options.target.querySelector(".mainDrawer-scrollContainer");
scrollContainer.classList.add("scrollY");
var TouchMenuLA = function() {
self = this, defaults = {
width: 260,
handleSize: 10,
disableMask: !1,
maxMaskOpacity: .5
}, this.isVisible = !1, this.initialize()
};
TouchMenuLA.prototype.initElements = function() {
options.target.classList.add("touch-menu-la"), options.target.style.width = options.width + "px", options.target.style.left = -options.width + "px", options.disableMask || (mask = document.createElement("div"), mask.className = "tmla-mask hide", document.body.appendChild(mask), dom.addEventListener(mask, dom.whichTransitionEvent(), onMaskTransitionEnd, {
passive: !0
}))
};
var menuTouchStartX, menuTouchStartY, menuTouchStartTime, edgeContainer = document.querySelector(".mainDrawerHandle"),
isPeeking = !1;
TouchMenuLA.prototype.animateToPosition = function(pos) {
requestAnimationFrame(function() {
options.target.style.transform = pos ? "translateX(" + pos + "px)" : "none"
})
}, TouchMenuLA.prototype.changeMenuPos = function() {
newPos <= options.width && this.animateToPosition(newPos)
}, TouchMenuLA.prototype.clickMaskClose = function() {
mask.addEventListener("click", function() {
self.close()
})
}, TouchMenuLA.prototype.checkMenuState = function(deltaX, deltaY) {
velocity >= .4 ? deltaX >= 0 || Math.abs(deltaY || 0) >= 70 ? self.open() : self.close() : newPos >= 100 ? self.open() : newPos && self.close()
}, TouchMenuLA.prototype.open = function() {
this.animateToPosition(options.width), currentPos = options.width, this.isVisible = !0, options.target.classList.add("drawer-open"), self.showMask(), self.invoke(options.onChange)
}, TouchMenuLA.prototype.close = function() {
this.animateToPosition(0), currentPos = 0, self.isVisible = !1, options.target.classList.remove("drawer-open"), self.hideMask(), self.invoke(options.onChange)
}, TouchMenuLA.prototype.toggle = function() {
self.isVisible ? self.close() : self.open()
};
var backgroundTouchStartX, backgroundTouchStartTime;
TouchMenuLA.prototype.showMask = function() {
mask.classList.remove("hide"), mask.offsetWidth, mask.classList.add("backdrop")
}, TouchMenuLA.prototype.hideMask = function() {
mask.classList.remove("backdrop")
}, TouchMenuLA.prototype.invoke = function(fn) {
fn && fn.apply(self)
};
var _edgeSwipeEnabled;
return TouchMenuLA.prototype.setEdgeSwipeEnabled = function(enabled) {
options.disableEdgeSwipe || browser.touch && (enabled ? _edgeSwipeEnabled || (_edgeSwipeEnabled = !0, dom.addEventListener(edgeContainer, "touchstart", onEdgeTouchStart, {
passive: !0
}), dom.addEventListener(edgeContainer, "touchend", onEdgeTouchEnd, {
passive: !0
}), dom.addEventListener(edgeContainer, "touchcancel", onEdgeTouchEnd, {
passive: !0
})) : _edgeSwipeEnabled && (_edgeSwipeEnabled = !1, dom.removeEventListener(edgeContainer, "touchstart", onEdgeTouchStart, {
passive: !0
}), dom.removeEventListener(edgeContainer, "touchend", onEdgeTouchEnd, {
passive: !0
}), dom.removeEventListener(edgeContainer, "touchcancel", onEdgeTouchEnd, {
passive: !0
})))
}, TouchMenuLA.prototype.initialize = function() {
options = Object.assign(defaults, options || {}), browser.edge && (options.disableEdgeSwipe = !0), self.initElements(), browser.touch && (dom.addEventListener(options.target, "touchstart", onMenuTouchStart, {
passive: !0
}), dom.addEventListener(options.target, "touchmove", onMenuTouchMove, {
passive: !0
}), dom.addEventListener(options.target, "touchend", onMenuTouchEnd, {
passive: !0
}), dom.addEventListener(options.target, "touchcancel", onMenuTouchEnd, {
passive: !0
}), dom.addEventListener(mask, "touchstart", onBackgroundTouchStart, {
passive: !0
}), dom.addEventListener(mask, "touchmove", onBackgroundTouchMove, {}), dom.addEventListener(mask, "touchend", onBackgroundTouchEnd, {
passive: !0
}), dom.addEventListener(mask, "touchcancel", onBackgroundTouchEnd, {
passive: !0
})), self.clickMaskClose()
}, new TouchMenuLA
}
});

View file

@ -0,0 +1,410 @@
define(["browser", "datetime", "backdrop", "libraryBrowser", "listView", "imageLoader", "playbackManager", "nowPlayingHelper", "events", "connectionManager", "apphost", "globalize", "cardStyle", "emby-itemscontainer", "css!css/nowplaying.css", "emby-ratingbutton"], function(browser, datetime, backdrop, libraryBrowser, listView, imageLoader, playbackManager, nowPlayingHelper, events, connectionManager, appHost, globalize) {
"use strict";
function showAudioMenu(context, player, button, item) {
var currentIndex = playbackManager.getAudioStreamIndex(player),
streams = playbackManager.audioTracks(player),
menuItems = streams.map(function(s) {
var menuItem = {
name: s.DisplayTitle,
id: s.Index
};
return s.Index == currentIndex && (menuItem.selected = !0), menuItem
});
require(["actionsheet"], function(actionsheet) {
actionsheet.show({
items: menuItems,
positionTo: button,
callback: function(id) {
playbackManager.setAudioStreamIndex(parseInt(id), player)
}
})
})
}
function showSubtitleMenu(context, player, button, item) {
var currentIndex = playbackManager.getSubtitleStreamIndex(player),
streams = playbackManager.subtitleTracks(player),
menuItems = streams.map(function(s) {
var menuItem = {
name: s.DisplayTitle,
id: s.Index
};
return s.Index == currentIndex && (menuItem.selected = !0), menuItem
});
menuItems.unshift({
id: -1,
name: globalize.translate("ButtonOff"),
selected: null == currentIndex
}), require(["actionsheet"], function(actionsheet) {
actionsheet.show({
items: menuItems,
positionTo: button,
callback: function(id) {
playbackManager.setSubtitleStreamIndex(parseInt(id), player)
}
})
})
}
function getNowPlayingNameHtml(nowPlayingItem, includeNonNameInfo) {
return nowPlayingHelper.getNowPlayingNames(nowPlayingItem, includeNonNameInfo).map(function(i) {
return i.text
}).join("<br/>")
}
function seriesImageUrl(item, options) {
if ("Episode" !== item.Type) return null;
if (options = options || {}, options.type = options.type || "Primary", "Primary" === options.type && item.SeriesPrimaryImageTag) return options.tag = item.SeriesPrimaryImageTag, connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options);
if ("Thumb" === options.type) {
if (item.SeriesThumbImageTag) return options.tag = item.SeriesThumbImageTag, connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options);
if (item.ParentThumbImageTag) return options.tag = item.ParentThumbImageTag, connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.ParentThumbItemId, options)
}
return null
}
function imageUrl(item, options) {
return options = options || {}, options.type = options.type || "Primary", item.ImageTags && item.ImageTags[options.type] ? (options.tag = item.ImageTags[options.type], connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.PrimaryImageItemId || item.Id, options)) : item.AlbumId && item.AlbumPrimaryImageTag ? (options.tag = item.AlbumPrimaryImageTag, connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.AlbumId, options)) : null
}
function updateNowPlayingInfo(context, state) {
var item = state.NowPlayingItem,
displayName = item ? getNowPlayingNameHtml(item).replace("<br/>", " - ") : "";
context.querySelector(".nowPlayingPageTitle").innerHTML = displayName, displayName.length > 0 ? context.querySelector(".nowPlayingPageTitle").classList.remove("hide") : context.querySelector(".nowPlayingPageTitle").classList.add("hide");
var url = item ? seriesImageUrl(item, {
maxHeight: 300
}) || imageUrl(item, {
maxHeight: 300
}) : null;
if (console.log("updateNowPlayingInfo"), setImageUrl(context, url), item) {
backdrop.setBackdrops([item]);
var apiClient = connectionManager.getApiClient(item.ServerId);
apiClient.getItem(apiClient.getCurrentUserId(), item.Id).then(function(fullItem) {
var userData = fullItem.UserData || {},
likes = null == userData.Likes ? "" : userData.Likes;
context.querySelector(".nowPlayingPageUserDataButtons").innerHTML = '<button is="emby-ratingbutton" type="button" class="listItemButton paper-icon-button-light" data-id="' + fullItem.Id + '" data-serverid="' + fullItem.ServerId + '" data-itemtype="' + fullItem.Type + '" data-likes="' + likes + '" data-isfavorite="' + userData.IsFavorite + '"><i class="md-icon">&#xE87D;</i></button>'
})
} else backdrop.clear(), context.querySelector(".nowPlayingPageUserDataButtons").innerHTML = ""
}
function setImageUrl(context, url) {
currentImgUrl = url;
var imgContainer = context.querySelector(".nowPlayingPageImageContainer");
url ? (imgContainer.innerHTML = '<img class="nowPlayingPageImage" src="' + url + '" />', imgContainer.classList.remove("hide")) : (imgContainer.classList.add("hide"), imgContainer.innerHTML = "")
}
function buttonEnabled(btn, enabled) {
btn.disabled = !enabled
}
function buttonVisible(btn, enabled) {
enabled ? btn.classList.remove("hide") : btn.classList.add("hide")
}
function updateSupportedCommands(context, commands) {
for (var all = context.querySelectorAll(".btnCommand"), i = 0, length = all.length; i < length; i++) buttonEnabled(all[i], -1 != commands.indexOf(all[i].getAttribute("data-command")))
}
var currentImgUrl;
return function() {
function toggleRepeat(player) {
if (player) switch (playbackManager.getRepeatMode(player)) {
case "RepeatNone":
playbackManager.setRepeatMode("RepeatAll", player);
break;
case "RepeatAll":
playbackManager.setRepeatMode("RepeatOne", player);
break;
case "RepeatOne":
playbackManager.setRepeatMode("RepeatNone", player)
}
}
function updatePlayerState(player, context, state) {
lastPlayerState = state;
var item = state.NowPlayingItem,
playerInfo = playbackManager.getPlayerInfo(),
supportedCommands = playerInfo.supportedCommands;
currentPlayerSupportedCommands = supportedCommands;
var playState = state.PlayState || {};
buttonVisible(context.querySelector(".btnToggleFullscreen"), item && "Video" == item.MediaType && -1 != supportedCommands.indexOf("ToggleFullscreen")), updateAudioTracksDisplay(player, context), updateSubtitleTracksDisplay(player, context), -1 != supportedCommands.indexOf("DisplayMessage") ? context.querySelector(".sendMessageSection").classList.remove("hide") : context.querySelector(".sendMessageSection").classList.add("hide"), -1 != supportedCommands.indexOf("SendString") ? context.querySelector(".sendTextSection").classList.remove("hide") : context.querySelector(".sendTextSection").classList.add("hide"), buttonVisible(context.querySelector(".btnStop"), null != item), buttonVisible(context.querySelector(".btnNextTrack"), null != item), buttonVisible(context.querySelector(".btnPreviousTrack"), null != item), buttonVisible(context.querySelector(".btnRewind"), null != item), buttonVisible(context.querySelector(".btnFastForward"), null != item);
var positionSlider = context.querySelector(".nowPlayingPositionSlider");
if (positionSlider && !positionSlider.dragging) {
positionSlider.disabled = !playState.CanSeek;
var isProgressClear = state.MediaSource && null == state.MediaSource.RunTimeTicks;
positionSlider.setIsClear(isProgressClear)
}
updatePlayPauseState(playState.IsPaused, null != item), updateTimeDisplay(playState.PositionTicks, item ? item.RunTimeTicks : null), updatePlayerVolumeState(context, playState.IsMuted, playState.VolumeLevel), item && "Video" == item.MediaType ? context.classList.remove("hideVideoButtons") : context.classList.add("hideVideoButtons"), updateRepeatModeDisplay(playState.RepeatMode), updateNowPlayingInfo(context, state)
}
function updateAudioTracksDisplay(player, context) {
var supportedCommands = currentPlayerSupportedCommands;
buttonVisible(context.querySelector(".btnAudioTracks"), playbackManager.audioTracks(player).length > 1 && -1 != supportedCommands.indexOf("SetAudioStreamIndex"))
}
function updateSubtitleTracksDisplay(player, context) {
var supportedCommands = currentPlayerSupportedCommands;
buttonVisible(context.querySelector(".btnSubtitles"), playbackManager.subtitleTracks(player).length && -1 != supportedCommands.indexOf("SetSubtitleStreamIndex"))
}
function updateRepeatModeDisplay(repeatMode) {
var context = dlg,
toggleRepeatButton = context.querySelector(".repeatToggleButton");
"RepeatAll" == repeatMode ? (toggleRepeatButton.innerHTML = "<i class='md-icon'>repeat</i>", toggleRepeatButton.classList.add("repeatButton-active")) : "RepeatOne" == repeatMode ? (toggleRepeatButton.innerHTML = "<i class='md-icon'>repeat_one</i>", toggleRepeatButton.classList.add("repeatButton-active")) : (toggleRepeatButton.innerHTML = "<i class='md-icon'>repeat</i>", toggleRepeatButton.classList.remove("repeatButton-active"))
}
function updatePlayerVolumeState(context, isMuted, volumeLevel) {
var view = context,
supportedCommands = currentPlayerSupportedCommands,
showMuteButton = !0,
showVolumeSlider = !0; - 1 === supportedCommands.indexOf("Mute") && (showMuteButton = !1), -1 === supportedCommands.indexOf("SetVolume") && (showVolumeSlider = !1), currentPlayer.isLocalPlayer && appHost.supports("physicalvolumecontrol") && (showMuteButton = !1, showVolumeSlider = !1), isMuted ? (view.querySelector(".buttonMute").setAttribute("title", globalize.translate("Unmute")), view.querySelector(".buttonMute i").innerHTML = "&#xE04F;") : (view.querySelector(".buttonMute").setAttribute("title", globalize.translate("Mute")), view.querySelector(".buttonMute i").innerHTML = "&#xE050;"), showMuteButton ? view.querySelector(".buttonMute").classList.remove("hide") : view.querySelector(".buttonMute").classList.add("hide");
var nowPlayingVolumeSlider = context.querySelector(".nowPlayingVolumeSlider"),
nowPlayingVolumeSliderContainer = context.querySelector(".nowPlayingVolumeSliderContainer");
nowPlayingVolumeSlider && (showVolumeSlider ? nowPlayingVolumeSliderContainer.classList.remove("hide") : nowPlayingVolumeSliderContainer.classList.add("hide"), nowPlayingVolumeSlider.dragging || (nowPlayingVolumeSlider.value = volumeLevel || 0))
}
function updatePlayPauseState(isPaused, isActive) {
var context = dlg,
btnPlayPause = context.querySelector(".btnPlayPause");
btnPlayPause.querySelector("i").innerHTML = isPaused ? "play_arrow" : "pause", buttonVisible(btnPlayPause, isActive)
}
function updateTimeDisplay(positionTicks, runtimeTicks) {
var context = dlg,
positionSlider = context.querySelector(".nowPlayingPositionSlider");
if (positionSlider && !positionSlider.dragging)
if (runtimeTicks) {
var pct = positionTicks / runtimeTicks;
pct *= 100, positionSlider.value = pct
} else positionSlider.value = 0;
context.querySelector(".positionTime").innerHTML = null == positionTicks ? "--:--" : datetime.getDisplayRunningTime(positionTicks), context.querySelector(".runtime").innerHTML = null != runtimeTicks ? datetime.getDisplayRunningTime(runtimeTicks) : "--:--"
}
function getPlaylistItems(player) {
return playbackManager.getPlaylist(player)
}
function loadPlaylist(context, player) {
getPlaylistItems(player).then(function(items) {
var html = "";
html += listView.getListViewHtml({
items: items,
smallIcon: !0,
action: "setplaylistindex",
enableUserDataButtons: !1,
rightButtons: [{
icon: "&#xE15D;",
title: globalize.translate("ButtonRemove"),
id: "remove"
}],
dragHandle: !0
}), items.length ? context.querySelector(".playlistSection").classList.remove("hide") : context.querySelector(".playlistSection").classList.add("hide");
var itemsContainer = context.querySelector(".playlist");
itemsContainer.innerHTML = html;
var playlistItemId = playbackManager.getCurrentPlaylistItemId(player);
if (playlistItemId) {
var img = itemsContainer.querySelector('.listItem[data-playlistItemId="' + playlistItemId + '"] .listItemImage');
img && (img.classList.remove("lazy"), img.classList.add("playlistIndexIndicatorImage"))
}
imageLoader.lazyChildren(itemsContainer)
})
}
function onPlaybackStart(e, state) {
console.log("remotecontrol event: " + e.type);
var player = this;
onStateChanged.call(player, e, state)
}
function onRepeatModeChange(e) {
var player = this;
updateRepeatModeDisplay(playbackManager.getRepeatMode(player))
}
function onPlaylistUpdate(e) {
loadPlaylist(dlg, this)
}
function onPlaylistItemRemoved(e, info) {
for (var context = dlg, playlistItemIds = info.playlistItemIds, i = 0, length = playlistItemIds.length; i < length; i++) {
var listItem = context.querySelector('.listItem[data-playlistItemId="' + playlistItemIds[i] + '"]');
listItem && listItem.parentNode.removeChild(listItem)
}
}
function onPlaybackStopped(e, state) {
console.log("remotecontrol event: " + e.type);
var player = this;
state.NextMediaType || (updatePlayerState(player, dlg, {}), loadPlaylist(dlg))
}
function onPlayPauseStateChanged(e) {
updatePlayPauseState(this.paused(), !0)
}
function onStateChanged(event, state) {
var player = this;
updatePlayerState(player, dlg, state), loadPlaylist(dlg, player)
}
function onTimeUpdate(e) {
var now = (new Date).getTime();
if (!(now - lastUpdateTime < 700)) {
lastUpdateTime = now;
var player = this;
currentRuntimeTicks = playbackManager.duration(player), updateTimeDisplay(playbackManager.currentTime(player), currentRuntimeTicks)
}
}
function onVolumeChanged(e) {
var player = this;
updatePlayerVolumeState(dlg, player.isMuted(), player.getVolume())
}
function releaseCurrentPlayer() {
var player = currentPlayer;
player && (events.off(player, "playbackstart", onPlaybackStart), events.off(player, "statechange", onStateChanged), events.off(player, "repeatmodechange", onRepeatModeChange), events.off(player, "playlistitemremove", onPlaylistUpdate), events.off(player, "playlistitemmove", onPlaylistUpdate), events.off(player, "playbackstop", onPlaybackStopped), events.off(player, "volumechange", onVolumeChanged), events.off(player, "pause", onPlayPauseStateChanged), events.off(player, "unpause", onPlayPauseStateChanged), events.off(player, "timeupdate", onTimeUpdate), currentPlayer = null)
}
function bindToPlayer(context, player) {
if (releaseCurrentPlayer(), currentPlayer = player, player) {
var state = playbackManager.getPlayerState(player);
onStateChanged.call(player, {
type: "init"
}, state), events.on(player, "playbackstart", onPlaybackStart), events.on(player, "statechange", onStateChanged), events.on(player, "repeatmodechange", onRepeatModeChange), events.on(player, "playlistitemremove", onPlaylistItemRemoved), events.on(player, "playlistitemmove", onPlaylistUpdate), events.on(player, "playbackstop", onPlaybackStopped), events.on(player, "volumechange", onVolumeChanged), events.on(player, "pause", onPlayPauseStateChanged), events.on(player, "unpause", onPlayPauseStateChanged), events.on(player, "timeupdate", onTimeUpdate);
var playerInfo = playbackManager.getPlayerInfo(),
supportedCommands = playerInfo.supportedCommands;
currentPlayerSupportedCommands = supportedCommands, updateSupportedCommands(context, supportedCommands)
}
}
function onBtnCommandClick() {
currentPlayer && (this.classList.contains("repeatToggleButton") ? toggleRepeat(currentPlayer) : playbackManager.sendCommand({
Name: this.getAttribute("data-command")
}, currentPlayer))
}
function getSaveablePlaylistItems() {
return getPlaylistItems(currentPlayer).then(function(items) {
return items.filter(function(i) {
return i.Id && i.ServerId
})
})
}
function savePlaylist() {
require(["playlistEditor"], function(playlistEditor) {
getSaveablePlaylistItems().then(function(items) {
var serverId = items.length ? items[0].ServerId : ApiClient.serverId();
(new playlistEditor).show({
items: items.map(function(i) {
return i.Id
}),
serverId: serverId,
enableAddToPlayQueue: !1,
defaultValue: "new"
})
})
})
}
function bindEvents(context) {
for (var btnCommand = context.querySelectorAll(".btnCommand"), i = 0, length = btnCommand.length; i < length; i++) btnCommand[i].addEventListener("click", onBtnCommandClick);
context.querySelector(".btnToggleFullscreen").addEventListener("click", function(e) {
currentPlayer && playbackManager.sendCommand({
Name: e.target.getAttribute("data-command")
}, currentPlayer)
}), context.querySelector(".btnAudioTracks").addEventListener("click", function(e) {
currentPlayer && lastPlayerState && lastPlayerState.NowPlayingItem && showAudioMenu(context, currentPlayer, e.target, lastPlayerState.NowPlayingItem)
}), context.querySelector(".btnSubtitles").addEventListener("click", function(e) {
currentPlayer && lastPlayerState && lastPlayerState.NowPlayingItem && showSubtitleMenu(context, currentPlayer, e.target, lastPlayerState.NowPlayingItem)
}), context.querySelector(".btnStop").addEventListener("click", function() {
currentPlayer && playbackManager.stop(currentPlayer)
}), context.querySelector(".btnPlayPause").addEventListener("click", function() {
currentPlayer && playbackManager.playPause(currentPlayer)
}), context.querySelector(".btnNextTrack").addEventListener("click", function() {
currentPlayer && playbackManager.nextTrack(currentPlayer)
}), context.querySelector(".btnRewind").addEventListener("click", function() {
currentPlayer && playbackManager.rewind(currentPlayer)
}), context.querySelector(".btnFastForward").addEventListener("click", function() {
currentPlayer && playbackManager.fastForward(currentPlayer)
}), context.querySelector(".btnPreviousTrack").addEventListener("click", function() {
currentPlayer && playbackManager.previousTrack(currentPlayer)
}), context.querySelector(".nowPlayingPositionSlider").addEventListener("change", function() {
var value = this.value;
if (currentPlayer) {
var newPercent = parseFloat(value);
playbackManager.seekPercent(newPercent, currentPlayer)
}
}), context.querySelector(".nowPlayingPositionSlider").getBubbleText = function(value) {
var state = lastPlayerState;
if (!state || !state.NowPlayingItem || !currentRuntimeTicks) return "--:--";
var ticks = currentRuntimeTicks;
return ticks /= 100, ticks *= value, datetime.getDisplayRunningTime(ticks)
}, context.querySelector(".nowPlayingVolumeSlider").addEventListener("change", function() {
playbackManager.setVolume(this.value, currentPlayer)
}), context.querySelector(".buttonMute").addEventListener("click", function() {
playbackManager.toggleMute(currentPlayer)
});
var playlistContainer = context.querySelector(".playlist");
playlistContainer.addEventListener("action-remove", function(e) {
playbackManager.removeFromPlaylist([e.detail.playlistItemId], currentPlayer)
}), playlistContainer.addEventListener("itemdrop", function(e) {
var newIndex = e.detail.newIndex,
playlistItemId = e.detail.playlistItemId;
playbackManager.movePlaylistItem(playlistItemId, newIndex, currentPlayer)
}), context.querySelector(".btnSavePlaylist").addEventListener("click", savePlaylist)
}
function onPlayerChange() {
bindToPlayer(dlg, playbackManager.getCurrentPlayer())
}
function onMessageSubmit(e) {
var form = e.target;
return playbackManager.sendCommand({
Name: "DisplayMessage",
Arguments: {
Header: form.querySelector("#txtMessageTitle").value,
Text: form.querySelector("#txtMessageText", form).value
}
}, currentPlayer), form.querySelector("input").value = "", require(["toast"], function(toast) {
toast("Message sent.")
}), e.preventDefault(), e.stopPropagation(), !1
}
function onSendStringSubmit(e) {
var form = e.target;
return playbackManager.sendCommand({
Name: "SendString",
Arguments: {
String: form.querySelector("#txtTypeText", form).value
}
}, currentPlayer), form.querySelector("input").value = "", require(["toast"], function(toast) {
toast("Text sent.")
}), e.preventDefault(), e.stopPropagation(), !1
}
function init(ownerView, context) {
bindEvents(context), context.querySelector(".sendMessageForm").addEventListener("submit", onMessageSubmit), context.querySelector(".typeTextForm").addEventListener("submit", onSendStringSubmit), events.on(playbackManager, "playerchange", onPlayerChange)
}
function onDialogClosed(e) {
releaseCurrentPlayer(), events.off(playbackManager, "playerchange", onPlayerChange), lastPlayerState = null
}
function onShow(context, tab) {
currentImgUrl = null, bindToPlayer(context, playbackManager.getCurrentPlayer())
}
var dlg, currentPlayer, lastPlayerState, currentPlayerSupportedCommands = [],
lastUpdateTime = 0,
currentRuntimeTicks = 0,
self = this;
self.init = function(ownerView, context) {
dlg = context, init(ownerView, dlg)
}, self.onShow = function() {
onShow(dlg, window.location.hash)
}, self.destroy = function() {
onDialogClosed()
}
}
});

View file

@ -0,0 +1,78 @@
define(["dialogHelper", "dom", "layoutManager", "connectionManager", "globalize", "loading", "material-icons", "formDialogStyle", "emby-button", "emby-itemscontainer", "cardStyle"], function(dialogHelper, dom, layoutManager, connectionManager, globalize, loading) {
"use strict";
function getEditorHtml() {
var html = "";
return html += '<div class="formDialogContent scrollY">', html += '<div class="dialogContentInner dialog-content-centered">', html += '<div class="loadingContent hide">', html += "<h1>" + globalize.translate("DetectingDevices") + "...</h1>", html += "<p>" + globalize.translate("MessagePleaseWait") + "</p>", html += "</div>", html += '<h1 style="margin-bottom:.25em;" class="devicesHeader hide">' + globalize.translate("HeaderNewDevices") + "</h1>", html += '<div is="emby-itemscontainer" class="results vertical-wrap">', html += "</div>", html += "</div>", html += "</div>"
}
function getDeviceHtml(device) {
var padderClass, html = "",
cssClass = "card scalableCard",
cardBoxCssClass = "cardBox visualCardBox";
return cssClass += " backdropCard backdropCard-scalable", padderClass = "cardPadder-backdrop", layoutManager.tv && (cssClass += " card-focusscale", cardBoxCssClass += " cardBox-focustransform"), cardBoxCssClass += " card-focuscontent", html += '<button type="button" class="' + cssClass + '" data-id="' + device.DeviceId + '" style="min-width:33.3333%;">', 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"><i class="cardImageIcon md-icon">dvr</i></div>', html += "</div>", html += "</div>", html += '<div class="cardFooter visualCardBox-cardFooter">', html += '<div class="cardText cardTextCentered">' + getTunerName(device.Type) + "</div>", html += '<div class="cardText cardTextCentered cardText-secondary">' + device.FriendlyName + "</div>", html += '<div class="cardText cardText-secondary cardTextCentered">', html += device.Url || "&nbsp;", html += "</div>", html += "</div>", html += "</div>", html += "</button>"
}
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 renderDevices(view, devices) {
var i, length, html = "";
for (i = 0, length = devices.length; i < length; i++) html += getDeviceHtml(devices[i]);
devices.length ? view.querySelector(".devicesHeader").classList.remove("hide") : (html = "<p><br/>" + globalize.translate("NoNewDevicesFound") + "</p>", view.querySelector(".devicesHeader").classList.add("hide"));
var elem = view.querySelector(".results");
elem.innerHTML = html, layoutManager.tv && focusManager.autoFocus(elem)
}
function discoverDevices(view, apiClient) {
return loading.show(), view.querySelector(".loadingContent").classList.remove("hide"), ApiClient.getJSON(ApiClient.getUrl("LiveTv/Tuners/Discvover", {
NewDevicesOnly: !0
})).then(function(devices) {
currentDevices = devices, renderDevices(view, devices), view.querySelector(".loadingContent").classList.add("hide"), loading.hide()
})
}
function tunerPicker() {
this.show = function(options) {
var dialogOptions = {
removeOnClose: !0,
scrollY: !1
};
layoutManager.tv ? dialogOptions.size = "fullscreen" : dialogOptions.size = "small";
var dlg = dialogHelper.createDialog(dialogOptions);
dlg.classList.add("formDialog");
var html = "";
html += '<div class="formDialogHeader">', html += '<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><i class="md-icon">&#xE5C4;</i></button>', html += '<h3 class="formDialogHeaderTitle">', html += globalize.translate("HeaderLiveTvTunerSetup"), html += "</h3>", html += "</div>", html += getEditorHtml(), dlg.innerHTML = html, dlg.querySelector(".btnCancel").addEventListener("click", function() {
dialogHelper.close(dlg)
});
var deviceResult;
dlg.querySelector(".results").addEventListener("click", function(e) {
var tunerCard = dom.parentWithClass(e.target, "card");
if (tunerCard) {
var deviceId = tunerCard.getAttribute("data-id");
deviceResult = currentDevices.filter(function(d) {
return d.DeviceId === deviceId
})[0], dialogHelper.close(dlg)
}
}), layoutManager.tv && centerFocus(dlg.querySelector(".formDialogContent"), !1, !0);
var apiClient = connectionManager.getApiClient(options.serverId);
return discoverDevices(dlg, apiClient), layoutManager.tv && centerFocus(dlg.querySelector(".formDialogContent"), !1, !1), dialogHelper.open(dlg).then(function() {
return deviceResult ? Promise.resolve(deviceResult) : Promise.reject()
})
}
}
var currentDevices = [];
return tunerPicker
});

View file

@ -0,0 +1,174 @@
define(["jQuery", "loading", "emby-checkbox", "listViewStyle", "emby-input", "emby-select", "emby-linkbutton", "flexStyles"], function($, loading) {
"use strict";
return function(page, providerId, options) {
function reload() {
loading.show(), ApiClient.getNamedConfiguration("livetv").then(function(config) {
var info = config.ListingProviders.filter(function(i) {
return i.Id === providerId
})[0] || {};
listingsId = info.ListingsId, $("#selectListing", page).val(info.ListingsId || ""), page.querySelector(".txtUser").value = info.Username || "", page.querySelector(".txtPass").value = "", page.querySelector(".txtZipCode").value = info.ZipCode || "", info.Username && info.Password ? page.querySelector(".listingsSection").classList.remove("hide") : page.querySelector(".listingsSection").classList.add("hide"), page.querySelector(".chkAllTuners").checked = info.EnableAllTuners, page.querySelector(".chkAllTuners").checked ? page.querySelector(".selectTunersSection").classList.add("hide") : page.querySelector(".selectTunersSection").classList.remove("hide"), setCountry(info), refreshTunerDevices(page, info, config.TunerHosts)
})
}
function setCountry(info) {
ApiClient.getJSON(ApiClient.getUrl("LiveTv/ListingProviders/SchedulesDirect/Countries")).then(function(result) {
var i, length, countryList = [];
for (var region in result) {
var countries = result[region];
if (countries.length && "ZZZ" !== region)
for (i = 0, length = countries.length; i < length; i++) countryList.push({
name: countries[i].fullName,
value: countries[i].shortName
})
}
countryList.sort(function(a, b) {
return a.name > b.name ? 1 : a.name < b.name ? -1 : 0
}), $("#selectCountry", page).html(countryList.map(function(c) {
return '<option value="' + c.value + '">' + c.name + "</option>"
}).join("")).val(info.Country || ""), $(page.querySelector(".txtZipCode")).trigger("change")
}, function() {
Dashboard.alert({
message: Globalize.translate("ErrorGettingTvLineups")
})
}), loading.hide()
}
function sha256(str) {
if (!self.TextEncoder) return Promise.resolve("");
var buffer = new TextEncoder("utf-8").encode(str);
return crypto.subtle.digest("SHA-256", buffer).then(function(hash) {
return hex(hash)
})
}
function hex(buffer) {
for (var hexCodes = [], view = new DataView(buffer), i = 0; i < view.byteLength; i += 4) {
var value = view.getUint32(i),
stringValue = value.toString(16),
paddedValue = ("00000000" + stringValue).slice(-"00000000".length);
hexCodes.push(paddedValue)
}
return hexCodes.join("")
}
function submitLoginForm() {
loading.show(), sha256(page.querySelector(".txtPass").value).then(function(passwordHash) {
var info = {
Type: "SchedulesDirect",
Username: page.querySelector(".txtUser").value,
EnableAllTuners: !0,
Password: passwordHash,
Pw: page.querySelector(".txtPass").value
},
id = providerId;
id && (info.Id = id), ApiClient.ajax({
type: "POST",
url: ApiClient.getUrl("LiveTv/ListingProviders", {
ValidateLogin: !0
}),
data: JSON.stringify(info),
contentType: "application/json",
dataType: "json"
}).then(function(result) {
Dashboard.processServerConfigurationUpdateResult(), providerId = result.Id, reload()
}, function() {
Dashboard.alert({
message: Globalize.translate("ErrorSavingTvProvider")
})
})
})
}
function submitListingsForm() {
var selectedListingsId = $("#selectListing", page).val();
if (!selectedListingsId) return void Dashboard.alert({
message: Globalize.translate("ErrorPleaseSelectLineup")
});
loading.show();
var id = providerId;
ApiClient.getNamedConfiguration("livetv").then(function(config) {
var info = config.ListingProviders.filter(function(i) {
return i.Id === id
})[0];
info.ZipCode = page.querySelector(".txtZipCode").value, info.Country = $("#selectCountry", page).val(), info.ListingsId = selectedListingsId, info.EnableAllTuners = page.querySelector(".chkAllTuners").checked, info.EnabledTuners = info.EnableAllTuners ? [] : $(".chkTuner", page).get().filter(function(i) {
return i.checked
}).map(function(i) {
return i.getAttribute("data-id")
}), ApiClient.ajax({
type: "POST",
url: ApiClient.getUrl("LiveTv/ListingProviders", {
ValidateListings: !0
}),
data: JSON.stringify(info),
contentType: "application/json"
}).then(function(result) {
loading.hide(), !1 !== options.showConfirmation && Dashboard.processServerConfigurationUpdateResult(), Events.trigger(self, "submitted")
}, function() {
loading.hide(), Dashboard.alert({
message: Globalize.translate("ErrorAddingListingsToSchedulesDirect")
})
})
})
}
function refreshListings(value) {
if (!value) return void $("#selectListing", page).html("");
loading.show(), ApiClient.ajax({
type: "GET",
url: ApiClient.getUrl("LiveTv/ListingProviders/Lineups", {
Id: providerId,
Location: value,
Country: $("#selectCountry", page).val()
}),
dataType: "json"
}).then(function(result) {
$("#selectListing", page).html(result.map(function(o) {
return '<option value="' + o.Id + '">' + o.Name + "</option>"
})), listingsId && $("#selectListing", page).val(listingsId), loading.hide()
}, function(result) {
Dashboard.alert({
message: Globalize.translate("ErrorGettingTvLineups")
}), refreshListings(""), loading.hide()
})
}
function getTunerName(providerId) {
switch (providerId = providerId.toLowerCase()) {
case "m3u":
return "M3U Playlist";
case "hdhomerun":
return "HDHomerun";
case "satip":
return "DVB";
default:
return "Unknown"
}
}
function refreshTunerDevices(page, providerInfo, devices) {
for (var html = "", i = 0, length = devices.length; i < length; i++) {
var device = devices[i];
html += '<div class="listItem">';
var enabledTuners = providerInfo.EnabledTuners || [],
isChecked = providerInfo.EnableAllTuners || -1 !== enabledTuners.indexOf(device.Id),
checkedAttribute = isChecked ? " checked" : "";
html += '<label class="checkboxContainer listItemCheckboxContainer"><input type="checkbox" is="emby-checkbox" data-id="' + device.Id + '" class="chkTuner" ' + checkedAttribute + "/><span></span></label>", html += '<div class="listItemBody two-line">', html += '<div class="listItemBodyText">', html += device.FriendlyName || getTunerName(device.Type), html += "</div>", html += '<div class="listItemBodyText secondary">', html += device.Url, html += "</div>", html += "</div>", html += "</div>"
}
page.querySelector(".tunerList").innerHTML = html
}
var listingsId, self = this;
self.submit = function() {
page.querySelector(".btnSubmitListingsContainer").click()
}, self.init = function() {
options = options || {}, !1 !== options.showCancelButton ? page.querySelector(".btnCancel").classList.remove("hide") : page.querySelector(".btnCancel").classList.add("hide"), !1 !== options.showSubmitButton ? page.querySelector(".btnSubmitListings").classList.remove("hide") : page.querySelector(".btnSubmitListings").classList.add("hide"), $(".formLogin", page).on("submit", function() {
return submitLoginForm(), !1
}), $(".formListings", page).on("submit", function() {
return submitListingsForm(), !1
}), $(".txtZipCode", page).on("change", function() {
refreshListings(this.value)
}), page.querySelector(".chkAllTuners").addEventListener("change", function(e) {
e.target.checked ? page.querySelector(".selectTunersSection").classList.add("hide") : page.querySelector(".selectTunersSection").classList.remove("hide")
}), $(".createAccountHelp", page).html(Globalize.translate("MessageCreateAccountAt", '<a is="emby-linkbutton" class="button-link" href="http://www.schedulesdirect.org" target="_blank">http://www.schedulesdirect.org</a>')), reload()
}
}
});

View file

@ -0,0 +1,73 @@
<div class="verticalSection">
<div class="sectionTitleContainer flex align-items-center">
<h1 class="sectionTitle">Schedules Direct</h1>
<a is="emby-linkbutton" class="raised button-alt headerHelpButton" target="_blank" href="https://web.archive.org/web/20181216120305/https://github.com/MediaBrowser/Wiki/wiki/Live-TV">${Help}</a>
</div>
<p class="createAccountHelp"></p>
</div>
<div style="margin:1.5em 0 1em;" class="flex align-items-center">
<h2 style="background-color:rgba(82,181,75,.8);color:#fff;margin: 0;border-radius:100em;height:1.7em;width:1.7em;" class="flex align-items-center justify-content-center">
1
</h2>
<h3 style="margin:0 0 0 .5em;">
${GuideProviderLogin}
</h3>
</div>
<form class="formLogin">
<!-- Terrible, but this hidden field prevents the yellow background in chrome -->
<div style="height:0; overflow: hidden;"><input type="text" name="fakeusernameremembered" tabindex="-1" /><input type="password" name="fakepasswordremembered" tabindex="-1" /></div>
<div>
<br />
<div class="inputContainer">
<input is="emby-input" class="txtUser" label="${LabelUsername}" required="required" autocomplete="off" />
</div>
<div class="inputContainer">
<input is="emby-input" class="txtPass" label="${LabelPassword}" required="required" autocomplete="off" type="password" />
</div>
<div>
<button is="emby-button" type="submit" class="raised button-submit block"><span>${ButtonSave}</span></button>
</div>
</div>
</form>
<br />
<div class="listingsSection hide">
<div style="display: flex; align-items: center;margin:1.5em 0 1em;">
<h2 style="background-color:rgba(82,181,75,.8);color:#fff;margin: 0;border-radius:100em;height:1.7em;width:1.7em;" class="flex align-items-center justify-content-center">
2
</h2>
<h3 style="margin:0 0 0 .5em;">
${GuideProviderSelectListings}
</h3>
</div>
<form class="formListings">
<div>
<div class="selectContainer">
<select is="emby-select" id="selectCountry" data-mini="true" required="required" label="${LabelCountry}"></select>
</div>
<div class="inputContainer">
<input is="emby-input" class="txtZipCode" label="${LabelZipCode}" required="required" />
</div>
<div class="selectContainer">
<select is="emby-select" id="selectListing" data-mini="true" required="required" label="${LabelLineup}"></select>
</div>
<div>
<label class="checkboxContainer">
<input type="checkbox" is="emby-checkbox" class="chkAllTuners" />
<span>${OptionEnableForAllTuners}</span>
</label>
<div class="selectTunersSection hide">
<h3 class="checkboxListLabel">${HeaderTuners}</h3>
<div class="checkboxList paperList checkboxList-paperList tunerList">
</div>
</div>
</div>
<br />
<div>
<button is="emby-button" type="submit" class="raised button-submit block btnSubmitListingsContainer btnSubmitListings hide"><span>${ButtonSave}</span></button>
<button is="emby-button" type="button" class="raised button-cancel block btnCancel hide" onclick="history.back();"><span>${ButtonCancel}</span></button>
</div>
</div>
</form>
</div>

View file

@ -0,0 +1,197 @@
define(["jQuery", "registrationServices", "loading", "emby-checkbox", "emby-input", "listViewStyle", "paper-icon-button-light"], function ($__q, registrationServices, loading) {
"use strict";
return function (page, providerId, options) {
function getListingProvider(config, id) {
if (config && id) {
var result = config.ListingProviders.filter(function (i__w) {
return i__w.Id === id;
})[0];
if (result) {
return Promise.resolve(result);
}
return getListingProvider();
}
return ApiClient.getJSON(ApiClient.getUrl("LiveTv/ListingProviders/Default"));
}
function reload() {
loading.show();
ApiClient.getNamedConfiguration("livetv").then(function (config) {
getListingProvider(config, providerId).then(function (info) {
page.querySelector(".txtPath").value = info.Path || "";
page.querySelector(".txtKids").value = (info.KidsCategories || []).join("|");
page.querySelector(".txtNews").value = (info.NewsCategories || []).join("|");
page.querySelector(".txtSports").value = (info.SportsCategories || []).join("|");
page.querySelector(".txtMovies").value = (info.MovieCategories || []).join("|");
page.querySelector(".txtMoviePrefix").value = info.MoviePrefix || "";
page.querySelector(".txtUserAgent").value = info.UserAgent || "";
page.querySelector(".chkAllTuners").checked = info.EnableAllTuners;
if (page.querySelector(".chkAllTuners").checked) {
page.querySelector(".selectTunersSection").classList.add("hide");
} else {
page.querySelector(".selectTunersSection").classList.remove("hide");
}
refreshTunerDevices(page, info, config.TunerHosts);
loading.hide();
});
});
}
function getCategories(txtInput) {
var value = txtInput.value;
if (value) {
return value.split("|");
}
return [];
}
function submitListingsForm() {
loading.show();
var id = providerId;
ApiClient.getNamedConfiguration("livetv").then(function (config) {
var info = config.ListingProviders.filter(function (i__e) {
return i__e.Id === id;
})[0] || {};
info.Type = "xmltv";
info.Path = page.querySelector(".txtPath").value;
info.MoviePrefix = page.querySelector(".txtMoviePrefix").value || null;
info.UserAgent = page.querySelector(".txtUserAgent").value || null;
info.MovieCategories = getCategories(page.querySelector(".txtMovies"));
info.KidsCategories = getCategories(page.querySelector(".txtKids"));
info.NewsCategories = getCategories(page.querySelector(".txtNews"));
info.SportsCategories = getCategories(page.querySelector(".txtSports"));
info.EnableAllTuners = page.querySelector(".chkAllTuners").checked;
info.EnabledTuners = info.EnableAllTuners ? [] : $__q(".chkTuner", page).get().filter(function (i__r) {
return i__r.checked;
}).map(function (i__t) {
return i__t.getAttribute("data-id");
});
ApiClient.ajax({
type: "POST",
url: ApiClient.getUrl("LiveTv/ListingProviders", {
ValidateListings: true
}),
data: JSON.stringify(info),
contentType: "application/json"
}).then(function (result) {
loading.hide();
if (false !== options.showConfirmation) {
Dashboard.processServerConfigurationUpdateResult();
}
Events.trigger(self, "submitted");
}, function () {
loading.hide();
Dashboard.alert({
message: Globalize.translate("ErrorAddingXmlTvFile")
});
});
});
}
function getTunerName(providerId) {
switch (providerId = providerId.toLowerCase()) {
case "m3u":
return "M3U Playlist";
case "hdhomerun":
return "HDHomerun";
case "satip":
return "DVB";
default:
return "Unknown";
}
}
function refreshTunerDevices(page, providerInfo, devices) {
var html = "";
for (var i__y = 0, length = devices.length; i__y < length; i__y++) {
var device = devices[i__y];
html += '<div class="listItem">';
var enabledTuners = providerInfo.EnabledTuners || [];
var isChecked = providerInfo.EnableAllTuners || -1 !== enabledTuners.indexOf(device.Id);
var checkedAttribute = isChecked ? " checked" : "";
html += '<label class="listItemCheckboxContainer"><input type="checkbox" is="emby-checkbox" class="chkTuner" data-id="' + device.Id + '" ' + checkedAttribute + "><span></span></label>";
html += '<div class="listItemBody two-line">';
html += '<div class="listItemBodyText">';
html += device.FriendlyName || getTunerName(device.Type);
html += "</div>";
html += '<div class="listItemBodyText secondary">';
html += device.Url;
html += "</div>";
html += "</div>";
html += "</div>";
}
page.querySelector(".tunerList").innerHTML = html;
}
function onSelectPathClick(e__u) {
var page = $__q(e__u.target).parents(".xmltvForm")[0];
require(["directorybrowser"], function (directoryBrowser) {
var picker = new directoryBrowser();
picker.show({
includeFiles: true,
callback: function (path) {
if (path) {
var txtPath = page.querySelector(".txtPath");
txtPath.value = path;
txtPath.focus();
}
picker.close();
}
});
});
}
var self = this;
self.submit = function () {
page.querySelector(".btnSubmitListings").click();
};
self.init = function () {
options = options || {};
if (false !== options.showCancelButton) {
page.querySelector(".btnCancel").classList.remove("hide");
} else {
page.querySelector(".btnCancel").classList.add("hide");
}
if (false !== options.showSubmitButton) {
page.querySelector(".btnSubmitListings").classList.remove("hide");
} else {
page.querySelector(".btnSubmitListings").classList.add("hide");
}
$__q("form", page).on("submit", function () {
submitListingsForm();
return false;
});
page.querySelector("#btnSelectPath").addEventListener("click", onSelectPathClick);
page.querySelector(".chkAllTuners").addEventListener("change", function (e__i) {
if (e__i.target.checked) {
page.querySelector(".selectTunersSection").classList.add("hide");
} else {
page.querySelector(".selectTunersSection").classList.remove("hide");
}
});
reload();
};
};
});

View file

@ -0,0 +1,62 @@
<div class="verticalSection">
<div class="sectionTitleContainer flex align-items-center">
<h1 class="sectionTitle">Xml TV</h1>
<a is="emby-linkbutton" class="raised button-alt headerHelpButton" target="_blank" href="https://web.archive.org/web/20181216120305/https://github.com/MediaBrowser/Wiki/wiki/Live-TV">${Help}</a>
</div>
</div>
<form class="xmltvForm">
<div>
<div class="inputContainer">
<div style="display: flex; align-items: center;">
<div style="flex-grow:1;">
<input is="emby-input" class="txtPath" label="${LabelFileOrUrl}" required="required" autocomplete="off" />
</div>
<button type="button" is="paper-icon-button-light" id="btnSelectPath" class="emby-input-iconbutton"><i class="md-icon">search</i></button>
</div>
<div class="fieldDescription">${XmlTvPathHelp}</div>
</div>
<div class="inputContainer">
<input is="emby-input" class="txtMovies" label="${LabelMovieCategories}" autocomplete="off" />
<div class="fieldDescription">${XmlTvMovieCategoriesHelp}</div>
</div>
<div class="inputContainer">
<input is="emby-input" class="txtMoviePrefix" label="${LabelMoviePrefix}" autocomplete="off" />
<div class="fieldDescription">${LabelMoviePrefixHelp}</div>
</div>
<div class="inputContainer">
<input is="emby-input" class="txtKids" label="${LabelKidsCategories}" autocomplete="off" />
<div class="fieldDescription">${XmlTvKidsCategoriesHelp}</div>
</div>
<div class="inputContainer">
<input is="emby-input" class="txtNews" label="${LabelNewsCategories}" autocomplete="off" />
<div class="fieldDescription"></div>
<div class="fieldDescription">${XmlTvNewsCategoriesHelp}</div>
</div>
<div class="inputContainer">
<input is="emby-input" class="txtSports" label="${LabelSportsCategories}" autocomplete="off" />
<div class="fieldDescription">${XmlTvSportsCategoriesHelp}</div>
</div>
<div class="inputContainer fldUserAgent">
<input is="emby-input" type="text" class="txtUserAgent" label="${LabelUserAgent}" autocomplete="off" />
<div class="fieldDescription">${UserAgentHelp}</div>
</div>
</div>
<div>
<label class="checkboxContainer">
<input type="checkbox" is="emby-checkbox" class="chkAllTuners" />
<span>${OptionEnableForAllTuners}</span>
</label>
<div class="selectTunersSection hide">
<h3 class="checkboxListLabel">${HeaderTuners}</h3>
<div class="checkboxList paperList checkboxList-paperList tunerList">
</div>
</div>
</div>
<div>
<button is="emby-button" type="submit" class="raised button-submit block btnSubmitListings hide"><span>${ButtonSave}</span></button>
<button is="emby-button" type="button" class="raised button-cancel block btnCancel hide" onclick="history.back();"><span>${ButtonCancel}</span></button>
</div>
</form>

View file

@ -0,0 +1,121 @@
define(["browser", "dom", "layoutManager", "css!bower_components/emby-webcomponents/viewmanager/viewcontainer-lite"], function(browser, dom, layoutManager) {
"use strict";
function setControllerClass(view, options) {
if (options.controllerFactory) return Promise.resolve();
var controllerUrl = view.getAttribute("data-controller");
return controllerUrl ? (0 === controllerUrl.indexOf("__plugin/") && (controllerUrl = controllerUrl.substring("__plugin/".length)), controllerUrl = Dashboard.getConfigurationResourceUrl(controllerUrl), getRequirePromise([controllerUrl]).then(function(ControllerFactory) {
options.controllerFactory = ControllerFactory
})) : Promise.resolve()
}
function getRequirePromise(deps) {
return new Promise(function(resolve, reject) {
require(deps, resolve)
})
}
function loadView(options) {
if (!options.cancel) {
var selected = selectedPageIndex,
previousAnimatable = -1 === selected ? null : allPages[selected],
pageIndex = selected + 1;
pageIndex >= pageContainerCount && (pageIndex = 0);
var isPluginpage = -1 !== options.url.toLowerCase().indexOf("/configurationpage"),
newViewInfo = normalizeNewView(options, isPluginpage),
newView = newViewInfo.elem,
dependencies = "string" == typeof newView ? null : newView.getAttribute("data-require");
return dependencies = dependencies ? dependencies.split(",") : [], isPluginpage && dependencies.push("legacy/dashboard"), newViewInfo.hasjQuerySelect && dependencies.push("legacy/selectmenu"), newViewInfo.hasjQueryChecked && dependencies.push("fnchecked"), newViewInfo.hasjQuery && dependencies.push("jQuery"), (isPluginpage || newView.classList && newView.classList.contains("type-interior")) && dependencies.push("dashboardcss"), new Promise(function(resolve, reject) {
dependencies.join(",");
require(dependencies, function() {
var currentPage = allPages[pageIndex];
currentPage && triggerDestroy(currentPage);
var view = newView;
"string" == typeof view && (view = document.createElement("div"), view.innerHTML = newView), view.classList.add("mainAnimatedPage"), currentPage ? newViewInfo.hasScript && window.$ ? (view = $(view).appendTo(mainAnimatedPages)[0], mainAnimatedPages.removeChild(currentPage)) : mainAnimatedPages.replaceChild(view, currentPage) : newViewInfo.hasScript && window.$ ? view = $(view).appendTo(mainAnimatedPages)[0] : mainAnimatedPages.appendChild(view), options.type && view.setAttribute("data-type", options.type);
var properties = [];
options.fullscreen && properties.push("fullscreen"), properties.length && view.setAttribute("data-properties", properties.join(","));
allPages[pageIndex] = view, setControllerClass(view, options).then(function() {
onBeforeChange && onBeforeChange(view, !1, options), beforeAnimate(allPages, pageIndex, selected), selectedPageIndex = pageIndex, currentUrls[pageIndex] = options.url, !options.cancel && previousAnimatable && afterAnimate(allPages, pageIndex), window.$ && ($.mobile = $.mobile || {}, $.mobile.activePage = view), resolve(view)
})
})
})
}
}
function replaceAll(str, find, replace) {
return str.split(find).join(replace)
}
function parseHtml(html, hasScript) {
hasScript && (html = replaceAll(html, "\x3c!--<script", "<script"), html = replaceAll(html, "<\/script>--\x3e", "<\/script>"));
var wrapper = document.createElement("div");
return wrapper.innerHTML = html, wrapper.querySelector('div[data-role="page"]')
}
function normalizeNewView(options, isPluginpage) {
var viewHtml = options.view;
if (-1 === viewHtml.indexOf('data-role="page"')) return viewHtml;
var hasScript = -1 !== viewHtml.indexOf("<script"),
elem = parseHtml(viewHtml, hasScript);
hasScript && (hasScript = null != elem.querySelector("script"));
var hasjQuery = !1,
hasjQuerySelect = !1,
hasjQueryChecked = !1;
return isPluginpage && (hasjQuery = -1 != viewHtml.indexOf("jQuery") || -1 != viewHtml.indexOf("$(") || -1 != viewHtml.indexOf("$."), hasjQueryChecked = -1 != viewHtml.indexOf(".checked("), hasjQuerySelect = -1 != viewHtml.indexOf(".selectmenu(")), {
elem: elem,
hasScript: hasScript,
hasjQuerySelect: hasjQuerySelect,
hasjQueryChecked: hasjQueryChecked,
hasjQuery: hasjQuery
}
}
function beforeAnimate(allPages, newPageIndex, oldPageIndex) {
for (var i = 0, length = allPages.length; i < length; i++) newPageIndex === i || oldPageIndex === i || allPages[i].classList.add("hide")
}
function afterAnimate(allPages, newPageIndex) {
for (var i = 0, length = allPages.length; i < length; i++) newPageIndex === i || allPages[i].classList.add("hide")
}
function setOnBeforeChange(fn) {
onBeforeChange = fn
}
function tryRestoreView(options) {
var url = options.url,
index = currentUrls.indexOf(url);
if (-1 !== index) {
var animatable = allPages[index],
view = animatable;
if (view) {
if (options.cancel) return;
var selected = selectedPageIndex,
previousAnimatable = -1 === selected ? null : allPages[selected];
return setControllerClass(view, options).then(function() {
return onBeforeChange && onBeforeChange(view, !0, options), beforeAnimate(allPages, index, selected), animatable.classList.remove("hide"), selectedPageIndex = index, !options.cancel && previousAnimatable && afterAnimate(allPages, index), window.$ && ($.mobile = $.mobile || {}, $.mobile.activePage = view), view
})
}
}
return Promise.reject()
}
function triggerDestroy(view) {
view.dispatchEvent(new CustomEvent("viewdestroy", {}))
}
function reset() {
allPages = [], currentUrls = [], mainAnimatedPages.innerHTML = "", selectedPageIndex = -1
}
var onBeforeChange, mainAnimatedPages = document.querySelector(".mainAnimatedPages"),
allPages = [],
currentUrls = [],
pageContainerCount = 3,
selectedPageIndex = -1;
return reset(), mainAnimatedPages.classList.remove("hide"), {
loadView: loadView,
tryRestoreView: tryRestoreView,
reset: reset,
setOnBeforeChange: setOnBeforeChange
}
});