2024-05-14 21:10:53 +02:00
|
|
|
<!-- SPDX-License-Identifier: Beerware -->
|
|
|
|
<!-- Wolfgang Kroener wrote this file. As long as you retain this notice -->
|
|
|
|
<!-- you can do whatever you want with this stuff. If we meet some day, -->
|
|
|
|
<!-- and you think this stuff is worth it, you can buy me a beer in return -->
|
|
|
|
|
|
|
|
<!doctype html>
|
|
|
|
<html lang="en">
|
|
|
|
|
|
|
|
<head>
|
|
|
|
<meta charset="utf-8">
|
|
|
|
<title>band plan</title>
|
|
|
|
<style>
|
|
|
|
body>div {
|
|
|
|
padding-bottom: 1em;
|
|
|
|
}
|
2024-05-15 12:31:36 +02:00
|
|
|
div#togglebuttons>button {
|
|
|
|
margin: 0 0.1em;
|
|
|
|
}
|
2024-05-26 15:12:32 +02:00
|
|
|
div>label {
|
|
|
|
padding-right: 1em;
|
|
|
|
}
|
2024-05-14 21:10:53 +02:00
|
|
|
</style>
|
|
|
|
</head>
|
|
|
|
|
|
|
|
<body>
|
|
|
|
<link href="https://unpkg.com/tabulator-tables/dist/css/tabulator.min.css" rel="stylesheet">
|
|
|
|
<script src="https://unpkg.com/browser-cjs/require.min.js"></script>
|
|
|
|
<script src="https://unpkg.com/tabulator-tables/dist/js/tabulator.min.js"></script>
|
|
|
|
<script src="https://unpkg.com/jspdf/dist/jspdf.umd.min.js"></script>
|
|
|
|
<script src="https://unpkg.com/jspdf-autotable/dist/jspdf.plugin.autotable.min.js"></script>
|
|
|
|
<script>
|
|
|
|
// require in browser-cjs
|
|
|
|
// eslint-disable-next-line no-undef
|
|
|
|
const yaml = require('https://unpkg.com/js-yaml/dist/js-yaml.min.js');
|
|
|
|
// eslint-disable-next-line no-undef
|
|
|
|
const Measures = require('https://unpkg.com/measures/dist/measures.cjs.js');
|
|
|
|
// eslint-disable-next-line no-undef
|
|
|
|
const path = require('https://unpkg.com/path-browserify/index.js');
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
2024-05-26 12:50:29 +02:00
|
|
|
location.search.substr(1).split("&").forEach((param) => {
|
|
|
|
let p = param.split("=");
|
|
|
|
let key = p[0];
|
|
|
|
if (key === "base") {
|
|
|
|
document.getElementById("base_plan").value = decodeURIComponent(p[1]);
|
|
|
|
}
|
|
|
|
if (key === "add") {
|
2024-05-26 15:12:32 +02:00
|
|
|
document.getElementById("extension_plan").value = decodeURIComponent(p[1]);
|
2024-05-26 12:50:29 +02:00
|
|
|
}
|
|
|
|
});
|
2024-05-14 21:10:53 +02:00
|
|
|
update_plan();
|
2024-05-15 21:00:20 +02:00
|
|
|
|
2024-05-23 17:47:11 +02:00
|
|
|
document.getElementById("base_plan").addEventListener("keydown", (event) => {
|
2024-05-15 21:00:20 +02:00
|
|
|
if (event.key === 'Enter') {
|
|
|
|
update_plan()
|
|
|
|
}
|
|
|
|
});
|
2024-05-14 21:10:53 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
// eslint-disable-next-line no-unused-vars
|
|
|
|
function filter_with_not(headerValue, rowValue, rowData, filterParams){
|
|
|
|
if (headerValue.startsWith('!')) {
|
|
|
|
// filter not
|
|
|
|
const str = headerValue.slice(1);
|
|
|
|
if ((str.length > 0) && (typeof rowValue !== "undefined")) {
|
|
|
|
return !rowValue.toString().includes(str);
|
|
|
|
} else {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// filter normal
|
|
|
|
if (typeof rowValue !== "undefined") {
|
|
|
|
return rowValue.toString().includes(headerValue);
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-15 17:54:17 +02:00
|
|
|
function display_error(e) {
|
|
|
|
let t = document.getElementById("data-table");
|
|
|
|
t.innerHTML = "";
|
|
|
|
t.removeAttribute("class");
|
|
|
|
console.log(e);
|
|
|
|
document.getElementById("error").innerText = e;
|
|
|
|
}
|
|
|
|
|
2024-05-23 17:47:11 +02:00
|
|
|
function isInRange(number, min, max) {
|
|
|
|
return number >= min && number <= max;
|
|
|
|
}
|
2024-05-14 21:10:53 +02:00
|
|
|
|
2024-05-23 17:47:11 +02:00
|
|
|
function add_power(add, p) {
|
2024-05-24 19:41:19 +02:00
|
|
|
for (const [key, value] of Object.entries(add.power)) {
|
|
|
|
p["power_" + key] = value;
|
2024-05-23 17:47:11 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-26 15:12:32 +02:00
|
|
|
function update_table(bases, extensions) {
|
2024-05-23 17:47:11 +02:00
|
|
|
var tdata = [];
|
2024-05-24 19:55:54 +02:00
|
|
|
var base_header = {};
|
2024-05-26 15:12:32 +02:00
|
|
|
var ext_header = {};
|
2024-05-24 19:23:31 +02:00
|
|
|
|
2024-05-24 19:55:54 +02:00
|
|
|
// first, fill base_header
|
2024-05-26 15:12:32 +02:00
|
|
|
bases.forEach((base) => {
|
|
|
|
if ((typeof base.header !== "undefined") && base.header) {
|
|
|
|
base_header = structuredClone(base);
|
2024-05-24 19:23:31 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// then fill tdata
|
2024-05-26 15:12:32 +02:00
|
|
|
bases.forEach((base) => {
|
|
|
|
if ((typeof base.header === "undefined") || (base.header == false)) {
|
|
|
|
let p = {};
|
2024-05-24 19:23:31 +02:00
|
|
|
|
2024-05-26 15:12:32 +02:00
|
|
|
// first, fill ext_header
|
|
|
|
extensions.forEach((ext) => {
|
|
|
|
if ((typeof ext.header !== "undefined") && ext.header) {
|
|
|
|
ext_header = structuredClone(ext);
|
|
|
|
}
|
|
|
|
});
|
2024-05-24 19:55:54 +02:00
|
|
|
|
2024-05-26 15:12:32 +02:00
|
|
|
// then fill tdata
|
|
|
|
let already_added = false;
|
|
|
|
extensions.forEach((ext) => {
|
|
|
|
if ((typeof ext.header === "undefined") || (ext.header == false)) {
|
|
|
|
base_header.columns.forEach((col) => {
|
|
|
|
if (base_header.columns.includes(col)) {
|
|
|
|
p[col] = base[col];
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
let [ext_start, ext_end] = ext.frequency.split('-');
|
|
|
|
if (typeof base.frequency === "number") {
|
|
|
|
// at single frequency
|
|
|
|
if (isInRange(base.frequency, ext_start, ext_end)) {
|
|
|
|
add_power(ext, p);
|
|
|
|
} else {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// frequeny range
|
|
|
|
let [base_start, base_end] = base.frequency.split('-');
|
|
|
|
if ((ext_start == base_start) && (ext_end == base_end)) {
|
|
|
|
// ranges are the same
|
|
|
|
add_power(ext, p);
|
|
|
|
} else if (isInRange(base_start, ext_start, ext_end) && isInRange(base_end, ext_start, ext_end)) {
|
|
|
|
// base range is inside of ext range
|
|
|
|
add_power(ext, p);
|
2024-05-24 19:23:31 +02:00
|
|
|
} else {
|
2024-05-26 15:12:32 +02:00
|
|
|
// base range is split by ext range
|
|
|
|
if (isInRange(ext_start, base_start, base_end)) {
|
|
|
|
// base range starts below ext range
|
|
|
|
let q = structuredClone(p);
|
|
|
|
let start = ext_start;
|
|
|
|
let end = base_end < ext_end ? base_end : ext_end;
|
|
|
|
q["frequency"] = start + '-' + end;
|
|
|
|
add_power(ext, q);
|
|
|
|
tdata.push(q);
|
|
|
|
already_added = true;
|
|
|
|
} else if (isInRange(ext_end, base_start, base_end)) {
|
|
|
|
// ext range starts below base range
|
|
|
|
let q = structuredClone(p);
|
|
|
|
let start = base_start;
|
|
|
|
let end = ext_end;
|
|
|
|
q["frequency"] = start + '-' + end;
|
|
|
|
add_power(ext, q);
|
|
|
|
tdata.push(q);
|
|
|
|
already_added = true;
|
2024-05-24 19:23:31 +02:00
|
|
|
}
|
2024-05-23 17:47:11 +02:00
|
|
|
}
|
|
|
|
}
|
2024-05-14 21:10:53 +02:00
|
|
|
}
|
|
|
|
});
|
2024-05-26 15:12:32 +02:00
|
|
|
if (!already_added) {
|
|
|
|
tdata.push(p);
|
|
|
|
}
|
|
|
|
already_added = false;
|
2024-05-24 19:23:31 +02:00
|
|
|
}
|
2024-05-23 17:47:11 +02:00
|
|
|
});
|
2024-05-15 17:54:17 +02:00
|
|
|
|
2024-05-23 17:47:11 +02:00
|
|
|
// require in tabular-tables
|
|
|
|
// eslint-disable-next-line no-undef
|
|
|
|
var table = new Tabulator("#data-table", {
|
|
|
|
layout: "fitDataTable",
|
|
|
|
initialSort: [
|
|
|
|
{column: "frequency", dir: "asc"},
|
|
|
|
{column: "band", dir: "desc"},
|
|
|
|
],
|
|
|
|
selectableRows: true,
|
|
|
|
data: tdata,
|
2024-05-23 18:00:11 +02:00
|
|
|
autoColumns:"full",
|
|
|
|
// add filter for all columns
|
|
|
|
autoColumnsDefinitions: function(definitions){
|
|
|
|
definitions.forEach((column) => {
|
|
|
|
if (column.field == "band") {
|
2024-05-24 18:47:12 +02:00
|
|
|
column.sorter = function(a, b) {
|
2024-05-23 18:00:11 +02:00
|
|
|
return parseFloat(new Measures().from(a).to('m')) - parseFloat(new Measures().from(b).to('m'));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (column.field == "frequency") {
|
|
|
|
column.sorter = "number";
|
|
|
|
}
|
|
|
|
|
|
|
|
column.headerFilter = "input";
|
|
|
|
column.headerFilterFunc = filter_with_not;
|
2024-05-24 19:55:54 +02:00
|
|
|
if (typeof base_header.titles[column.field] !== "undefined") {
|
|
|
|
column.title = base_header.titles[column.field];
|
|
|
|
}
|
2024-05-26 15:12:32 +02:00
|
|
|
if (typeof ext_header.titles[column.field] !== "undefined") {
|
|
|
|
column.title = ext_header.titles[column.field];
|
2024-05-24 19:55:54 +02:00
|
|
|
}
|
2024-05-23 18:00:11 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
return definitions;
|
|
|
|
},
|
2024-05-23 17:47:11 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
document.getElementById("download").addEventListener("click", () => {
|
|
|
|
let download_range = "all";
|
|
|
|
if (table.getSelectedRows().length > 0) {
|
|
|
|
download_range = "selected";
|
|
|
|
}
|
|
|
|
table.download(
|
|
|
|
"pdf",
|
|
|
|
path.basename(document.getElementById('base_plan').value, '.yml') + '.pdf',
|
|
|
|
{ orientation:"portrait" },
|
|
|
|
download_range);
|
|
|
|
});
|
|
|
|
|
|
|
|
table.on("tableBuilt", () => {
|
|
|
|
var div_togglebuttons = document.getElementById("togglebuttons");
|
|
|
|
div_togglebuttons.innerHTML = "<span>Toggle column:</span>";
|
|
|
|
table.getColumns().forEach((col) => {
|
|
|
|
let col_name = col.getField();
|
|
|
|
let b = document.createElement("button");
|
|
|
|
b.innerText = col_name;
|
|
|
|
b.addEventListener("click", () => {
|
|
|
|
table.toggleColumn(col_name);
|
|
|
|
});
|
|
|
|
div_togglebuttons.appendChild(b);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
document.getElementById("error").innerText = "";
|
|
|
|
}
|
|
|
|
|
|
|
|
function update_plan() {
|
|
|
|
Promise.all([
|
|
|
|
fetch(document.getElementById('base_plan').value, { mode: 'cors' }),
|
2024-05-26 15:12:32 +02:00
|
|
|
fetch(document.getElementById('extension_plan').value, { mode: 'cors' }),
|
2024-05-23 17:47:11 +02:00
|
|
|
]).then((res) => {
|
|
|
|
res.forEach((r) => {
|
|
|
|
if (!r.ok) {
|
|
|
|
throw new Error(r.status);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
Promise.all([res[0].text(), res[1].text()]).then((d) => {
|
|
|
|
try {
|
|
|
|
update_table(yaml.load(d[0]),yaml.load(d[1]));
|
2024-05-15 17:54:17 +02:00
|
|
|
} catch (e) {
|
|
|
|
display_error(e);
|
|
|
|
}
|
2024-05-23 17:47:11 +02:00
|
|
|
}).catch((e) => {
|
|
|
|
display_error(e);
|
|
|
|
});
|
2024-05-15 17:54:17 +02:00
|
|
|
}).catch((e) => {
|
|
|
|
display_error(e);
|
2024-05-14 21:10:53 +02:00
|
|
|
});
|
|
|
|
}
|
2024-05-15 17:54:17 +02:00
|
|
|
|
|
|
|
// used in button
|
|
|
|
// eslint-disable-next-line no-unused-vars
|
|
|
|
function help() {
|
|
|
|
alert(
|
|
|
|
'Plan: use your own yml file for a band plan, URLs are possible, CORS needs to be allowed for the file\n'
|
|
|
|
+ 'Filter rows with text in column headings, use filter starting with "!" as exclusion\n'
|
|
|
|
+ 'Download list: saves .pdf of the current list\n'
|
|
|
|
+ 'Selection of rows with mouse possible'
|
|
|
|
);
|
|
|
|
}
|
2024-05-14 21:10:53 +02:00
|
|
|
</script>
|
|
|
|
<div>
|
2024-05-26 15:12:32 +02:00
|
|
|
<label>Base Plan:
|
|
|
|
<input name="plan" id="base_plan" type="text" value="band-plan-iaru_r1_hf.yml">
|
|
|
|
</label>
|
|
|
|
<label>Extension:
|
|
|
|
<input name="plan" id="extension_plan" type="text" value="band-plan-de.yml">
|
|
|
|
</label>
|
|
|
|
<button onclick="update_plan()">Change plans</button>
|
2024-05-14 21:10:53 +02:00
|
|
|
</div>
|
2024-05-15 12:31:36 +02:00
|
|
|
<div id="togglebuttons"></div>
|
2024-05-14 21:10:53 +02:00
|
|
|
<div>
|
2024-05-15 17:54:17 +02:00
|
|
|
<button id="download">Download list</button>
|
|
|
|
<button onclick="help()">Help</button>
|
2024-05-14 21:10:53 +02:00
|
|
|
</div>
|
|
|
|
<div id="data-table"></div>
|
|
|
|
<div id="error"></div>
|
|
|
|
</body>
|
|
|
|
</html>
|