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 >
2024-06-02 12:40:00 +02:00
< script src = "https://unpkg.com/js-quantities/build/quantities.js" > < / script >
2024-05-30 17:57:12 +02:00
<!--
for pdf saving
2024-05-14 21:10:53 +02:00
< script src = "https://unpkg.com/jspdf/dist/jspdf.umd.min.js" > < / script >
2024-05-30 17:57:12 +02:00
< script src = "https://unpkg.com/dompurify/dist/purify.min.js" > < / script >
< script src = "https://unpkg.com/html2canvas-pro/dist/html2canvas-pro.min.js" > < / script >
-->
2024-05-14 21:10:53 +02:00
< script >
// require in browser-cjs
// eslint-disable-next-line no-undef
2024-05-27 15:36:51 +02:00
const yaml = require("https://unpkg.com/js-yaml/dist/js-yaml.min.js");
2024-05-14 21:10:53 +02:00
// eslint-disable-next-line no-undef
2024-05-27 15:36:51 +02:00
const path = require("https://unpkg.com/path-browserify/index.js");
2024-05-14 21:10:53 +02:00
2024-05-26 16:53:34 +02:00
function create_element_link(element, href) {
element.addEventListener("click", () => {
2024-05-27 15:36:51 +02:00
let lnk = document.createElement("a");
2024-05-26 16:53:34 +02:00
if (typeof href === "function") {
lnk.href = href();
} else {
lnk.href = href;
}
if (document.createEvent) {
let e = document.createEvent("MouseEvents");
e.initMouseEvent("click", true, true, window,
0, 0, 0, 0, 0, false, false, false,
false, 0, null);
lnk.dispatchEvent(e);
} else if (lnk.fireEvent) {
lnk.fireEvent("onclick");
}
});
}
2024-05-27 15:36:51 +02:00
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];
2024-06-03 21:37:29 +02:00
["base", "regulation", "css"].forEach((id) => {
2024-05-28 21:55:39 +02:00
if (key === id) {
document.getElementById(id + "_input").value = decodeURIComponent(p[1]);
}
});
2024-05-26 12:50:29 +02:00
});
2024-05-28 21:55:39 +02:00
update_inputs();
2024-05-15 21:00:20 +02:00
2024-06-03 21:37:29 +02:00
["base", "regulation", "css"].forEach((id) => {
2024-05-28 21:55:39 +02:00
// download buttons
create_element_link(document.getElementById("download_" + id), document.getElementById(id + "_input").value);
// enter on input fields
document.getElementById(id + "_input").addEventListener("keydown", (event) => {
2024-05-27 15:36:51 +02:00
if (event.key === "Enter") {
2024-05-28 21:55:39 +02:00
update_inputs()
2024-05-26 17:05:45 +02:00
}
});
2024-05-15 21:00:20 +02:00
});
2024-05-26 16:53:34 +02:00
create_element_link(document.getElementById("create_link"), function() {
2024-05-27 15:36:51 +02:00
return window.location.href.split("?")[0] + "?"
2024-05-28 21:55:39 +02:00
+ "base=" + document.getElementById("base_input").value
2024-06-03 21:37:29 +02:00
+ "& regulation=" + document.getElementById("regulation_input").value
2024-05-28 21:55:39 +02:00
+ "& css=" + document.getElementById("css_input").value;
2024-05-26 16:53:34 +02:00
});
2024-05-14 21:10:53 +02:00
});
// eslint-disable-next-line no-unused-vars
function filter_with_not(headerValue, rowValue, rowData, filterParams){
2024-05-27 15:36:51 +02:00
if (headerValue.startsWith("!")) {
2024-05-14 21:10:53 +02:00
// 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) {
2024-05-26 17:08:14 +02:00
return Number(number) >= Number(min) & & Number(number) < = Number(max);
2024-05-23 17:47:11 +02:00
}
2024-05-14 21:10:53 +02:00
2024-06-03 21:48:54 +02:00
function add_column(row, cols, header, minimum_fraction_digits = 0) {
const formatter = new Intl.NumberFormat("en-US", {
minimumFractionDigits: minimum_fraction_digits,
maximumFractionDigits: 100,
2024-06-13 12:51:54 +02:00
useGrouping: false,
2024-06-03 21:48:54 +02:00
});
2024-05-26 20:49:21 +02:00
header.columns.forEach((col) => {
if (header.columns.includes(col)) {
2024-05-27 15:36:51 +02:00
if (typeof cols[col] === "object") {
2024-05-26 20:49:21 +02:00
for (const [key, value] of Object.entries(cols[col])) {
2024-06-03 21:48:54 +02:00
if ((key === "frequency") & & (typeof value === "number")) {
row[col + "_" + key] = formatter.format(value);
} else {
row[col + "_" + key] = value;
}
2024-05-26 20:49:21 +02:00
}
} else {
2024-06-03 21:48:54 +02:00
if ((col === "frequency") & & (typeof cols[col] === "number")) {
row[col] = formatter.format(cols[col]);
} else {
row[col] = cols[col];
}
2024-05-26 20:49:21 +02:00
}
}
});
2024-05-23 17:47:11 +02:00
}
2024-05-26 20:57:45 +02:00
function fill_comments(header) {
for (const [key, value] of Object.entries(header)) {
if (typeof value.comments !== "undefined") {
let s = "";
value.comments.forEach((comment) => {
for (const [ckey, cvalue] of Object.entries(comment)) {
s += ckey + ": " + cvalue + "< br > ";
}
});
document.getElementById("comments_" + key).innerHTML = s;
2024-06-03 21:37:29 +02:00
} else {
document.getElementById("comments_" + key).innerHTML = "";
2024-05-26 20:57:45 +02:00
}
};
}
2024-05-23 17:47:11 +02:00
2024-06-03 21:37:29 +02:00
function update_table(bases, regulations) {
2024-05-26 20:21:38 +02:00
let tdata = [];
2024-05-26 20:49:21 +02:00
let 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) {
2024-05-26 20:49:21 +02:00
header["base"] = 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)) {
2024-05-26 16:59:13 +02:00
let row = {};
2024-05-24 19:23:31 +02:00
2024-06-03 21:37:29 +02:00
// first, fill reg_header
regulations.forEach((reg) => {
if ((typeof reg.header !== "undefined") & & reg.header) {
header["regulation"] = structuredClone(reg);
2024-05-26 15:12:32 +02:00
}
});
2024-05-24 19:55:54 +02:00
2024-05-26 20:57:45 +02:00
fill_comments(header);
2024-05-26 15:12:32 +02:00
// then fill tdata
2024-06-03 21:37:29 +02:00
regulations.forEach((reg) => {
if ((typeof reg.header === "undefined") || (reg.header == false)) {
2024-06-03 21:48:54 +02:00
add_column(row, base, header["base"], header["base"].frequency_fraction_digits);
2024-05-26 15:12:32 +02:00
2024-06-03 21:37:29 +02:00
let [reg_start, reg_end] = reg.frequency.split("-");
reg_start = Qty(Number(reg_start), header["regulation"].frequency_unit).to(header["base"].frequency_unit).scalar;
reg_end = Qty(Number(reg_end), header["regulation"].frequency_unit).to(header["base"].frequency_unit).scalar;
2024-05-26 15:12:32 +02:00
if (typeof base.frequency === "number") {
// at single frequency
2024-06-03 21:37:29 +02:00
if (isInRange(base.frequency, reg_start, reg_end)) {
2024-06-03 21:48:54 +02:00
add_column(row, reg, header["regulation"], header["base"].frequency_fraction_digits);
2024-05-27 21:38:59 +02:00
tdata.push(row);
2024-05-26 15:12:32 +02:00
}
} else {
// frequeny range
2024-05-27 15:36:51 +02:00
let [base_start, base_end] = base.frequency.split("-");
2024-06-03 21:37:29 +02:00
if (isInRange(base_start, reg_start, reg_end) & & isInRange(base_end, reg_start, reg_end)) {
// base range is inside of reg range or the same
add_column(row, reg, header["regulation"]);
2024-05-27 21:38:59 +02:00
tdata.push(row);
2024-05-24 19:23:31 +02:00
} else {
2024-06-03 21:37:29 +02:00
// base range is split by reg range
if ((base_end > reg_start) & & isInRange(reg_start, base_start, base_end)) {
// base range starts below reg range
2024-05-26 16:59:13 +02:00
let r = structuredClone(row);
2024-06-03 21:37:29 +02:00
let start = reg_start;
let end = base_end < reg_end ? base_end : reg_end ;
2024-05-27 15:36:51 +02:00
r["frequency"] = start + "-" + end;
2024-06-03 21:37:29 +02:00
add_column(r, reg, header["regulation"]);
2024-05-26 16:59:13 +02:00
tdata.push(r);
2024-06-03 21:37:29 +02:00
} else if ((reg_end > base_start) & & isInRange(reg_end, base_start, base_end)) {
// reg range starts below base range
2024-05-26 16:59:13 +02:00
let r = structuredClone(row);
2024-05-26 15:12:32 +02:00
let start = base_start;
2024-06-03 21:37:29 +02:00
let end = reg_end;
2024-05-27 15:36:51 +02:00
r["frequency"] = start + "-" + end;
2024-06-03 21:37:29 +02:00
add_column(r, reg, header["regulation"]);
2024-05-26 16:59:13 +02:00
tdata.push(r);
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-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
2024-05-26 20:21:38 +02:00
let table = new Tabulator("#data-table", {
2024-05-23 17:47:11 +02:00
layout: "fitDataTable",
initialSort: [
{column: "frequency", dir: "asc"},
{column: "band", dir: "desc"},
],
selectableRows: true,
2024-05-30 20:04:48 +02:00
selectableRowsRangeMode: "click",
2024-05-23 17:47:11 +02:00
data: tdata,
2024-05-30 17:57:12 +02:00
autoColumns: "full",
downloadEncoder: function(fileContents, mimeType){
// add header and footer for a complete html file
2024-06-04 18:59:33 +02:00
let header = "<!doctype html> < html > < head > < meta charset = \"utf-8\" > < style > " ;
2024-05-30 17:57:12 +02:00
for (let i = 0; i < document.styleSheets.length ; i + + ) {
let css = document.styleSheets[i];
if (css.ownerNode.id === "link_css") {
for (let j = 0; j < css.cssRules.length ; j + + ) {
header += css.cssRules[j].cssText + "\n";
}
}
}
header += "< / style > < / head > < body > ";
let footer = "< / body > < / html > ";
// add footer for horizontal line
let t = document.createElement("div");
t.insertAdjacentHTML("beforeend", fileContents);
t.firstChild.insertAdjacentHTML("beforeend", "< tfoot > < tr > < td colspan = \"100%\" > < / td > < / tr > < / tfoot > ");
fileContents = t.innerHTML;
fileContents = fileContents.replaceAll(">undefined< ", ">< ");
// also save as pdf
// currently scaling is wrong, no idea how to fix
//const doc = new jspdf.jsPDF("p", "mm", "a4");
//doc.html(header + fileContents + footer, {
// callback: (doc) => {
// doc.save();
// },
// filename: path.basename(document.getElementById("base_input").value, ".yml") + ".pdf",
// autoPaging: "text",
// html2canvas: {
// scale: 0.5,
// },
//});
return new Blob([header, fileContents, footer], {type:mimeType});
},
2024-05-23 18:00:11 +02:00
// 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-06-02 12:40:00 +02:00
return Qty(a).sub(Qty(b)).toBase().scalar;
2024-05-23 18:00:11 +02:00
}
}
if (column.field == "frequency") {
column.sorter = "number";
}
column.headerFilter = "input";
column.headerFilterFunc = filter_with_not;
2024-05-26 20:49:21 +02:00
if (typeof header["base"].titles[column.field] !== "undefined") {
column.title = header["base"].titles[column.field];
2024-05-24 19:55:54 +02:00
}
2024-06-03 21:37:29 +02:00
if (typeof header["regulation"].titles[column.field] !== "undefined") {
column.title = header["regulation"].titles[column.field];
2024-05-24 19:55:54 +02:00
}
2024-05-28 21:55:39 +02:00
// add css styles for formatting based on cell values
// e. g. cell-bandwidth-2700
// list like cw, narrow will be split to two entries
column.formatter = function(cell) {
2024-05-29 17:47:19 +02:00
let f = String(cell.getField()).toLowerCase().replace(/[\W]+/g, " ").replaceAll(" ", "_").replace(/__+/g, "_");
let v = String(cell.getValue()).toLowerCase().replaceAll("+", "_plus_").replace(/[^\w,-]+/g, " ").replaceAll(" ", "_").replace(/__+/g, "_");
2024-05-28 21:55:39 +02:00
v.split(",").forEach((s) => {
cell.getElement().classList.add("cell-" + f + "-" + s.replace(/^_+|_+$/g, ""));
});
cell.getElement().classList.add("cell-" + f);
2024-05-29 17:47:19 +02:00
if ((f === "frequency") & & (v.indexOf("-") > -1)) {
cell.getElement().classList.add("cell-" + f + "-" + "is_range");
}
2024-05-28 21:55:39 +02:00
return cell.getValue();
}
2024-05-23 18:00:11 +02:00
});
return definitions;
},
2024-05-28 21:55:39 +02:00
// add css styles for formatting based on cell data
// e. g. row-mode-cw, row-bandwidth-200
// list like cw, narrow will be split to two entries
rowFormatter: function(row){
for (const [key, value] of Object.entries(row.getData())) {
2024-05-29 17:47:19 +02:00
let k = String(key).toLowerCase().replace(/[\W]+/g, " ").replaceAll(" ", "_").replace(/__+/g, "_");
let v = String(value).toLowerCase().replaceAll("+", "_plus_").replace(/[^\w,-]+/g, " ").replaceAll(" ", "_").replace(/__+/g, "_");
2024-05-28 21:55:39 +02:00
v.split(",").forEach((s) => {
row.getElement().classList.add("row-" + k + "-" + s.replace(/^_+|_+$/g, ""));
});
2024-05-29 17:47:19 +02:00
if ((k === "frequency") & & (v.indexOf("-") > -1)) {
row.getElement().classList.add("row-" + k + "-" + "is_range");
}
2024-05-28 21:55:39 +02:00
}
},
2024-05-23 17:47:11 +02:00
});
document.getElementById("download").addEventListener("click", () => {
2024-05-30 17:58:52 +02:00
let download_range = "active";
2024-05-23 17:47:11 +02:00
if (table.getSelectedRows().length > 0) {
download_range = "selected";
}
2024-05-30 17:57:12 +02:00
table.downloadToTab(
"html",
path.basename(document.getElementById("base_input").value, ".yml") + ".html",
{ },
download_range,
);
2024-05-23 17:47:11 +02:00
});
table.on("tableBuilt", () => {
2024-05-26 20:21:38 +02:00
let div_togglebuttons = document.getElementById("togglebuttons");
2024-05-23 17:47:11 +02:00
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 = "";
}
2024-05-28 21:55:39 +02:00
function update_inputs() {
2024-06-03 22:11:20 +02:00
let urls = [
2024-05-28 21:55:39 +02:00
fetch(document.getElementById("base_input").value, { mode: "cors" }),
2024-06-03 22:11:20 +02:00
];
let reg = document.getElementById("regulation_input").value;
if (reg !== "") {
urls.push(fetch(reg, { mode: "cors" }));
}
Promise.all(urls).then((res) => {
2024-05-23 17:47:11 +02:00
res.forEach((r) => {
if (!r.ok) {
throw new Error(r.status);
}
});
2024-06-03 22:11:20 +02:00
let ymls = [res[0].text()];
if (reg !== "") {
ymls.push(res[1].text());
} else {
// make dummy yml file with one big frequency range
ymls.push("- header: true\n columns: []\n titles: []\n frequency_unit: GHz\n- frequency: 0-" + 9007199254740991);
}
Promise.all(ymls).then((d) => {
2024-05-23 17:47:11 +02:00
try {
2024-06-03 22:11:20 +02:00
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-28 21:55:39 +02:00
// add stylesheet, clear before adding
let el = document.getElementById("link_css");
if (el !== null) {
el.remove();
}
let href = document.getElementById("css_input").value;
if (href !== "") {
let link = document.createElement("link");
link.type = "text/css";
link.rel = "stylesheet";
link.href = href;
link.id = "link_css";
2024-05-30 18:25:33 +02:00
link.setAttribute("crossorigin", "anonymous");
2024-05-28 21:55:39 +02:00
document.head.appendChild(link);
}
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(
2024-06-03 21:37:29 +02:00
"Base plan, regulation and css: use your own files for the band plan and css, URLs are possible, CORS needs to be allowed for the files, download standard files for examples\n"
+ "When Regulation is empty, all frequencies from base plan will be included\n"
+ "When CSS is empty or URL not available, colors, etc. will be gone\n"
2024-05-27 15:36:51 +02:00
+ "Filter rows with text in column headings, use filter starting with \"!\" as exclusion\n"
+ "Download list: saves .pdf of the current list (selection of rows apply)\n"
+ "Selection of rows with mouse possible\n"
2024-05-28 21:55:39 +02:00
+ "Download plans/css: get the yml and css files\n"
+ "Create link: get link for currently selected plans and css for bookmarking\n"
2024-05-15 17:54:17 +02:00
);
}
2024-05-14 21:10:53 +02:00
< / script >
< div >
2024-05-26 15:12:32 +02:00
< label > Base Plan:
2024-05-28 21:55:39 +02:00
< input id = "base_input" type = "text" value = "band-plan-iaru_r1_hf.yml" >
2024-05-26 15:12:32 +02:00
< / label >
2024-06-03 21:37:29 +02:00
< label > Regulation:
< input id = "regulation_input" type = "text" value = "regulations-de.yml" >
2024-05-28 21:55:39 +02:00
< / label >
< label > CSS:
< input id = "css_input" type = "text" value = "standard.css" >
2024-05-26 15:12:32 +02:00
< / label >
2024-05-28 21:55:39 +02:00
< button onclick = "update_inputs()" > Update< / 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 >
2024-05-26 17:05:45 +02:00
< button id = "download_base" > Download base plan< / button >
2024-06-03 21:37:29 +02:00
< button id = "download_regulation" > Download regulation< / button >
2024-05-28 21:55:39 +02:00
< button id = "download_css" > Download css file< / button >
2024-05-26 16:53:34 +02:00
< button id = "create_link" > Create link< / button >
2024-05-15 17:54:17 +02:00
< button onclick = "help()" > Help< / button >
2024-05-14 21:10:53 +02:00
< / div >
< div id = "data-table" > < / div >
< div id = "error" > < / div >
2024-05-26 20:57:45 +02:00
< div id = "comments_base" > < / div >
2024-06-03 21:37:29 +02:00
< div id = "comments_regulation" > < / div >
2024-05-26 17:12:23 +02:00
< div >
< p > This information is supplied without liability.< / p >
< p > Source is at < a href = "https://src.dm5wk.de/dm5wk/band-plan-web/" > https://src.dm5wk.de/dm5wk/band-plan-web/< / a > < / p >
< / div >
2024-05-14 21:10:53 +02:00
< / body >
< / html >