1248 lines
36 KiB
JavaScript
1248 lines
36 KiB
JavaScript
// Copyright 2017, 2018, 2019, 2020 Max H. Parke KA1RBI
|
|
// Copyright 2020, 2021 Michael Rose
|
|
//
|
|
// This file is part of OP25
|
|
//
|
|
// OP25 is free software; you can redistribute it and/or modify it
|
|
// under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation; either version 3, or (at your option)
|
|
// any later version.
|
|
//
|
|
// OP25 is distributed in the hope that it will be useful, but WITHOUT
|
|
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
|
// License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with OP25; see the file COPYING. If not, write to the Free
|
|
// Software Foundation, Inc., 51 Franklin Street, Boston, MA
|
|
// 02110-1301, USA.
|
|
|
|
// Last Update: 04-Jun-2021
|
|
|
|
|
|
var delay = 250;
|
|
|
|
const params = new URLSearchParams(window.location.search); // load query string from url
|
|
|
|
window.file = params.get('file');
|
|
window.mode = params.get('mode');
|
|
window.nac = params.get('nac');
|
|
window.sys = params.get('sys');
|
|
var css = params.get('css') ? params.get('css') : 'dark';
|
|
|
|
// file - the tsv filename to work with (only matters for tg, src, sa - hard coded in the python for color)
|
|
// id - the talkgroup or source ID to jump to upon load
|
|
// css - [ light | dark ] - the display mode
|
|
// mode - [ tg | src | color ] what is being worked on
|
|
|
|
window.imp = false;
|
|
var unsaved = false;
|
|
|
|
var error1 = "ID and Tag fields are required. \n\n Error 1";
|
|
var error2 = "Talkgroup and Priority / Color fields must be an integer. \n\n Error 2";
|
|
var error3 = "Priority / Color field must be an integer. \n\n Error 3";
|
|
var error4 = "ID already exists and must be unique. \n\n Error 4";
|
|
var error5 = "Import file not specified. \n\n Error 5";
|
|
var error6 = "Editor table not present. \n\n Error 6";
|
|
var error7 = 'not supported. Expecting CSV or TSV. \n\n Error 7';
|
|
var error8 = 'Failed to load file: ';
|
|
var error9 = '';
|
|
var error10 = 'All fields are required. \n\n Error 10';
|
|
var error11 = 'Duplicate rffs/site entries not permitted. \n\n Error 11';
|
|
var error12 = 'Invalid system ID. Input out of range. \n\n Error 12';
|
|
var error13 = 'Duplicate System ID. \n\nError 13';
|
|
|
|
$(window).on('load', function() {
|
|
document.documentElement.setAttribute('data-theme', css);
|
|
var tgid = params.get('id');
|
|
if (tgid)
|
|
var x = setTimeout(function(){ scrollToAnchor(tgid) }, 300);
|
|
var oc = "this.blur(); command_reload_tsv(" + nac + ");";
|
|
$('#btnReload').attr("onclick", oc);
|
|
$('#searchInput').val(null);
|
|
$('#csv-upload').hide();
|
|
$('#loading').hide();
|
|
$('#csvfile').val(null).on('change',readSingleFile);
|
|
generateCSS();
|
|
beginJsonSettings();
|
|
accColorSel();
|
|
uiColorRefresh();
|
|
|
|
switch(mode) {
|
|
case 'tg':
|
|
window.modeTitle = "Talkgroup";
|
|
$('#btnLoad').text('Load Talkgroups');
|
|
document.querySelectorAll('button.saveButton').forEach(elem => {
|
|
elem.innerHTML = "Save Talkgroups";
|
|
});
|
|
$('#title').text('Talkgroup TSV Editor - OP25');
|
|
break;
|
|
|
|
case 'src':
|
|
window.modeTitle = "Source";
|
|
$('#btnLoad').text('Load Source IDs');
|
|
document.querySelectorAll('button.saveButton').forEach(elem => {
|
|
elem.innerHTML = "Save Source IDs";
|
|
});
|
|
$('#title').text('Source IDs TSV Editor - OP25');
|
|
break;
|
|
|
|
case 'alias':
|
|
window.modeTitle = "Alias";
|
|
$('#btnLoad').text('Load Aliases');
|
|
document.querySelectorAll('button.saveButton').forEach(elem => {
|
|
elem.innerHTML = "Save Aliases";
|
|
});
|
|
$('#title').text('System Site Alias Editor - OP25');
|
|
break;
|
|
|
|
case 'color':
|
|
window.modeTitle = "Color";
|
|
$('#btnLoad').text('Load Colors');
|
|
document.querySelectorAll('button.saveButton').forEach(elem => {
|
|
elem.innerHTML = "Save Colors";
|
|
});
|
|
$('#title').text('Colors Editor - OP25');
|
|
$('#btnColorEditor').hide();
|
|
$('.importExport').hide(); // hide these buttons -- import/export function not written yet
|
|
break;
|
|
} // end switch
|
|
|
|
});
|
|
|
|
$(document).ready(function() {
|
|
// nothing here right now
|
|
});
|
|
|
|
window.onbeforeunload = function(){
|
|
if (unsaved == true)
|
|
return 'Are you sure you want to leave? Unsaved changes will be lost.';
|
|
};
|
|
|
|
// delete button
|
|
$('input[class="delbutton"]').click(function(e){
|
|
$(this).closest('tr').remove();
|
|
})
|
|
|
|
function tsv_onload() {
|
|
switch(mode) {
|
|
case "alias":
|
|
beginAlias();
|
|
break;
|
|
case "color":
|
|
beginColor();
|
|
break;
|
|
default:
|
|
begin();
|
|
}
|
|
}
|
|
|
|
function begin() {
|
|
// console.log('begin() started...');
|
|
$( "#loading" ).show();
|
|
if (begin.caller.name != "completeSave")
|
|
$('#talkgroups').hide(250);
|
|
$("#message").text("Fetching TSV...");
|
|
file = window.file;
|
|
$.ajax({
|
|
url : file,
|
|
type : 'GET',
|
|
success : buildTable,
|
|
error : function(XMLHttpRequest, textStatus, errorThrown) {alert('File Acces Error: \n\nFile:' + file + '\n\n' + errorThrown + '\n\n');}
|
|
});
|
|
}
|
|
|
|
function beginAlias() {
|
|
// console.log('beginAlias() started...');
|
|
$( "#loading" ).show();
|
|
if (beginAlias.caller.name != "completeSave")
|
|
$('#talkgroups').hide(250);
|
|
$("#message").text("Fetching Info...");
|
|
file = window.file;
|
|
$.ajax({
|
|
url : file,
|
|
type : 'GET',
|
|
success : buildAliasTable,
|
|
error : function(XMLHttpRequest, textStatus, errorThrown) {alert('File Acces Error: \n\nFile:' + file + '\n\n' + errorThrown + '\n\n'); buildAliasTable();}
|
|
});
|
|
}
|
|
|
|
function beginColor() {
|
|
console.log('beginColor started...');
|
|
$('#searchInput').hide();
|
|
$("#main").html('Loading...');
|
|
$(".saveButton").attr("onclick", "this.blur(); saveColorTable();");
|
|
$("#btnLoad").attr("onclick", "this.blur(); beginColor();");
|
|
$( "#loading" ).show();
|
|
generateCSS();
|
|
colorFile = 'color-map.json';
|
|
$.ajax({
|
|
url : colorFile,
|
|
type : 'GET',
|
|
success : buildColorTable,
|
|
error : function(XMLHttpRequest, textStatus, errorThrown) {alert('File Acces Error: \n\nFile:' + colorFile + '\n\n' + errorThrown + '\n\n');}
|
|
});
|
|
}
|
|
|
|
|
|
function buildTable(d) {
|
|
// console.log('build table started... ~207');
|
|
if (!d) {
|
|
location.reload();
|
|
}
|
|
|
|
$("#message").text("Building table...");
|
|
|
|
var x = "";
|
|
lines = [];
|
|
|
|
data = d.split(/\r?\n/); // split by newline
|
|
|
|
for(var i = 0; i < data.length; i++){
|
|
lines.push(data[i].split(/['\t']+/)); // split by tab \t
|
|
}
|
|
|
|
i = 0;
|
|
// TODO split priority and color into 2 fields?
|
|
aLen = lines.length;
|
|
|
|
var html = '<div id="editor">';
|
|
html += '<table class="editor" id="tsv-editor">';
|
|
html += '<th>New ' + window.modeTitle + ' ID</th>';
|
|
html += '<th>New ' + window.modeTitle + ' Tag</th>';
|
|
html += '<th class="pricol">Priority / Color</th>';
|
|
html += '<th>Actions</th>';
|
|
|
|
html += '<tr><td><input type="text" id="newtg"></td>';
|
|
html += ' <td><input type="text" id="newtag"></td>';
|
|
html += ' <td><input type="text" id="newcol"></td>';
|
|
html += ' <td align="center"><button id="btnAddNew" onclick="this.blur; addNew();"> Add New </button></td></tr>';
|
|
html += '</table><br>';
|
|
|
|
html += '<span class="label">Editing TSV: </span><span class="value">' + file + '</span> ';
|
|
html += '<span id="records"> </span> ';
|
|
html += '<span class="label">NAC: </span><span class="value">' + (nac) + ' - 0x' + (hex(parseInt(nac)).toUpperCase()) + '</span> ';
|
|
html += '<span class="value" id="unsaved" style="display: none;">Unsaved Changes</span>';
|
|
html += '<br><br>';
|
|
|
|
html += '<table class="editor" id="talkgroups">';
|
|
html += '<th>Row</th>';
|
|
html += '<th>' + window.modeTitle + ' ID</th>';
|
|
html += '<th>' + window.modeTitle + ' Tag</th>';
|
|
html += '<th class="pricol" >Priority / Color</th>';
|
|
html += '<th>Actions</th>';
|
|
|
|
for (var i = 0; i < aLen - 1; i++) {
|
|
|
|
x = i;
|
|
var id = lines[i][0];
|
|
var tag = lines[i][1];
|
|
var col = lines[i][2]; // ? lines[i][2] : 0;
|
|
|
|
var eleid_id = 'ID' + i + id + parseInt(Math.random() * 10000000); // unique IDs for each td, not used for anything right now.
|
|
var eleid_tag = 'ID' + i + id + parseInt(Math.random() * 10000000);
|
|
var eleid_col = 'ID' + i + col + parseInt(Math.random() * 10000000);
|
|
|
|
html += '<tr>';
|
|
html += '<td id="">' + (i + 1) + '<a href="#" name="'+ id + '"></a></td>';
|
|
|
|
html += '<td id="' + (eleid_id) + '" style="width: 150px;"><input type="text" id="' + x + '" value="' + id + '" class="tg tedit"></td>';
|
|
html += '<td id="' + (eleid_tag) + '"><input type="text" id="tag' + x + '" value="' + tag + '" class="tag tedit"></td>';
|
|
html += '<td id="' + (eleid_col) + '" style="width: 150px;"><input maxlength="4" type="text" id="col' + x + '" value="' + col + '" class="col tedit"></td>';
|
|
|
|
html += '<td align="center">';
|
|
html += '<button title="Delete" class="delbutton" onclick="this.blur; deleteRow(\'' + id + '\', \'' + tag +'\');">✘</button></td>';
|
|
|
|
html += '</tr>';
|
|
}
|
|
|
|
html += '</table></div><br><br><br><br>';
|
|
|
|
var s = document.getElementById("main"); // todo - use jQ attri() method to assign custom attributes.
|
|
s.innerHTML = html;
|
|
s.content = 'editor';
|
|
|
|
$('#main').data('content', 'editor');
|
|
|
|
$("#main").html(html);
|
|
$('#talkgroups').show(150);
|
|
|
|
$('#records').html('<span class="label">Records: </span><span class="value">' + (getHigh() + 1) + ' </span>');
|
|
|
|
$('#message').text('Ready');
|
|
|
|
if (mode == 'src') $('.pricol').text('Color');
|
|
|
|
document.querySelectorAll(".tedit").forEach(item => {
|
|
item.addEventListener('change', event => {
|
|
isUnsaved();
|
|
});
|
|
});
|
|
|
|
// enter key behavior (clicks Add New button)
|
|
$('#newtg').on('keypress', function(e) {
|
|
if(e.which == 13) {
|
|
$('#btnAddNew').trigger('click');
|
|
}
|
|
});
|
|
|
|
$('#newtag').on('keypress', function(e) {
|
|
if(e.which == 13) {
|
|
$('#btnAddNew').trigger('click');
|
|
}
|
|
});
|
|
|
|
$('#newcol').on('keypress', function(e) {
|
|
if(e.which == 13) {
|
|
$('#btnAddNew').trigger('click');
|
|
$('#newtg').focus();
|
|
}
|
|
});
|
|
|
|
$( "#loading" ).hide();
|
|
if (window.imp == true) //only flag as unsaved if buildTable was used after import
|
|
isUnsaved();
|
|
}
|
|
|
|
function deleteRow(id, tag) {
|
|
var r = confirm(id + ' - ' + tag + '\n\nConfirm delete.');
|
|
if (r == true) {
|
|
var td = event.target.parentNode;
|
|
var tr = td.parentNode; // the row to be removed
|
|
tr.parentNode.removeChild(tr);
|
|
isUnsaved();
|
|
}
|
|
}
|
|
|
|
function saveTable() {
|
|
// console.log('saveTable()');
|
|
if (!document.getElementById("talkgroups")) {
|
|
jAlert(error6, 'Error');
|
|
return 0;
|
|
}
|
|
|
|
$( "#loading" ).show();
|
|
window.location.href = "#top";
|
|
// $('#talkgroups').hide(250);
|
|
$('#message').text('Saving...');
|
|
disableSave(); // disable save buttons momentarily
|
|
|
|
var tgs, tag, col;
|
|
data = [];
|
|
|
|
table = document.getElementById("talkgroups");
|
|
rows = table.rows.length;
|
|
|
|
var els = document.getElementsByClassName("tg"); // only the ID field is "tg"
|
|
|
|
for(var i = 0; i < els.length; i++) {
|
|
|
|
x = els[i].id;
|
|
|
|
tgs = document.getElementById(x).value;
|
|
tag = document.getElementById("tag" + (x)).value;
|
|
col = document.getElementById("col" + (x)).value;
|
|
|
|
if (tgs == "" || tag == "") {
|
|
jAlert(error1 + '\n\nEmpty fields on Row ' + (i + 1) + '\n\n', 'Error');
|
|
enableSave();
|
|
return;
|
|
}
|
|
|
|
if (!Number.isInteger(Number(tgs)) && mode=='tg') {
|
|
jAlert(error2 + ' \n\n' + tgs + ' looks like a ' + (typeof tgs) + ' on Row ' + (i + 1) + '.', 'Error');
|
|
enableSave();
|
|
return;
|
|
}
|
|
|
|
if (!Number.isInteger(Number(col))) {
|
|
jAlert(error3 + ' \n\n' + col + ' looks like a ' + (typeof col) + ' on Row ' + (i + 1) + '.', 'Error');
|
|
enableSave();
|
|
return;
|
|
}
|
|
|
|
data[i] = new Array(tgs, tag, col);
|
|
|
|
}
|
|
|
|
if (mode == "tg") {
|
|
data.sort(function(a, b) {
|
|
return a[0] - b[0];
|
|
});
|
|
}
|
|
|
|
// have to sort Source IDs differently because there could be wildcards/strings in there.
|
|
if (mode == "src") {
|
|
data.sort(function( a , b){
|
|
if(a[0] > b[0]) return 1;
|
|
if(a[0] < b[0]) return -1;
|
|
return 0;
|
|
});
|
|
|
|
data.sort(function(a, b){
|
|
return a[0].length - b[0].length;
|
|
});
|
|
}
|
|
|
|
//json output - not currently used here
|
|
var jsonOutput = JSON.stringify(data, undefined, 2);
|
|
|
|
var textOutput = "";
|
|
for(var i = 0; i < data.length; i++) {
|
|
textOutput += (data[i][0] + '\t' + data[i][1] + '\t' + data[i][2] + '\n');
|
|
}
|
|
|
|
save_tsv('config-tsvsave', textOutput, window.file); // send data to server
|
|
|
|
setTimeout(completeSave, delay); // artificial delays to allow http_server.py time to process things
|
|
setTimeout(function(){ command_reload_tsv(nac); }, delay); // tell OP25 to reload the TSVs
|
|
setTimeout(enableSave, delay);
|
|
$('#talkgroups').show(delay);
|
|
$( "#loading" ).hide();
|
|
isSaved();
|
|
|
|
}
|
|
|
|
function addNew() {
|
|
var id = getHigh() + 1;
|
|
var table = document.getElementById("talkgroups");
|
|
var x;
|
|
|
|
var newtg = $('#newtg').val(); // we're re-using the same AddNew code in Alias editor as well, hence the variable names.
|
|
var newtag = $('#newtag').val();
|
|
var newcol = $('#newcol').val();
|
|
|
|
var alias = newtg;
|
|
var r = newtag;
|
|
var s = newcol;
|
|
|
|
if (mode=="tg" || mode == "src") {
|
|
if (newtg == "" || newtag == "" ) { // || newcol == "") {
|
|
jAlert(error1, 'Error');
|
|
return;
|
|
}
|
|
|
|
if (!Number.isInteger(Number(newtg)) && mode=='tg') {
|
|
jAlert(error2 + ' \n\n<b>' + newtg + '</b> looks like a ' + (typeof newtg) + '.', 'Error');
|
|
return;
|
|
}
|
|
|
|
if (!Number.isInteger(Number(newcol))) {
|
|
jAlert(error3 + ' \n\n<b>' + newcol + '</b> looks like a ' + (typeof newtg) + '.', 'Error');
|
|
return;
|
|
}
|
|
|
|
var els = document.getElementsByClassName("tg");
|
|
for(var i = 0; i < els.length; i++) {
|
|
x = parseInt(els[i].value);
|
|
if (newtg == x) {
|
|
jAlert(error4, 'Error');
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mode=="alias") {
|
|
if (alias == "" || r == "" || s == "") {
|
|
jAlert(error10, 'Error');
|
|
return;
|
|
}
|
|
}
|
|
|
|
var row = table.insertRow(1);
|
|
var cell0 = row.insertCell(0);
|
|
var cell1 = row.insertCell(1);
|
|
var cell2 = row.insertCell(2);
|
|
var cell3 = row.insertCell(3);
|
|
var cell4 = row.insertCell(4);
|
|
|
|
if (mode == "tg" || mode == "src") {
|
|
|
|
cell0.innerHTML = '<span class="newtg">New</span>';
|
|
cell1.innerHTML = '<td><span class="newtg"><input type="text" id="' + id + '" value="' + newtg + '" class="tg"></td>';
|
|
cell2.innerHTML = '<td><span class="newtg"><td><input type="text" id="tag' + id + '" value="' + newtag + '"></td>';
|
|
cell3.innerHTML = '<td style="width: 100px;"><span class="newtg"><input type="text" id="col' + id + '" value="' + newcol + '"></td>';
|
|
cell4.innerHTML = '<td align="center"><button class="saveButton" id="btnSave3" onclick="this.blur; saveTable();"> Save </button> ';
|
|
cell4.style = "text-align:center";
|
|
cell4.innerHTML += '<button title="Delete" class="delbutton" onclick="this.blur; deleteRow();">✘</button></td>';
|
|
}
|
|
|
|
if (mode == "alias") {
|
|
|
|
cell0.innerHTML = '<span class="newtg">New</span>';
|
|
cell1.innerHTML = '<td><span class="newtg"><input type="text" id="' + id + '" value="' + alias + '" class="tg"></td>';
|
|
cell2.innerHTML = '<td><span class="newtg"><td><input type="text" id="rfss' + id + '" value="' + r + '"></td>';
|
|
cell3.innerHTML = '<td style="width: 100px;"><span class="newtg"><input type="text" id="site' + id + '" value="' + s + '"></td>';
|
|
cell4.innerHTML = '';
|
|
cell4.style = "text-align:center";
|
|
cell4.innerHTML += '<button title="Delete" class="delbutton" onclick="this.blur; deleteRow();">✘</button></td>';
|
|
}
|
|
|
|
|
|
$('#newtg').val('');
|
|
$('#newtag').val('');
|
|
$('#newcol').val('');
|
|
|
|
isUnsaved();
|
|
}
|
|
|
|
function getHigh() {
|
|
var x = [];
|
|
var els = document.getElementsByClassName("tg");
|
|
for(var i = 0; i < els.length; i++) {
|
|
x[i] = (parseInt(els[i].id));
|
|
}
|
|
x = Math.max(...x);
|
|
// console.log(x);
|
|
if (x == -Infinity) x = -1;
|
|
return x;
|
|
}
|
|
|
|
function scrollToAnchor(a){
|
|
var f;
|
|
var els = document.getElementsByClassName("tg");
|
|
for(var i = 0; i < els.length; i++) {
|
|
if (a == (parseInt(els[i].value))) {
|
|
var x = document.getElementById(i);
|
|
x.focus();
|
|
x.scrollIntoView({
|
|
behavior : 'smooth',
|
|
block : 'center',
|
|
inline : 'center'
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
|
|
jAlert (modeTitle + ' ' + a + ' not found. There may be wildcard matches. \n\n Creating new. ', 'Add New');
|
|
$('#newtg').val(a);
|
|
$('#newtg').focus();
|
|
}
|
|
|
|
function tsv_csv() { // export to CSV file
|
|
var filetag = 'op25_tsv_' + mode;
|
|
var csv = "";
|
|
var separator = ",";
|
|
var rows = [];
|
|
var tgs = $('.tg');
|
|
var tag = $('.tag');
|
|
var col = $('.col');
|
|
|
|
for(var i = 0; i < tgs.length; i++) {
|
|
rows.push ( [ tgs[i].value, tag[i].value, col[i].value ] );
|
|
}
|
|
|
|
var filename = 'export_' + filetag + '_' + new Date().toLocaleDateString() + '.csv';
|
|
$("#unsaved").show();
|
|
exportToCsv(filename, rows); // in main.js
|
|
|
|
}
|
|
|
|
function clearMessage() {
|
|
$('#message').text('');
|
|
}
|
|
|
|
function refresh() {
|
|
location.reload();
|
|
}
|
|
|
|
function up() {
|
|
window.location.href = '#top';
|
|
}
|
|
|
|
function completeSave() {
|
|
switch(mode) {
|
|
case "alias":
|
|
beginAlias();
|
|
break;
|
|
case "color":
|
|
beginColor();
|
|
break;
|
|
default:
|
|
// console.log('executing default...');
|
|
begin();
|
|
}
|
|
}
|
|
|
|
function disableSave() {
|
|
$('.saveButton').prop('disabled', true);
|
|
// document.querySelectorAll('button.saveButton').forEach(elem => {
|
|
// elem.disabled = true;
|
|
// });
|
|
}
|
|
|
|
function enableSave(){
|
|
$('.saveButton').prop('disabled', false);
|
|
// document.querySelectorAll('button.saveButton').forEach(elem => {
|
|
// elem.disabled = false;
|
|
// });
|
|
}
|
|
|
|
function help(){
|
|
$.ajax({
|
|
url : 'tsv-help.html',
|
|
type : 'GET',
|
|
success : dispHelp
|
|
});
|
|
}
|
|
|
|
function dispHelp(h) {
|
|
var s = document.getElementById("main");
|
|
// if (s.content == 'help')
|
|
// return;
|
|
|
|
if ($('#main').data('content') == 'help')
|
|
return;
|
|
|
|
// window.editorTable = s.innerHTML;
|
|
// s.innerHTML = h;
|
|
window.editorTable = $('#main').html();
|
|
$('#main').html(h);
|
|
s.content = 'help';
|
|
$('#main').data('content', 'help');
|
|
}
|
|
|
|
function closeHelp() {
|
|
var s = document.getElementById("main");
|
|
s.innerHTML = window.editorTable;
|
|
s.content = 'editor';
|
|
$('#main').data('content', 'editor');
|
|
}
|
|
|
|
function isNum(x) {
|
|
if (!x) return;
|
|
y = Number.isInteger(x);
|
|
return y;
|
|
}
|
|
|
|
function command_reload_tsv(nac) {
|
|
$('#message').text('Sending command...');
|
|
reload_tsv(parseInt(nac));
|
|
$('#message').text('Command Sent');
|
|
}
|
|
|
|
function dispImport() {
|
|
$('#csv-upload').show();
|
|
}
|
|
|
|
function hideImport() {
|
|
$('#csv-upload').hide();
|
|
}
|
|
|
|
function isUnsaved() {
|
|
unsaved = true;
|
|
$( "#unsaved" ).show();
|
|
}
|
|
|
|
function isSaved() {
|
|
$( "#unsaved" ).hide();
|
|
unsaved = false;
|
|
}
|
|
|
|
function readSingleFile(evt) {
|
|
if (!evt.target.files) {
|
|
alert(error5);
|
|
return;
|
|
}
|
|
|
|
var f = evt.target.files[0];
|
|
if (f) {
|
|
isUnsaved();
|
|
var ftype = (evt.target.value).substr((evt.target.value).length - 3).toUpperCase();
|
|
|
|
switch (ftype) {
|
|
case 'CSV':
|
|
var sep = ",";
|
|
break;
|
|
case 'TSV':
|
|
var sep = "\t";
|
|
break;
|
|
default:
|
|
jAlert('File type ' + ftype + ' ' + error7, 'Error');
|
|
return;
|
|
}
|
|
|
|
var r = new FileReader();
|
|
if (!r) return;
|
|
r.onload = function(e) {
|
|
var contents = e.target.result;
|
|
// document.write("File Uploaded! <br />" + "name: " + f.name + "<br />" + "content: " + 'contents hidden' + "<br />" + "type: " + f.type + "<br />" + "size: " + f.size + " bytes <hr />");
|
|
var lines = contents.split("\n"), output = [];
|
|
for (var i=0; i<lines.length; i++){
|
|
if (lines[i].length > 0)
|
|
output.push(lines[i].split(sep).join("\t") + "\n");
|
|
}
|
|
output = "" + output.join("") + "";
|
|
isUnsaved();
|
|
window.imp = true;
|
|
buildTable(output);
|
|
}
|
|
r.readAsText(f);
|
|
isUnsaved();
|
|
} else {
|
|
jAlert(error8 + evt.target.value, 'Error');
|
|
}
|
|
}
|
|
|
|
function buildAliasTable(d) { // geting JSON with this one
|
|
|
|
if (!d) {
|
|
location.reload();
|
|
}
|
|
var lines = [];
|
|
var i = 0;
|
|
var sysid = window.sys;
|
|
var alias;
|
|
var systems = [];
|
|
window.aliasData = d;
|
|
|
|
for (r in d) {
|
|
systems.push(r);
|
|
}
|
|
|
|
for (r in d[sysid]) {
|
|
for (s in d[sys][r]) {
|
|
alias = d[sys][r][s]['alias'];
|
|
// console.log (sysid + ' / ' + hex(parseInt(sysid)).toUpperCase() + ' - ' + alias + ' - R=' + r + ' - S=' + s);
|
|
lines.push([alias, r, s]);
|
|
}
|
|
}
|
|
|
|
$("#message").text("Building table...");
|
|
aLen = lines.length;
|
|
|
|
var html = '<div id="editor">';
|
|
html += '<table class="editor" id="tsv-editor">';
|
|
html += '<th>New ' + window.modeTitle + '</th>';
|
|
html += '<th>RFSS ID (dec)</th>';
|
|
html += '<th>Site ID (dec)</th>';
|
|
html += '<th>Actions</th>';
|
|
|
|
html += '<tr><td><input type="text" id="newtg"></td>';
|
|
html += ' <td><input type="text" id="newtag"></td>';
|
|
html += ' <td><input type="text" id="newcol"></td>';
|
|
html += ' <td align="center"><button id="btnAddNew" onclick="this.blur; addNew();"> Add New </button></td></tr>';
|
|
html += '</table><br>';
|
|
|
|
html += '<span class="label">Editing: </span><span class="value">' + file + '</span> ';
|
|
html += '<span id="records"> </span> ';
|
|
html += '<span class="label">System: </span><span class="value">' + (sysid) + ' - 0x' + (hex(parseInt(sysid)).toUpperCase()) + '</span> ';
|
|
html += '<span class="label">Change: </span>';
|
|
html += '<span><select name="selSystem" id="selSystem" style="width: 100px;"></select></span>';
|
|
html += '<span class="value" id="unsaved" style="display: none;"> Unsaved</span>';
|
|
html += '<br><br>';
|
|
|
|
html += '<table class="editor" id="talkgroups">';
|
|
|
|
html += '<th width="">Row</th>';
|
|
html += '<th width="300">' + window.modeTitle + '</th>';
|
|
html += '<th width="">RFSS ID<br>(dec)</th>';
|
|
html += '<th width="">Site ID<br>(dec)</th>';
|
|
html += '<th wifth="120">Actions</th>';
|
|
|
|
if (aLen == 0) {
|
|
jAlert('No Site Aliases found for System ' + sysid + '. Try creating some.', 'Create New');
|
|
}
|
|
|
|
for (var i = 0; i < aLen; i++) {
|
|
|
|
x = i;
|
|
var alias = lines[i][0];
|
|
var rfss = lines[i][1];
|
|
var site = lines[i][2];
|
|
|
|
var eleid_alias = 'ID' + i + site + parseInt(Math.random() * 10000000); // unique IDs for each td, not used for anything right now.
|
|
var eleid_rfss = 'ID' + i + site + parseInt(Math.random() * 10000000);
|
|
var eleid_site = 'ID' + i + site + parseInt(Math.random() * 10000000);
|
|
|
|
html += '<tr>';
|
|
html += '<td id="">' + (i + 1) + '<a href="#" name="'+ alias + '"></a></td>';
|
|
|
|
html += '<td id="' + (eleid_alias) + '" style="width: 150px;"><input type="text" id="' + x + '" value="' + alias + '" class="tg tedit"></td>';
|
|
html += '<td id="' + (eleid_rfss) + '"><input type="text" id="rfss' + x + '" value="' + rfss + '" class="rfss tedit"></td>';
|
|
html += '<td id="' + (eleid_site) + '" style="width: 150px;"><input maxlength="4" type="text" id="site' + x + '" value="' + site + '" class="site tedit"></td>';
|
|
|
|
html += '<td align="center">';
|
|
html += '<button title="Delete" class="delbutton" onclick="this.blur; deleteRow(\'' + alias + '\', \'' + site +'\');">✘</button></td>';
|
|
|
|
html += '</tr>';
|
|
}
|
|
|
|
html += '</table></div><br><br><br><br>';
|
|
|
|
var s = document.getElementById("main"); // todo - use jQ attri() method to assign custom attributes.
|
|
s.innerHTML = html;
|
|
s.content = 'editor';
|
|
|
|
$('#main').data('content', 'editor');
|
|
|
|
$("#main").html(html);
|
|
$('#talkgroups').show(150);
|
|
|
|
$('#records').html('<span class="label">Records: </span><span class="value">' + (getHigh() + 1) + ' </span>');
|
|
|
|
$('#message').text('Ready');
|
|
|
|
if (mode == 'src') $('.pricol').text('Color');
|
|
|
|
document.querySelectorAll(".tedit").forEach(item => {
|
|
item.addEventListener('change', event => {
|
|
isUnsaved();
|
|
});
|
|
});
|
|
|
|
$( "#loading" ).hide();
|
|
if (window.imp == true) //only flag as unsaved if buildTable was used after import
|
|
isUnsaved();
|
|
|
|
// enter key behavior (clicks Add New button)
|
|
$('#newtg').on('keypress', function(e) {
|
|
if(e.which == 13) {
|
|
$('#btnAddNew').trigger('click');
|
|
}
|
|
});
|
|
|
|
$('#newtag').on('keypress', function(e) {
|
|
if(e.which == 13) {
|
|
$('#btnAddNew').trigger('click');
|
|
}
|
|
});
|
|
|
|
$('#newcol').on('keypress', function(e) {
|
|
if(e.which == 13) {
|
|
$('#btnAddNew').trigger('click');
|
|
$('#newtg').focus();
|
|
}
|
|
});
|
|
|
|
populateChange(systems);
|
|
|
|
}
|
|
|
|
function populateChange(sys) {
|
|
var s, h;
|
|
$('#selSystem').append('<option value="' + 0 + '">' + 'Select...' + '</option>');
|
|
for (i in sys) {
|
|
s = sys[i];
|
|
h = hex(parseInt(s));
|
|
$('#selSystem').append('<option value="' + s + '">' + s + ' - 0x' + h.toUpperCase() + '</option>');
|
|
}
|
|
$('#selSystem').append('<option value="' + 'new' + '">' + 'Add New...' + '</option>');
|
|
$('#selSystem').on('change', function() {
|
|
changeSys(sys);
|
|
});
|
|
}
|
|
|
|
function changeSys(sys) {
|
|
|
|
if (sys == 0)
|
|
return;
|
|
|
|
var sel = $('#selSystem').val();
|
|
|
|
var sysd = 0;
|
|
|
|
if (sel == 'new') {
|
|
var p = prompt('Enter new System ID in hex.\n\n');
|
|
|
|
if (!p) { // Cancel btn
|
|
$('#selSystem').val(0);
|
|
return;
|
|
}
|
|
|
|
if (!(p.match("^[0-9a-fA-F]+$") !== null)) { // valid hex?
|
|
jAlert(error12, 'Invalid Entry');
|
|
$('#selSystem').val(0);
|
|
return;
|
|
}
|
|
|
|
sysd = dec(p);
|
|
|
|
if (sysd < 1 || sysd > 4095) {
|
|
jAlert(error12, 'Invalid Entry');
|
|
$('#selSystem').val(0);
|
|
return;
|
|
}
|
|
|
|
if (sys.includes( sysd.toString())) {
|
|
jAlert(error13, 'Invalid Entry');
|
|
$('#selSystem').val(0);
|
|
return;
|
|
}
|
|
|
|
$('#selSystem').val(0);
|
|
|
|
sel = sysd;
|
|
|
|
} // end if new
|
|
|
|
var url = 'alias-edit.html?file=site-alias.json&mode=alias&sys=' + sel;
|
|
window.sys = sel;
|
|
beginAlias();
|
|
}
|
|
|
|
function deleteSys() {
|
|
system = window.sys;
|
|
|
|
if (Object.keys(aliasData).length == 1) {
|
|
// console.log('cannot delete last system');
|
|
return;
|
|
}
|
|
|
|
var r = confirm("Confirm: Delete site aliases for System " + system + ' - 0x' + hex(parseInt(sys)).toUpperCase() + '\n\nWARNING: This action cannot be reversed.\n\n');
|
|
if (r == true) {
|
|
delete window.aliasData[system.toString()];
|
|
$('#selSystem option[value="' + system + '"]').remove();
|
|
var jsonOutput = JSON.stringify(aliasData, undefined, 2);
|
|
$( "#loading" ).show();
|
|
save_tsv('config-tsvsave', jsonOutput, window.file); // send data to server
|
|
$('#selSystem :nth-child(1)').prop('selected', true); // select first option (below Select...)
|
|
var newSys = $('#selSystem option').eq(1).val();
|
|
window.sys = newSys;
|
|
$('#loading').hide();
|
|
beginAlias();
|
|
} else {
|
|
// cancel
|
|
return;
|
|
}
|
|
}
|
|
|
|
function saveAliasTable() {
|
|
if (!document.getElementById("talkgroups")) {
|
|
jAlert(error6, 'Error');
|
|
return 0;
|
|
}
|
|
|
|
d = window.aliasData;
|
|
var sysid = window.sys;
|
|
|
|
$( "#loading" ).show();
|
|
window.location.href = "#top";
|
|
// $('#talkgroups').hide(250);
|
|
$('#message').text('Saving...');
|
|
|
|
disableSave(); // disable save buttons momentarily
|
|
|
|
var tgs, tag, col;
|
|
data = [];
|
|
out = [];
|
|
rs = [];
|
|
|
|
table = document.getElementById("talkgroups");
|
|
rows = table.rows.length;
|
|
|
|
// check for dupe rfss/site combos
|
|
var els = document.getElementsByClassName("tg");
|
|
for(var y = 0; y < els.length; y++) {
|
|
rfss = document.getElementById("rfss" + (y)).value;
|
|
site = document.getElementById("site" + (y)).value;
|
|
rs.push([rfss, site]);
|
|
}
|
|
var arr = rs.map(JSON.stringify);
|
|
rl = arr.length;
|
|
var sar = new Set(arr);
|
|
sl = sar.size;
|
|
if (rl != sl ) {
|
|
jAlert(error11, 'Error');
|
|
$('#message').text('Ready');
|
|
$( "#loading" ).hide();
|
|
enableSave();
|
|
return;
|
|
}
|
|
|
|
for(var i = 0; i < els.length; i++) {
|
|
|
|
x = els[i].id;
|
|
|
|
alias = document.getElementById(x).value;
|
|
rfss = document.getElementById("rfss" + (x)).value;
|
|
site = document.getElementById("site" + (x)).value;
|
|
|
|
if (alias == "" || rfss == "" || site == "") {
|
|
jAlert(error10 + '\n\nEmpty fields on Row ' + (i + 1) + '\n\n', 'Error');
|
|
enableSave();
|
|
return;
|
|
}
|
|
|
|
data[i] = new Array(alias, rfss, site);
|
|
|
|
}
|
|
|
|
data.sort(function(a, b) {
|
|
return a[2] - b[2];
|
|
});
|
|
|
|
// these get added to site-alias.json when no data is present initially - just delete them.
|
|
delete d['readyState'];
|
|
delete d['responseText'];
|
|
delete d['status'];
|
|
delete d['statusText'];
|
|
|
|
delete d[sys];
|
|
|
|
for(var i = 0; i < data.length; i++) {
|
|
alias = data[i][0];
|
|
r = data[i][1];
|
|
s = data[i][2];
|
|
d[sys] = d[sys] || {};
|
|
d[sys][r] = d[sys][r] || {};
|
|
d[sys][r][s] = d[sys][r][s] || {};
|
|
d[sys][r][s]['alias'] = alias;
|
|
}
|
|
|
|
//json output
|
|
var jsonOutput = JSON.stringify(d, undefined, 2);
|
|
save_tsv('config-tsvsave', jsonOutput, window.file); // send data to server
|
|
|
|
setTimeout(completeSave, delay); // artificial delays to allow http_server.py time to process things
|
|
setTimeout(enableSave, delay);
|
|
$('#talkgroups').show(delay);
|
|
$('#loading').hide();
|
|
localStorage.AliasTableUpdated == true;
|
|
isSaved();
|
|
|
|
}
|
|
|
|
function buildColorTable(d) {
|
|
|
|
// console.log('build color table started...');
|
|
|
|
var html = '<span class="value" id="unsaved" style="display: none;">Unsaved Changes</span>';
|
|
html += '<br><br>';
|
|
|
|
html += '<div id="editor">';
|
|
html += '<table class="editor" id="tsv-editor">';
|
|
|
|
html += '<th>Color</th>';
|
|
html += '<th>Font Color</th>';
|
|
html += '<th>Background</th>';
|
|
html += '<th title="thank Max for this...">Blink</th>';
|
|
html += '<th>Sample Text</th>';
|
|
|
|
for (i in d) {
|
|
if (!d[i])
|
|
continue;
|
|
if (d[i][0] > 99)
|
|
continue;
|
|
|
|
var x = i;
|
|
|
|
var color = d[i][1];
|
|
var backg = d[i][2];
|
|
var blink = d[i][3];
|
|
|
|
var blinkOpt = (d[i][3] == true) ? "Yes" : "No";
|
|
|
|
html += '<tr>';
|
|
|
|
html += '<td style="text-align: center;" id=""><b>' + (parseInt(x)) + '</b></td>';
|
|
|
|
html += '<td style="width: 150px; text-align: center;"><input title="' + x + '" type="text" id="' + (x + 'color') + '" value="' + color + '" class="tg tedit coloredit"></td>';
|
|
|
|
html += '<td style="width: 150px; text-align: center;"><input title="' + x + '" type="text" id="' + (x + 'backg') + '" value="' + backg + '" class="tg tedit bgedit"></td>';
|
|
|
|
html += '<td style="width: 150px; text-align: center;">';
|
|
html += '<input type="checkbox" title="' + x + '" id="' + (x + 'blink') + '" class="tg tedit blinkedit">';
|
|
html += '<label for="' + (x + 'blink') + '"><span></span></label>';
|
|
html +='</td>';
|
|
|
|
html += '<td style="text-align: center;"><span id="s' + x + '" class="c' + parseInt(x) + '">Sample Text 12345 </span>';
|
|
|
|
html += '</tr>';
|
|
|
|
}
|
|
|
|
html += '</table></div><br><br><br><br>';
|
|
generateCSS();
|
|
var s = document.getElementById("main"); // todo - use jQ attri() method to assign custom attributes.
|
|
s.innerHTML = html;
|
|
s.content = 'editor';
|
|
|
|
$('#main').data('content', 'editor');
|
|
|
|
$("#main").html(html);
|
|
$('#talkgroups').show(150);
|
|
|
|
for (i in d) {
|
|
if (!d[i])
|
|
continue;
|
|
if (d[i][0] > 99)
|
|
continue;
|
|
$('#' + (i) + 'blink').prop('checked', d[i][3]);
|
|
}
|
|
|
|
// color picker: Spectrum - https://github.com/bgrins/spectrum
|
|
// Copyright (c) Brian Grinstead
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining
|
|
// a copy of this software and associated documentation files (the
|
|
// "Software"), to deal in the Software without restriction, including
|
|
// without limitation the rights to use, copy, modify, merge, publish,
|
|
// distribute, sublicense, and/or sell copies of the Software, and to
|
|
// permit persons to whom the Software is furnished to do so, subject to
|
|
// the following conditions...
|
|
// ** see complete license in source css & js
|
|
|
|
$(".coloredit").spectrum({
|
|
preferredFormat: "hex",
|
|
localStorageKey: "spectrum.homepage",
|
|
chooseText: "Select",
|
|
clickoutFiresChange: false,
|
|
containerClassName: 'colorPicker',
|
|
replacerClassName: 'colorReplacer',
|
|
showInput: true,
|
|
showPalette: true,
|
|
showInitial: true,
|
|
allowEmpty: false,
|
|
palette: [
|
|
["#000","#444","#666","#999","#ccc","#eee","#f3f3f3","#fff"],
|
|
["#f00","#f90","#ff0","#0f0","#0ff","#00f","#90f","#f0f"],
|
|
["#f4cccc","#fce5cd","#fff2cc","#d9ead3","#d0e0e3","#cfe2f3","#d9d2e9","#ead1dc"],
|
|
["#ea9999","#f9cb9c","#ffe599","#b6d7a8","#a2c4c9","#9fc5e8","#b4a7d6","#d5a6bd"],
|
|
["#e06666","#f6b26b","#ffd966","#93c47d","#76a5af","#6fa8dc","#8e7cc3","#c27ba0"],
|
|
["#c00","#e69138","#f1c232","#6aa84f","#45818e","#3d85c6","#674ea7","#a64d79"],
|
|
["#900","#b45f06","#bf9000","#38761d","#134f5c","#0b5394","#351c75","#741b47"],
|
|
["#600","#783f04","#7f6000","#274e13","#0c343d","#073763","#20124d","#4c1130"]
|
|
]
|
|
});
|
|
|
|
$(".bgedit").spectrum({
|
|
preferredFormat: "hex",
|
|
localStorageKey: "spectrum.homepage",
|
|
clickoutFiresChange: false,
|
|
containerClassName: 'colorPicker',
|
|
replacerClassName: 'colorReplacer',
|
|
chooseText: "Select",
|
|
showInput: true,
|
|
showPalette: true,
|
|
showInitial: true,
|
|
allowEmpty: true,
|
|
palette: [
|
|
["#000","#444","#666","#999","#ccc","#eee","#f3f3f3","#fff"],
|
|
["#f00","#f90","#ff0","#0f0","#0ff","#00f","#90f","#f0f"],
|
|
["#f4cccc","#fce5cd","#fff2cc","#d9ead3","#d0e0e3","#cfe2f3","#d9d2e9","#ead1dc"],
|
|
["#ea9999","#f9cb9c","#ffe599","#b6d7a8","#a2c4c9","#9fc5e8","#b4a7d6","#d5a6bd"],
|
|
["#e06666","#f6b26b","#ffd966","#93c47d","#76a5af","#6fa8dc","#8e7cc3","#c27ba0"],
|
|
["#c00","#e69138","#f1c232","#6aa84f","#45818e","#3d85c6","#674ea7","#a64d79"],
|
|
["#900","#b45f06","#bf9000","#38761d","#134f5c","#0b5394","#351c75","#741b47"],
|
|
["#600","#783f04","#7f6000","#274e13","#0c343d","#073763","#20124d","#4c1130"]
|
|
]
|
|
});
|
|
|
|
$('.coloredit').on('change', function(){
|
|
var v = this.value ? this.value : "initial";
|
|
var target = '#s' + (this.title);
|
|
$(target).css('color', v);
|
|
isUnsaved();
|
|
});
|
|
|
|
$('.bgedit').on('change', function(){
|
|
var v = this.value ? this.value : "initial";
|
|
var target = '#s' + (this.title);
|
|
$(target).css('background-color', v);
|
|
isUnsaved();
|
|
});
|
|
|
|
$('.blinkedit').on('change', function(){
|
|
var target = '#s' + (this.title);
|
|
var z = this.checked == true ? "blinker 1s linear infinite" : "none";
|
|
$(target).css('animation', z);
|
|
isUnsaved();
|
|
});
|
|
|
|
$('#loading').hide();
|
|
$('#message').text('Ready');
|
|
}
|
|
|
|
function saveColorTable() {
|
|
|
|
$( "#loading" ).show();
|
|
window.location.href = "#top";
|
|
$('#message').text('Saving...');
|
|
|
|
var i = null;
|
|
var c = null;
|
|
var b = null;
|
|
var blink = null;
|
|
var css = [];
|
|
css[0] = new Array (500,"placeholder","do-not-use",false); // hack shack :( avoids a NULL at [0] in the resulting json
|
|
for (i = 1; i < 100; i++){
|
|
c = $('#' + (i) + 'color').val();
|
|
b = $('#' + (i) + 'backg').val();
|
|
blink = $('#' + (i) + 'blink').is(':checked') ? true : false;
|
|
css[i] = new Array(i, c, b, blink);
|
|
}
|
|
|
|
var jsonOutput = JSON.stringify(css, undefined, 2);
|
|
|
|
save_tsv('config-tsvsave', jsonOutput, 'color-map.json');
|
|
$('#message').text('Colors saved. Ready.');
|
|
setTimeout(function() { $( "#loading" ).hide(); }, 250); // a little delay for the UI's benefit.
|
|
localStorage.ColorsTableUpdated == true;
|
|
isSaved();
|
|
}
|
|
|
|
function generateCSS() {
|
|
// console.log('generateCSS started...');
|
|
if (localStorage.ColorsTableUpdated == false)
|
|
return;
|
|
colorFile = 'color-map.json';
|
|
$.ajax({
|
|
url : colorFile,
|
|
type : 'GET',
|
|
success : applyCSS,
|
|
// error : function(XMLHttpRequest, textStatus, errorThrown) { alert('File Acces Error: \n\nFile:' + colorFile + '\n\n' + errorThrown + '\n\n'); }
|
|
});
|
|
|
|
}
|
|
|
|
function applyCSS(d) {
|
|
var sheet = (function() {
|
|
var sheets = document.styleSheets,
|
|
stylesheet = sheets[(sheets.length - 1)];
|
|
|
|
for(var i in document.styleSheets ){
|
|
if( sheets[i].title == 'tgcolors') {
|
|
for (var y=0; y<sheets[i].cssRules.length; y++) {
|
|
sheets[i].deleteRule (y);
|
|
}
|
|
return sheets[i]; // re-use the same inline style sheet
|
|
}
|
|
}
|
|
var style = document.createElement("style");
|
|
style.title = 'tgcolors';
|
|
// WebKit hack :(
|
|
style.appendChild(document.createTextNode(""));
|
|
// Add the <style> element to the page
|
|
document.head.appendChild(style);
|
|
localStorage.ColorsTableUpdated == false;
|
|
return style.sheet;
|
|
|
|
})();
|
|
|
|
for (i in d) {
|
|
if (d[i]) {
|
|
w = ' .c' + d[i][0] + ' { ';
|
|
x = 'color: ' + d[i][1] + '; ';
|
|
y = 'background-color: ' + d[i][2] + '; ';
|
|
z = 'animation: ' + (d[i][3] == true ? "blinker 1s linear infinite;" : "none;") + '} \n';
|
|
sheet.insertRule( (w + x + y + z) );
|
|
}
|
|
}
|
|
|
|
setTimeout(enableSave, delay);
|
|
$('#talkgroups').show(delay);
|
|
$( "#loading" ).hide();
|
|
}
|
|
|
|
function reloadCss() { // not used right now
|
|
var links = document.getElementsByTagName("link");
|
|
for (var cl in links)
|
|
{
|
|
var link = links[cl];
|
|
if (link.rel === "stylesheet")
|
|
link.href += "";
|
|
}
|
|
}
|