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:
parent
09513af31b
commit
4678528d00
657 changed files with 422 additions and 0 deletions
51
src/components/accessschedule/accessschedule.js
Normal file
51
src/components/accessschedule/accessschedule.js
Normal 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()
|
||||
})
|
||||
}
|
||||
}
|
||||
});
|
39
src/components/accessschedule/accessschedule.template.html
Normal file
39
src/components/accessschedule/accessschedule.template.html
Normal file
|
@ -0,0 +1,39 @@
|
|||
<div class="formDialogHeader">
|
||||
<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><i class="md-icon"></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>
|
89
src/components/activitylog.js
Normal file
89
src/components/activitylog.js
Normal 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
191
src/components/apphost.js
Normal 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
|
||||
});
|
31
src/components/categorysyncbuttons.js
Normal file
31
src/components/categorysyncbuttons.js
Normal 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)
|
||||
}
|
||||
}
|
||||
});
|
96
src/components/channelmapper/channelmapper.js
Normal file
96
src/components/channelmapper/channelmapper.js
Normal 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"></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)
|
||||
})
|
||||
}
|
||||
}
|
||||
});
|
8
src/components/directorybrowser/directorybrowser.css
Normal file
8
src/components/directorybrowser/directorybrowser.css
Normal file
|
@ -0,0 +1,8 @@
|
|||
#ulDirectoryPickerList a {
|
||||
padding-top: .4em;
|
||||
padding-bottom: .4em
|
||||
}
|
||||
|
||||
.lblDirectoryPickerPath {
|
||||
white-space: nowrap
|
||||
}
|
145
src/components/directorybrowser/directorybrowser.js
Normal file
145
src/components/directorybrowser/directorybrowser.js
Normal 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"></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
|
||||
});
|
177
src/components/favoriteitems.js
Normal file
177
src/components/favoriteitems.js
Normal 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"></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
|
||||
}
|
||||
});
|
198
src/components/filterdialog/filterdialog.js
Normal file
198
src/components/filterdialog/filterdialog.js
Normal 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)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
});
|
154
src/components/filterdialog/filterdialog.template.html
Normal file
154
src/components/filterdialog/filterdialog.template.html
Normal 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>
|
36
src/components/filterdialog/style.css
Normal file
36
src/components/filterdialog/style.css
Normal 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
|
||||
}
|
||||
}
|
32
src/components/groupedcards.js
Normal file
32
src/components/groupedcards.js
Normal 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
|
||||
}
|
||||
});
|
58
src/components/guestinviter/guestinviter.js
Normal file
58
src/components/guestinviter/guestinviter.js
Normal 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)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
});
|
36
src/components/guestinviter/guestinviter.template.html
Normal file
36
src/components/guestinviter/guestinviter.template.html
Normal file
|
@ -0,0 +1,36 @@
|
|||
<div class="formDialogHeader">
|
||||
<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1">
|
||||
<i class="md-icon"></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>
|
28
src/components/humanedate.js
Normal file
28
src/components/humanedate.js
Normal 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
57
src/components/iap.js
Normal 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
|
||||
}
|
||||
});
|
77
src/components/imageoptionseditor/imageoptionseditor.js
Normal file
77
src/components/imageoptionseditor/imageoptionseditor.js
Normal 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
|
||||
});
|
|
@ -0,0 +1,78 @@
|
|||
<div class="formDialogHeader">
|
||||
<button type="button" is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><i class="md-icon"></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>
|
344
src/components/libraryoptionseditor/libraryoptionseditor.js
Normal file
344
src/components/libraryoptionseditor/libraryoptionseditor.js
Normal 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
|
||||
}
|
||||
});
|
|
@ -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>
|
135
src/components/medialibrarycreator/medialibrarycreator.js
Normal file
135
src/components/medialibrarycreator/medialibrarycreator.js
Normal 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("&", "&");
|
||||
$("#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
|
||||
});
|
|
@ -0,0 +1,45 @@
|
|||
<div class="formDialogHeader">
|
||||
<button type="button" is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><i class="md-icon"></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>
|
149
src/components/medialibraryeditor/medialibraryeditor.js
Normal file
149
src/components/medialibraryeditor/medialibraryeditor.js
Normal 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
|
||||
});
|
|
@ -0,0 +1,31 @@
|
|||
<div class="formDialogHeader">
|
||||
<button type="button" is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><i class="md-icon"></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>
|
53
src/components/navdrawer/navdrawer.css
Normal file
53
src/components/navdrawer/navdrawer.css
Normal 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
|
||||
}
|
179
src/components/navdrawer/navdrawer.js
Normal file
179
src/components/navdrawer/navdrawer.js
Normal 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
|
||||
}
|
||||
});
|
410
src/components/remotecontrol.js
Normal file
410
src/components/remotecontrol.js
Normal 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"></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 = "") : (view.querySelector(".buttonMute").setAttribute("title", globalize.translate("Mute")), view.querySelector(".buttonMute i").innerHTML = ""), 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: "",
|
||||
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()
|
||||
}
|
||||
}
|
||||
});
|
78
src/components/tunerpicker.js
Normal file
78
src/components/tunerpicker.js
Normal 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 || " ", 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"></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
|
||||
});
|
174
src/components/tvproviders/schedulesdirect.js
Normal file
174
src/components/tvproviders/schedulesdirect.js
Normal 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()
|
||||
}
|
||||
}
|
||||
});
|
73
src/components/tvproviders/schedulesdirect.template.html
Normal file
73
src/components/tvproviders/schedulesdirect.template.html
Normal 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>
|
197
src/components/tvproviders/xmltv.js
Normal file
197
src/components/tvproviders/xmltv.js
Normal 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();
|
||||
};
|
||||
};
|
||||
});
|
62
src/components/tvproviders/xmltv.template.html
Normal file
62
src/components/tvproviders/xmltv.template.html
Normal 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>
|
121
src/components/viewcontainer-lite.js
Normal file
121
src/components/viewcontainer-lite.js
Normal 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
|
||||
}
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue