Connecting...
+]*><\/p>/, "");
+ const app = loadApp({server: makeServer(), html});
+ assert.equal(app.document.getElementById("sr-announcer"), null);
+
+ app.window.announce("Saved");
+
+ const region = app.document.getElementById("announcer");
+ assert.ok(region, "region created");
+ assert.equal(region.getAttribute("aria-live"), "polite");
+ assert.equal(region.className, "sr-only");
+ assert.equal(region.textContent, "Saved");
+
+ app.window.announce("Again");
+ assert.equal(app.document.querySelectorAll("#announcer").length, 1);
+ assert.equal(region.textContent, "Again");
+});
+
+test("server error responses alert and announce", () => {
+ const app = loadApp({server: makeServer()});
+ app.window.createLayout();
+
+ app.evalIn('new Server("saveSettings").request({settings: {}})');
+ const socket = app.sockets[app.sockets.length - 1];
+ socket.readyState = 1;
+ socket.onopen();
+ const sent = JSON.parse(socket.sent[0]);
+
+ socket.onmessage({data: JSON.stringify({id: sent.id, status: false, err: "Setting rejected"})});
+
+ return new Promise(resolve => setImmediate(resolve)).then(() => {
+ assert.deepEqual(app.alerts, ["Setting rejected"]);
+ assert.equal(app.document.getElementById("sr-announcer").textContent, "Setting rejected");
+ assert.equal(app.document.getElementById("connection-indicator").className, "status-offline");
+ });
+});
diff --git a/ts/base.ts b/ts/base.ts
index 293b9b1..58c1bec 100644
--- a/ts/base.ts
+++ b/ts/base.ts
@@ -60,6 +60,35 @@ function showElement(elmID, type) {
element.className = cssClass;
}
+// announce writes a message into the ARIA live region so assistive
+// technology hears it; the region is created when the page has none.
+function announce(message:string) {
+ if (document.body == null) {
+ return
+ }
+
+ var region = document.getElementById("announcer")
+ if (region == null) {
+ region = document.getElementById("sr-announcer")
+ }
+ if (region == null) {
+ region = document.createElement("DIV")
+ region.id = "announcer"
+ region.className = "sr-only"
+ region.setAttribute("aria-live", "polite")
+ region.setAttribute("aria-atomic", "true")
+ document.body.appendChild(region)
+ }
+
+ region.textContent = message
+}
+
+// alertUser shows the browser alert and mirrors it into the live region.
+function alertUser(message:string) {
+ announce(message)
+ alert(message)
+}
+
function setConnectionState(state:string, text:string = "") {
var label:string = text
@@ -92,9 +121,17 @@ function setConnectionState(state:string, text:string = "") {
indicator.innerText = label
}
-function changeButtonAction(element, buttonID, attribute) {
- var value = element.options[element.selectedIndex].value;
- document.getElementById(buttonID).setAttribute(attribute, value)
+// markChangedOnChange flags a control as edited on "change" so that
+// saveSettings / donePopupData pick it up, then runs an optional follow-up.
+// Callers build many controls through one reused `var`, so the listener must
+// close over this parameter and not over the caller's variable.
+function markChangedOnChange(element:HTMLElement, then?:(element:HTMLElement) => void) {
+ element.addEventListener("change", function() {
+ element.className = "changed"
+ if (then != undefined) {
+ then(element)
+ }
+ })
}
function getLocalData(dataType, id):object {
@@ -501,7 +538,7 @@ function changeChannelNumber(element) {
var channels = getObjKeys(data)
if (isNaN(newNumber)) {
- alert("Invalid channel number")
+ alertUser("Invalid channel number")
return
}
@@ -584,7 +621,7 @@ function toggleChannelStatus(id:string) {
if (channel["x-xmltv-file"] == "-" || channel["x-mapping"] == "-") {
if (BULK_EDIT == false) {
- alert(channel["x-name"] + ": Missing XMLTV file / channel")
+ alertUser(channel["x-name"] + ": Missing XMLTV file / channel")
checkbox.checked = false
}
@@ -649,7 +686,7 @@ function restore() {
};
} else {
- alert("File could not be loaded")
+ alertUser("File could not be loaded")
}
restore.remove()
@@ -707,7 +744,7 @@ function uploadLogo() {
};
} else {
- alert("File could not be loaded")
+ alertUser("File could not be loaded")
}
upload.remove()
diff --git a/ts/configuration.ts b/ts/configuration.ts
index 687c295..2229def 100644
--- a/ts/configuration.ts
+++ b/ts/configuration.ts
@@ -144,7 +144,7 @@ function saveWizard() {
if (value.length == 0) {
var msg = name.toUpperCase() + ": " + "Missing input"
- alert(msg)
+ alertUser(msg)
return
}
diff --git a/ts/content.ts b/ts/content.ts
new file mode 100644
index 0000000..b6347b0
--- /dev/null
+++ b/ts/content.ts
@@ -0,0 +1,764 @@
+class Content {
+
+ DocumentID:string = "content"
+ TableID:string = "content_table"
+ DivID:string
+ headerClass:string = "content_table_header"
+ interactionID:string = "content-interaction"
+
+ createHeadline(value):any {
+ var element = document.createElement("H3")
+ element.textContent = value
+ return element
+ }
+
+ createHR():any {
+ var element = document.createElement("HR")
+ return element
+ }
+
+ createInteraction():any {
+ var element = document.createElement("DIV")
+ element.setAttribute("id", this.interactionID)
+ return element
+ }
+
+ createDIV():any {
+ var element = document.createElement("DIV")
+ element.id = this.DivID
+ return element
+ }
+
+ createTABLE():any {
+ var element = document.createElement("TABLE")
+ element.id = this.TableID
+ return element
+ }
+
+ createTableRow():any {
+ var element = document.createElement("TR")
+ element.className = this.headerClass
+ return element
+ }
+
+ createTableContent(menuKey:string):string[] {
+
+ var data = new Object()
+ var rows = new Array()
+
+ switch(menuKey) {
+ case "playlist":
+ var fileTypes = new Array("m3u", "hdhr")
+
+ fileTypes.forEach(fileType => {
+
+ data = SERVER["settings"]["files"][fileType]
+
+ var keys = getObjKeys(data)
+
+ keys.forEach(key => {
+ var tr = document.createElement("TR")
+ tr.id = key
+
+ tr.addEventListener("click", function() { openPopUp(fileType, tr) })
+
+ var cell:Cell = new Cell()
+ cell.child = true
+ cell.childType = "P"
+ cell.value = data[key]["name"]
+ tr.appendChild(cell.createCell())
+
+ var cell:Cell = new Cell()
+ cell.child = true
+ cell.childType = "P"
+ if (SERVER["settings"]["buffer"] != "-") {
+ cell.value = data[key]["tuner"]
+ } else {
+ cell.value = "-"
+ }
+
+ tr.appendChild(cell.createCell())
+
+ var cell:Cell = new Cell()
+ cell.child = true
+ cell.childType = "P"
+ cell.value = data[key]["last.update"]
+ tr.appendChild(cell.createCell())
+
+ var cell:Cell = new Cell()
+ cell.child = true
+ cell.childType = "P"
+ cell.value = data[key]["provider.availability"]
+ tr.appendChild(cell.createCell())
+
+ var cell:Cell = new Cell()
+ cell.child = true
+ cell.childType = "P"
+ cell.value = data[key]["type"].toUpperCase();
+ tr.appendChild(cell.createCell())
+
+ var cell:Cell = new Cell()
+ cell.child = true
+ cell.childType = "P"
+ cell.value = data[key]["compatibility"]["streams"]
+ tr.appendChild(cell.createCell())
+
+ var cell:Cell = new Cell()
+ cell.child = true
+ cell.childType = "P"
+ cell.value = data[key]["compatibility"]["group.title"]
+ tr.appendChild(cell.createCell())
+
+ var cell:Cell = new Cell()
+ cell.child = true
+ cell.childType = "P"
+ cell.value = data[key]["compatibility"]["tvg.id"]
+ tr.appendChild(cell.createCell())
+
+ var cell:Cell = new Cell()
+ cell.child = true
+ cell.childType = "P"
+ cell.value = data[key]["compatibility"]["stream.id"]
+ tr.appendChild(cell.createCell())
+
+ rows.push(tr)
+ });
+
+ });
+ break
+
+ case "filter":
+ delete SERVER["settings"]["filter"][-1]
+ data = SERVER["settings"]["filter"]
+ var keys = getObjKeys(data)
+ keys.forEach(key => {
+ var tr = document.createElement("TR")
+ tr.id = key
+
+ tr.addEventListener("click", function() { openPopUp(data[key]["type"], tr) })
+
+ var cell:Cell = new Cell()
+ cell.child = true
+ cell.childType = "P"
+ cell.value = data[key]["name"]
+ tr.appendChild(cell.createCell())
+
+ var cell:Cell = new Cell()
+ cell.child = true
+ cell.childType = "P"
+ switch (data[key]["type"]) {
+ case "custom-filter":
+ cell.value = "Custom"
+ break;
+
+ case "group-title":
+ cell.value = "Group"
+ break;
+
+ case "regex-filter":
+ cell.value = "Regex"
+ break;
+
+ default:
+ break;
+ }
+
+ tr.appendChild(cell.createCell())
+
+ var cell:Cell = new Cell()
+ cell.child = true
+ cell.childType = "P"
+ cell.value = data[key]["filter"]
+ tr.appendChild(cell.createCell())
+
+ rows.push(tr)
+
+ });
+ break
+
+ case "xmltv":
+ var fileTypes = new Array("xmltv")
+
+ fileTypes.forEach(fileType => {
+
+ data = SERVER["settings"]["files"][fileType]
+
+ var keys = getObjKeys(data)
+
+ keys.forEach(key => {
+ var tr = document.createElement("TR")
+
+ tr.id = key
+ tr.addEventListener("click", function() { openPopUp(fileType, tr) })
+
+ var cell:Cell = new Cell()
+ cell.child = true
+ cell.childType = "P"
+ cell.value = data[key]["name"]
+ tr.appendChild(cell.createCell())
+
+ var cell:Cell = new Cell()
+ cell.child = true
+ cell.childType = "P"
+ cell.value = data[key]["last.update"]
+ tr.appendChild(cell.createCell())
+
+ var cell:Cell = new Cell()
+ cell.child = true
+ cell.childType = "P"
+ cell.value = data[key]["provider.availability"]
+ tr.appendChild(cell.createCell())
+
+ var cell:Cell = new Cell()
+ cell.child = true
+ cell.childType = "P"
+ cell.value = data[key]["compatibility"]["xmltv.channels"]
+ tr.appendChild(cell.createCell())
+
+ var cell:Cell = new Cell()
+ cell.child = true
+ cell.childType = "P"
+ cell.value = data[key]["compatibility"]["xmltv.programs"]
+ tr.appendChild(cell.createCell())
+
+ rows.push(tr)
+ });
+
+ });
+ break
+
+ case "users":
+ var fileTypes = new Array("users")
+
+ fileTypes.forEach(fileType => {
+ data = SERVER[fileType]
+
+ var keys = getObjKeys(data)
+
+ keys.forEach(key => {
+ var tr = document.createElement("TR")
+ tr.id = key
+ tr.addEventListener("click", function() { openPopUp(fileType, tr) })
+
+ var cell:Cell = new Cell()
+ cell.child = true
+ cell.childType = "P"
+ cell.value = data[key]["data"]["username"]
+ tr.appendChild(cell.createCell())
+
+ var cell:Cell = new Cell()
+ cell.child = true
+ cell.childType = "P"
+ cell.value = "******"
+ tr.appendChild(cell.createCell())
+
+ var cell:Cell = new Cell()
+ cell.child = true
+ cell.childType = "P"
+ if (data[key]["data"]["authentication.web"] == true) {
+ cell.value = "✓"
+ } else {
+ cell.value = "-"
+ }
+ tr.appendChild(cell.createCell())
+
+ var cell:Cell = new Cell()
+ cell.child = true
+ cell.childType = "P"
+ if (data[key]["data"]["authentication.pms"] == true) {
+ cell.value = "✓"
+ } else {
+ cell.value = "-"
+ }
+ tr.appendChild(cell.createCell())
+
+ var cell:Cell = new Cell()
+ cell.child = true
+ cell.childType = "P"
+ if (data[key]["data"]["authentication.m3u"] == true) {
+ cell.value = "✓"
+ } else {
+ cell.value = "-"
+ }
+ tr.appendChild(cell.createCell())
+
+ var cell:Cell = new Cell()
+ cell.child = true
+ cell.childType = "P"
+ if (data[key]["data"]["authentication.xml"] == true) {
+ cell.value = "✓"
+ } else {
+ cell.value = "-"
+ }
+ tr.appendChild(cell.createCell())
+
+ var cell:Cell = new Cell()
+ cell.child = true
+ cell.childType = "P"
+ if (data[key]["data"]["authentication.api"] == true) {
+ cell.value = "✓"
+ } else {
+ cell.value = "-"
+ }
+ tr.appendChild(cell.createCell())
+
+ rows.push(tr)
+ });
+
+ });
+ break
+
+ case "mapping":
+ BULK_EDIT = false
+ createSearchObj()
+ checkUndo("epgMapping")
+ console.log("MAPPING")
+ data = SERVER["xepg"]["epgMapping"]
+
+ var keys = getObjKeys(data)
+ keys.forEach(key => {
+ var tr = document.createElement("TR")
+ tr.id = key
+
+
+ switch (data[key]["x-active"]) {
+ case true:
+ tr.className = "activeEPG"
+ break;
+
+ case false:
+ tr.className = "notActiveEPG"
+ break;
+ }
+
+ // Bulk
+ var cell:Cell = new Cell()
+ cell.child = true
+ cell.childType = "BULK"
+ cell.value = false
+ tr.appendChild(cell.createCell())
+
+ // Kanalnummer
+ var cell:Cell = new Cell()
+ cell.child = true
+ cell.childType = "INPUTCHANNEL"
+ cell.value = data[key]["x-channelID"]
+ tr.appendChild(cell.createCell())
+
+ // Logo
+ var cell:Cell = new Cell()
+ cell.child = true
+ cell.childType = "IMG"
+ cell.imageURL = data[key]["tvg-logo"]
+ tr.appendChild(cell.createCell())
+
+ // Kanalname
+ var cell:Cell = new Cell()
+ cell.child = true
+ cell.childType = "P"
+ cell.className = data[key]["x-category"]
+ cell.value = data[key]["x-name"]
+ tr.appendChild(cell.createCell())
+
+
+ // Playlist
+ var cell:Cell = new Cell()
+ cell.child = true
+ cell.childType = "P"
+ //cell.value = data[key]["_file.m3u.name"]
+ cell.value = getValueFromProviderFile(data[key]["_file.m3u.id"], "m3u", "name")
+ tr.appendChild(cell.createCell())
+
+
+ // Gruppe (group-title)
+ var cell:Cell = new Cell()
+ cell.child = true
+ cell.childType = "P"
+ cell.value = data[key]["x-group-title"]
+ tr.appendChild(cell.createCell())
+
+ // XMLTV Datei
+ var cell:Cell = new Cell()
+ cell.child = true
+ cell.childType = "P"
+
+ if (data[key]["x-xmltv-file"] != "-") {
+ cell.value = getValueFromProviderFile(data[key]["x-xmltv-file"], "xmltv", "name")
+ } else {
+ cell.value = data[key]["x-xmltv-file"]
+ }
+
+ tr.appendChild(cell.createCell())
+
+ // XMLTV Kanal
+ var cell:Cell = new Cell()
+ cell.child = true
+ cell.childType = "P"
+ //var value = str.substring(1, 4);
+ var value = data[key]["x-mapping"]
+ if (value.length > 20) {
+ value = data[key]["x-mapping"].substring(0, 20) + "..."
+ }
+ cell.value = value
+ tr.appendChild(cell.createCell())
+
+ var cell:Cell = new Cell()
+ cell.child = true
+ cell.childType = "EDIT"
+ cell.value = "Edit"
+ var editTd = cell.createCell()
+ var editButton = (editTd.firstChild as HTMLInputElement)
+ editButton.addEventListener("click", function() { openPopUp("mapping", editButton) })
+ editButton.setAttribute("id", key)
+ editButton.setAttribute("aria-label", "Edit " + data[key]["x-name"])
+ tr.appendChild(editTd)
+
+ rows.push(tr)
+ });
+
+ break
+
+ case "settings":
+ alert()
+ break
+
+ default:
+ console.log("Table content (menuKey):", menuKey);
+
+ break
+
+ }
+
+ return rows
+
+ }
+
+ return
+}
+
+class Cell {
+ child:Boolean
+ childType:string
+ value:any
+ className:string
+ tdClassName:string
+ imageURL:string
+ onclick:boolean
+ onclickHandler:() => void
+
+ createCell():any {
+ var td = document.createElement("TD")
+
+
+ if (this.child == true) {
+ var element:any
+
+ switch(this.childType){
+ case "P":
+ element = document.createElement(this.childType);
+ element.textContent = this.value
+ element.className = this.className
+ break
+
+ case "INPUT":
+ element = document.createElement(this.childType);
+ (element as HTMLInputElement).value = this.value;
+ (element as HTMLInputElement).type = "text";
+ break
+
+ case "INPUTCHANNEL":
+ element = document.createElement("INPUT");
+ (element as HTMLInputElement).addEventListener("change", function() { changeChannelNumber(element) });
+ (element as HTMLInputElement).value = this.value;
+ (element as HTMLInputElement).type = "text";
+ break
+
+ case "BULK":
+ element = document.createElement("INPUT");
+ (element as HTMLInputElement).checked = this.value;
+ (element as HTMLInputElement).type = "checkbox";
+ (element as HTMLInputElement).className = "bulk hideBulk";
+ (element as HTMLInputElement).addEventListener("click", function(event) {
+ scheduleChannelRangeSelection((element as HTMLInputElement), (event as MouseEvent))
+ })
+ break
+
+ case "BULK_HEAD":
+ element = document.createElement("INPUT");
+ (element as HTMLInputElement).checked = this.value;
+ (element as HTMLInputElement).type = "checkbox";
+ (element as HTMLInputElement).className = "bulk hideBulk";
+ (element as HTMLInputElement).addEventListener("click", function() { selectAllChannels() })
+ break
+
+ case "IMG":
+ element = document.createElement(this.childType);
+ element.setAttribute("src", this.imageURL)
+ if (this.imageURL != "") {
+ // A broken logo falls back to no image; once, so the empty src cannot loop.
+ element.addEventListener("error", function() { element.setAttribute("src", "") }, {once: true})
+ }
+ break
+
+ case "EDIT":
+ element = document.createElement("INPUT");
+ (element as HTMLInputElement).type = "button";
+ (element as HTMLInputElement).value = this.value;
+ (element as HTMLInputElement).className = "mapping-edit-button";
+ break
+ }
+
+ td.appendChild(element)
+
+ } else {
+ td.textContent = this.value
+ }
+
+ if (this.onclick == true) {
+ td.addEventListener("click", this.onclickHandler)
+ td.className = "pointer"
+ }
+
+ if (this.tdClassName != undefined) {
+ td.className = this.tdClassName
+ }
+
+ return td
+ }
+
+ return
+}
+
+class ShowContent extends Content {
+ menuID:number
+
+ constructor(menuID:number) {
+ super()
+ this.menuID = menuID
+ }
+
+ createInput(type:string, name:string, value:string,):any {
+
+ var input = document.createElement("INPUT")
+ input.setAttribute("type", type)
+ input.setAttribute("name", name)
+ input.setAttribute("value", value)
+ return input
+ }
+
+ show():void {
+ COLUMN_TO_SORT = -1
+ // Alten Inhalt löschen
+ var doc = document.getElementById(this.DocumentID)
+ doc.innerHTML = ""
+ showPreview(false)
+
+ // Überschrift
+ var headline:string[] = menuItems[this.menuID].headline
+
+ var menuKey = menuItems[this.menuID].menuKey
+ var h = this.createHeadline(headline)
+ doc.appendChild(h)
+
+ var hr = this.createHR()
+ doc.appendChild(hr)
+
+ // Interaktion
+ var div =this.createInteraction()
+ doc.appendChild(div)
+ var interaction = document.getElementById(this.interactionID)
+ switch (menuKey) {
+ case "playlist":
+ var input = this.createInput("button", menuKey, "New")
+ input.setAttribute("id", "-")
+ input.addEventListener("click", function() { openPopUp("playlist") })
+ interaction.appendChild(input)
+ break;
+
+ case "filter":
+ var input = this.createInput("button", menuKey, "New")
+ input.setAttribute("id", -1)
+ input.addEventListener("click", function(event) { openPopUp("filter", event.currentTarget) })
+ interaction.appendChild(input)
+ break;
+
+
+ case "xmltv":
+ var input = this.createInput("button", menuKey, "New")
+ input.setAttribute("id", "xmltv")
+ input.addEventListener("click", function() { openPopUp("xmltv") })
+ interaction.appendChild(input)
+ break;
+
+ case "users":
+ var input = this.createInput("button", menuKey, "New")
+ input.setAttribute("id", "users")
+ input.addEventListener("click", function() { openPopUp("users") })
+ interaction.appendChild(input)
+ break;
+
+ case "mapping":
+ showElement("loading", true)
+ var input = this.createInput("button", menuKey, "Save")
+ input.addEventListener("click", function() { savePopupData("mapping", "", false, 0) })
+ interaction.appendChild(input)
+
+ var input = this.createInput("button", menuKey, "Bulk Edit")
+ input.addEventListener("click", function() { bulkEdit() })
+ interaction.appendChild(input)
+
+ var input = this.createInput("search", "search", "")
+ input.setAttribute("id", "searchMapping")
+ input.setAttribute("placeholder", "Search")
+ input.className = "search"
+ input.addEventListener("input", function() { searchInMapping() })
+ interaction.appendChild(input)
+ break;
+
+ case "settings":
+ var input = this.createInput("button", menuKey, "Save")
+ input.addEventListener("click", function() { saveSettings() })
+ interaction.appendChild(input)
+
+ var input = this.createInput("button", menuKey, "Backup")
+ input.addEventListener("click", function() { backup() })
+ interaction.appendChild(input)
+
+ var input = this.createInput("button", menuKey, "Restore")
+ input.addEventListener("click", function() { restore() })
+ interaction.appendChild(input)
+
+ var wrapper = document.createElement("DIV")
+ wrapper.setAttribute("id", "box-wrapper")
+ doc.appendChild(wrapper)
+
+ this.DivID = "content_settings"
+ var settings = this.createDIV()
+ wrapper.appendChild(settings)
+
+ showSettings()
+
+ return
+ break
+
+ case "log":
+ var input = this.createInput("button", menuKey, "Reset Logs")
+ input.addEventListener("click", function() { resetLogs() })
+ interaction.appendChild(input)
+
+ var wrapper = document.createElement("DIV")
+ wrapper.setAttribute("id", "box-wrapper")
+ doc.appendChild(wrapper)
+
+ this.DivID = "content_log"
+ var logs = this.createDIV()
+ wrapper.appendChild(logs)
+
+ showLogs(true)
+
+ return
+ break
+
+ case "logout":
+ location.reload()
+ document.cookie = "Token= ; expires = Thu, 01 Jan 1970 00:00:00 GMT"
+ break
+
+ default:
+ console.log("Show content (menuKey):", menuKey);
+ break;
+ }
+
+ // Tabelle erstellen (falls benötigt)
+ var tableHeader:string[] = menuItems[this.menuID].tableHeader
+ if (tableHeader.length > 0) {
+ var wrapper = document.createElement("DIV")
+ doc.appendChild(wrapper)
+ wrapper.setAttribute("id", "box-wrapper")
+
+ var table = this.createTABLE()
+ wrapper.appendChild(table)
+
+ var header = this.createTableRow()
+ table.appendChild(header)
+
+ // Kopfzeile der Tablle
+ tableHeader.forEach(element => {
+ var cell:Cell = new Cell()
+ cell.child = true
+ cell.childType = "P"
+ cell.value = element
+ if (element == "BULK") {
+ cell.childType = "BULK_HEAD";
+ cell.value = false
+ }
+
+ if (menuKey == "mapping") {
+
+ if (element == "Ch. No.") {
+ cell.onclick = true
+ cell.onclickHandler = function() { sortTable(1) }
+ cell.tdClassName = "sortThis"
+ }
+
+ if (element == "Channel Name") {
+ cell.onclick = true
+ cell.onclickHandler = function() { sortTable(3) }
+ }
+
+ if (element == "Playlist") {
+ cell.onclick = true
+ cell.onclickHandler = function() { sortTable(4) }
+ }
+
+ if (element == "Group Title") {
+ cell.onclick = true
+ cell.onclickHandler = function() { sortTable(5) }
+ }
+
+ }
+
+ header.appendChild(cell.createCell())
+ });
+
+ table.appendChild(header)
+
+ // Inhalt der Tabelle
+ var rows:any = this.createTableContent(menuKey)
+ rows.forEach(tr => {
+ table.appendChild(tr)
+ });
+
+ }
+
+ switch (menuKey) {
+ case "mapping":
+ sortTable(1)
+ break;
+
+ case "filter":
+ showPreview(true)
+ sortTable(0)
+ break
+
+ default:
+ COLUMN_TO_SORT = -1
+ sortTable(0)
+ break;
+ }
+
+ showElement("loading", false)
+ }
+
+}
+
+function openThisMenu(element) {
+ var id = element.id
+ var content:ShowContent = new ShowContent(id)
+ setActiveMenu(id)
+ content.show()
+ closeLayoutMenuIfMobile()
+ calculateWrapperHeight()
+
+ return
+}
diff --git a/ts/menu.ts b/ts/menu.ts
new file mode 100644
index 0000000..26d2435
--- /dev/null
+++ b/ts/menu.ts
@@ -0,0 +1,414 @@
+class MainMenu {
+ DocumentID:string = "main-menu"
+ HTMLTag:string = "LI"
+ ImagePath:string = "img/"
+
+ createIMG(src):any {
+ var element = document.createElement("IMG")
+ element.setAttribute("src", this.ImagePath + src)
+ return element
+ }
+
+ createValue(value):any {
+ var element = document.createElement("P")
+ element.textContent = value
+ return element
+ }
+}
+
+class MainMenuItem extends MainMenu {
+ menuKey:string
+ value:string
+ imgSrc:string
+ headline:string
+ id:string
+ tableHeader:string[]
+
+ constructor(menuKey:string, value:string, image:string, headline:string) {
+ super()
+ this.menuKey = menuKey
+ this.value = value
+ this.imgSrc = image
+ this.headline = headline
+ }
+
+ createItem():void {
+ var item = document.createElement("LI")
+ item.addEventListener("click", function() { openThisMenu(item) })
+ item.setAttribute("id", this.id)
+ item.setAttribute("data-menu", this.menuKey)
+ var img = this.createIMG(this.imgSrc)
+ var value = this.createValue(this.value)
+
+ item.appendChild(img)
+ item.appendChild(value)
+
+ var doc = document.getElementById(this.DocumentID)
+ doc.appendChild(item)
+
+ switch(this.menuKey) {
+ case "playlist":
+ this.tableHeader = ["Playlist", "Tuner", "Last Update", "Availability %", "Type", "Streams", "group-title %", "tvg-id %", "Unique ID %"]
+ break
+
+ case "xmltv":
+ this.tableHeader = ["Guide", "Last Update", "Availability %", "Channels", "Programs"]
+ break
+
+ case "filter":
+ this.tableHeader = ["Filter Name", "Filter Type", "Filter"]
+ break
+
+ case "users":
+ this.tableHeader = ["Username", "Password", "WEB", "PMS", "M3U", "XML", "API"]
+ break
+
+ case "mapping":
+ this.tableHeader = ["BULK", "Ch. No.", "Logo", "Channel Name", "Playlist", "Group Title", "XMLTV File", "XMLTV ID", "Edit"]
+ break
+
+ }
+
+ //console.log(this.menuKey, this.tableHeader);
+
+ }
+}
+
+var SHELL_LAYOUT_READY:boolean = false
+
+function setLayoutMenuState(open:boolean) {
+ if (document.body == null) {
+ return
+ }
+
+ if (open == true) {
+ document.body.classList.add("menu-open")
+ } else {
+ document.body.classList.remove("menu-open")
+ }
+}
+
+function toggleLayoutMenu() {
+ if (document.body == null) {
+ return
+ }
+
+ var isOpen:boolean = document.body.classList.contains("menu-open")
+ setLayoutMenuState(!isOpen)
+}
+
+function closeLayoutMenuIfMobile() {
+ if (window.innerWidth <= 900) {
+ setLayoutMenuState(false)
+ }
+}
+
+function setActiveMenu(menuID:string) {
+
+ ACTIVE_MENU_ID = menuID.toString()
+ var menu = document.getElementById("main-menu")
+ if (menu == null) {
+ return
+ }
+
+ var items = menu.getElementsByTagName("LI")
+ for (var i = 0; i < items.length; i++) {
+ items[i].classList.remove("menu-active")
+ }
+
+ var activeItem = document.getElementById(ACTIVE_MENU_ID)
+ var activeMenuKey:string = ""
+ if (activeItem != null) {
+ activeItem.classList.add("menu-active")
+ var menuKeyValue = activeItem.getAttribute("data-menu")
+ if (menuKeyValue != null) {
+ activeMenuKey = menuKeyValue
+ }
+ }
+
+ if (document.body != null) {
+ if (activeMenuKey.length > 0) {
+ document.body.setAttribute("data-active-menu", activeMenuKey)
+ } else {
+ document.body.removeAttribute("data-active-menu")
+ }
+
+ if (activeMenuKey == "log") {
+ document.body.classList.add("menu-log-focus")
+ } else {
+ document.body.classList.remove("menu-log-focus")
+ }
+ }
+}
+
+function renderStatusCards() {
+ var wrapper = document.getElementById("status-cards")
+ if (wrapper == null || SERVER.hasOwnProperty("clientInfo") == false) {
+ return
+ }
+
+ var info = SERVER["clientInfo"]
+ var errors:number = parseInt(info["errors"], 10)
+ var warnings:number = parseInt(info["warnings"], 10)
+ var cards:any[] = [
+ {label: "Streams", value: info["streams"], tone: "ok"},
+ {label: "EPG Source", value: info["epgSource"], tone: "neutral"},
+ {label: "XEPG Channels", value: info["xepg"], tone: "ok"},
+ {label: "Errors", value: info["errors"], tone: errors > 0 ? "error" : "ok"},
+ {label: "Warnings", value: info["warnings"], tone: warnings > 0 ? "warn" : "ok"},
+ {label: "DVR", value: info["DVR"], tone: "neutral"},
+ ]
+
+ wrapper.innerHTML = ""
+ cards.forEach(card => {
+
+ var box = document.createElement("DIV")
+ box.className = "status-card status-card-" + card.tone
+
+ var label = document.createElement("P")
+ label.className = "status-card-label"
+ label.innerText = card.label
+
+ var value = document.createElement("P")
+ value.className = "status-card-value"
+ if (card.value == undefined || card.value == "") {
+ value.innerText = "-"
+ } else {
+ value.innerText = card.value
+ }
+
+ box.appendChild(label)
+ box.appendChild(value)
+ wrapper.appendChild(box)
+ });
+
+}
+
+function initShellLayout() {
+
+ if (SHELL_LAYOUT_READY == true) {
+ return
+ }
+
+ var toggle = document.getElementById("menu-toggle")
+ if (toggle != null) {
+ toggle.onclick = function() {
+ toggleLayoutMenu()
+ }
+ }
+
+ var overlay = document.getElementById("layout-overlay")
+ if (overlay != null) {
+ overlay.onclick = function() {
+ setLayoutMenuState(false)
+ }
+ }
+
+ document.addEventListener("keydown", function(event) {
+
+ if (event.key == "Escape") {
+ setLayoutMenuState(false)
+ showElement("popup", false)
+ return
+ }
+
+ if (event.key == "/") {
+ var target = event.target as HTMLElement
+ var onInput = target.tagName == "INPUT" || target.tagName == "TEXTAREA" || target.tagName == "SELECT"
+
+ if (onInput == true) {
+ return
+ }
+
+ var search = document.getElementById("searchMapping")
+ if (search != null) {
+ event.preventDefault();
+ (search as HTMLInputElement).focus()
+ }
+ }
+
+ });
+
+ setConnectionState("idle")
+ SHELL_LAYOUT_READY = true
+}
+
+function shouldPollLogs():boolean {
+ if (document.hidden == true) {
+ return false
+ }
+
+ if (document.getElementById("content_log") == null) {
+ return false
+ }
+
+ if (ACTIVE_MENU_ID.length < 1) {
+ return false
+ }
+
+ var activeItem = document.getElementById(ACTIVE_MENU_ID)
+ if (activeItem == null) {
+ return false
+ }
+
+ return activeItem.getAttribute("data-menu") == "log"
+}
+
+function PageReady() {
+
+ initShellLayout()
+
+ var server:Server = new Server("getServerConfig")
+ server.request(new Object())
+
+ var bootstrapAttempts:number = 0
+ var maxBootstrapAttempts:number = 5
+ var bootstrapTimer:number = window.setInterval(function() {
+
+ if (SERVER.hasOwnProperty("clientInfo") == true) {
+ window.clearInterval(bootstrapTimer)
+ return
+ }
+
+ if (WS.hasQueued("getServerConfig")) {
+ return
+ }
+
+ bootstrapAttempts++
+ var retryServer:Server = new Server("getServerConfig")
+ retryServer.request(new Object())
+
+ if (bootstrapAttempts >= maxBootstrapAttempts) {
+ window.clearInterval(bootstrapTimer)
+ }
+
+ }, 3000)
+
+ window.addEventListener("resize", function(){
+ if (window.innerWidth > 900) {
+ setLayoutMenuState(false)
+ }
+ calculateWrapperHeight();
+ }, true);
+
+ setInterval(function(){
+ if (shouldPollLogs() == false) {
+ return
+ }
+ updateLog()
+ }, 10000);
+
+
+ return
+}
+
+function isClientInfoHttpURL(value:string):boolean {
+ return /^https?:\/\//i.test(value)
+}
+
+function setClientInfoValue(key:string, value:any) {
+ var element = document.getElementById(key)
+ if (element == null) {
+ return
+ }
+
+ var textValue = ""
+ if (value != undefined && value != null) {
+ textValue = String(value)
+ }
+
+ if ((key == "m3u-url" || key == "xepg-url") && isClientInfoHttpURL(textValue)) {
+ element.innerHTML = ""
+ var anchor = document.createElement("a")
+ anchor.href = textValue
+ anchor.target = "_blank"
+ anchor.rel = "noopener noreferrer"
+ anchor.textContent = textValue
+ element.appendChild(anchor)
+ return
+ }
+
+ element.textContent = textValue
+}
+
+// menuItemVisible reports whether a menu entry applies to the current server state.
+function menuItemVisible(item:MainMenuItem):boolean {
+ switch (item.menuKey) {
+
+ case "users":
+ case "logout":
+ return SERVER["settings"]["authentication.web"] == true
+
+ case "mapping":
+ case "xmltv":
+ return SERVER["clientInfo"]["epgSource"] == "XEPG"
+
+ default:
+ return true
+ }
+}
+
+// renderedMenuKeys lists the menu entries currently in the document, in order.
+function renderedMenuKeys(menu:HTMLElement):string[] {
+ var keys:string[] = []
+ var items = menu.getElementsByTagName("LI")
+ for (var i = 0; i < items.length; i++) {
+ keys.push(items[i].getAttribute("data-menu"))
+ }
+ return keys
+}
+
+function createLayout() {
+
+ // Client Info
+ var obj = SERVER["clientInfo"]
+ var keys = getObjKeys(obj);
+ for (var i = 0; i < keys.length; i++) {
+
+ setClientInfoValue(keys[i], obj[keys[i]])
+
+ }
+ renderStatusCards()
+
+ var menu = document.getElementById("main-menu")
+ if (menu == null) {
+ return
+ }
+
+ // Menu: rebuilt only when the set of visible entries changes, so that
+ // keyboard focus inside the list survives routine server responses.
+ var visibleKeys:string[] = []
+ for (let i = 0; i < menuItems.length; i++) {
+ menuItems[i].id = i
+ if (menuItemVisible(menuItems[i])) {
+ visibleKeys.push(menuItems[i].menuKey)
+ }
+ }
+
+ if (visibleKeys.join(",") != renderedMenuKeys(menu).join(",")) {
+ menu.innerHTML = ""
+ for (let i = 0; i < menuItems.length; i++) {
+ if (menuItemVisible(menuItems[i])) {
+ menuItems[i].createItem()
+ }
+ }
+ }
+
+ if (ACTIVE_MENU_ID.length > 0 && document.getElementById(ACTIVE_MENU_ID)) {
+ setActiveMenu(ACTIVE_MENU_ID)
+ }
+
+ var content = document.getElementById("content")
+ if (ACTIVE_MENU_ID.length == 0 && content != null) {
+
+ if (content.innerHTML.replace(/\\s/g, "").length == 0) {
+ var firstItem = menu.getElementsByTagName("LI")[0] as HTMLElement
+ if (firstItem != undefined) {
+ firstItem.click()
+ }
+ }
+
+ }
+
+ return
+}
diff --git a/ts/menu_ts.ts b/ts/menu_ts.ts
deleted file mode 100644
index 012a55d..0000000
--- a/ts/menu_ts.ts
+++ /dev/null
@@ -1,2477 +0,0 @@
-
-class MainMenu {
- DocumentID:string = "main-menu"
- HTMLTag:string = "LI"
- ImagePath:string = "img/"
-
- createIMG(src):any {
- var element = document.createElement("IMG")
- element.setAttribute("src", this.ImagePath + src)
- return element
- }
-
- createValue(value):any {
- var element = document.createElement("P")
- element.textContent = value
- return element
- }
-}
-
-class MainMenuItem extends MainMenu {
- menuKey:string
- value:string
- imgSrc:string
- headline:string
- id:string
- tableHeader:string[]
-
- constructor(menuKey:string, value:string, image:string, headline:string) {
- super()
- this.menuKey = menuKey
- this.value = value
- this.imgSrc = image
- this.headline = headline
- }
-
- createItem():void {
- var item = document.createElement("LI")
- item.setAttribute("onclick", "javascript: openThisMenu(this)")
- item.setAttribute("id", this.id)
- item.setAttribute("data-menu", this.menuKey)
- var img = this.createIMG(this.imgSrc)
- var value = this.createValue(this.value)
-
- item.appendChild(img)
- item.appendChild(value)
-
- var doc = document.getElementById(this.DocumentID)
- doc.appendChild(item)
-
- switch(this.menuKey) {
- case "playlist":
- this.tableHeader = ["Playlist", "Tuner", "Last Update", "Availability %", "Type", "Streams", "group-title %", "tvg-id %", "Unique ID %"]
- break
-
- case "xmltv":
- this.tableHeader = ["Guide", "Last Update", "Availability %", "Channels", "Programs"]
- break
-
- case "filter":
- this.tableHeader = ["Filter Name", "Filter Type", "Filter"]
- break
-
- case "users":
- this.tableHeader = ["Username", "Password", "WEB", "PMS", "M3U", "XML", "API"]
- break
-
- case "mapping":
- this.tableHeader = ["BULK", "Ch. No.", "Logo", "Channel Name", "Playlist", "Group Title", "XMLTV File", "XMLTV ID", "Edit"]
- break
-
- }
-
- //console.log(this.menuKey, this.tableHeader);
-
- }
-}
-
-class Content {
-
- DocumentID:string = "content"
- TableID:string = "content_table"
- DivID:string
- headerClass:string = "content_table_header"
- interactionID:string = "content-interaction"
-
- createHeadline(value):any {
- var element = document.createElement("H3")
- element.textContent = value
- return element
- }
-
- createHR():any {
- var element = document.createElement("HR")
- return element
- }
-
- createInteraction():any {
- var element = document.createElement("DIV")
- element.setAttribute("id", this.interactionID)
- return element
- }
-
- createDIV():any {
- var element = document.createElement("DIV")
- element.id = this.DivID
- return element
- }
-
- createTABLE():any {
- var element = document.createElement("TABLE")
- element.id = this.TableID
- return element
- }
-
- createTableRow():any {
- var element = document.createElement("TR")
- element.className = this.headerClass
- return element
- }
-
- createTableContent(menuKey:string):string[] {
-
- var data = new Object()
- var rows = new Array()
-
- switch(menuKey) {
- case "playlist":
- var fileTypes = new Array("m3u", "hdhr")
-
- fileTypes.forEach(fileType => {
-
- data = SERVER["settings"]["files"][fileType]
-
- var keys = getObjKeys(data)
-
- keys.forEach(key => {
- var tr = document.createElement("TR")
- tr.id = key
-
- tr.setAttribute('onclick', 'javascript: openPopUp("' + fileType + '", this)')
-
- var cell:Cell = new Cell()
- cell.child = true
- cell.childType = "P"
- cell.value = data[key]["name"]
- tr.appendChild(cell.createCell())
-
- var cell:Cell = new Cell()
- cell.child = true
- cell.childType = "P"
- if (SERVER["settings"]["buffer"] != "-") {
- cell.value = data[key]["tuner"]
- } else {
- cell.value = "-"
- }
-
- tr.appendChild(cell.createCell())
-
- var cell:Cell = new Cell()
- cell.child = true
- cell.childType = "P"
- cell.value = data[key]["last.update"]
- tr.appendChild(cell.createCell())
-
- var cell:Cell = new Cell()
- cell.child = true
- cell.childType = "P"
- cell.value = data[key]["provider.availability"]
- tr.appendChild(cell.createCell())
-
- var cell:Cell = new Cell()
- cell.child = true
- cell.childType = "P"
- cell.value = data[key]["type"].toUpperCase();
- tr.appendChild(cell.createCell())
-
- var cell:Cell = new Cell()
- cell.child = true
- cell.childType = "P"
- cell.value = data[key]["compatibility"]["streams"]
- tr.appendChild(cell.createCell())
-
- var cell:Cell = new Cell()
- cell.child = true
- cell.childType = "P"
- cell.value = data[key]["compatibility"]["group.title"]
- tr.appendChild(cell.createCell())
-
- var cell:Cell = new Cell()
- cell.child = true
- cell.childType = "P"
- cell.value = data[key]["compatibility"]["tvg.id"]
- tr.appendChild(cell.createCell())
-
- var cell:Cell = new Cell()
- cell.child = true
- cell.childType = "P"
- cell.value = data[key]["compatibility"]["stream.id"]
- tr.appendChild(cell.createCell())
-
- rows.push(tr)
- });
-
- });
- break
-
- case "filter":
- delete SERVER["settings"]["filter"][-1]
- data = SERVER["settings"]["filter"]
- var keys = getObjKeys(data)
- keys.forEach(key => {
- var tr = document.createElement("TR")
- tr.id = key
-
- tr.setAttribute('onclick', 'javascript: openPopUp("' + data[key]["type"] + '", this)')
-
- var cell:Cell = new Cell()
- cell.child = true
- cell.childType = "P"
- cell.value = data[key]["name"]
- tr.appendChild(cell.createCell())
-
- var cell:Cell = new Cell()
- cell.child = true
- cell.childType = "P"
- switch (data[key]["type"]) {
- case "custom-filter":
- cell.value = "Custom"
- break;
-
- case "group-title":
- cell.value = "Group"
- break;
-
- default:
- break;
- }
-
- tr.appendChild(cell.createCell())
-
- var cell:Cell = new Cell()
- cell.child = true
- cell.childType = "P"
- cell.value = data[key]["filter"]
- tr.appendChild(cell.createCell())
-
- rows.push(tr)
-
- });
- break
-
- case "xmltv":
- var fileTypes = new Array("xmltv")
-
- fileTypes.forEach(fileType => {
-
- data = SERVER["settings"]["files"][fileType]
-
- var keys = getObjKeys(data)
-
- keys.forEach(key => {
- var tr = document.createElement("TR")
-
- tr.id = key
- tr.setAttribute('onclick', 'javascript: openPopUp("' + fileType + '", this)')
-
- var cell:Cell = new Cell()
- cell.child = true
- cell.childType = "P"
- cell.value = data[key]["name"]
- tr.appendChild(cell.createCell())
-
- var cell:Cell = new Cell()
- cell.child = true
- cell.childType = "P"
- cell.value = data[key]["last.update"]
- tr.appendChild(cell.createCell())
-
- var cell:Cell = new Cell()
- cell.child = true
- cell.childType = "P"
- cell.value = data[key]["provider.availability"]
- tr.appendChild(cell.createCell())
-
- var cell:Cell = new Cell()
- cell.child = true
- cell.childType = "P"
- cell.value = data[key]["compatibility"]["xmltv.channels"]
- tr.appendChild(cell.createCell())
-
- var cell:Cell = new Cell()
- cell.child = true
- cell.childType = "P"
- cell.value = data[key]["compatibility"]["xmltv.programs"]
- tr.appendChild(cell.createCell())
-
- rows.push(tr)
- });
-
- });
- break
-
- case "users":
- var fileTypes = new Array("users")
-
- fileTypes.forEach(fileType => {
- data = SERVER[fileType]
-
- var keys = getObjKeys(data)
-
- keys.forEach(key => {
- var tr = document.createElement("TR")
- tr.id = key
- tr.setAttribute('onclick', 'javascript: openPopUp("' + fileType + '", this)')
-
- var cell:Cell = new Cell()
- cell.child = true
- cell.childType = "P"
- cell.value = data[key]["data"]["username"]
- tr.appendChild(cell.createCell())
-
- var cell:Cell = new Cell()
- cell.child = true
- cell.childType = "P"
- cell.value = "******"
- tr.appendChild(cell.createCell())
-
- var cell:Cell = new Cell()
- cell.child = true
- cell.childType = "P"
- if (data[key]["data"]["authentication.web"] == true) {
- cell.value = "✓"
- } else {
- cell.value = "-"
- }
- tr.appendChild(cell.createCell())
-
- var cell:Cell = new Cell()
- cell.child = true
- cell.childType = "P"
- if (data[key]["data"]["authentication.pms"] == true) {
- cell.value = "✓"
- } else {
- cell.value = "-"
- }
- tr.appendChild(cell.createCell())
-
- var cell:Cell = new Cell()
- cell.child = true
- cell.childType = "P"
- if (data[key]["data"]["authentication.m3u"] == true) {
- cell.value = "✓"
- } else {
- cell.value = "-"
- }
- tr.appendChild(cell.createCell())
-
- var cell:Cell = new Cell()
- cell.child = true
- cell.childType = "P"
- if (data[key]["data"]["authentication.xml"] == true) {
- cell.value = "✓"
- } else {
- cell.value = "-"
- }
- tr.appendChild(cell.createCell())
-
- var cell:Cell = new Cell()
- cell.child = true
- cell.childType = "P"
- if (data[key]["data"]["authentication.api"] == true) {
- cell.value = "✓"
- } else {
- cell.value = "-"
- }
- tr.appendChild(cell.createCell())
-
- rows.push(tr)
- });
-
- });
- break
-
- case "mapping":
- BULK_EDIT = false
- createSearchObj()
- checkUndo("epgMapping")
- console.log("MAPPING")
- data = SERVER["xepg"]["epgMapping"]
-
- var keys = getObjKeys(data)
- keys.forEach(key => {
- var tr = document.createElement("TR")
- tr.id = key
-
- //tr.setAttribute('oncontextmenu', 'javascript: rightClick(this)')
-
- switch (data[key]["x-active"]) {
- case true:
- tr.className = "activeEPG"
- break;
-
- case false:
- tr.className = "notActiveEPG"
- break;
- }
-
- // Bulk
- var cell:Cell = new Cell()
- cell.child = true
- cell.childType = "BULK"
- cell.value = false
- tr.appendChild(cell.createCell())
-
- // Kanalnummer
- var cell:Cell = new Cell()
- cell.child = true
- cell.childType = "INPUTCHANNEL"
- cell.value = data[key]["x-channelID"]
- //td.setAttribute('onclick', 'javascript: changeChannelNumber("' + key + '", this)')
- tr.appendChild(cell.createCell())
-
- // Logo
- var cell:Cell = new Cell()
- cell.child = true
- cell.childType = "IMG"
- cell.imageURL = data[key]["tvg-logo"]
- tr.appendChild(cell.createCell())
-
- // Kanalname
- var cell:Cell = new Cell()
- cell.child = true
- cell.childType = "P"
- cell.className = data[key]["x-category"]
- cell.value = data[key]["x-name"]
- tr.appendChild(cell.createCell())
-
-
- // Playlist
- var cell:Cell = new Cell()
- cell.child = true
- cell.childType = "P"
- //cell.value = data[key]["_file.m3u.name"]
- cell.value = getValueFromProviderFile(data[key]["_file.m3u.id"], "m3u", "name")
- tr.appendChild(cell.createCell())
-
-
- // Gruppe (group-title)
- var cell:Cell = new Cell()
- cell.child = true
- cell.childType = "P"
- cell.value = data[key]["x-group-title"]
- tr.appendChild(cell.createCell())
-
- // XMLTV Datei
- var cell:Cell = new Cell()
- cell.child = true
- cell.childType = "P"
-
- if (data[key]["x-xmltv-file"] != "-") {
- cell.value = getValueFromProviderFile(data[key]["x-xmltv-file"], "xmltv", "name")
- } else {
- cell.value = data[key]["x-xmltv-file"]
- }
-
- tr.appendChild(cell.createCell())
-
- // XMLTV Kanal
- var cell:Cell = new Cell()
- cell.child = true
- cell.childType = "P"
- //var value = str.substring(1, 4);
- var value = data[key]["x-mapping"]
- if (value.length > 20) {
- value = data[key]["x-mapping"].substring(0, 20) + "..."
- }
- cell.value = value
- tr.appendChild(cell.createCell())
-
- var cell:Cell = new Cell()
- cell.child = true
- cell.childType = "EDIT"
- cell.value = "Edit"
- var editTd = cell.createCell()
- var editButton = (editTd.firstChild as HTMLInputElement)
- editButton.setAttribute('onclick', 'javascript: openPopUp("mapping", this)')
- editButton.setAttribute("id", key)
- editButton.setAttribute("aria-label", "Edit " + data[key]["x-name"])
- tr.appendChild(editTd)
-
- rows.push(tr)
- });
-
- break
-
- case "settings":
- alert()
- break
-
- default:
- console.log("Table content (menuKey):", menuKey);
-
- break
-
- }
-
- return rows
-
- }
-
- return
-}
-
-class Cell {
- child:Boolean
- childType:string
- value:any
- className:string
- tdClassName:string
- imageURL:string
- onclick:boolean
- onclickFunktion:string
-
- createCell():any {
- var td = document.createElement("TD")
-
-
- if (this.child == true) {
- var element:any
-
- switch(this.childType){
- case "P":
- element = document.createElement(this.childType);
- element.textContent = this.value
- element.className = this.className
- break
-
- case "INPUT":
- element = document.createElement(this.childType);
- (element as HTMLInputElement).value = this.value;
- (element as HTMLInputElement).type = "text";
- break
-
- case "INPUTCHANNEL":
- element = document.createElement("INPUT");
- (element as HTMLInputElement).setAttribute("onchange", "javscript: changeChannelNumber(this)");
- (element as HTMLInputElement).value = this.value;
- (element as HTMLInputElement).type = "text";
- break
-
- case "BULK":
- element = document.createElement("INPUT");
- (element as HTMLInputElement).checked = this.value;
- (element as HTMLInputElement).type = "checkbox";
- (element as HTMLInputElement).className = "bulk hideBulk";
- (element as HTMLInputElement).addEventListener("click", function(event) {
- scheduleChannelRangeSelection((element as HTMLInputElement), (event as MouseEvent))
- })
- break
-
- case "BULK_HEAD":
- element = document.createElement("INPUT");
- (element as HTMLInputElement).checked = this.value;
- (element as HTMLInputElement).type = "checkbox";
- (element as HTMLInputElement).className = "bulk hideBulk";
- (element as HTMLInputElement).setAttribute("onclick", "javascript: selectAllChannels()")
- break
-
- case "IMG":
- element = document.createElement(this.childType);
- element.setAttribute("src", this.imageURL)
- if (this.imageURL != "") {
- element.setAttribute("onerror", "javascript: this.onerror=null;this.src=''" )
- //onerror="this.onerror=null;this.src='missing.gif';"
- }
- break
-
- case "EDIT":
- element = document.createElement("INPUT");
- (element as HTMLInputElement).type = "button";
- (element as HTMLInputElement).value = this.value;
- (element as HTMLInputElement).className = "mapping-edit-button";
- break
- }
-
- td.appendChild(element)
-
- } else {
- td.textContent = this.value
- }
-
- if (this.onclick == true) {
- td.setAttribute("onclick", this.onclickFunktion)
- td.className = "pointer"
- }
-
- if (this.tdClassName != undefined) {
- td.className = this.tdClassName
- }
-
- return td
- }
-
- return
-}
-
-class ShowContent extends Content {
- menuID:number
-
- constructor(menuID:number) {
- super()
- this.menuID = menuID
- }
-
- createInput(type:string, name:string, value:string,):any {
-
- var input = document.createElement("INPUT")
- input.setAttribute("type", type)
- input.setAttribute("name", name)
- input.setAttribute("value", value)
- return input
- }
-
- show():void {
- COLUMN_TO_SORT = -1
- // Alten Inhalt löschen
- var doc = document.getElementById(this.DocumentID)
- doc.innerHTML = ""
- showPreview(false)
-
- // Überschrift
- var headline:string[] = menuItems[this.menuID].headline
-
- var menuKey = menuItems[this.menuID].menuKey
- var h = this.createHeadline(headline)
- doc.appendChild(h)
-
- var hr = this.createHR()
- doc.appendChild(hr)
-
- // Interaktion
- var div =this.createInteraction()
- doc.appendChild(div)
- var interaction = document.getElementById(this.interactionID)
- switch (menuKey) {
- case "playlist":
- var input = this.createInput("button", menuKey, "New")
- input.setAttribute("id", "-")
- input.setAttribute("onclick", 'javascript: openPopUp("playlist")')
- interaction.appendChild(input)
- break;
-
- case "filter":
- var input = this.createInput("button", menuKey, "New")
- input.setAttribute("id", -1)
- input.setAttribute("onclick", 'javascript: openPopUp("filter", this)')
- interaction.appendChild(input)
- break;
-
-
- case "xmltv":
- var input = this.createInput("button", menuKey, "New")
- input.setAttribute("id", "xmltv")
- input.setAttribute("onclick", 'javascript: openPopUp("xmltv")')
- interaction.appendChild(input)
- break;
-
- case "users":
- var input = this.createInput("button", menuKey, "New")
- input.setAttribute("id", "users")
- input.setAttribute("onclick", 'javascript: openPopUp("users")')
- interaction.appendChild(input)
- break;
-
- case "mapping":
- showElement("loading", true)
- var input = this.createInput("button", menuKey, "Save")
- input.setAttribute("onclick", 'javascript: savePopupData("mapping", "", "")')
- interaction.appendChild(input)
-
- var input = this.createInput("button", menuKey, "Bulk Edit")
- input.setAttribute("onclick", 'javascript: bulkEdit()')
- interaction.appendChild(input)
-
- var input = this.createInput("search", "search", "")
- input.setAttribute("id", "searchMapping")
- input.setAttribute("placeholder", "Search")
- input.className = "search"
- input.setAttribute("oninput", 'javascript: searchInMapping()')
- interaction.appendChild(input)
- break;
-
- case "settings":
- var input = this.createInput("button", menuKey, "Save")
- input.setAttribute("onclick", 'javascript: saveSettings();')
- interaction.appendChild(input)
-
- var input = this.createInput("button", menuKey, "Backup")
- input.setAttribute("onclick", 'javascript: backup();')
- interaction.appendChild(input)
-
- var input = this.createInput("button", menuKey, "Restore")
- input.setAttribute("onclick", 'javascript: restore();')
- interaction.appendChild(input)
-
- var wrapper = document.createElement("DIV")
- wrapper.setAttribute("id", "box-wrapper")
- doc.appendChild(wrapper)
-
- this.DivID = "content_settings"
- var settings = this.createDIV()
- wrapper.appendChild(settings)
-
- showSettings()
-
- return
- break
-
- case "log":
- var input = this.createInput("button", menuKey, "Reset Logs")
- input.setAttribute("onclick", 'javascript: resetLogs();')
- interaction.appendChild(input)
-
- var wrapper = document.createElement("DIV")
- wrapper.setAttribute("id", "box-wrapper")
- doc.appendChild(wrapper)
-
- this.DivID = "content_log"
- var logs = this.createDIV()
- wrapper.appendChild(logs)
-
- showLogs(true)
-
- return
- break
-
- case "logout":
- location.reload()
- document.cookie = "Token= ; expires = Thu, 01 Jan 1970 00:00:00 GMT"
- break
-
- default:
- console.log("Show content (menuKey):", menuKey);
- break;
- }
-
- // Tabelle erstellen (falls benötigt)
- var tableHeader:string[] = menuItems[this.menuID].tableHeader
- if (tableHeader.length > 0) {
- var wrapper = document.createElement("DIV")
- doc.appendChild(wrapper)
- wrapper.setAttribute("id", "box-wrapper")
-
- var table = this.createTABLE()
- wrapper.appendChild(table)
-
- var header = this.createTableRow()
- table.appendChild(header)
-
- // Kopfzeile der Tablle
- tableHeader.forEach(element => {
- var cell:Cell = new Cell()
- cell.child = true
- cell.childType = "P"
- cell.value = element
- if (element == "BULK") {
- cell.childType = "BULK_HEAD";
- cell.value = false
- }
-
- if (menuKey == "mapping") {
-
- if (element == "Ch. No.") {
- cell.onclick = true
- cell.onclickFunktion = "javascript: sortTable(1);"
- cell.tdClassName = "sortThis"
- }
-
- if (element == "Channel Name") {
- cell.onclick = true
- cell.onclickFunktion = "javascript: sortTable(3);"
- }
-
- if (element == "Playlist") {
- cell.onclick = true
- cell.onclickFunktion = "javascript: sortTable(4);"
- }
-
- if (element == "Group Title") {
- cell.onclick = true
- cell.onclickFunktion = "javascript: sortTable(5);"
- }
-
- }
-
- header.appendChild(cell.createCell())
- });
-
- table.appendChild(header)
-
- // Inhalt der Tabelle
- var rows:any = this.createTableContent(menuKey)
- rows.forEach(tr => {
- table.appendChild(tr)
- });
-
- }
-
- switch (menuKey) {
- case "mapping":
- sortTable(1)
- break;
-
- case "filter":
- showPreview(true)
- sortTable(0)
- break
-
- default:
- COLUMN_TO_SORT = -1
- sortTable(0)
- break;
- }
-
- showElement("loading", false)
- }
-
-}
-
-var SHELL_LAYOUT_READY:boolean = false
-
-function setLayoutMenuState(open:boolean) {
- if (document.body == null) {
- return
- }
-
- if (open == true) {
- document.body.classList.add("menu-open")
- } else {
- document.body.classList.remove("menu-open")
- }
-}
-
-function toggleLayoutMenu() {
- if (document.body == null) {
- return
- }
-
- var isOpen:boolean = document.body.classList.contains("menu-open")
- setLayoutMenuState(!isOpen)
-}
-
-function closeLayoutMenuIfMobile() {
- if (window.innerWidth <= 900) {
- setLayoutMenuState(false)
- }
-}
-
-function setActiveMenu(menuID:string) {
-
- ACTIVE_MENU_ID = menuID.toString()
- var menu = document.getElementById("main-menu")
- if (menu == null) {
- return
- }
-
- var items = menu.getElementsByTagName("LI")
- for (var i = 0; i < items.length; i++) {
- items[i].classList.remove("menu-active")
- }
-
- var activeItem = document.getElementById(ACTIVE_MENU_ID)
- var activeMenuKey:string = ""
- if (activeItem != null) {
- activeItem.classList.add("menu-active")
- var menuKeyValue = activeItem.getAttribute("data-menu")
- if (menuKeyValue != null) {
- activeMenuKey = menuKeyValue
- }
- }
-
- if (document.body != null) {
- if (activeMenuKey.length > 0) {
- document.body.setAttribute("data-active-menu", activeMenuKey)
- } else {
- document.body.removeAttribute("data-active-menu")
- }
-
- if (activeMenuKey == "log") {
- document.body.classList.add("menu-log-focus")
- } else {
- document.body.classList.remove("menu-log-focus")
- }
- }
-}
-
-function renderStatusCards() {
- var wrapper = document.getElementById("status-cards")
- if (wrapper == null || SERVER.hasOwnProperty("clientInfo") == false) {
- return
- }
-
- var info = SERVER["clientInfo"]
- var errors:number = parseInt(info["errors"], 10)
- var warnings:number = parseInt(info["warnings"], 10)
- var cards:any[] = [
- {label: "Streams", value: info["streams"], tone: "ok"},
- {label: "EPG Source", value: info["epgSource"], tone: "neutral"},
- {label: "XEPG Channels", value: info["xepg"], tone: "ok"},
- {label: "Errors", value: info["errors"], tone: errors > 0 ? "error" : "ok"},
- {label: "Warnings", value: info["warnings"], tone: warnings > 0 ? "warn" : "ok"},
- {label: "DVR", value: info["DVR"], tone: "neutral"},
- ]
-
- wrapper.innerHTML = ""
- cards.forEach(card => {
-
- var box = document.createElement("DIV")
- box.className = "status-card status-card-" + card.tone
-
- var label = document.createElement("P")
- label.className = "status-card-label"
- label.innerText = card.label
-
- var value = document.createElement("P")
- value.className = "status-card-value"
- if (card.value == undefined || card.value == "") {
- value.innerText = "-"
- } else {
- value.innerText = card.value
- }
-
- box.appendChild(label)
- box.appendChild(value)
- wrapper.appendChild(box)
- });
-
-}
-
-function initShellLayout() {
-
- if (SHELL_LAYOUT_READY == true) {
- return
- }
-
- var toggle = document.getElementById("menu-toggle")
- if (toggle != null) {
- toggle.onclick = function() {
- toggleLayoutMenu()
- }
- }
-
- var overlay = document.getElementById("layout-overlay")
- if (overlay != null) {
- overlay.onclick = function() {
- setLayoutMenuState(false)
- }
- }
-
- document.addEventListener("keydown", function(event) {
-
- if (event.key == "Escape") {
- setLayoutMenuState(false)
- showElement("popup", false)
- return
- }
-
- if (event.key == "/") {
- var target = event.target as HTMLElement
- var onInput = target.tagName == "INPUT" || target.tagName == "TEXTAREA" || target.tagName == "SELECT"
-
- if (onInput == true) {
- return
- }
-
- var search = document.getElementById("searchMapping")
- if (search != null) {
- event.preventDefault();
- (search as HTMLInputElement).focus()
- }
- }
-
- });
-
- setConnectionState("idle")
- SHELL_LAYOUT_READY = true
-}
-
-function shouldPollLogs():boolean {
- if (document.hidden == true) {
- return false
- }
-
- if (document.getElementById("content_log") == null) {
- return false
- }
-
- if (ACTIVE_MENU_ID.length < 1) {
- return false
- }
-
- var activeItem = document.getElementById(ACTIVE_MENU_ID)
- if (activeItem == null) {
- return false
- }
-
- return activeItem.getAttribute("data-menu") == "log"
-}
-
-function PageReady() {
-
- initShellLayout()
-
- var server:Server = new Server("getServerConfig")
- server.request(new Object())
-
- var bootstrapAttempts:number = 0
- var maxBootstrapAttempts:number = 5
- var bootstrapTimer:number = window.setInterval(function() {
-
- if (SERVER.hasOwnProperty("clientInfo") == true) {
- window.clearInterval(bootstrapTimer)
- return
- }
-
- if (WS.hasQueued("getServerConfig")) {
- return
- }
-
- bootstrapAttempts++
- var retryServer:Server = new Server("getServerConfig")
- retryServer.request(new Object())
-
- if (bootstrapAttempts >= maxBootstrapAttempts) {
- window.clearInterval(bootstrapTimer)
- }
-
- }, 3000)
-
- window.addEventListener("resize", function(){
- if (window.innerWidth > 900) {
- setLayoutMenuState(false)
- }
- calculateWrapperHeight();
- }, true);
-
- setInterval(function(){
- if (shouldPollLogs() == false) {
- return
- }
- updateLog()
- }, 10000);
-
-
- return
-}
-
-function isClientInfoHttpURL(value:string):boolean {
- return /^https?:\/\//i.test(value)
-}
-
-function setClientInfoValue(key:string, value:any) {
- var element = document.getElementById(key)
- if (element == null) {
- return
- }
-
- var textValue = ""
- if (value != undefined && value != null) {
- textValue = String(value)
- }
-
- if ((key == "m3u-url" || key == "xepg-url") && isClientInfoHttpURL(textValue)) {
- element.innerHTML = ""
- var anchor = document.createElement("a")
- anchor.href = textValue
- anchor.target = "_blank"
- anchor.rel = "noopener noreferrer"
- anchor.textContent = textValue
- element.appendChild(anchor)
- return
- }
-
- element.textContent = textValue
-}
-
-function createLayout() {
-
- // Client Info
- var obj = SERVER["clientInfo"]
- var keys = getObjKeys(obj);
- for (var i = 0; i < keys.length; i++) {
-
- setClientInfoValue(keys[i], obj[keys[i]])
-
- }
- renderStatusCards()
-
- if (!document.getElementById("main-menu")) {
- return
- }
-
-
-
- // Menü erstellen
- document.getElementById("main-menu").innerHTML = ""
- for (let i = 0; i < menuItems.length; i++) {
-
- menuItems[i].id = i
-
- switch (menuItems[i]["menuKey"]) {
-
- case "users":
- case "logout":
- if (SERVER["settings"]["authentication.web"] == true) {
- menuItems[i].createItem()
- }
- break
-
- case "mapping":
- case "xmltv":
- if (SERVER["clientInfo"]["epgSource"] == "XEPG") {
- menuItems[i].createItem()
- }
- break
-
- default:
- menuItems[i].createItem()
- break
- }
-
- }
-
- if (ACTIVE_MENU_ID.length > 0 && document.getElementById(ACTIVE_MENU_ID)) {
- setActiveMenu(ACTIVE_MENU_ID)
- }
-
- var content = document.getElementById("content")
- var menu = document.getElementById("main-menu")
- if (ACTIVE_MENU_ID.length == 0 && content != null && menu != null) {
-
- if (content.innerHTML.replace(/\\s/g, "").length == 0) {
- var firstItem = menu.getElementsByTagName("LI")[0] as HTMLElement
- if (firstItem != undefined) {
- firstItem.click()
- }
- }
-
- }
-
- return
-}
-
-function openThisMenu(element) {
- var id = element.id
- var content:ShowContent = new ShowContent(id)
- setActiveMenu(id)
- content.show()
- closeLayoutMenuIfMobile()
- calculateWrapperHeight()
-
- return
-}
-
-class PopupWindow {
- DocumentID:string = "popup-custom"
- InteractionID:string = "interaction"
- doc = document.getElementById(this.DocumentID)
-
- createTitle(title:string):any {
- var td = document.createElement("TD")
- td.className = "left"
- td.innerHTML = title + ":"
- return td
- }
-
- createContent(element):any {
- var td = document.createElement("TD")
- td.appendChild(element)
- return td
- }
-
- createInteraction():any {
- var div = document.createElement("div")
- div.setAttribute("id", "popup-interaction")
- div.className = "interaction"
- this.doc.appendChild(div)
- }
-}
-
-class PopupContent extends PopupWindow{
-
- table = document.createElement("TABLE")
-
- createHeadline(headline):void {
- this.doc.innerHTML = ""
- var element = document.createElement("H3")
- element.innerHTML = headline.toUpperCase()
- this.doc.appendChild(element)
-
- // Tabelle erstellen
- this.table = document.createElement("TABLE")
- this.doc.appendChild(this.table)
- }
-
- appendRow(title:string, element:any):void {
- var tr = document.createElement("TR")
-
- // Bezeichnung
- if (title.length != 0) {
- tr.appendChild(this.createTitle(title))
- }
-
-
- // Content
- tr.appendChild(this.createContent(element))
- this.table.appendChild(tr)
- }
-
-
- createInput(type:string, name:string, value:string):any {
-
- var input = document.createElement("INPUT")
- if (value == undefined) {
- value = ""
- }
-
- input.setAttribute("type", type)
- input.setAttribute("name", name)
- input.setAttribute("value", value)
- return input
- }
-
- createCheckbox(name:string):any {
- var input = document.createElement("INPUT")
-
- input.setAttribute("type", "checkbox")
- input.setAttribute("name", name)
- return input
- }
-
- createSelect(text:string[], values:string[], set:string, dbKey:string):any {
- var select = document.createElement("SELECT")
- select.setAttribute("name", dbKey)
- for (let i = 0; i < text.length; i++) {
- var option = document.createElement("OPTION")
- option.setAttribute("value", values[i])
- option.innerText = text[i]
- select.appendChild(option)
- }
- if(set != "") {
- (select as HTMLSelectElement).value = set
- }
-
- if (set == undefined) {
- (select as HTMLSelectElement).value = values[0]
- }
-
- return select
- }
-
- selectOption(select:any, value:string):any {
- //select.selectedOptions = value
- var s:HTMLSelectElement = (select as HTMLSelectElement)
- s.options[s.selectedIndex].value = value
- return select
- }
-
- // isHTML must only be true for static text written in the source; never for data.
- description(value:string, isHTML:boolean = false):any {
- var tr = document.createElement("TR")
- var td = document.createElement("TD")
- var span = document.createElement("PRE")
-
- if (isHTML) {
- span.innerHTML = value
- } else {
- span.textContent = value
- }
-
- tr.appendChild(td)
-
- tr.appendChild(this.createContent(span))
-
- this.table.appendChild(tr)
- }
-
- // Interaktion
- addInteraction(element:any) {
- var interaction = document.getElementById("popup-interaction")
- interaction.appendChild(element)
- }
-}
-
-function openPopUp(dataType, element) {
-
- var data:object = new Object();
- var id:any
- switch (element) {
- case undefined:
-
- switch (dataType) {
- case "group-title":
- if (id == undefined) {
- id = -1
- }
- data = getLocalData("filter", id)
- data["type"] = "group-title"
- break;
-
- case "custom-filter":
- if (id == undefined) {
- id = -1
- }
- data = getLocalData("filter", id)
- data["type"] = "custom-filter"
- break;
-
- default:
- data["id.provider"] = "-"
- data["type"] = dataType
- id = "-"
- break;
- }
-
- break
-
- default:
- id = element.id
- data = getLocalData(dataType, id)
- break;
- }
-
- var content:PopupContent = new PopupContent()
-
- switch (dataType) {
- case "playlist":
- content.createHeadline("Playlist type")
- // Type
- var text:string[] = ["M3U", "HDHomeRun"]
- var values:string[] = ["javascript: openPopUp('m3u')", "javascript: openPopUp('hdhr')"]
- var select = content.createSelect(text, values, "", "type")
- select.setAttribute("id", "type")
- select.setAttribute("onchange", 'javascript: changeButtonAction(this, "next", "onclick")') // changeButtonAction
- content.appendRow("Type", select)
-
- // Interaktion
- content.createInteraction()
- // Abbrechen
- var input = content.createInput("button", "cancel", "Cancel")
- input.setAttribute("onclick", 'javascript: showElement("popup", false);')
- content.addInteraction(input)
-
- // Weiter
- var input = content.createInput("button", "next", "Next")
- input.setAttribute("onclick", 'javascript: openPopUp("m3u")')
- input.setAttribute("id", 'next')
- content.addInteraction(input)
- break
-
- case "m3u":
- content.createHeadline(dataType)
- // Name
- var dbKey:string = "name"
- var input = content.createInput("text", dbKey, data[dbKey])
- input.setAttribute("placeholder", "Playlist name")
- content.appendRow("Name", input)
-
- // Beschreibung
- var dbKey:string = "description"
- var input = content.createInput("text", dbKey, data[dbKey])
- input.setAttribute("placeholder", "Description")
- content.appendRow("Description", input)
-
- // URL
- var dbKey:string = "file.source"
- var input = content.createInput("text", dbKey, data[dbKey])
- input.setAttribute("placeholder", "File path or URL of the M3U")
- content.appendRow("M3U File", input)
-
- // Tuner
- if (SERVER["settings"]["buffer"] != "-") {
- var text:string[] = new Array()
- var values:string[] = new Array()
-
- for (var i = 1; i <= 100; i++) {
- text.push(i.toString())
- values.push(i.toString())
- }
-
- var dbKey:string = "tuner"
- var select = content.createSelect(text, values, data[dbKey], dbKey)
- select.setAttribute("onfocus", "javascript: return;")
- content.appendRow("Tuner / Streams", select)
- } else {
- var dbKey:string = "tuner"
- if (data[dbKey] == undefined) {
- data[dbKey] = 1
- }
- var input = content.createInput("text", dbKey, data[dbKey])
- input.setAttribute("readonly", "true")
- input.className = "notAvailable"
- content.appendRow("Tuner / Streams", input)
- }
-
- content.description("Number of parallel connections that can be established to the provider.
Only available with activated buffer.
New settings will only be applied after quitting all streams.", true)
-
- // Interaktion
- content.createInteraction()
- // Löschen
- if (data["id.provider"]!= "-") {
- var input = content.createInput("button", "delete", "Delete")
- input.className = "delete"
- input.setAttribute('onclick', 'javascript: savePopupData("m3u", "' + id + '", true, 0)')
- content.addInteraction(input)
- } else {
- var input = content.createInput("button", "back", "Back")
- input.setAttribute("onclick", 'javascript: openPopUp("playlist")')
- content.addInteraction(input)
- }
-
- // Abbrechen
- var input = content.createInput("button", "cancel", "Cancel")
- input.setAttribute("onclick", 'javascript: showElement("popup", false);')
- content.addInteraction(input)
-
- // Aktualisieren
- if (data["id.provider"]!= "-") {
- var input = content.createInput("button", "update", "Update")
- input.setAttribute('onclick', 'javascript: savePopupData("m3u", "' + id + '", false, 1)')
- content.addInteraction(input)
- }
-
- // Speichern
- var input = content.createInput("button", "save", "Save")
- input.setAttribute('onclick', 'javascript: savePopupData("m3u", "' + id + '", false, 0)')
- content.addInteraction(input)
- break
-
- case "hdhr":
- content.createHeadline(dataType)
- // Name
- var dbKey:string = "name"
- var input = content.createInput("text", dbKey, data[dbKey])
- input.setAttribute("placeholder", "Playlist name")
- content.appendRow("Name", input)
-
- // Beschreibung
- var dbKey:string = "description"
- var input = content.createInput("text", dbKey, data[dbKey])
- input.setAttribute("placeholder", "Description")
- content.appendRow("Description", input)
-
- // URL
- var dbKey:string = "file.source"
- var input = content.createInput("text", dbKey, data[dbKey])
- input.setAttribute("placeholder", "IP address and port (192.168.1.10:5004)")
- content.appendRow("HDHomeRun IP", input)
-
- // Tuner
- if (SERVER["settings"]["buffer"] != "-") {
- var text:string[] = new Array()
- var values:string[] = new Array()
-
- for (var i = 1; i <= 100; i++) {
- text.push(i.toString())
- values.push(i.toString())
- }
-
- var dbKey:string = "tuner"
- var select = content.createSelect(text, values, data[dbKey], dbKey)
- select.setAttribute("onfocus", "javascript: return;")
- content.appendRow("Tuner / Streams", select)
- } else {
- var dbKey:string = "tuner"
- if (data[dbKey] == undefined) {
- data[dbKey] = 1
- }
- var input = content.createInput("text", dbKey, data[dbKey])
- input.setAttribute("readonly", "true")
- input.className = "notAvailable"
- content.appendRow("Tuner / Streams", input)
- }
-
- content.description("Number of parallel connections that can be established to the provider.
Only available with activated buffer.
New settings will only be applied after quitting all streams.", true)
-
- // Interaktion
- content.createInteraction()
- // Löschen
- if (data["id.provider"]!= "-") {
- var input = content.createInput("button", "delete", "Delete")
- input.setAttribute('onclick', 'javascript: savePopupData("hdhr", "' + id + '", true, 0)')
- input.className = "delete"
- content.addInteraction(input)
- } else {
- var input = content.createInput("button", "back", "Back")
- input.setAttribute("onclick", 'javascript: openPopUp("playlist")')
- content.addInteraction(input)
- }
-
- // Abbrechen
- var input = content.createInput("button", "cancel", "Cancel")
- input.setAttribute("onclick", 'javascript: showElement("popup", false);')
- content.addInteraction(input)
-
- // Aktualisieren
- if (data["id.provider"]!= "-") {
- var input = content.createInput("button", "update", "Update")
- input.setAttribute('onclick', 'javascript: savePopupData("hdhr", "' + id + '", false, 1)')
- content.addInteraction(input)
- }
-
- // Speichern
- var input = content.createInput("button", "save", "Save")
- input.setAttribute('onclick', 'javascript: savePopupData("hdhr", "' + id + '", false, 0)')
- content.addInteraction(input)
- break
-
- case "filter":
- content.createHeadline(dataType)
-
- // Type
- var dbKey:string = "type"
- var text:string[] = ["M3U: " + "Group Title", "xTeVe: " + "Custom Filter"]
- var values:string[] = ["javascript: openPopUp('group-title')", "javascript: openPopUp('custom-filter')"]
- var select = content.createSelect(text, values, "javascript: openPopUp('group-title')", dbKey)
- select.setAttribute("id", id)
- select.setAttribute("onchange", 'javascript: changeButtonAction(this, "next", "onclick");') // changeButtonAction
- content.appendRow("Type", select)
-
- // Interaktion
- content.createInteraction()
- // Abbrechen
- var input = content.createInput("button", "cancel", "Cancel")
- input.setAttribute("onclick", 'javascript: showElement("popup", false);')
- content.addInteraction(input)
-
- // Weiter
- var input = content.createInput("button", "next", "Next")
- input.setAttribute("onclick", 'javascript: openPopUp("group-title")')
- input.setAttribute("id", 'next')
- content.addInteraction(input)
- break
-
- case "custom-filter":
- case "group-title":
-
- switch (dataType) {
- case "custom-filter":
- content.createHeadline("Custom")
- break;
-
- case "group-title":
- content.createHeadline("Group")
- break;
- }
-
- // Name
- var dbKey:string = "name"
- var input = content.createInput("text", dbKey, data[dbKey])
- input.setAttribute("placeholder", "Filter name")
- content.appendRow("Filter Name", input)
-
- // Beschreibung
- var dbKey:string = "description"
- var input = content.createInput("text", dbKey, data[dbKey])
- input.setAttribute("placeholder", "Description")
- content.appendRow("Description", input)
-
- // Typ
- var dbKey:string = "type"
- var input = content.createInput("hidden", dbKey, data[dbKey])
- content.appendRow("", input)
-
- var filterType = data[dbKey]
-
- switch (filterType) {
-
- case "custom-filter":
- // Groß- Kleinschreibung beachten
- var dbKey:string = "caseSensitive"
- var input = content.createCheckbox(dbKey)
- input.checked = data[dbKey]
- content.appendRow("Case Sensitive", input)
-
- // Filterregel (Benutzerdefiniert)
- var dbKey:string = "filter"
- var input = content.createInput("text", dbKey, data[dbKey])
- input.setAttribute("placeholder", "Sport {HD} !{ES,IT}")
- content.appendRow("Filter Rule", input)
-
- break;
-
- case "group-title":
- //alert(dbKey + " " + filterType)
- // Filter basierend auf den Gruppen in der M3U
- var dbKey:string = "filter"
- var groupsM3U = getLocalData("m3uGroups", "")
- var text:string[] = groupsM3U["text"]
- var values:string[] = groupsM3U["value"]
-
- var select = content.createSelect(text, values, data[dbKey], dbKey)
- select.setAttribute("onchange", "javascript: this.className = 'changed'")
- content.appendRow("Group Title", select)
- content.description("Select a M3U group. (Counter)
Changing the group title in the M3U invalidates the filter.", true)
-
- // Groß- Kleinschreibung beachten
- var dbKey:string = "caseSensitive"
- var input = content.createCheckbox(dbKey)
- input.checked = data[dbKey]
- content.appendRow("Case Sensitive", input)
-
-
- var dbKey:string = "include"
- var input = content.createInput("text", dbKey, data[dbKey])
- input.setAttribute("placeholder", "FHD,UHD")
-
- content.appendRow("Include", input)
- content.description("Channel name must include.
(Comma separated) Comma means or", true)
-
- var dbKey:string = "exclude"
- var input = content.createInput("text", dbKey, data[dbKey])
- input.setAttribute("placeholder", "ES,IT")
- content.appendRow("Exclude", input)
- content.description("Channel name must not contain.
(Comma separated) Comma means or", true)
-
- break
-
- default:
- break;
- }
-
- // Interaktion
- content.createInteraction()
-
- // Löschen
- var input = content.createInput("button", "delete", "Delete")
- input.setAttribute('onclick', 'javascript: savePopupData("filter", "' + id + '", true, 0)')
- input.className = "delete"
- content.addInteraction(input)
-
- // Abbrechen
- var input = content.createInput("button", "cancel", "Cancel")
- input.setAttribute("onclick", 'javascript: showElement("popup", false);')
- content.addInteraction(input)
-
- // Speichern
- var input = content.createInput("button", "save", "Save")
- input.setAttribute('onclick', 'javascript: savePopupData("filter", "' + id + '", false, 0)')
- content.addInteraction(input)
-
- break
-
- case "xmltv":
- content.createHeadline(dataType)
- // Name
- var dbKey:string = "name"
- var input = content.createInput("text", dbKey, data[dbKey])
- input.setAttribute("placeholder", "Guide name")
- content.appendRow("Name", input)
-
- // Beschreibung
- var dbKey:string = "description"
- var input = content.createInput("text", dbKey, data[dbKey])
- input.setAttribute("placeholder", "Description")
- content.appendRow("Description", input)
-
- // URL
- var dbKey:string = "file.source"
- var input = content.createInput("text", dbKey, data[dbKey])
- input.setAttribute("placeholder", "File path or URL of the XMLTV")
- content.appendRow("XMLTV File", input)
-
- // Interaktion
- content.createInteraction()
- // Löschen
- if (data["id.provider"]!= "-") {
- var input = content.createInput("button", "delete", "Delete")
- input.setAttribute('onclick', 'javascript: savePopupData("xmltv", "' + id + '", true, 0)')
- input.className = "delete"
- content.addInteraction(input)
- }
-
- // Abbrechen
- var input = content.createInput("button", "cancel", "Cancel")
- input.setAttribute("onclick", 'javascript: showElement("popup", false);')
- content.addInteraction(input)
-
- // Aktualisieren
- if (data["id.provider"]!= "-") {
- var input = content.createInput("button", "update", "Update")
- input.setAttribute('onclick', 'javascript: savePopupData("xmltv", "' + id + '", false, 1)')
- content.addInteraction(input)
- }
-
- // Speichern
- var input = content.createInput("button", "save", "Save")
- input.setAttribute('onclick', 'javascript: savePopupData("xmltv", "' + id + '", false, 0)')
- content.addInteraction(input)
- break
-
- case "users":
- content.createHeadline("Users")
- // Benutzername
- var dbKey:string = "username"
- var input = content.createInput("text", dbKey, data[dbKey])
- input.setAttribute("placeholder", "Username")
- content.appendRow("Username", input)
-
- // Neues Passwort
- var dbKey:string = "password"
- var input = content.createInput("password", dbKey, "")
- input.setAttribute("placeholder", "Password")
- content.appendRow("Password", input)
-
- // Bestätigung
- var dbKey:string = "confirm"
- var input = content.createInput("password", dbKey, "")
- input.setAttribute("placeholder", "Password confirm")
- content.appendRow("Confirm", input)
-
- // Berechtigung WEB
- var dbKey:string = "authentication.web"
- var input = content.createCheckbox(dbKey)
- input.checked = data[dbKey]
- if (data["defaultUser"] == true) {
- input.setAttribute("onclick", "javascript: return false")
- }
- content.appendRow("Web Access", input)
-
- // Berechtigung PMS
- var dbKey:string = "authentication.pms"
- var input = content.createCheckbox(dbKey)
- input.checked = data[dbKey]
- content.appendRow("PMS Access", input)
-
- // Berechtigung M3U
- var dbKey:string = "authentication.m3u"
- var input = content.createCheckbox(dbKey)
- input.checked = data[dbKey]
- content.appendRow("M3U Access", input)
-
- // Berechtigung XML
- var dbKey:string = "authentication.xml"
- var input = content.createCheckbox(dbKey)
- input.checked = data[dbKey]
- content.appendRow("XML Access", input)
-
- // Berechtigung API
- var dbKey:string = "authentication.api"
- var input = content.createCheckbox(dbKey)
- input.checked = data[dbKey]
- content.appendRow("API Access", input)
-
- // Interaktion
- content.createInteraction()
-
- // Löschen
- if (data["defaultUser"]!= true && id != "-") {
- var input = content.createInput("button", "delete", "Delete")
- input.className = "delete"
- input.setAttribute('onclick', 'javascript: savePopupData("' + dataType + '", "' + id + '", true, 0)')
- content.addInteraction(input)
- }
-
- // Abbrechen
- var input = content.createInput("button", "cancel", "Cancel")
- input.setAttribute("onclick", 'javascript: showElement("popup", false);')
- content.addInteraction(input)
-
- // Speichern
- var input = content.createInput("button", "save", "Save")
- input.setAttribute("onclick", 'javascript: savePopupData("' + dataType + '", "' + id + '", "false");')
- content.addInteraction(input)
-
- break
-
- case "mapping":
- content.createHeadline("Mapping")
- // Aktiv
- var dbKey:string = "x-active"
- var input = content.createCheckbox(dbKey)
- input.checked = data[dbKey]
- input.id = "active"
- //input.setAttribute("onchange", "javascript: this.className = 'changed'")
- input.setAttribute("onchange", "javascript: toggleChannelStatus('" + id + "', this)")
- content.appendRow("Active", input)
-
- // Kanalname
- var dbKey:string = "x-name"
- var input = content.createInput("text", dbKey, data[dbKey])
- input.setAttribute("onchange", "javascript: this.className = 'changed'")
- if (BULK_EDIT == true) {
- input.style.border = "solid 1px red"
- input.setAttribute("readonly", "true")
- }
- content.appendRow("Channel Name", input)
-
- content.description(data["name"])
-
- // Beschreibung
- var dbKey:string = "x-description"
- var input = content.createInput("text", dbKey, data[dbKey])
- input.setAttribute("placeholder", "Used by the Dummy as an XML description")
- input.setAttribute("onchange", "javascript: this.className = 'changed'")
- content.appendRow("Channel Description", input)
-
- // Aktualisierung des Kanalnamens
- if (data.hasOwnProperty("_uuid.key")) {
- if (data["_uuid.key"] != "") {
- var dbKey:string = "x-update-channel-name"
- var input = content.createCheckbox(dbKey)
- input.setAttribute("onchange", "javascript: this.className = 'changed'")
- input.checked = data[dbKey]
- content.appendRow("Update Channel Name", input)
- }
- }
-
- // Logo URL (Kanal)
- var dbKey:string = "tvg-logo"
- var input = content.createInput("text", dbKey, data[dbKey])
- input.setAttribute("onchange", "javascript: this.className = 'changed'")
- input.setAttribute("id", "channel-icon")
- content.appendRow("Logo URL", input)
-
- // Aktualisierung des Kanallogos
- var dbKey:string = "x-update-channel-icon"
- var input = content.createCheckbox(dbKey)
- input.checked = data[dbKey]
- input.setAttribute("id", "update-icon")
- input.setAttribute("onchange", "javascript: this.className = 'changed'; changeChannelLogo('" + id + "');")
- content.appendRow("Update Channel Logo", input)
-
- // Erweitern der EPG Kategorie
- var dbKey:string = "x-category"
- var text:string[] = ["-", "Kids (Emby only)", "News", "Movie", "Series", "Sports"]
- var values:string[] = ["", "Kids", "News", "Movie", "Series", "Sports"]
- var select = content.createSelect(text, values, data[dbKey], dbKey)
- select.setAttribute("onchange", "javascript: this.className = 'changed'")
- content.appendRow("EPG Category", select)
-
- // M3U Gruppentitel
- var dbKey:string = "x-group-title"
- var input = content.createInput("text", dbKey, data[dbKey])
- input.setAttribute("onchange", "javascript: this.className = 'changed'")
- content.appendRow("Group Title (xteve.m3u)", input)
-
- if (data["group-title"] != undefined) {
- content.description(data["group-title"])
- }
-
- // XMLTV Datei
- var dbKey:string = "x-xmltv-file"
- var xmlFile = data[dbKey]
- var xmltv:XMLTVFile = new XMLTVFile()
- var select = xmltv.getFiles(data[dbKey])
- select.setAttribute("name", dbKey)
- select.setAttribute("id", "popup-xmltv")
- select.setAttribute("onchange", "javascript: this.className = 'changed'; setXmltvChannel('" + id + "',this);")
- content.appendRow("XMLTV File", select)
- var file = data[dbKey]
-
- // XMLTV Mapping
- var dbKey:string = "x-mapping"
- var xmltv:XMLTVFile = new XMLTVFile()
- var select = xmltv.getPrograms(file, data[dbKey])
- select.setAttribute("name", dbKey)
- select.setAttribute("id", "popup-mapping")
- select.setAttribute("onchange", "javascript: this.className = 'changed'; checkXmltvChannel('" + id + "',this,'" + xmlFile + "');")
-
- sortSelect(select)
- content.appendRow("XMLTV Channel", select)
-
- // Interaktion
- content.createInteraction()
-
- // Logo hochladen
- var input = content.createInput("button", "cancel", "Upload Logo")
- input.setAttribute("onclick", 'javascript: uploadLogo();')
- content.addInteraction(input)
-
- // Abbrechen
- var input = content.createInput("button", "cancel", "Cancel")
- input.setAttribute("onclick", 'javascript: showElement("popup", false);')
- content.addInteraction(input)
-
- // Fertig
- var ids:string[] = new Array()
- ids = getAllSelectedChannels()
- if (ids.length == 0) {
- ids.push(id)
- }
-
- var input = content.createInput("button", "save", "Done")
- input.setAttribute("onclick", 'javascript: donePopupData("' + dataType + '", "' + ids + '", "false");')
- content.addInteraction(input)
- break
-
- default:
- break;
- }
-
- showPopUpElement('popup-custom');
-}
-
-class XMLTVFile {
- File:string
-
- getFiles(set:string):any {
- var fileIDs:string[] = getObjKeys(SERVER["xepg"]["xmltvMap"])
- var values = new Array("-");
- var text = new Array("-");
-
- for (let i = 0; i < fileIDs.length; i++) {
- if (fileIDs[i] != "xTeVe Dummy") {
- values.push(getValueFromProviderFile(fileIDs[i], "xmltv", "file.xteve"))
- text.push(getValueFromProviderFile(fileIDs[i], "xmltv", "name"))
- } else {
- values.push(fileIDs[i])
- text.push(fileIDs[i])
- }
-
- }
-
- var select = document.createElement("SELECT")
- for (let i = 0; i < text.length; i++) {
- var option = document.createElement("OPTION")
- option.setAttribute("value", values[i])
- option.innerText = text[i]
- select.appendChild(option)
- }
-
- if(set != "") {
- (select as HTMLSelectElement).value = set
- }
-
- return select
- }
-
- getPrograms(file:string, set:string):any {
- //var fileIDs:string[] = getObjKeys(SERVER["xepg"]["xmltvMap"])
- var values = getObjKeys(SERVER["xepg"]["xmltvMap"][file]);
- var text = new Array()
- var displayName:string
-
- for (let i = 0; i < values.length; i++) {
- if (SERVER["xepg"]["xmltvMap"][file][values[i]].hasOwnProperty('display-name') == true) {
- displayName = SERVER["xepg"]["xmltvMap"][file][values[i]]["display-name"];
- } else {
- displayName = "-"
- }
-
- text[i] = displayName + " (" + values[i] + ")";
- }
-
- text.unshift("-");
- values.unshift("-");
-
- var select = document.createElement("SELECT")
- for (let i = 0; i < text.length; i++) {
- var option = document.createElement("OPTION")
- option.setAttribute("value", values[i])
- option.innerText = text[i]
- select.appendChild(option)
- }
-
- if(set != "") {
- (select as HTMLSelectElement).value = set
- }
-
- if ((select as HTMLSelectElement).value != set) {
- (select as HTMLSelectElement).value = "-"
- }
-
- return select
- }
-
- return
-}
-
-function getValueFromProviderFile(file:string, fileType, key) {
-
- if (file == "xTeVe Dummy") {
- return file
- }
-
- var fileID:string
- var indicator = file.charAt(0)
-
- switch (indicator) {
- case "M":
- fileType = "m3u"
- fileID = file
- break;
-
- case "H":
- fileType = "hdhr"
- fileID = file
- break;
-
- case "X":
- fileType = "xmltv"
- fileID = file.substring(0, file.lastIndexOf('.'))
- break;
-
- }
-
- if (SERVER["settings"]["files"][fileType].hasOwnProperty(fileID) == true) {
- var data = SERVER["settings"]["files"][fileType][fileID];
- return data[key]
- }
-
- return
-
-}
-
-function setXmltvChannel(id, element) {
-
- var xmltv:XMLTVFile = new XMLTVFile()
- var xmlFile = element.value
-
- var tvgId:string = SERVER["xepg"]["epgMapping"][id]["tvg-id"]
- var td = document.getElementById("popup-mapping").parentElement
- td.innerHTML = ""
-
- var select = xmltv.getPrograms(element.value, tvgId)
- select.setAttribute("name", "x-mapping")
- select.setAttribute("id", "popup-mapping")
- select.setAttribute("onchange", "javascript: this.className = 'changed'; checkXmltvChannel('" + id + "',this,'" + xmlFile + "');")
- select.className = "changed"
- sortSelect(select)
- td.appendChild(select);
-
- checkXmltvChannel(id, select, xmlFile)
-}
-
-function checkXmltvChannel(id:string, element:any, xmlFile) {
-
- var value = (element as HTMLSelectElement).value
- var bool:boolean
- var checkbox = document.getElementById('active')
- var channel:any = SERVER["xepg"]["epgMapping"][id]
- var updateLogo:boolean
-
-
- if (value == "-") {
- bool = false
- } else {
- bool = true
- }
-
- (checkbox as HTMLInputElement).checked = bool
- checkbox.className = "changed"
- console.log(xmlFile);
-
- // Kanallogo aktualisieren
- /*
- updateLogo = (document.getElementById("update-icon") as HTMLInputElement).checked
- console.log(updateLogo);
- */
-
- if(xmlFile != "xTeVe Dummy" && bool == true) {
-
- //(document.getElementById("update-icon") as HTMLInputElement).checked = true;
- //(document.getElementById("update-icon") as HTMLInputElement).className = "changed";
-
- console.log("ID", id)
- changeChannelLogo(id)
-
- return
- }
-
- if (xmlFile == "xTeVe Dummy") {
- (document.getElementById("update-icon") as HTMLInputElement).checked = false;
- (document.getElementById("update-icon") as HTMLInputElement).className = "changed";
- }
-
- return
-}
-
-function changeChannelLogo(id:string) {
-
- var updateLogo:boolean
- var channel:any = SERVER["xepg"]["epgMapping"][id]
-
- var f = (document.getElementById("popup-xmltv") as HTMLSelectElement);
- var xmltvFile = f.options[f.selectedIndex].value;
-
- var m = (document.getElementById("popup-mapping") as HTMLSelectElement);
- var xMapping = m.options[m.selectedIndex].value;
-
- var xmltvLogo = SERVER["xepg"]["xmltvMap"][xmltvFile][xMapping]["icon"]
- updateLogo = (document.getElementById("update-icon") as HTMLInputElement).checked
-
- if (updateLogo == true && xmltvFile != "xTeVe Dummy") {
-
- if (SERVER["xepg"]["xmltvMap"][xmltvFile].hasOwnProperty(xMapping)) {
- var logo = xmltvLogo
- } else {
- logo = channel["tvg-logo"]
- }
-
- var logoInput = (document.getElementById("channel-icon") as HTMLInputElement);
- logoInput.value = logo
- if (BULK_EDIT == false) {
- logoInput.className = "changed"
- }
-
- }
-
-}
-
-function savePopupData(dataType:string, id:string, remove:Boolean, option:number) {
-
- if (dataType == "mapping") {
-
- var data = new Object()
- console.log("Save mapping data")
-
- cmd = "saveEpgMapping"
- data["epgMapping"] = SERVER["xepg"]["epgMapping"]
-
- console.log("SEND TO SERVER");
-
- var server:Server = new Server(cmd)
- server.request(data)
-
- delete UNDO["epgMapping"]
-
- return
- }
-
- console.log("Save popup data")
- var div = document.getElementById("popup-custom")
-
- var inputs = div.getElementsByTagName("TABLE")[0].getElementsByTagName("INPUT");
- var selects = div.getElementsByTagName("TABLE")[0].getElementsByTagName("SELECT");
-
- var input = new Object();
- var confirmMsg: string
-
- for (let i = 0; i < selects.length; i++) {
-
- var name:string
- name = (selects[i] as HTMLSelectElement).name
- var value = (selects[i] as HTMLSelectElement).value
-
- switch (name) {
- case "tuner":
- input[name] = parseInt(value)
- break;
-
- default:
- input[name] = value
- break;
- }
-
- }
-
- for (let i = 0; i < inputs.length; i++) {
-
- switch ((inputs[i] as HTMLInputElement).type) {
-
- case "checkbox":
- name = (inputs[i] as HTMLInputElement).name
- input[name] = (inputs[i] as HTMLInputElement).checked
- break
-
- case "text":
- case "hidden":
- case "password":
-
- name = (inputs[i] as HTMLInputElement).name
-
- switch (name) {
- case "tuner":
- input[name] = parseInt((inputs[i] as HTMLInputElement).value)
- break;
-
- default:
- input[name] = (inputs[i] as HTMLInputElement).value
- break;
- }
-
- break
-
- }
-
- }
-
- var data = new Object()
-
- var cmd:string
-
- if (remove == true) {
- input["delete"] = true
- }
-
- switch (dataType) {
- case "users":
-
- confirmMsg = "Delete this user?"
- if (id == "-") {
- cmd = "saveNewUser"
- data["userData"] = input
- } else {
- cmd = "saveUserData"
- var d = new Object()
- d[id] = input
- data["userData"] = d
- }
-
- break;
-
- case "m3u":
-
- confirmMsg = "Delete this playlist?"
- switch (option) {
- // Popup: Save
- case 0:
- cmd = "saveFilesM3U"
- break
-
- // Popup: Update
- case 1:
- cmd = "updateFileM3U"
- break
-
- }
-
- data["files"] = new Object
- data["files"][dataType] = new Object
- data["files"][dataType][id] = input
-
- break
-
- case "hdhr":
-
- confirmMsg = "Delete this HDHomeRun tuner?"
- switch (option) {
- // Popup: Save
- case 0:
- cmd = "saveFilesHDHR"
- break
-
- // Popup: Update
- case 1:
- cmd = "updateFileHDHR"
- break
-
- }
-
- data["files"] = new Object
- data["files"][dataType] = new Object
- data["files"][dataType][id] = input
-
- break
-
- case "xmltv":
-
- confirmMsg = "Delete this XMLTV file?"
- switch (option) {
- // Popup: Save
- case 0:
- cmd = "saveFilesXMLTV"
- break
-
- // Popup: Update
- case 1:
- cmd = "updateFileXMLTV"
- break
-
- }
-
- data["files"] = new Object
- data["files"][dataType] = new Object
- data["files"][dataType][id] = input
-
- break
-
- case "filter":
-
- confirmMsg = "Delete this filter?"
- cmd = "saveFilter"
- data["filter"] = new Object
- data["filter"][id] = input
- break
-
- default:
- console.log(dataType, id);
- return
- break;
-
- }
-
- if (remove == true) {
-
- if (!confirm(confirmMsg)) {
- showElement("popup", false)
- return
- }
-
- }
-
- console.log("SEND TO SERVER");
-
- console.log(data);
-
- var server:Server = new Server(cmd)
- server.request(data)
-
-}
-
-function donePopupData(dataType:string, idsStr:string) {
-
- var ids:string[] = idsStr.split(',');
- var div = document.getElementById("popup-custom")
- var inputs = div.getElementsByClassName("changed")
-
- ids.forEach(id => {
- var input = new Object();
- input = SERVER["xepg"]["epgMapping"][id]
-
- console.log(input);
-
- for (let i = 0; i < inputs.length; i++) {
-
- var name:string
- var value:any
-
- switch (inputs[i].tagName) {
-
- case "INPUT":
- switch ((inputs[i] as HTMLInputElement).type) {
- case "checkbox":
- name = (inputs[i] as HTMLInputElement).name
- value = (inputs[i] as HTMLInputElement).checked
- input[name] = value
- break
-
- case "text":
- name = (inputs[i] as HTMLInputElement).name
- value = (inputs[i] as HTMLInputElement).value
- input[name] = value
- break
-
- }
-
- break
-
- case "SELECT":
- name = (inputs[i] as HTMLSelectElement).name
- value = (inputs[i] as HTMLSelectElement).value
- input[name] = value
- break
-
- }
-
- switch (name) {
-
-
- case "tvg-logo":
- //(document.getElementById(id).childNodes[2].firstChild as HTMLElement).setAttribute("src", value)
- break
-
- case "x-name":
- (document.getElementById(id).childNodes[3].firstChild as HTMLElement).textContent = value
- break
-
- case "x-category":
- (document.getElementById(id).childNodes[3].firstChild as HTMLElement).className = value
- break
-
- case "x-group-title":
- (document.getElementById(id).childNodes[5].firstChild as HTMLElement).textContent = value
- break
-
- case "x-xmltv-file":
- if (value != "xTeVe Dummy" && value != "-") {
- value = getValueFromProviderFile(value, "xmltv", "name")
- }
-
- if (value == "-") {
- input["x-active"] = false
- }
-
- (document.getElementById(id).childNodes[6].firstChild as HTMLElement).textContent = value
- break
-
- case "x-mapping":
- if (value == "-") {
- input["x-active"] = false
- }
-
- (document.getElementById(id).childNodes[7].firstChild as HTMLElement).textContent = value
-
- break
-
- default:
-
- }
-
- createSearchObj()
- searchInMapping()
-
- }
-
- if (input["x-active"] == false) {
- document.getElementById(id).className = "notActiveEPG"
- } else {
- document.getElementById(id).className = "activeEPG"
- }
-
- console.log(input["tvg-logo"]);
- (document.getElementById(id).childNodes[2].firstChild as HTMLElement).setAttribute("src", input["tvg-logo"])
-
-
- });
-
- showElement("popup", false);
-
- return
-}
-
-function showPreview(element:boolean) {
-
- var div = document.getElementById("myStreamsBox")
- switch (element) {
-
- case false:
- div.className = "notVisible"
- return
- break;
- }
-
- var streams:string[] = ["activeStreams", "inactiveStreams"]
-
- streams.forEach(preview => {
-
- var table = document.getElementById(preview)
- table.innerHTML = ""
- var obj:string[] = SERVER["data"]["StreamPreviewUI"][preview]
-
- obj.forEach(channel => {
-
- var tr = document.createElement("TR")
- var tdKey = document.createElement("TD")
- var tdVal = document.createElement("TD")
-
- tdKey.className = "tdKey"
- tdVal.className = "tdVal"
-
- switch (preview) {
- case "activeStreams":
- tdKey.innerText = "Channel: (+)"
- break;
-
- case "inactiveStreams":
- tdKey.innerText = "Channel: (-)"
- break;
- }
-
- tdVal.innerText = channel
- tr.appendChild(tdKey)
- tr.appendChild(tdVal)
-
- table.appendChild(tr)
-
- });
-
- });
-
- showElement("loading", false)
- div.className = "visible"
-
- return
-}
diff --git a/ts/network.ts b/ts/network.ts
index 419c0ec..e7d256b 100644
--- a/ts/network.ts
+++ b/ts/network.ts
@@ -228,7 +228,7 @@ class Server {
if (response["status"] == false) {
setConnectionState("offline")
- alert(response["err"])
+ alertUser(response["err"])
if (response.hasOwnProperty("reload")) {
location.reload()
}
@@ -266,7 +266,7 @@ class Server {
}
if (response.hasOwnProperty("alert")) {
- alert(response["alert"])
+ alertUser(response["alert"])
}
if (response.hasOwnProperty("reload")) {
@@ -295,7 +295,7 @@ class Server {
setConnectionState(state)
if (WS_AVAILABLE == false && !isLogUpdate && requestCmd != "getServerConfig") {
- alert("No websocket connection to xTeVe could be established. Check your network configuration.")
+ alertUser("No websocket connection to xTeVe could be established. Check your network configuration.")
}
})
diff --git a/ts/popup.ts b/ts/popup.ts
new file mode 100644
index 0000000..8a8a434
--- /dev/null
+++ b/ts/popup.ts
@@ -0,0 +1,1171 @@
+class PopupWindow {
+ DocumentID:string = "popup-custom"
+ InteractionID:string = "interaction"
+ doc = document.getElementById(this.DocumentID)
+
+ createTitle(title:string):any {
+ var td = document.createElement("TD")
+ td.className = "left"
+ td.innerHTML = title + ":"
+ return td
+ }
+
+ createContent(element):any {
+ var td = document.createElement("TD")
+ td.appendChild(element)
+ return td
+ }
+
+ createInteraction():any {
+ var div = document.createElement("div")
+ div.setAttribute("id", "popup-interaction")
+ div.className = "interaction"
+ this.doc.appendChild(div)
+ }
+}
+
+class PopupContent extends PopupWindow{
+
+ table = document.createElement("TABLE")
+
+ createHeadline(headline):void {
+ this.doc.innerHTML = ""
+ var element = document.createElement("H3")
+ element.innerHTML = headline.toUpperCase()
+ this.doc.appendChild(element)
+
+ // Tabelle erstellen
+ this.table = document.createElement("TABLE")
+ this.doc.appendChild(this.table)
+ }
+
+ appendRow(title:string, element:any):void {
+ var tr = document.createElement("TR")
+
+ // Bezeichnung
+ if (title.length != 0) {
+ tr.appendChild(this.createTitle(title))
+ }
+
+
+ // Content
+ tr.appendChild(this.createContent(element))
+ this.table.appendChild(tr)
+ }
+
+
+ createInput(type:string, name:string, value:string):any {
+
+ var input = document.createElement("INPUT")
+ if (value == undefined) {
+ value = ""
+ }
+
+ input.setAttribute("type", type)
+ input.setAttribute("name", name)
+ input.setAttribute("value", value)
+ return input
+ }
+
+ createCheckbox(name:string):any {
+ var input = document.createElement("INPUT")
+
+ input.setAttribute("type", "checkbox")
+ input.setAttribute("name", name)
+ return input
+ }
+
+ createSelect(text:string[], values:string[], set:string, dbKey:string):any {
+ var select = document.createElement("SELECT")
+ select.setAttribute("name", dbKey)
+ for (let i = 0; i < text.length; i++) {
+ var option = document.createElement("OPTION")
+ option.setAttribute("value", values[i])
+ option.innerText = text[i]
+ select.appendChild(option)
+ }
+ if(set != "") {
+ (select as HTMLSelectElement).value = set
+ }
+
+ if (set == undefined) {
+ (select as HTMLSelectElement).value = values[0]
+ }
+
+ return select
+ }
+
+ selectOption(select:any, value:string):any {
+ //select.selectedOptions = value
+ var s:HTMLSelectElement = (select as HTMLSelectElement)
+ s.options[s.selectedIndex].value = value
+ return select
+ }
+
+ // isHTML must only be true for static text written in the source; never for data.
+ description(value:string, isHTML:boolean = false):any {
+ var tr = document.createElement("TR")
+ var td = document.createElement("TD")
+ var span = document.createElement("PRE")
+
+ if (isHTML) {
+ span.innerHTML = value
+ } else {
+ span.textContent = value
+ }
+
+ tr.appendChild(td)
+
+ tr.appendChild(this.createContent(span))
+
+ this.table.appendChild(tr)
+ }
+
+ // Interaktion
+ addInteraction(element:any) {
+ var interaction = document.getElementById("popup-interaction")
+ interaction.appendChild(element)
+ }
+}
+
+// Buffer choices a playlist can make; "default" means the global setting.
+const PLAYLIST_BUFFER_OPTIONS = [
+ { text: "Default (global setting)", value: "default" },
+ { text: "No buffer (pass the URL through)", value: "-" },
+ { text: "xTeVe", value: "xteve" },
+ { text: "FFmpeg", value: "ffmpeg" },
+ { text: "VLC", value: "vlc" }
+]
+
+function createBufferSelect(content:PopupContent, data:object):HTMLSelectElement {
+ var text:string[] = PLAYLIST_BUFFER_OPTIONS.map(function(o) { return o.text })
+ var values:string[] = PLAYLIST_BUFFER_OPTIONS.map(function(o) { return o.value })
+ var current:string = data["buffer"]
+ if (values.indexOf(current) == -1) {
+ current = "default"
+ }
+ return content.createSelect(text, values, current, "buffer")
+}
+
+// playlistBufferActive: is a buffer in use for this playlist (its own choice,
+// or the global setting when it has none)? Decides whether the tuner count
+// can be edited.
+function playlistBufferActive(data:object):boolean {
+ var own:string = data["buffer"]
+ if (own != undefined && own != "default" && own != "") {
+ return own != "-"
+ }
+ return SERVER["settings"]["buffer"] != "-"
+}
+
+function openPopUp(dataType, element?) {
+
+ var data:object = new Object();
+ var id:any
+ switch (element) {
+ case undefined:
+
+ switch (dataType) {
+ case "group-title":
+ if (id == undefined) {
+ id = -1
+ }
+ data = getLocalData("filter", id)
+ data["type"] = "group-title"
+ break;
+
+ case "custom-filter":
+ if (id == undefined) {
+ id = -1
+ }
+ data = getLocalData("filter", id)
+ data["type"] = "custom-filter"
+ break;
+
+ case "regex-filter":
+ if (id == undefined) {
+ id = -1
+ }
+ data = getLocalData("filter", id)
+ data["type"] = "regex-filter"
+ break;
+
+ default:
+ data["id.provider"] = "-"
+ data["type"] = dataType
+ id = "-"
+ break;
+ }
+
+ break
+
+ default:
+ id = element.id
+ data = getLocalData(dataType, id)
+ break;
+ }
+
+ var content:PopupContent = new PopupContent()
+
+ switch (dataType) {
+ case "playlist":
+ content.createHeadline("Playlist type")
+ // Type
+ var text:string[] = ["M3U", "HDHomeRun"]
+ var values:string[] = ["m3u", "hdhr"]
+ var select = content.createSelect(text, values, "", "type")
+ select.setAttribute("id", "type")
+ content.appendRow("Type", select)
+ const typeSelect:HTMLSelectElement = select
+
+ // Interaktion
+ content.createInteraction()
+ // Abbrechen
+ var input = content.createInput("button", "cancel", "Cancel")
+ input.addEventListener("click", function() { showElement("popup", false) })
+ content.addInteraction(input)
+
+ // Weiter
+ var input = content.createInput("button", "next", "Next")
+ input.addEventListener("click", function() { openPopUp(typeSelect.value) })
+ input.setAttribute("id", 'next')
+ content.addInteraction(input)
+ break
+
+ case "m3u":
+ content.createHeadline(dataType)
+ // Name
+ var dbKey:string = "name"
+ var input = content.createInput("text", dbKey, data[dbKey])
+ input.setAttribute("placeholder", "Playlist name")
+ content.appendRow("Name", input)
+
+ // Beschreibung
+ var dbKey:string = "description"
+ var input = content.createInput("text", dbKey, data[dbKey])
+ input.setAttribute("placeholder", "Description")
+ content.appendRow("Description", input)
+
+ // URL
+ var dbKey:string = "file.source"
+ var input = content.createInput("text", dbKey, data[dbKey])
+ input.setAttribute("placeholder", "File path or URL of the M3U")
+ content.appendRow("M3U File", input)
+
+ // Buffer (per playlist override of the global setting)
+ var bufferSelect = createBufferSelect(content, data)
+ content.appendRow("Buffer", bufferSelect)
+
+ // Tuner
+ if (playlistBufferActive(data)) {
+ var text:string[] = new Array()
+ var values:string[] = new Array()
+
+ for (var i = 1; i <= 100; i++) {
+ text.push(i.toString())
+ values.push(i.toString())
+ }
+
+ var dbKey:string = "tuner"
+ var select = content.createSelect(text, values, data[dbKey], dbKey)
+ content.appendRow("Tuner / Streams", select)
+ } else {
+ var dbKey:string = "tuner"
+ if (data[dbKey] == undefined) {
+ data[dbKey] = 1
+ }
+ var input = content.createInput("text", dbKey, data[dbKey])
+ input.setAttribute("readonly", "true")
+ input.className = "notAvailable"
+ content.appendRow("Tuner / Streams", input)
+ }
+
+ content.description("Number of parallel connections that can be established to the provider.
Only available with activated buffer.
New settings will only be applied after quitting all streams.", true)
+
+ // Interaktion
+ content.createInteraction()
+ // Löschen
+ if (data["id.provider"]!= "-") {
+ var input = content.createInput("button", "delete", "Delete")
+ input.className = "delete"
+ input.addEventListener("click", function() { savePopupData("m3u", String(id), true, 0) })
+ content.addInteraction(input)
+ } else {
+ var input = content.createInput("button", "back", "Back")
+ input.addEventListener("click", function() { openPopUp("playlist") })
+ content.addInteraction(input)
+ }
+
+ // Abbrechen
+ var input = content.createInput("button", "cancel", "Cancel")
+ input.addEventListener("click", function() { showElement("popup", false) })
+ content.addInteraction(input)
+
+ // Aktualisieren
+ if (data["id.provider"]!= "-") {
+ var input = content.createInput("button", "update", "Update")
+ input.addEventListener("click", function() { savePopupData("m3u", String(id), false, 1) })
+ content.addInteraction(input)
+ }
+
+ // Speichern
+ var input = content.createInput("button", "save", "Save")
+ input.addEventListener("click", function() { savePopupData("m3u", String(id), false, 0) })
+ content.addInteraction(input)
+ break
+
+ case "hdhr":
+ content.createHeadline(dataType)
+ // Name
+ var dbKey:string = "name"
+ var input = content.createInput("text", dbKey, data[dbKey])
+ input.setAttribute("placeholder", "Playlist name")
+ content.appendRow("Name", input)
+
+ // Beschreibung
+ var dbKey:string = "description"
+ var input = content.createInput("text", dbKey, data[dbKey])
+ input.setAttribute("placeholder", "Description")
+ content.appendRow("Description", input)
+
+ // URL
+ var dbKey:string = "file.source"
+ var input = content.createInput("text", dbKey, data[dbKey])
+ input.setAttribute("placeholder", "IP address and port (192.168.1.10:5004)")
+ content.appendRow("HDHomeRun IP", input)
+
+ // Buffer (per playlist override of the global setting)
+ var bufferSelect = createBufferSelect(content, data)
+ content.appendRow("Buffer", bufferSelect)
+
+ // Tuner
+ if (playlistBufferActive(data)) {
+ var text:string[] = new Array()
+ var values:string[] = new Array()
+
+ for (var i = 1; i <= 100; i++) {
+ text.push(i.toString())
+ values.push(i.toString())
+ }
+
+ var dbKey:string = "tuner"
+ var select = content.createSelect(text, values, data[dbKey], dbKey)
+ content.appendRow("Tuner / Streams", select)
+ } else {
+ var dbKey:string = "tuner"
+ if (data[dbKey] == undefined) {
+ data[dbKey] = 1
+ }
+ var input = content.createInput("text", dbKey, data[dbKey])
+ input.setAttribute("readonly", "true")
+ input.className = "notAvailable"
+ content.appendRow("Tuner / Streams", input)
+ }
+
+ content.description("Number of parallel connections that can be established to the provider.
Only available with activated buffer.
New settings will only be applied after quitting all streams.", true)
+
+ // Interaktion
+ content.createInteraction()
+ // Löschen
+ if (data["id.provider"]!= "-") {
+ var input = content.createInput("button", "delete", "Delete")
+ input.addEventListener("click", function() { savePopupData("hdhr", String(id), true, 0) })
+ input.className = "delete"
+ content.addInteraction(input)
+ } else {
+ var input = content.createInput("button", "back", "Back")
+ input.addEventListener("click", function() { openPopUp("playlist") })
+ content.addInteraction(input)
+ }
+
+ // Abbrechen
+ var input = content.createInput("button", "cancel", "Cancel")
+ input.addEventListener("click", function() { showElement("popup", false) })
+ content.addInteraction(input)
+
+ // Aktualisieren
+ if (data["id.provider"]!= "-") {
+ var input = content.createInput("button", "update", "Update")
+ input.addEventListener("click", function() { savePopupData("hdhr", String(id), false, 1) })
+ content.addInteraction(input)
+ }
+
+ // Speichern
+ var input = content.createInput("button", "save", "Save")
+ input.addEventListener("click", function() { savePopupData("hdhr", String(id), false, 0) })
+ content.addInteraction(input)
+ break
+
+ case "filter":
+ content.createHeadline(dataType)
+
+ // Type
+ var dbKey:string = "type"
+ var text:string[] = ["M3U: " + "Group Title", "xTeVe: " + "Custom Filter", "xTeVe: " + "Regular Expression"]
+ var values:string[] = ["group-title", "custom-filter", "regex-filter"]
+ var select = content.createSelect(text, values, "group-title", dbKey)
+ select.setAttribute("id", id)
+ content.appendRow("Type", select)
+ const filterTypeSelect:HTMLSelectElement = select
+
+ // Interaktion
+ content.createInteraction()
+ // Abbrechen
+ var input = content.createInput("button", "cancel", "Cancel")
+ input.addEventListener("click", function() { showElement("popup", false) })
+ content.addInteraction(input)
+
+ // Weiter
+ var input = content.createInput("button", "next", "Next")
+ input.addEventListener("click", function() { openPopUp(filterTypeSelect.value) })
+ input.setAttribute("id", 'next')
+ content.addInteraction(input)
+ break
+
+ case "custom-filter":
+ case "group-title":
+ case "regex-filter":
+
+ switch (dataType) {
+ case "custom-filter":
+ content.createHeadline("Custom")
+ break;
+
+ case "group-title":
+ content.createHeadline("Group")
+ break;
+
+ case "regex-filter":
+ content.createHeadline("Regular Expression")
+ break;
+ }
+
+ // Name
+ var dbKey:string = "name"
+ var input = content.createInput("text", dbKey, data[dbKey])
+ input.setAttribute("placeholder", "Filter name")
+ content.appendRow("Filter Name", input)
+
+ // Beschreibung
+ var dbKey:string = "description"
+ var input = content.createInput("text", dbKey, data[dbKey])
+ input.setAttribute("placeholder", "Description")
+ content.appendRow("Description", input)
+
+ // Typ
+ var dbKey:string = "type"
+ var input = content.createInput("hidden", dbKey, data[dbKey])
+ content.appendRow("", input)
+
+ var filterType = data[dbKey]
+
+ switch (filterType) {
+
+ case "custom-filter":
+ // Groß- Kleinschreibung beachten
+ var dbKey:string = "caseSensitive"
+ var input = content.createCheckbox(dbKey)
+ input.checked = data[dbKey]
+ content.appendRow("Case Sensitive", input)
+
+ // Filterregel (Benutzerdefiniert)
+ var dbKey:string = "filter"
+ var input = content.createInput("text", dbKey, data[dbKey])
+ input.setAttribute("placeholder", "Sport {HD} !{ES,IT}")
+ content.appendRow("Filter Rule", input)
+
+ break;
+
+ case "regex-filter":
+ // Groß- Kleinschreibung beachten
+ var dbKey:string = "caseSensitive"
+ var input = content.createCheckbox(dbKey)
+ input.checked = data[dbKey]
+ content.appendRow("Case Sensitive", input)
+
+ // Regulärer Ausdruck
+ var dbKey:string = "filter"
+ var input = content.createInput("text", dbKey, data[dbKey])
+ input.setAttribute("placeholder", "^(BBC|ITV) \\d+$")
+ content.appendRow("Regular Expression", input)
+ content.description("Go regular expression, tried against the channel name, then the group title, then the raw M3U attributes. Case-insensitive unless Case Sensitive is checked. Invalid patterns are rejected on save.")
+
+ break;
+
+ case "group-title":
+ //alert(dbKey + " " + filterType)
+ // Filter basierend auf den Gruppen in der M3U
+ var dbKey:string = "filter"
+ var groupsM3U = getLocalData("m3uGroups", "")
+ var text:string[] = groupsM3U["text"]
+ var values:string[] = groupsM3U["value"]
+
+ var select = content.createSelect(text, values, data[dbKey], dbKey)
+ markChangedOnChange(select)
+ content.appendRow("Group Title", select)
+ content.description("Select a M3U group. (Counter)
Changing the group title in the M3U invalidates the filter.", true)
+
+ // Groß- Kleinschreibung beachten
+ var dbKey:string = "caseSensitive"
+ var input = content.createCheckbox(dbKey)
+ input.checked = data[dbKey]
+ content.appendRow("Case Sensitive", input)
+
+
+ var dbKey:string = "include"
+ var input = content.createInput("text", dbKey, data[dbKey])
+ input.setAttribute("placeholder", "FHD,UHD")
+
+ content.appendRow("Include", input)
+ content.description("Channel name must include.
(Comma separated) Comma means or", true)
+
+ var dbKey:string = "exclude"
+ var input = content.createInput("text", dbKey, data[dbKey])
+ input.setAttribute("placeholder", "ES,IT")
+ content.appendRow("Exclude", input)
+ content.description("Channel name must not contain.
(Comma separated) Comma means or", true)
+
+ break
+
+ default:
+ break;
+ }
+
+ // Interaktion
+ content.createInteraction()
+
+ // Löschen
+ var input = content.createInput("button", "delete", "Delete")
+ input.addEventListener("click", function() { savePopupData("filter", String(id), true, 0) })
+ input.className = "delete"
+ content.addInteraction(input)
+
+ // Abbrechen
+ var input = content.createInput("button", "cancel", "Cancel")
+ input.addEventListener("click", function() { showElement("popup", false) })
+ content.addInteraction(input)
+
+ // Speichern
+ var input = content.createInput("button", "save", "Save")
+ input.addEventListener("click", function() { savePopupData("filter", String(id), false, 0) })
+ content.addInteraction(input)
+
+ break
+
+ case "xmltv":
+ content.createHeadline(dataType)
+ // Name
+ var dbKey:string = "name"
+ var input = content.createInput("text", dbKey, data[dbKey])
+ input.setAttribute("placeholder", "Guide name")
+ content.appendRow("Name", input)
+
+ // Beschreibung
+ var dbKey:string = "description"
+ var input = content.createInput("text", dbKey, data[dbKey])
+ input.setAttribute("placeholder", "Description")
+ content.appendRow("Description", input)
+
+ // URL
+ var dbKey:string = "file.source"
+ var input = content.createInput("text", dbKey, data[dbKey])
+ input.setAttribute("placeholder", "File path or URL of the XMLTV")
+ content.appendRow("XMLTV File", input)
+
+ // Interaktion
+ content.createInteraction()
+ // Löschen
+ if (data["id.provider"]!= "-") {
+ var input = content.createInput("button", "delete", "Delete")
+ input.addEventListener("click", function() { savePopupData("xmltv", String(id), true, 0) })
+ input.className = "delete"
+ content.addInteraction(input)
+ }
+
+ // Abbrechen
+ var input = content.createInput("button", "cancel", "Cancel")
+ input.addEventListener("click", function() { showElement("popup", false) })
+ content.addInteraction(input)
+
+ // Aktualisieren
+ if (data["id.provider"]!= "-") {
+ var input = content.createInput("button", "update", "Update")
+ input.addEventListener("click", function() { savePopupData("xmltv", String(id), false, 1) })
+ content.addInteraction(input)
+ }
+
+ // Speichern
+ var input = content.createInput("button", "save", "Save")
+ input.addEventListener("click", function() { savePopupData("xmltv", String(id), false, 0) })
+ content.addInteraction(input)
+ break
+
+ case "users":
+ content.createHeadline("Users")
+ // Benutzername
+ var dbKey:string = "username"
+ var input = content.createInput("text", dbKey, data[dbKey])
+ input.setAttribute("placeholder", "Username")
+ content.appendRow("Username", input)
+
+ // Neues Passwort
+ var dbKey:string = "password"
+ var input = content.createInput("password", dbKey, "")
+ input.setAttribute("placeholder", "Password")
+ content.appendRow("Password", input)
+
+ // Bestätigung
+ var dbKey:string = "confirm"
+ var input = content.createInput("password", dbKey, "")
+ input.setAttribute("placeholder", "Password confirm")
+ content.appendRow("Confirm", input)
+
+ // Berechtigung WEB
+ var dbKey:string = "authentication.web"
+ var input = content.createCheckbox(dbKey)
+ input.checked = data[dbKey]
+ if (data["defaultUser"] == true) {
+ input.addEventListener("click", function(event) { event.preventDefault() })
+ }
+ content.appendRow("Web Access", input)
+
+ // Berechtigung PMS
+ var dbKey:string = "authentication.pms"
+ var input = content.createCheckbox(dbKey)
+ input.checked = data[dbKey]
+ content.appendRow("PMS Access", input)
+
+ // Berechtigung M3U
+ var dbKey:string = "authentication.m3u"
+ var input = content.createCheckbox(dbKey)
+ input.checked = data[dbKey]
+ content.appendRow("M3U Access", input)
+
+ // Berechtigung XML
+ var dbKey:string = "authentication.xml"
+ var input = content.createCheckbox(dbKey)
+ input.checked = data[dbKey]
+ content.appendRow("XML Access", input)
+
+ // Berechtigung API
+ var dbKey:string = "authentication.api"
+ var input = content.createCheckbox(dbKey)
+ input.checked = data[dbKey]
+ content.appendRow("API Access", input)
+
+ // Interaktion
+ content.createInteraction()
+
+ // Löschen
+ if (data["defaultUser"]!= true && id != "-") {
+ var input = content.createInput("button", "delete", "Delete")
+ input.className = "delete"
+ input.addEventListener("click", function() { savePopupData(dataType, String(id), true, 0) })
+ content.addInteraction(input)
+ }
+
+ // Abbrechen
+ var input = content.createInput("button", "cancel", "Cancel")
+ input.addEventListener("click", function() { showElement("popup", false) })
+ content.addInteraction(input)
+
+ // Speichern
+ var input = content.createInput("button", "save", "Save")
+ input.addEventListener("click", function() { savePopupData(dataType, String(id), false, 0) })
+ content.addInteraction(input)
+
+ break
+
+ case "mapping":
+ content.createHeadline("Mapping")
+ // Aktiv
+ var dbKey:string = "x-active"
+ var input = content.createCheckbox(dbKey)
+ input.checked = data[dbKey]
+ input.id = "active"
+ //markChangedOnChange(input)
+ input.addEventListener("change", function() { toggleChannelStatus(id) })
+ content.appendRow("Active", input)
+
+ // Kanalname
+ var dbKey:string = "x-name"
+ var input = content.createInput("text", dbKey, data[dbKey])
+ markChangedOnChange(input)
+ if (BULK_EDIT == true) {
+ input.style.border = "solid 1px red"
+ input.setAttribute("readonly", "true")
+ }
+ content.appendRow("Channel Name", input)
+
+ content.description(data["name"])
+
+ // Beschreibung
+ var dbKey:string = "x-description"
+ var input = content.createInput("text", dbKey, data[dbKey])
+ input.setAttribute("placeholder", "Used by the Dummy as an XML description")
+ markChangedOnChange(input)
+ content.appendRow("Channel Description", input)
+
+ // Aktualisierung des Kanalnamens
+ if (data.hasOwnProperty("_uuid.key")) {
+ if (data["_uuid.key"] != "") {
+ var dbKey:string = "x-update-channel-name"
+ var input = content.createCheckbox(dbKey)
+ markChangedOnChange(input)
+ input.checked = data[dbKey]
+ content.appendRow("Update Channel Name", input)
+ }
+ }
+
+ // Logo URL (Kanal)
+ var dbKey:string = "tvg-logo"
+ var input = content.createInput("text", dbKey, data[dbKey])
+ markChangedOnChange(input)
+ input.setAttribute("id", "channel-icon")
+ content.appendRow("Logo URL", input)
+
+ // Aktualisierung des Kanallogos
+ var dbKey:string = "x-update-channel-icon"
+ var input = content.createCheckbox(dbKey)
+ input.checked = data[dbKey]
+ input.setAttribute("id", "update-icon")
+ markChangedOnChange(input, function() { changeChannelLogo(id) })
+ content.appendRow("Update Channel Logo", input)
+
+ // Erweitern der EPG Kategorie
+ var dbKey:string = "x-category"
+ var text:string[] = ["-", "Kids (Emby only)", "News", "Movie", "Series", "Sports"]
+ var values:string[] = ["", "Kids", "News", "Movie", "Series", "Sports"]
+ var select = content.createSelect(text, values, data[dbKey], dbKey)
+ markChangedOnChange(select)
+ content.appendRow("EPG Category", select)
+
+ // M3U Gruppentitel
+ var dbKey:string = "x-group-title"
+ var input = content.createInput("text", dbKey, data[dbKey])
+ markChangedOnChange(input)
+ content.appendRow("Group Title (xteve.m3u)", input)
+
+ if (data["group-title"] != undefined) {
+ content.description(data["group-title"])
+ }
+
+ // XMLTV Datei
+ var dbKey:string = "x-xmltv-file"
+ var xmlFile = data[dbKey]
+ var xmltv:XMLTVFile = new XMLTVFile()
+ var select = xmltv.getFiles(data[dbKey])
+ select.setAttribute("name", dbKey)
+ select.setAttribute("id", "popup-xmltv")
+ markChangedOnChange(select, function(element) { setXmltvChannel(id, element) })
+ content.appendRow("XMLTV File", select)
+ var file = data[dbKey]
+
+ // XMLTV Mapping
+ var dbKey:string = "x-mapping"
+ var xmltv:XMLTVFile = new XMLTVFile()
+ var select = xmltv.getPrograms(file, data[dbKey])
+ select.setAttribute("name", dbKey)
+ select.setAttribute("id", "popup-mapping")
+ markChangedOnChange(select, function(element) { checkXmltvChannel(id, element, xmlFile) })
+
+ sortSelect(select)
+ content.appendRow("XMLTV Channel", select)
+
+ // Interaktion
+ content.createInteraction()
+
+ // Logo hochladen
+ var input = content.createInput("button", "cancel", "Upload Logo")
+ input.addEventListener("click", function() { uploadLogo() })
+ content.addInteraction(input)
+
+ // Abbrechen
+ var input = content.createInput("button", "cancel", "Cancel")
+ input.addEventListener("click", function() { showElement("popup", false) })
+ content.addInteraction(input)
+
+ // Fertig
+ var ids:string[] = new Array()
+ ids = getAllSelectedChannels()
+ if (ids.length == 0) {
+ ids.push(id)
+ }
+
+ var input = content.createInput("button", "save", "Done")
+ input.addEventListener("click", function() { donePopupData(dataType, ids.join(",")) })
+ content.addInteraction(input)
+ break
+
+ default:
+ break;
+ }
+
+ showPopUpElement('popup-custom');
+}
+
+function savePopupData(dataType:string, id:string, remove:Boolean, option:number) {
+
+ if (dataType == "mapping") {
+
+ var data = new Object()
+ console.log("Save mapping data")
+
+ cmd = "saveEpgMapping"
+ data["epgMapping"] = SERVER["xepg"]["epgMapping"]
+
+ console.log("SEND TO SERVER");
+
+ var server:Server = new Server(cmd)
+ server.request(data)
+
+ delete UNDO["epgMapping"]
+
+ return
+ }
+
+ console.log("Save popup data")
+ var div = document.getElementById("popup-custom")
+
+ var inputs = div.getElementsByTagName("TABLE")[0].getElementsByTagName("INPUT");
+ var selects = div.getElementsByTagName("TABLE")[0].getElementsByTagName("SELECT");
+
+ var input = new Object();
+ var confirmMsg: string
+
+ for (let i = 0; i < selects.length; i++) {
+
+ var name:string
+ name = (selects[i] as HTMLSelectElement).name
+ var value = (selects[i] as HTMLSelectElement).value
+
+ switch (name) {
+ case "tuner":
+ input[name] = parseInt(value)
+ break;
+
+ default:
+ input[name] = value
+ break;
+ }
+
+ }
+
+ for (let i = 0; i < inputs.length; i++) {
+
+ switch ((inputs[i] as HTMLInputElement).type) {
+
+ case "checkbox":
+ name = (inputs[i] as HTMLInputElement).name
+ input[name] = (inputs[i] as HTMLInputElement).checked
+ break
+
+ case "text":
+ case "hidden":
+ case "password":
+
+ name = (inputs[i] as HTMLInputElement).name
+
+ switch (name) {
+ case "tuner":
+ input[name] = parseInt((inputs[i] as HTMLInputElement).value)
+ break;
+
+ default:
+ input[name] = (inputs[i] as HTMLInputElement).value
+ break;
+ }
+
+ break
+
+ }
+
+ }
+
+ var data = new Object()
+
+ var cmd:string
+
+ if (remove == true) {
+ input["delete"] = true
+ }
+
+ switch (dataType) {
+ case "users":
+
+ confirmMsg = "Delete this user?"
+ if (id == "-") {
+ cmd = "saveNewUser"
+ data["userData"] = input
+ } else {
+ cmd = "saveUserData"
+ var d = new Object()
+ d[id] = input
+ data["userData"] = d
+ }
+
+ break;
+
+ case "m3u":
+
+ confirmMsg = "Delete this playlist?"
+ switch (option) {
+ // Popup: Save
+ case 0:
+ cmd = "saveFilesM3U"
+ break
+
+ // Popup: Update
+ case 1:
+ cmd = "updateFileM3U"
+ break
+
+ }
+
+ data["files"] = new Object
+ data["files"][dataType] = new Object
+ data["files"][dataType][id] = input
+
+ break
+
+ case "hdhr":
+
+ confirmMsg = "Delete this HDHomeRun tuner?"
+ switch (option) {
+ // Popup: Save
+ case 0:
+ cmd = "saveFilesHDHR"
+ break
+
+ // Popup: Update
+ case 1:
+ cmd = "updateFileHDHR"
+ break
+
+ }
+
+ data["files"] = new Object
+ data["files"][dataType] = new Object
+ data["files"][dataType][id] = input
+
+ break
+
+ case "xmltv":
+
+ confirmMsg = "Delete this XMLTV file?"
+ switch (option) {
+ // Popup: Save
+ case 0:
+ cmd = "saveFilesXMLTV"
+ break
+
+ // Popup: Update
+ case 1:
+ cmd = "updateFileXMLTV"
+ break
+
+ }
+
+ data["files"] = new Object
+ data["files"][dataType] = new Object
+ data["files"][dataType][id] = input
+
+ break
+
+ case "filter":
+
+ confirmMsg = "Delete this filter?"
+ cmd = "saveFilter"
+ data["filter"] = new Object
+ data["filter"][id] = input
+ break
+
+ default:
+ console.log(dataType, id);
+ return
+ break;
+
+ }
+
+ if (remove == true) {
+
+ if (!confirm(confirmMsg)) {
+ showElement("popup", false)
+ return
+ }
+
+ }
+
+ console.log("SEND TO SERVER");
+
+ console.log(data);
+
+ var server:Server = new Server(cmd)
+ server.request(data)
+
+}
+
+function donePopupData(dataType:string, idsStr:string) {
+
+ var ids:string[] = idsStr.split(',');
+ var div = document.getElementById("popup-custom")
+ var inputs = div.getElementsByClassName("changed")
+
+ ids.forEach(id => {
+ var input = new Object();
+ input = SERVER["xepg"]["epgMapping"][id]
+
+ console.log(input);
+
+ for (let i = 0; i < inputs.length; i++) {
+
+ var name:string
+ var value:any
+
+ switch (inputs[i].tagName) {
+
+ case "INPUT":
+ switch ((inputs[i] as HTMLInputElement).type) {
+ case "checkbox":
+ name = (inputs[i] as HTMLInputElement).name
+ value = (inputs[i] as HTMLInputElement).checked
+ input[name] = value
+ break
+
+ case "text":
+ name = (inputs[i] as HTMLInputElement).name
+ value = (inputs[i] as HTMLInputElement).value
+ input[name] = value
+ break
+
+ }
+
+ break
+
+ case "SELECT":
+ name = (inputs[i] as HTMLSelectElement).name
+ value = (inputs[i] as HTMLSelectElement).value
+ input[name] = value
+ break
+
+ }
+
+ switch (name) {
+
+
+ case "tvg-logo":
+ //(document.getElementById(id).childNodes[2].firstChild as HTMLElement).setAttribute("src", value)
+ break
+
+ case "x-name":
+ (document.getElementById(id).childNodes[3].firstChild as HTMLElement).textContent = value
+ break
+
+ case "x-category":
+ (document.getElementById(id).childNodes[3].firstChild as HTMLElement).className = value
+ break
+
+ case "x-group-title":
+ (document.getElementById(id).childNodes[5].firstChild as HTMLElement).textContent = value
+ break
+
+ case "x-xmltv-file":
+ if (value != "xTeVe Dummy" && value != "-") {
+ value = getValueFromProviderFile(value, "xmltv", "name")
+ }
+
+ if (value == "-") {
+ input["x-active"] = false
+ }
+
+ (document.getElementById(id).childNodes[6].firstChild as HTMLElement).textContent = value
+ break
+
+ case "x-mapping":
+ if (value == "-") {
+ input["x-active"] = false
+ }
+
+ (document.getElementById(id).childNodes[7].firstChild as HTMLElement).textContent = value
+
+ break
+
+ default:
+
+ }
+
+ createSearchObj()
+ searchInMapping()
+
+ }
+
+ if (input["x-active"] == false) {
+ document.getElementById(id).className = "notActiveEPG"
+ } else {
+ document.getElementById(id).className = "activeEPG"
+ }
+
+ console.log(input["tvg-logo"]);
+ (document.getElementById(id).childNodes[2].firstChild as HTMLElement).setAttribute("src", input["tvg-logo"])
+
+
+ });
+
+ showElement("popup", false);
+
+ return
+}
+
+function showPreview(element:boolean) {
+
+ var div = document.getElementById("myStreamsBox")
+ switch (element) {
+
+ case false:
+ div.className = "notVisible"
+ return
+ break;
+ }
+
+ var streams:string[] = ["activeStreams", "inactiveStreams"]
+
+ streams.forEach(preview => {
+
+ var table = document.getElementById(preview)
+ table.innerHTML = ""
+ var obj:string[] = SERVER["data"]["StreamPreviewUI"][preview]
+
+ obj.forEach(channel => {
+
+ var tr = document.createElement("TR")
+ var tdKey = document.createElement("TD")
+ var tdVal = document.createElement("TD")
+
+ tdKey.className = "tdKey"
+ tdVal.className = "tdVal"
+
+ switch (preview) {
+ case "activeStreams":
+ tdKey.innerText = "Channel: (+)"
+ break;
+
+ case "inactiveStreams":
+ tdKey.innerText = "Channel: (-)"
+ break;
+ }
+
+ tdVal.innerText = channel
+ tr.appendChild(tdKey)
+ tr.appendChild(tdVal)
+
+ table.appendChild(tr)
+
+ });
+
+ });
+
+ showElement("loading", false)
+ div.className = "visible"
+
+ return
+}
diff --git a/ts/settings.ts b/ts/settings.ts
index 1ec4838..b1818f8 100644
--- a/ts/settings.ts
+++ b/ts/settings.ts
@@ -1,3 +1,119 @@
+// One entry per settings key. The categories in base.ts decide which keys
+// are shown and in which order; this table decides how each one renders.
+interface SettingsFieldOption {
+ text:string
+ value:string
+}
+
+interface SettingsField {
+ key:string
+ title:string
+ type:"text" | "checkbox" | "number" | "select" | "password"
+ placeholder?:string
+ autocomplete?:string
+ options?:SettingsFieldOption[]
+ // Static text written in the source; may contain HTML.
+ description:string
+}
+
+function settingsOptions(text:string[], values:string[]):SettingsFieldOption[] {
+ var options:SettingsFieldOption[] = []
+ for (var i = 0; i < text.length; i++) {
+ options.push({text: text[i], value: values[i]})
+ }
+ return options
+}
+
+function settingsNumberOptions(from:number, to:number):SettingsFieldOption[] {
+ var options:SettingsFieldOption[] = []
+ for (var i = from; i <= to; i++) {
+ options.push({text: i.toString(), value: i.toString()})
+ }
+ return options
+}
+
+const SETTINGS_FIELDS:SettingsField[] = [
+
+ // Text inputs
+ {key: "update", title: "Schedule for updating (Playlist, XMLTV, Backup)", type: "text", placeholder: "0000,1000,2000",
+ description: "Time in 24 hour format (0800 = 8:00 am). More times can be entered comma separated. Leave this field empty if no updates are to be carried out."},
+ {key: "backup.path", title: "Location for automatic backups", type: "text", placeholder: "/mnt/data/backup/xteve/",
+ description: "Before any update of the provider data by the schedule, xTeVe creates a backup. The path for the automatic backups can be changed. xTeVe requires write permission for this folder."},
+ {key: "temp.path", title: "Location for the temporary files", type: "text", placeholder: "/tmp/xteve/",
+ description: "Location for the buffer files."},
+ {key: "user.agent", title: "User Agent", type: "text", placeholder: "xTeVe",
+ description: "User Agent for HTTP requests. For every HTTP connection, this value is used for the user agent. Should only be changed if xTeVe is blocked."},
+ {key: "plex.url", title: "Plex Server URL", type: "text", placeholder: "http://plex-host:32400",
+ description: "Base URL of your Plex server. Example: http://192.168.1.10:32400"},
+ // The server sends the mask "********" and treats it coming back as "unchanged".
+ {key: "plex.token", title: "Plex API Token", type: "password", placeholder: "Plex X-Plex-Token", autocomplete: "off",
+ description: "Plex token used for authenticated API calls to refresh your DVR guide."},
+ {key: "buffer.timeout", title: "Timeout for new client connections", type: "text", placeholder: "100",
+ description: "The xTeVe buffer waits until new client connections are established. Helpful for fast channel switching. Value in milliseconds."},
+ {key: "ffmpeg.path", title: "FFmpeg Binary Path", type: "text", placeholder: "/path/to/ffmpeg",
+ description: "Path to FFmpeg binary."},
+ {key: "ffmpeg.options", title: "FFmpeg Options", type: "text", placeholder: "Leave blank to set default settings",
+ description: "FFmpeg options.
Only change if you know what you are doing.
Leave blank to set default settings."},
+ {key: "vlc.path", title: "VLC / CVLC Binary Path", type: "text", placeholder: "/path/to/cvlc",
+ description: "Path to VLC / CVLC binary."},
+ {key: "vlc.options", title: "VLC / CVLC Options", type: "text", placeholder: "Leave blank to set default settings",
+ description: "VLC / CVLC options.
Only change if you know what you are doing.
Leave blank to set default settings."},
+ {key: "udpxy", title: "UDPxy address", type: "text", placeholder: "host:port",
+ description: "The address of your UDPxy server. If set, and the channel URLs in the m3u is multicast, xTeVe will rewrite it so that it is accessed via the UDPxy service."},
+
+ // Checkboxes
+ {key: "authentication.web", title: "WEB Authentication", type: "checkbox",
+ description: "Access to the web interface only possible with credentials."},
+ {key: "authentication.pms", title: "PMS Authentication", type: "checkbox",
+ description: "Plex requests are only possible with authentication.
Warning!!! After activating this function xTeVe must be delete in the PMS DVR settings and set up again."},
+ {key: "authentication.m3u", title: "M3U Authentication", type: "checkbox",
+ description: "Downloading the xteve.m3u file via an HTTP request is only possible with authentication."},
+ {key: "authentication.xml", title: "XML Authentication", type: "checkbox",
+ description: "Downloading the xteve.xml file via an HTTP request is only possible with authentication"},
+ // Only rendered while web authentication is on (see SettingsCategoryItem.createCategory).
+ {key: "authentication.api", title: "API Authentication", type: "checkbox",
+ description: "Access to the API interface is only possible with authentication."},
+ {key: "files.update", title: "Updates all files at startup", type: "checkbox",
+ description: "Updates all playlists, tuner and XMLTV files at startup."},
+ {key: "cache.images", title: "Image Caching", type: "checkbox",
+ description: "All images from the XMLTV file are cached, allowing faster rendering of the grid in the client.
Downloading the images may take a while and will be done in the background."},
+ {key: "xepg.replace.missing.images", title: "Replace missing program images", type: "checkbox",
+ description: "If the poster in the XMLTV program is missing, the channel logo will be used."},
+ {key: "api", title: "API Interface", type: "checkbox",
+ description: "Via API interface it is possible to send commands to xTeVe. API documentation is here"},
+ {key: "use_plexAPI", title: "Use Plex API Refresh", type: "checkbox",
+ description: "When enabled, xTeVe calls Plex directly to refresh DVR guide data after lineup or XEPG updates."},
+
+ // Selects
+ {key: "xepg.missing.epg.mode", title: "Missing EPG Handling", type: "select",
+ options: settingsOptions(["Strict (deactivate channels)", "Relaxed (keep active / dummy guide)"], ["strict", "relaxed"]),
+ description: "Strict: channels are deactivated when XMLTV mappings disappear.
Relaxed: channels stay active with last-known mappings and fall back to a dummy guide whenever guide data cannot be resolved."},
+ {key: "tuner", title: "Number of Tuners", type: "select",
+ options: settingsNumberOptions(1, 100),
+ description: "Number of parallel connections that can be established to the provider.
Available for: Plex, Emby (HDHR), M3U (with active buffer).
After a change, xTeVe must be delete in the Plex / Emby DVR settings and set up again."},
+ {key: "epgSource", title: "EPG Source", type: "select",
+ options: settingsOptions(["PMS", "XEPG"], ["PMS", "XEPG"]),
+ description: "PMS:
- Use EPG data from Plex or Emby
XEPG:
- Use of one or more XMLTV files
- Channel management
- M3U / XMLTV export (HTTP link for IPTV apps)"},
+ {key: "backup.keep", title: "Number of backups to keep", type: "select",
+ options: settingsOptions(["5", "10", "20", "30", "40", "50"], ["5", "10", "20", "30", "40", "50"]),
+ description: "Number of backups to keep. Older backups are automatically deleted."},
+ {key: "buffer.size.kb", title: "Buffer Size", type: "select",
+ options: settingsOptions(["0.5 MB", "1 MB", "2 MB", "3 MB", "4 MB", "5 MB", "6 MB", "7 MB", "8 MB"], ["512", "1024", "2048", "3072", "4096", "5120", "6144", "7168", "8192"]),
+ description: "Buffer size in MB.
M3U8: If the TS segment smaller then the buffer size, the file size of the segment is used."},
+ {key: "buffer", title: "Stream Buffer", type: "select",
+ options: settingsOptions(["No Buffer (Client connects to the streaming server)", "xTeVe: (xTeVe connects to the streaming server)", "FFmpeg: (FFmpeg connects to the streaming server)", "VLC: (VLC connects to the streaming server)"], ["-", "xteve", "ffmpeg", "vlc"]),
+ description: "Functions of the buffer:
- The stream is passed from xTeVe, FFmpeg or VLC to Plex, Emby or M3U Player
- Small jerking of the streams can be compensated
- HLS / M3U8 support
- RTP / RTPS support (only FFmpeg or VLC)
- Re-streaming
- Separate tuner limit for each playlist"},
+]
+
+function getSettingsField(settingsKey:string):SettingsField {
+ for (var i = 0; i < SETTINGS_FIELDS.length; i++) {
+ if (SETTINGS_FIELDS[i].key == settingsKey) {
+ return SETTINGS_FIELDS[i]
+ }
+ }
+ return undefined
+}
+
class SettingsCategory {
DocumentID:string = "content_settings"
createCategoryHeadline(value:string):any {
@@ -11,558 +127,70 @@ class SettingsCategory {
return element
}
- createSettings(settingsKey:string):any {
- var setting = document.createElement("TR")
+ // createControl builds the input for one field, filled with the current value.
+ createControl(field:SettingsField, data:any):HTMLElement {
var content:PopupContent = new PopupContent()
- var data = SERVER["settings"][settingsKey]
+ var control:HTMLElement
- switch (settingsKey) {
+ switch (field.type) {
- // Texteingaben
- case "update":
- var tdLeft = document.createElement("TD")
- tdLeft.innerHTML = "Schedule for updating (Playlist, XMLTV, Backup)" + ":"
-
- var tdRight = document.createElement("TD")
- var input = content.createInput("text", "update", data.toString())
- input.setAttribute("placeholder", "0000,1000,2000")
- input.setAttribute("onchange", "javascript: this.className = 'changed'")
- tdRight.appendChild(input)
-
- setting.appendChild(tdLeft)
- setting.appendChild(tdRight)
+ case "checkbox":
+ control = content.createCheckbox(field.key);
+ (control as HTMLInputElement).checked = data
break
- case "backup.path":
- var tdLeft = document.createElement("TD")
- tdLeft.innerHTML = "Location for automatic backups" + ":"
-
- var tdRight = document.createElement("TD")
- var input = content.createInput("text", "backup.path", data)
- input.setAttribute("placeholder", "/mnt/data/backup/xteve/")
- input.setAttribute("onchange", "javascript: this.className = 'changed'")
- tdRight.appendChild(input)
-
- setting.appendChild(tdLeft)
- setting.appendChild(tdRight)
- break
-
- case "temp.path":
- var tdLeft = document.createElement("TD")
- tdLeft.innerHTML = "Location for the temporary files" + ":"
-
- var tdRight = document.createElement("TD")
- var input = content.createInput("text", "temp.path", data)
- input.setAttribute("placeholder", "/tmp/xteve/")
- input.setAttribute("onchange", "javascript: this.className = 'changed'")
- tdRight.appendChild(input)
-
- setting.appendChild(tdLeft)
- setting.appendChild(tdRight)
- break
-
- case "user.agent":
- var tdLeft = document.createElement("TD")
- tdLeft.innerHTML = "User Agent" + ":"
-
- var tdRight = document.createElement("TD")
- var input = content.createInput("text", "user.agent", data)
- input.setAttribute("placeholder", "xTeVe")
- input.setAttribute("onchange", "javascript: this.className = 'changed'")
- tdRight.appendChild(input)
-
- setting.appendChild(tdLeft)
- setting.appendChild(tdRight)
- break
-
- case "plex.url":
- var tdLeft = document.createElement("TD")
- tdLeft.innerHTML = "Plex Server URL" + ":"
-
- var tdRight = document.createElement("TD")
- var input = content.createInput("text", "plex.url", data)
- input.setAttribute("placeholder", "http://plex-host:32400")
- input.setAttribute("onchange", "javascript: this.className = 'changed'")
- tdRight.appendChild(input)
-
- setting.appendChild(tdLeft)
- setting.appendChild(tdRight)
- break
-
- case "plex.token":
- var tdLeft = document.createElement("TD")
- tdLeft.innerHTML = "Plex API Token" + ":"
-
- var tdRight = document.createElement("TD")
- var input = content.createInput("password", "plex.token", data)
- input.setAttribute("placeholder", "Plex X-Plex-Token")
- input.setAttribute("autocomplete", "off")
- input.setAttribute("onchange", "javascript: this.className = 'changed'")
- tdRight.appendChild(input)
-
- setting.appendChild(tdLeft)
- setting.appendChild(tdRight)
- break
-
- case "buffer.timeout":
- var tdLeft = document.createElement("TD")
- tdLeft.innerHTML = "Timeout for new client connections" + ":"
-
- var tdRight = document.createElement("TD")
- var input = content.createInput("text", "buffer.timeout", data)
- input.setAttribute("placeholder", "100")
- input.setAttribute("onchange", "javascript: this.className = 'changed'")
- tdRight.appendChild(input)
-
- setting.appendChild(tdLeft)
- setting.appendChild(tdRight)
- break
-
- case "ffmpeg.path":
- var tdLeft = document.createElement("TD")
- tdLeft.innerHTML = "FFmpeg Binary Path" + ":"
-
- var tdRight = document.createElement("TD")
- var input = content.createInput("text", "ffmpeg.path", data)
- input.setAttribute("placeholder", "/path/to/ffmpeg")
- input.setAttribute("onchange", "javascript: this.className = 'changed'")
- tdRight.appendChild(input)
-
- setting.appendChild(tdLeft)
- setting.appendChild(tdRight)
- break
-
- case "ffmpeg.options":
- var tdLeft = document.createElement("TD")
- tdLeft.innerHTML = "FFmpeg Options" + ":"
-
- var tdRight = document.createElement("TD")
- var input = content.createInput("text", "ffmpeg.options", data)
- input.setAttribute("placeholder", "Leave blank to set default settings")
- input.setAttribute("onchange", "javascript: this.className = 'changed'")
- tdRight.appendChild(input)
-
- setting.appendChild(tdLeft)
- setting.appendChild(tdRight)
- break
-
- case "vlc.path":
- var tdLeft = document.createElement("TD")
- tdLeft.innerHTML = "VLC / CVLC Binary Path" + ":"
-
- var tdRight = document.createElement("TD")
- var input = content.createInput("text", "vlc.path", data)
- input.setAttribute("placeholder", "/path/to/cvlc")
- input.setAttribute("onchange", "javascript: this.className = 'changed'")
- tdRight.appendChild(input)
-
- setting.appendChild(tdLeft)
- setting.appendChild(tdRight)
- break
-
- case "vlc.options":
- var tdLeft = document.createElement("TD")
- tdLeft.innerHTML = "VLC / CVLC Options" + ":"
-
- var tdRight = document.createElement("TD")
- var input = content.createInput("text", "vlc.options", data)
- input.setAttribute("placeholder", "Leave blank to set default settings")
- input.setAttribute("onchange", "javascript: this.className = 'changed'")
- tdRight.appendChild(input)
-
- setting.appendChild(tdLeft)
- setting.appendChild(tdRight)
- break
-
- // Checkboxen
- case "authentication.web":
- var tdLeft = document.createElement("TD")
- tdLeft.innerHTML = "WEB Authentication" + ":"
-
- var tdRight = document.createElement("TD")
- var input = content.createCheckbox(settingsKey)
- input.checked = data
- input.setAttribute("onchange", "javascript: this.className = 'changed'")
- tdRight.appendChild(input)
-
- setting.appendChild(tdLeft)
- setting.appendChild(tdRight)
- break
-
- case "authentication.pms":
- var tdLeft = document.createElement("TD")
- tdLeft.innerHTML = "PMS Authentication" + ":"
-
- var tdRight = document.createElement("TD")
- var input = content.createCheckbox(settingsKey)
- input.checked = data
- input.setAttribute("onchange", "javascript: this.className = 'changed'")
- tdRight.appendChild(input)
-
- setting.appendChild(tdLeft)
- setting.appendChild(tdRight)
- break
-
- case "authentication.m3u":
- var tdLeft = document.createElement("TD")
- tdLeft.innerHTML = "M3U Authentication" + ":"
-
- var tdRight = document.createElement("TD")
- var input = content.createCheckbox(settingsKey)
- input.checked = data
- input.setAttribute("onchange", "javascript: this.className = 'changed'")
- tdRight.appendChild(input)
-
- setting.appendChild(tdLeft)
- setting.appendChild(tdRight)
- break
-
- case "authentication.xml":
- var tdLeft = document.createElement("TD")
- tdLeft.innerHTML = "XML Authentication" + ":"
-
- var tdRight = document.createElement("TD")
- var input = content.createCheckbox(settingsKey)
- input.checked = data
- input.setAttribute("onchange", "javascript: this.className = 'changed'")
- tdRight.appendChild(input)
-
- setting.appendChild(tdLeft)
- setting.appendChild(tdRight)
- break
-
- case "authentication.api":
- var tdLeft = document.createElement("TD")
- tdLeft.innerHTML = "API Authentication" + ":"
-
- var tdRight = document.createElement("TD")
- var input = content.createCheckbox(settingsKey)
- input.checked = data
- input.setAttribute("onchange", "javascript: this.className = 'changed'")
- tdRight.appendChild(input)
-
- setting.appendChild(tdLeft)
- setting.appendChild(tdRight)
- break
-
- case "files.update":
- var tdLeft = document.createElement("TD")
- tdLeft.innerHTML = "Updates all files at startup" + ":"
-
- var tdRight = document.createElement("TD")
- var input = content.createCheckbox(settingsKey)
- input.checked = data
- input.setAttribute("onchange", "javascript: this.className = 'changed'")
- tdRight.appendChild(input)
-
- setting.appendChild(tdLeft)
- setting.appendChild(tdRight)
- break
-
- case "cache.images":
- var tdLeft = document.createElement("TD")
- tdLeft.innerHTML = "Image Caching" + ":"
-
- var tdRight = document.createElement("TD")
- var input = content.createCheckbox(settingsKey)
- input.checked = data
- input.setAttribute("onchange", "javascript: this.className = 'changed'")
- tdRight.appendChild(input)
-
- setting.appendChild(tdLeft)
- setting.appendChild(tdRight)
- break
-
- case "xepg.replace.missing.images":
- var tdLeft = document.createElement("TD")
- tdLeft.innerHTML = "Replace missing program images" + ":"
-
- var tdRight = document.createElement("TD")
- var input = content.createCheckbox(settingsKey)
- input.checked = data
- input.setAttribute("onchange", "javascript: this.className = 'changed'")
- tdRight.appendChild(input)
-
- setting.appendChild(tdLeft)
- setting.appendChild(tdRight)
- break
-
- case "api":
- var tdLeft = document.createElement("TD")
- tdLeft.innerHTML = "API Interface" + ":"
-
- var tdRight = document.createElement("TD")
- var input = content.createCheckbox(settingsKey)
- input.checked = data
- input.setAttribute("onchange", "javascript: this.className = 'changed'")
- tdRight.appendChild(input)
-
- setting.appendChild(tdLeft)
- setting.appendChild(tdRight)
- break
-
- case "use_plexAPI":
- var tdLeft = document.createElement("TD")
- tdLeft.innerHTML = "Use Plex API Refresh" + ":"
-
- var tdRight = document.createElement("TD")
- var input = content.createCheckbox(settingsKey)
- input.checked = data
- input.setAttribute("onchange", "javascript: this.className = 'changed'")
- tdRight.appendChild(input)
-
- setting.appendChild(tdLeft)
- setting.appendChild(tdRight)
- break
-
- // Select
- case "xepg.missing.epg.mode":
- var tdLeft = document.createElement("TD")
- tdLeft.innerHTML = "Missing EPG Handling" + ":"
-
- var tdRight = document.createElement("TD")
- var text:any[] = ["Strict (deactivate channels)", "Relaxed (keep active / dummy guide)"]
- var values:any[] = ["strict", "relaxed"]
-
- var select = content.createSelect(text, values, data, settingsKey)
- select.setAttribute("onchange", "javascript: this.className = 'changed'")
- tdRight.appendChild(select)
-
- setting.appendChild(tdLeft)
- setting.appendChild(tdRight)
- break
-
- case "tuner":
- var tdLeft = document.createElement("TD")
- tdLeft.innerHTML = "Number of Tuners" + ":"
-
- var tdRight = document.createElement("TD")
- var text = new Array()
- var values = new Array()
-
- for (var i = 1; i <= 100; i++) {
- text.push(i)
- values.push(i)
- }
-
- var select = content.createSelect(text, values, data, settingsKey)
- select.setAttribute("onchange", "javascript: this.className = 'changed'")
- tdRight.appendChild(select)
-
- setting.appendChild(tdLeft)
- setting.appendChild(tdRight)
- break
-
- case "epgSource":
- var tdLeft = document.createElement("TD")
- tdLeft.innerHTML = "EPG Source" + ":"
-
- var tdRight = document.createElement("TD")
- var text:any[] = ["PMS", "XEPG"]
- var values:any[] = ["PMS", "XEPG"]
-
- var select = content.createSelect(text, values, data, settingsKey)
- select.setAttribute("onchange", "javascript: this.className = 'changed'")
- tdRight.appendChild(select)
-
- setting.appendChild(tdLeft)
- setting.appendChild(tdRight)
- break
-
- case "backup.keep":
- var tdLeft = document.createElement("TD")
- tdLeft.innerHTML = "Number of backups to keep" + ":"
-
- var tdRight = document.createElement("TD")
- var text:any[] = ["5", "10", "20", "30", "40", "50"]
- var values:any[] = ["5", "10", "20", "30", "40", "50"]
-
- var select = content.createSelect(text, values, data, settingsKey)
- select.setAttribute("onchange", "javascript: this.className = 'changed'")
- tdRight.appendChild(select)
-
- setting.appendChild(tdLeft)
- setting.appendChild(tdRight)
- break
-
- case "buffer.size.kb":
- var tdLeft = document.createElement("TD")
- tdLeft.innerHTML = "Buffer Size" + ":"
-
- var tdRight = document.createElement("TD")
- var text:any[] = ["0.5 MB", "1 MB", "2 MB", "3 MB", "4 MB", "5 MB", "6 MB", "7 MB", "8 MB"]
- var values:any[] = ["512", "1024", "2048", "3072", "4096", "5120", "6144", "7168", "8192"]
-
- var select = content.createSelect(text, values, data, settingsKey)
- select.setAttribute("onchange", "javascript: this.className = 'changed'")
- tdRight.appendChild(select)
-
- setting.appendChild(tdLeft)
- setting.appendChild(tdRight)
- break
-
- case "buffer":
- var tdLeft = document.createElement("TD")
- tdLeft.innerHTML = "Stream Buffer" + ":"
-
- var tdRight = document.createElement("TD")
- var text:any[] = ["No Buffer (Client connects to the streaming server)", "xTeVe: (xTeVe connects to the streaming server)", "FFmpeg: (FFmpeg connects to the streaming server)", "VLC: (VLC connects to the streaming server)"]
- var values:any[] = ["-", "xteve", "ffmpeg", "vlc"]
-
- var select = content.createSelect(text, values, data, settingsKey)
- select.setAttribute("onchange", "javascript: this.className = 'changed'")
- tdRight.appendChild(select)
-
- setting.appendChild(tdLeft)
- setting.appendChild(tdRight)
- break
-
- case "udpxy":
-
- var tdLeft = document.createElement("TD");
- tdLeft.innerHTML = "UDPxy address" + ":"
-
- var tdRight = document.createElement("TD")
- var input = content.createInput("text", "udpxy", data)
- input.setAttribute("placeholder", "host:port")
- input.setAttribute("onchange", "javascript: this.className = 'changed'")
- tdRight.appendChild(input)
-
- setting.appendChild(tdLeft)
- setting.appendChild(tdRight)
- break
-
- }
-
- return setting
-
- }
-
-
- createDescription(settingsKey:string):any {
-
- var description = document.createElement("TR")
- var text:string
- switch (settingsKey) {
-
- case "authentication.web":
- text = "Access to the web interface only possible with credentials."
- break
-
- case "authentication.m3u":
- text = "Downloading the xteve.m3u file via an HTTP request is only possible with authentication."
- break
-
- case "authentication.pms":
- text = "Plex requests are only possible with authentication.
Warning!!! After activating this function xTeVe must be delete in the PMS DVR settings and set up again."
- break
-
- case "authentication.xml":
- text = "Downloading the xteve.xml file via an HTTP request is only possible with authentication"
- break
-
- case "authentication.api":
- if (SERVER["settings"]["authentication.web"] == true) {
- text = "Access to the API interface is only possible with authentication."
- }
- break
-
- case "backup.keep":
- text = "Number of backups to keep. Older backups are automatically deleted."
- break
-
- case "backup.path":
- text = "Before any update of the provider data by the schedule, xTeVe creates a backup. The path for the automatic backups can be changed. xTeVe requires write permission for this folder."
- break
-
- case "temp.path":
- text = "Location for the buffer files."
- break
-
- case "buffer":
- text = "Functions of the buffer:
- The stream is passed from xTeVe, FFmpeg or VLC to Plex, Emby or M3U Player
- Small jerking of the streams can be compensated
- HLS / M3U8 support
- RTP / RTPS support (only FFmpeg or VLC)
- Re-streaming
- Separate tuner limit for each playlist"
- break
-
- case "buffer.size.kb":
- text = "Buffer size in MB.
M3U8: If the TS segment smaller then the buffer size, the file size of the segment is used."
- break
-
- case "buffer.timeout":
- text = "The xTeVe buffer waits until new client connections are established. Helpful for fast channel switching. Value in milliseconds."
- break
-
- case "user.agent":
- text = "User Agent for HTTP requests. For every HTTP connection, this value is used for the user agent. Should only be changed if xTeVe is blocked."
- break
-
- case "plex.url":
- text = "Base URL of your Plex server. Example: http://192.168.1.10:32400"
- break
-
- case "plex.token":
- text = "Plex token used for authenticated API calls to refresh your DVR guide."
- break
-
- case "ffmpeg.path":
- text = "Path to FFmpeg binary."
- break
-
- case "ffmpeg.options":
- text = "FFmpeg options.
Only change if you know what you are doing.
Leave blank to set default settings."
- break
-
- case "vlc.path":
- text = "Path to VLC / CVLC binary."
- break
-
- case "vlc.options":
- text = "VLC / CVLC options.
Only change if you know what you are doing.
Leave blank to set default settings."
- break
-
- case "epgSource":
- text = "PMS:
- Use EPG data from Plex or Emby
XEPG:
- Use of one or more XMLTV files
- Channel management
- M3U / XMLTV export (HTTP link for IPTV apps)"
- break
-
- case "tuner":
- text = "Number of parallel connections that can be established to the provider.
Available for: Plex, Emby (HDHR), M3U (with active buffer).
After a change, xTeVe must be delete in the Plex / Emby DVR settings and set up again."
- break
-
- case "update":
- text = "Time in 24 hour format (0800 = 8:00 am). More times can be entered comma separated. Leave this field empty if no updates are to be carried out."
- break
-
- case "api":
- text = "Via API interface it is possible to send commands to xTeVe. API documentation is here"
- break
-
- case "use_plexAPI":
- text = "When enabled, xTeVe calls Plex directly to refresh DVR guide data after lineup or XEPG updates."
- break
-
- case "files.update":
- text = "Updates all playlists, tuner and XMLTV files at startup."
- break
-
- case "cache.images":
- text = "All images from the XMLTV file are cached, allowing faster rendering of the grid in the client.
Downloading the images may take a while and will be done in the background."
- break
-
- case "xepg.replace.missing.images":
- text = "If the poster in the XMLTV program is missing, the channel logo will be used."
- break
-
- case "xepg.missing.epg.mode":
- text = "Strict: channels are deactivated when XMLTV mappings disappear.
Relaxed: channels stay active with last-known mappings and fall back to a dummy guide whenever guide data cannot be resolved."
- break
-
- case "udpxy":
- text = "The address of your UDPxy server. If set, and the channel URLs in the m3u is multicast, xTeVe will rewrite it so that it is accessed via the UDPxy service."
+ case "select":
+ var text:string[] = field.options.map(function(option) { return option.text })
+ var values:string[] = field.options.map(function(option) { return option.value })
+ control = content.createSelect(text, values, data, field.key)
break
default:
- text = ""
+ // "text", "password", "number": lists (the update schedule) render comma separated.
+ var value:any = data
+ if (Array.isArray(data)) {
+ value = data.join(",")
+ }
+ control = content.createInput(field.type, field.key, value)
+ if (field.placeholder != undefined) {
+ control.setAttribute("placeholder", field.placeholder)
+ }
+ if (field.autocomplete != undefined) {
+ control.setAttribute("autocomplete", field.autocomplete)
+ }
break
+ }
+ markChangedOnChange(control)
+ return control
+ }
+
+ createSettings(settingsKey:string):any {
+ var setting = document.createElement("TR")
+ var field:SettingsField = getSettingsField(settingsKey)
+ if (field == undefined) {
+ return setting
+ }
+
+ var data = SERVER["settings"][settingsKey]
+
+ var tdLeft = document.createElement("TD")
+ tdLeft.textContent = field.title + ":"
+
+ var tdRight = document.createElement("TD")
+ tdRight.appendChild(this.createControl(field, data))
+
+ setting.appendChild(tdLeft)
+ setting.appendChild(tdRight)
+ return setting
+ }
+
+ createDescription(settingsKey:string):any {
+ var description = document.createElement("TR")
+ var field:SettingsField = getSettingsField(settingsKey)
+ var text:string = ""
+ if (field != undefined) {
+ text = field.description
}
var tdLeft = document.createElement("TD")
@@ -577,7 +205,6 @@ class SettingsCategory {
description.appendChild(tdRight)
return description
-
}
}
@@ -669,6 +296,7 @@ function saveSettings() {
break
case "text":
+ case "password":
name = (settings[i] as HTMLInputElement).name
value = (settings[i] as HTMLInputElement).value
diff --git a/ts/tsconfig.json b/ts/tsconfig.json
index 41433d0..004d754 100644
--- a/ts/tsconfig.json
+++ b/ts/tsconfig.json
@@ -11,12 +11,15 @@
"removeComments": false
},
"files": [
- "network_ts.ts",
- "menu_ts.ts",
- "settings_ts.ts",
- "logs_ts.ts",
- "base_ts.ts",
- "configuration_ts.ts",
- "authentication_ts.ts"
+ "network.ts",
+ "menu.ts",
+ "content.ts",
+ "popup.ts",
+ "xmltv.ts",
+ "settings.ts",
+ "logs.ts",
+ "base.ts",
+ "configuration.ts",
+ "authentication.ts"
]
}
diff --git a/ts/xmltv.ts b/ts/xmltv.ts
new file mode 100644
index 0000000..79adadc
--- /dev/null
+++ b/ts/xmltv.ts
@@ -0,0 +1,206 @@
+class XMLTVFile {
+ File:string
+
+ getFiles(set:string):any {
+ var fileIDs:string[] = getObjKeys(SERVER["xepg"]["xmltvMap"])
+ var values = new Array("-");
+ var text = new Array("-");
+
+ for (let i = 0; i < fileIDs.length; i++) {
+ if (fileIDs[i] != "xTeVe Dummy") {
+ values.push(getValueFromProviderFile(fileIDs[i], "xmltv", "file.xteve"))
+ text.push(getValueFromProviderFile(fileIDs[i], "xmltv", "name"))
+ } else {
+ values.push(fileIDs[i])
+ text.push(fileIDs[i])
+ }
+
+ }
+
+ var select = document.createElement("SELECT")
+ for (let i = 0; i < text.length; i++) {
+ var option = document.createElement("OPTION")
+ option.setAttribute("value", values[i])
+ option.innerText = text[i]
+ select.appendChild(option)
+ }
+
+ if(set != "") {
+ (select as HTMLSelectElement).value = set
+ }
+
+ return select
+ }
+
+ getPrograms(file:string, set:string):any {
+ //var fileIDs:string[] = getObjKeys(SERVER["xepg"]["xmltvMap"])
+ var values = getObjKeys(SERVER["xepg"]["xmltvMap"][file]);
+ var text = new Array()
+ var displayName:string
+
+ for (let i = 0; i < values.length; i++) {
+ if (SERVER["xepg"]["xmltvMap"][file][values[i]].hasOwnProperty('display-name') == true) {
+ displayName = SERVER["xepg"]["xmltvMap"][file][values[i]]["display-name"];
+ } else {
+ displayName = "-"
+ }
+
+ text[i] = displayName + " (" + values[i] + ")";
+ }
+
+ text.unshift("-");
+ values.unshift("-");
+
+ var select = document.createElement("SELECT")
+ for (let i = 0; i < text.length; i++) {
+ var option = document.createElement("OPTION")
+ option.setAttribute("value", values[i])
+ option.innerText = text[i]
+ select.appendChild(option)
+ }
+
+ if(set != "") {
+ (select as HTMLSelectElement).value = set
+ }
+
+ if ((select as HTMLSelectElement).value != set) {
+ (select as HTMLSelectElement).value = "-"
+ }
+
+ return select
+ }
+
+ return
+}
+
+function getValueFromProviderFile(file:string, fileType, key) {
+
+ if (file == "xTeVe Dummy") {
+ return file
+ }
+
+ var fileID:string
+ var indicator = file.charAt(0)
+
+ switch (indicator) {
+ case "M":
+ fileType = "m3u"
+ fileID = file
+ break;
+
+ case "H":
+ fileType = "hdhr"
+ fileID = file
+ break;
+
+ case "X":
+ fileType = "xmltv"
+ fileID = file.substring(0, file.lastIndexOf('.'))
+ break;
+
+ }
+
+ if (SERVER["settings"]["files"][fileType].hasOwnProperty(fileID) == true) {
+ var data = SERVER["settings"]["files"][fileType][fileID];
+ return data[key]
+ }
+
+ return
+
+}
+
+function setXmltvChannel(id, element) {
+
+ var xmltv:XMLTVFile = new XMLTVFile()
+ var xmlFile = element.value
+
+ var tvgId:string = SERVER["xepg"]["epgMapping"][id]["tvg-id"]
+ var td = document.getElementById("popup-mapping").parentElement
+ td.innerHTML = ""
+
+ var select = xmltv.getPrograms(element.value, tvgId)
+ select.setAttribute("name", "x-mapping")
+ select.setAttribute("id", "popup-mapping")
+ markChangedOnChange(select, function(element) { checkXmltvChannel(id, element, xmlFile) })
+ select.className = "changed"
+ sortSelect(select)
+ td.appendChild(select);
+
+ checkXmltvChannel(id, select, xmlFile)
+}
+
+function checkXmltvChannel(id:string, element:any, xmlFile) {
+
+ var value = (element as HTMLSelectElement).value
+ var bool:boolean
+ var checkbox = document.getElementById('active')
+ var channel:any = SERVER["xepg"]["epgMapping"][id]
+ var updateLogo:boolean
+
+
+ if (value == "-") {
+ bool = false
+ } else {
+ bool = true
+ }
+
+ (checkbox as HTMLInputElement).checked = bool
+ checkbox.className = "changed"
+ console.log(xmlFile);
+
+ // Kanallogo aktualisieren
+ /*
+ updateLogo = (document.getElementById("update-icon") as HTMLInputElement).checked
+ console.log(updateLogo);
+ */
+
+ if(xmlFile != "xTeVe Dummy" && bool == true) {
+
+ //(document.getElementById("update-icon") as HTMLInputElement).checked = true;
+ //(document.getElementById("update-icon") as HTMLInputElement).className = "changed";
+
+ console.log("ID", id)
+ changeChannelLogo(id)
+
+ return
+ }
+
+ if (xmlFile == "xTeVe Dummy") {
+ (document.getElementById("update-icon") as HTMLInputElement).checked = false;
+ (document.getElementById("update-icon") as HTMLInputElement).className = "changed";
+ }
+
+ return
+}
+
+function changeChannelLogo(id:string) {
+
+ var updateLogo:boolean
+ var channel:any = SERVER["xepg"]["epgMapping"][id]
+
+ var f = (document.getElementById("popup-xmltv") as HTMLSelectElement);
+ var xmltvFile = f.options[f.selectedIndex].value;
+
+ var m = (document.getElementById("popup-mapping") as HTMLSelectElement);
+ var xMapping = m.options[m.selectedIndex].value;
+
+ var xmltvLogo = SERVER["xepg"]["xmltvMap"][xmltvFile][xMapping]["icon"]
+ updateLogo = (document.getElementById("update-icon") as HTMLInputElement).checked
+
+ if (updateLogo == true && xmltvFile != "xTeVe Dummy") {
+
+ if (SERVER["xepg"]["xmltvMap"][xmltvFile].hasOwnProperty(xMapping)) {
+ var logo = xmltvLogo
+ } else {
+ logo = channel["tvg-logo"]
+ }
+
+ var logoInput = (document.getElementById("channel-icon") as HTMLInputElement);
+ logoInput.value = logo
+ if (BULK_EDIT == false) {
+ logoInput.className = "changed"
+ }
+
+ }
+
+}