continuous-integration/drone/push Build is passing
TypeScript:
- menu_ts.ts split into menu.ts, content.ts, popup.ts and xmltv.ts; the
other files drop their _ts suffix. Bundle order fixed in ts/tsconfig.json.
- All 94 string event handlers (setAttribute("onclick", "javascript: ..."))
replaced with addEventListener closures. changeButtonAction, which
rewrote an onclick attribute from select values, is gone.
- Settings rows are generated from one SETTINGS_FIELDS table instead of
~20 copy-pasted blocks; rendered HTML is byte-identical to before.
saveSettings now serialises password inputs, so a changed Plex token is
actually sent (the server treats the mask as unchanged).
- createLayout rebuilds the menu list only when the set of visible items
changes, so focus in the menu survives a refresh.
- announce()/alertUser(): alerts are mirrored into an aria-live region.
- Filter popup offers a third type, Regular Expression (regex-filter), and
the filter table labels it. Playlist popups get a Buffer select
(default / none / xTeVe / FFmpeg / VLC) saved as the playlist's
"buffer" parameter; the tuner field stays editable when the
playlist's own buffer is active.
Tests: tests/ holds 27 jsdom tests (Node's built-in runner, jsdom pinned)
that load the built bundle with a fixture server payload: menu visibility
rules, mapping table renders names as text, settings panel fields, popup
flows, sorting, bulk select, layout refresh keeps nodes, live region,
regex option, buffer select. npm test runs in the Drone webui-check step.
README-DEV documents the layout and the test workflow.
804 lines
18 KiB
TypeScript
804 lines
18 KiB
TypeScript
var SERVER = new Object()
|
|
var BULK_EDIT:Boolean = false
|
|
var COLUMN_TO_SORT:number
|
|
var SEARCH_MAPPING = new Object()
|
|
var UNDO = new Object()
|
|
var WS_AVAILABLE = false
|
|
var ACTIVE_MENU_ID:string = ""
|
|
var LAST_BULK_CHECKBOX:HTMLInputElement = null
|
|
|
|
|
|
// Menü
|
|
var menuItems = new Array()
|
|
menuItems.push(new MainMenuItem("playlist", "Playlist", "m3u.png", "Local or remote playlists"))
|
|
//menuItems.push(new MainMenuItem("pmsID", "PMS ID", "number.png", ""))
|
|
menuItems.push(new MainMenuItem("filter", "Filter", "filter.png", "Filter playlist"))
|
|
menuItems.push(new MainMenuItem("xmltv", "XMLTV", "xmltv.png", "Local or remote XMLTV files"))
|
|
menuItems.push(new MainMenuItem("mapping", "Mapping", "mapping.png", "Map playlist channels to EPG channels"))
|
|
menuItems.push(new MainMenuItem("users", "Users", "users.png", "User management"))
|
|
menuItems.push(new MainMenuItem("settings", "Settings", "settings.png", "Settings"))
|
|
menuItems.push(new MainMenuItem("log", "Log", "log.png", "Log"))
|
|
menuItems.push(new MainMenuItem("logout", "Logout", "logout.png", "Logout"))
|
|
|
|
// Kategorien für die Einstellungen
|
|
var settingsCategory = new Array()
|
|
settingsCategory.push(new SettingsCategoryItem("General", "tuner,epgSource,api,use_plexAPI,plex.url,plex.token"));settingsCategory.push(new SettingsCategoryItem("Files", "update,files.update,temp.path,cache.images,xepg.missing.epg.mode,xepg.replace.missing.images"))
|
|
settingsCategory.push(new SettingsCategoryItem("Streaming", "buffer,udpxy,buffer.size.kb,buffer.timeout,user.agent,ffmpeg.path,ffmpeg.options,vlc.path,vlc.options"))
|
|
settingsCategory.push(new SettingsCategoryItem("Backup", "backup.path,backup.keep"))
|
|
settingsCategory.push(new SettingsCategoryItem("Authentication", "authentication.web,authentication.pms,authentication.m3u,authentication.xml,authentication.api"))
|
|
|
|
function showPopUpElement(elm) {
|
|
|
|
var allElements = new Array("popup-custom");
|
|
|
|
for (var i = 0; i < allElements.length; i++) {
|
|
showElement(allElements[i], false)
|
|
}
|
|
|
|
showElement(elm, true)
|
|
|
|
setTimeout(function(){
|
|
showElement("popup", true);
|
|
}, 10);
|
|
|
|
return
|
|
}
|
|
|
|
function showElement(elmID, type) {
|
|
|
|
var cssClass:string
|
|
switch(type) {
|
|
case true: cssClass = "block"; break;
|
|
case false: cssClass = "none"; break;
|
|
}
|
|
|
|
var element = document.getElementById(elmID)
|
|
if (element == null) {
|
|
return
|
|
}
|
|
|
|
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
|
|
if (label == undefined || label.length == 0) {
|
|
switch (state) {
|
|
case "online":
|
|
label = "Connected"
|
|
break
|
|
|
|
case "busy":
|
|
label = "Syncing"
|
|
break
|
|
|
|
case "offline":
|
|
label = "Offline"
|
|
break
|
|
|
|
default:
|
|
label = "Connecting"
|
|
break
|
|
}
|
|
}
|
|
|
|
var indicator = document.getElementById("connection-indicator")
|
|
if (indicator == null) {
|
|
return
|
|
}
|
|
|
|
indicator.className = "status-" + state
|
|
indicator.innerText = label
|
|
}
|
|
|
|
// 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 {
|
|
var data = new Object()
|
|
switch(dataType) {
|
|
case "m3u":
|
|
data = SERVER["settings"]["files"][dataType][id]
|
|
break
|
|
|
|
case "hdhr":
|
|
data = SERVER["settings"]["files"][dataType][id]
|
|
break
|
|
|
|
case "filter":
|
|
case "custom-filter":
|
|
case "group-title":
|
|
if (id == -1) {
|
|
data["active"] = true
|
|
data["caseSensitive"] = false
|
|
data["description"] = ""
|
|
data["exclude"] = ""
|
|
data["filter"] = ""
|
|
data["include"] = ""
|
|
data["name"] = ""
|
|
data["type"] = "group-title"
|
|
SERVER["settings"]["filter"][id] = data
|
|
}
|
|
data = SERVER["settings"]["filter"][id]
|
|
break
|
|
|
|
case "xmltv":
|
|
data = SERVER["settings"]["files"][dataType][id]
|
|
break
|
|
|
|
case "users":
|
|
data = SERVER["users"][id]["data"]
|
|
break
|
|
|
|
case "mapping":
|
|
data = SERVER["xepg"]["epgMapping"][id]
|
|
break
|
|
|
|
case "m3uGroups":
|
|
data = SERVER["data"]["playlist"]["m3u"]["groups"]
|
|
break
|
|
}
|
|
|
|
return data
|
|
}
|
|
|
|
function getObjKeys(obj) {
|
|
var keys = new Array();
|
|
|
|
for (var i in obj) {
|
|
if (obj.hasOwnProperty(i)) {
|
|
keys.push(i);
|
|
}
|
|
}
|
|
|
|
return keys;
|
|
}
|
|
|
|
function getAllSelectedChannels():string[] {
|
|
|
|
var channels:string[] = new Array()
|
|
|
|
if (BULK_EDIT == false) {
|
|
return channels
|
|
}
|
|
|
|
var trs = document.getElementById("content_table").getElementsByTagName("TR")
|
|
|
|
for (var i = 1; i < trs.length; i++) {
|
|
|
|
if ((trs[i] as HTMLElement).style.display != "none") {
|
|
|
|
if ((trs[i].firstChild.firstChild as HTMLInputElement).checked == true) {
|
|
channels.push(trs[i].id)
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return channels
|
|
}
|
|
|
|
function scheduleChannelRangeSelection(checkbox:HTMLInputElement, event:MouseEvent) {
|
|
|
|
var shiftPressed = false
|
|
if (event != undefined && event.shiftKey == true) {
|
|
shiftPressed = true
|
|
}
|
|
|
|
// Run after the native checkbox toggle so we copy the final checked state.
|
|
setTimeout(function() {
|
|
selectChannelRange(checkbox, shiftPressed)
|
|
}, 0)
|
|
}
|
|
|
|
function selectChannelRange(checkbox:HTMLInputElement, shiftPressed:boolean) {
|
|
|
|
if (BULK_EDIT == false || checkbox == undefined || checkbox == null) {
|
|
return
|
|
}
|
|
|
|
var table = document.getElementById("content_table")
|
|
if (table == null) {
|
|
return
|
|
}
|
|
|
|
var trs = table.getElementsByTagName("TR")
|
|
var visibleCheckboxes:HTMLInputElement[] = new Array()
|
|
|
|
for (var i = 1; i < trs.length; i++) {
|
|
if ((trs[i] as HTMLElement).style.display != "none") {
|
|
var bulkCheckbox = (trs[i] as HTMLTableRowElement).querySelector("input.bulk") as HTMLInputElement
|
|
if (bulkCheckbox != null) {
|
|
visibleCheckboxes.push(bulkCheckbox)
|
|
}
|
|
}
|
|
}
|
|
|
|
var currentIndex = visibleCheckboxes.indexOf(checkbox)
|
|
var previousIndex = -1
|
|
if (LAST_BULK_CHECKBOX != null) {
|
|
previousIndex = visibleCheckboxes.indexOf(LAST_BULK_CHECKBOX)
|
|
}
|
|
|
|
if (shiftPressed == true && previousIndex > -1 && currentIndex > -1) {
|
|
var start = Math.min(previousIndex, currentIndex)
|
|
var end = Math.max(previousIndex, currentIndex)
|
|
for (var i = start; i <= end; i++) {
|
|
visibleCheckboxes[i].checked = checkbox.checked
|
|
}
|
|
}
|
|
|
|
LAST_BULK_CHECKBOX = checkbox
|
|
}
|
|
|
|
function selectAllChannels() {
|
|
|
|
var bulk:Boolean = false
|
|
var trs = document.getElementById("content_table").getElementsByTagName("TR")
|
|
|
|
if ((trs[0].firstChild.firstChild as HTMLInputElement).checked == true) {
|
|
bulk = true
|
|
}
|
|
|
|
for (var i = 1; i < trs.length; i++) {
|
|
|
|
if ((trs[i] as HTMLElement).style.display != "none") {
|
|
|
|
switch (bulk) {
|
|
|
|
case true:
|
|
(trs[i].firstChild.firstChild as HTMLInputElement).checked = true
|
|
break
|
|
|
|
case false:
|
|
(trs[i].firstChild.firstChild as HTMLInputElement).checked = false
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
LAST_BULK_CHECKBOX = null
|
|
return
|
|
}
|
|
|
|
function bulkEdit() {
|
|
|
|
BULK_EDIT = !BULK_EDIT
|
|
var className:string
|
|
var rows = document.getElementsByClassName("bulk");
|
|
|
|
switch (BULK_EDIT) {
|
|
case true:
|
|
className = "bulk showBulk"
|
|
break;
|
|
|
|
case false:
|
|
className = "bulk hideBulk"
|
|
break;
|
|
}
|
|
|
|
for (var i = 0; i < rows.length; i++) {
|
|
rows[i].className = className;
|
|
(rows[i] as HTMLInputElement).checked = false
|
|
}
|
|
|
|
LAST_BULK_CHECKBOX = null
|
|
return
|
|
}
|
|
|
|
function sortTable(column) {
|
|
//console.log(columm);
|
|
|
|
if (column == COLUMN_TO_SORT) {
|
|
return;
|
|
}
|
|
|
|
|
|
var table = document.getElementById("content_table");
|
|
var tableHead = table.getElementsByTagName("TR")[0];
|
|
var tableItems = tableHead.getElementsByTagName("TD");
|
|
|
|
var sortObj = new Object();
|
|
var x, xValue;
|
|
var tableHeader
|
|
var sortByString = false
|
|
|
|
if (column > 0 && COLUMN_TO_SORT > 0) {
|
|
tableItems[COLUMN_TO_SORT].className = "pointer";
|
|
tableItems[column].className = "sortThis";
|
|
}
|
|
|
|
COLUMN_TO_SORT = column;
|
|
|
|
|
|
var rows = (table as HTMLTableElement).rows;
|
|
|
|
if (rows[1] != undefined) {
|
|
tableHeader = rows[0]
|
|
|
|
x = rows[1].getElementsByTagName("TD")[column];
|
|
|
|
for (i = 1; i < rows.length; i++) {
|
|
|
|
x = rows[i].getElementsByTagName("TD")[column];
|
|
|
|
switch(x.childNodes[0].tagName.toLowerCase()) {
|
|
case "input":
|
|
xValue = x.getElementsByTagName("INPUT")[0].value.toLowerCase();
|
|
break;
|
|
|
|
case "p":
|
|
xValue = x.getElementsByTagName("P")[0].innerText.toLowerCase();
|
|
break;
|
|
|
|
default: console.log(x.childNodes[0].tagName);
|
|
}
|
|
|
|
if (xValue == "" || Number.isNaN(xValue)) {
|
|
|
|
xValue = i
|
|
sortObj[i] = rows[i];
|
|
|
|
} else {
|
|
|
|
switch(isNaN(xValue)) {
|
|
case false:
|
|
|
|
xValue = parseFloat(xValue);
|
|
sortObj[xValue] = rows[i]
|
|
break;
|
|
|
|
case true:
|
|
|
|
sortByString = true
|
|
sortObj[xValue.toLowerCase() + i] = rows[i]
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
while (table.firstChild) {
|
|
table.removeChild(table.firstChild);
|
|
}
|
|
|
|
var sortValues = getObjKeys(sortObj)
|
|
|
|
if (sortByString == true) {
|
|
sortValues.sort()
|
|
console.log(sortValues);
|
|
} else {
|
|
function sortFloat(a, b) {
|
|
return a - b;
|
|
}
|
|
sortValues.sort(sortFloat);
|
|
}
|
|
|
|
table.appendChild(tableHeader)
|
|
|
|
for (var i = 0; i < sortValues.length; i++) {
|
|
|
|
table.appendChild(sortObj[sortValues[i]])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
function createSearchObj() {
|
|
|
|
SEARCH_MAPPING = new Object()
|
|
var data = SERVER["xepg"]["epgMapping"]
|
|
var channels = getObjKeys(data)
|
|
|
|
var channelKeys:string[] = ["x-active", "x-channelID", "x-name", "_file.m3u.name", "x-group-title", "x-xmltv-file"]
|
|
|
|
channels.forEach(id => {
|
|
|
|
channelKeys.forEach(key => {
|
|
|
|
if (key == "x-active") {
|
|
|
|
switch (data[id][key]) {
|
|
case true:
|
|
SEARCH_MAPPING[id] = "online "
|
|
break;
|
|
|
|
case false:
|
|
SEARCH_MAPPING[id] = "offline "
|
|
break;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (key == "x-xmltv-file") {
|
|
var xmltvFile = getValueFromProviderFile(data[id][key], "xmltv", "name")
|
|
|
|
if (xmltvFile != undefined) {
|
|
SEARCH_MAPPING[id] = SEARCH_MAPPING[id] + xmltvFile + " "
|
|
}
|
|
|
|
} else {
|
|
SEARCH_MAPPING[id] = SEARCH_MAPPING[id] + data[id][key] + " "
|
|
}
|
|
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
function searchInMapping() {
|
|
|
|
var searchValue = (document.getElementById("searchMapping") as HTMLInputElement).value;
|
|
var trs = document.getElementById("content_table").getElementsByTagName("TR")
|
|
|
|
for (var i = 1; i < trs.length; ++i) {
|
|
|
|
var id = trs[i].getAttribute("id")
|
|
var element = SEARCH_MAPPING[id]
|
|
|
|
switch (element.toLowerCase().includes(searchValue.toLowerCase())) {
|
|
case true:
|
|
document.getElementById(id).style.display = ""
|
|
break;
|
|
|
|
case false:
|
|
document.getElementById(id).style.display = "none"
|
|
break;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
function calculateWrapperHeight() {
|
|
|
|
var elm = document.getElementById("box-wrapper");
|
|
var content = document.getElementById("content");
|
|
|
|
if (elm != null && content != null){
|
|
|
|
var contentTop = content.getBoundingClientRect().top
|
|
var freeSpace = window.innerHeight - contentTop - 26
|
|
|
|
if (freeSpace < 180) {
|
|
freeSpace = 180
|
|
}
|
|
|
|
elm.style.height = freeSpace + "px";
|
|
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
function changeChannelNumber(element) {
|
|
|
|
var dbID = element.parentNode.parentNode.id
|
|
|
|
var newNumber:number = parseFloat(element.value)
|
|
var channelNumbers:number[] = []
|
|
var data = SERVER["xepg"]["epgMapping"]
|
|
var channels = getObjKeys(data)
|
|
|
|
if (isNaN(newNumber)) {
|
|
alertUser("Invalid channel number")
|
|
return
|
|
}
|
|
|
|
channels.forEach(id => {
|
|
|
|
var channelNumber = parseFloat(data[id]["x-channelID"])
|
|
channelNumbers.push(channelNumber)
|
|
|
|
})
|
|
|
|
for (var i = 0; i < channelNumbers.length; i++) {
|
|
|
|
if (channelNumbers.indexOf(newNumber) == -1) {
|
|
break
|
|
}
|
|
|
|
if (Math.floor(newNumber) == newNumber) {
|
|
newNumber = newNumber + 1
|
|
} else {
|
|
newNumber = newNumber + 0.1;
|
|
newNumber.toFixed(1)
|
|
newNumber = Math.round(newNumber * 10) / 10
|
|
}
|
|
|
|
}
|
|
|
|
data[dbID]["x-channelID"] = newNumber.toString()
|
|
element.value = newNumber
|
|
|
|
console.log(data[dbID]["x-channelID"])
|
|
|
|
if (COLUMN_TO_SORT == 1) {
|
|
COLUMN_TO_SORT = -1
|
|
sortTable(1)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
function backup() {
|
|
|
|
var data = new Object()
|
|
console.log("Backup data")
|
|
|
|
var cmd = "xteveBackup"
|
|
|
|
console.log("SEND TO SERVER");
|
|
console.log(data)
|
|
|
|
var server:Server = new Server(cmd)
|
|
server.request(data)
|
|
|
|
return
|
|
}
|
|
|
|
function toggleChannelStatus(id:string) {
|
|
|
|
var element:any
|
|
var status:boolean
|
|
|
|
if(document.getElementById("active")) {
|
|
var checkbox = (document.getElementById("active") as HTMLInputElement)
|
|
status = (checkbox).checked
|
|
}
|
|
|
|
|
|
var ids:string[] = getAllSelectedChannels()
|
|
if (ids.length == 0) {
|
|
ids.push(id)
|
|
}
|
|
|
|
ids.forEach(id => {
|
|
|
|
var channel = SERVER["xepg"]["epgMapping"][id]
|
|
|
|
channel["x-active"] = status
|
|
|
|
switch (channel["x-active"]) {
|
|
case true:
|
|
if (channel["x-xmltv-file"] == "-" || channel["x-mapping"] == "-") {
|
|
|
|
if (BULK_EDIT == false) {
|
|
alertUser(channel["x-name"] + ": Missing XMLTV file / channel")
|
|
checkbox.checked = false
|
|
}
|
|
|
|
channel["x-active"] = false
|
|
|
|
}
|
|
|
|
break
|
|
|
|
case false:
|
|
// code...
|
|
break;
|
|
}
|
|
|
|
if (channel["x-active"] == false) {
|
|
document.getElementById(id).className = "notActiveEPG"
|
|
} else {
|
|
document.getElementById(id).className = "activeEPG"
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
function restore() {
|
|
|
|
if (document.getElementById('upload')) {
|
|
document.getElementById('upload').remove()
|
|
}
|
|
|
|
var restore = document.createElement("INPUT");
|
|
restore.setAttribute("type", "file");
|
|
restore.setAttribute("class", "notVisible");
|
|
restore.setAttribute("name", "");
|
|
restore.id = "upload";
|
|
|
|
document.body.appendChild(restore);
|
|
restore.click();
|
|
|
|
restore.onchange = function() {
|
|
|
|
var filename = (restore as HTMLInputElement).files[0].name
|
|
var check = confirm("File: " + filename + "\nAll data will be replaced with those from the backup. Should the files be restored?");
|
|
|
|
if (check == true) {
|
|
|
|
var reader = new FileReader();
|
|
var file = (document.querySelector('input[type=file]') as HTMLInputElement).files[0];
|
|
|
|
if (file) {
|
|
|
|
reader.readAsDataURL(file);
|
|
reader.onload = function() {
|
|
console.log(reader.result);
|
|
var data = new Object();
|
|
var cmd = "xteveRestore"
|
|
data["base64"] = reader.result
|
|
|
|
var server:Server = new Server(cmd)
|
|
server.request(data)
|
|
|
|
};
|
|
|
|
} else {
|
|
alertUser("File could not be loaded")
|
|
}
|
|
|
|
restore.remove()
|
|
return
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
function uploadLogo() {
|
|
|
|
if (document.getElementById('upload')) {
|
|
document.getElementById('upload').remove()
|
|
}
|
|
|
|
var upload = document.createElement("INPUT");
|
|
upload.setAttribute("type", "file");
|
|
upload.setAttribute("class", "notVisible");
|
|
upload.setAttribute("name", "");
|
|
upload.id = "upload";
|
|
|
|
document.body.appendChild(upload);
|
|
upload.click();
|
|
|
|
upload.onblur = function() {
|
|
alert()
|
|
}
|
|
|
|
upload.onchange = function() {
|
|
|
|
var filename = (upload as HTMLInputElement).files[0].name
|
|
|
|
var reader = new FileReader();
|
|
var file = (document.querySelector('input[type=file]') as HTMLInputElement).files[0];
|
|
|
|
if (file) {
|
|
|
|
reader.readAsDataURL(file);
|
|
reader.onload = function() {
|
|
console.log(reader.result);
|
|
var data = new Object();
|
|
var cmd = "uploadLogo"
|
|
data["base64"] = reader.result
|
|
data["filename"] = file.name
|
|
|
|
var server:Server = new Server(cmd)
|
|
server.request(data)
|
|
|
|
var updateLogo = (document.getElementById('update-icon') as HTMLInputElement)
|
|
updateLogo.checked = false
|
|
updateLogo.className = "changed"
|
|
|
|
};
|
|
|
|
} else {
|
|
alertUser("File could not be loaded")
|
|
}
|
|
|
|
upload.remove()
|
|
return
|
|
}
|
|
|
|
}
|
|
|
|
function checkUndo(key:string) {
|
|
|
|
switch (key) {
|
|
case "epgMapping":
|
|
if (UNDO.hasOwnProperty(key)) {
|
|
SERVER["xepg"][key] = JSON.parse(JSON.stringify(UNDO[key]))
|
|
} else {
|
|
UNDO[key] = JSON.parse(JSON.stringify(SERVER["xepg"][key]));
|
|
}
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
function sortSelect(elem) {
|
|
|
|
var tmpAry = [];
|
|
var selectedValue = elem[elem.selectedIndex].value;
|
|
|
|
for (var i=0;i<elem.options.length;i++) tmpAry.push(elem.options[i]);
|
|
|
|
tmpAry.sort(function(a,b){ return (a.text < b.text)?-1:1; });
|
|
while (elem.options.length > 0) elem.options[0] = null;
|
|
|
|
var newSelectedIndex = 0;
|
|
|
|
for (var i=0;i<tmpAry.length;i++) {
|
|
|
|
elem.options[i] = tmpAry[i];
|
|
if(elem.options[i].value == selectedValue) newSelectedIndex = i;
|
|
|
|
}
|
|
|
|
elem.selectedIndex = newSelectedIndex; // Set new selected index after sorting
|
|
return;
|
|
}
|
|
|
|
function updateLog() {
|
|
|
|
var server:Server = new Server("updateLog")
|
|
server.request(new Object())
|
|
|
|
}
|