Commit pirate JS files
This commit is contained in:
790
js/WsprSearchUiDataTableRowProController.js
Normal file
790
js/WsprSearchUiDataTableRowProController.js
Normal file
@@ -0,0 +1,790 @@
|
||||
/*
|
||||
Copyright (c) 2023-forever Douglas Malnati. All rights reserved.
|
||||
|
||||
See the /faq/tos page for details.
|
||||
|
||||
(If this generated header is stamped on a file which is a 3rd party file or under a different license or copyright, then ignore this copyright statement and use that file's terms.)
|
||||
*/
|
||||
|
||||
import { CollapsableTitleBox, DialogBox } from './DomWidgets.js';
|
||||
import { WSPR } from '/js/WSPR.js';
|
||||
|
||||
export class WsprSearchUiDataTableRowProController
|
||||
{
|
||||
constructor(cfg)
|
||||
{
|
||||
this.cfg = cfg || {};
|
||||
this.data = this.cfg.data || {};
|
||||
|
||||
this.dialog = new DialogBox();
|
||||
this.dialog.SetTitleBar("Pro Row Insight");
|
||||
this.hasShown = false;
|
||||
this.colWidthList = ["20%", "12%", "6%", "37%", "25%"];
|
||||
|
||||
let content = this.dialog.GetContentContainer();
|
||||
content.style.width = "1110px";
|
||||
content.style.minWidth = "1110px";
|
||||
content.style.minHeight = "520px";
|
||||
content.style.maxHeight = "calc(100vh - 120px)";
|
||||
|
||||
this.body = document.createElement("div");
|
||||
this.body.style.padding = "8px";
|
||||
this.body.style.display = "flex";
|
||||
this.body.style.flexDirection = "column";
|
||||
this.body.style.gap = "8px";
|
||||
content.appendChild(this.body);
|
||||
|
||||
this.#RefreshBody();
|
||||
}
|
||||
|
||||
GetUI()
|
||||
{
|
||||
return this.dialog.GetUI();
|
||||
}
|
||||
|
||||
Show()
|
||||
{
|
||||
this.#RefreshBody();
|
||||
if (!this.hasShown)
|
||||
{
|
||||
let ui = this.dialog.GetUI();
|
||||
ui.style.left = "80px";
|
||||
ui.style.top = "80px";
|
||||
this.hasShown = true;
|
||||
}
|
||||
this.dialog.Show();
|
||||
}
|
||||
|
||||
#RefreshBody()
|
||||
{
|
||||
this.body.innerHTML = "";
|
||||
this.rxRecord__nodeSet = new Map();
|
||||
this.rxRecordHighlightNodeSet = new Set();
|
||||
|
||||
let dt = this.data.dt;
|
||||
let rowIdx = this.data.rowIdx;
|
||||
let dtUtc = "";
|
||||
let dtLocal = "";
|
||||
if (dt && rowIdx != undefined)
|
||||
{
|
||||
dtUtc = dt.Get(rowIdx, "DateTimeUtc") || "";
|
||||
dtLocal = dt.Get(rowIdx, "DateTimeLocal") || "";
|
||||
}
|
||||
this.dialog.SetTitleBar(`Pro Row Insight - DateTimeUtc: ${dtUtc} | DateTimeLocal: ${dtLocal}`);
|
||||
|
||||
let slotMsgListList = this.#GetSlotMsgListList();
|
||||
for (let slot = 0; slot < 5; ++slot)
|
||||
{
|
||||
this.body.appendChild(this.#MakeSlotSection(slot, slotMsgListList[slot] || []));
|
||||
}
|
||||
}
|
||||
|
||||
#GetSlotMsgListList()
|
||||
{
|
||||
let slotMsgListList = [[], [], [], [], []];
|
||||
|
||||
let dt = this.data.dt;
|
||||
let rowIdx = this.data.rowIdx;
|
||||
let wsprSearch = this.data.wsprSearch;
|
||||
let rowMeta = dt && rowIdx != undefined ? dt.GetRowMetaData(rowIdx) : null;
|
||||
let time = rowMeta?.time;
|
||||
|
||||
// Preferred source: full per-window slot message lists from WsprSearch.
|
||||
let windowData = wsprSearch?.time__windowData?.get?.(time);
|
||||
if (windowData?.slotDataList)
|
||||
{
|
||||
for (let slot = 0; slot < 5; ++slot)
|
||||
{
|
||||
slotMsgListList[slot] = windowData.slotDataList[slot]?.msgList || [];
|
||||
}
|
||||
return slotMsgListList;
|
||||
}
|
||||
|
||||
// Fallback source: selected single-candidate-per-slot snapshot.
|
||||
let slotMsgList = rowMeta?.slotMsgList || [];
|
||||
for (let slot = 0; slot < 5; ++slot)
|
||||
{
|
||||
let msg = slotMsgList[slot];
|
||||
if (msg)
|
||||
{
|
||||
slotMsgListList[slot] = [msg];
|
||||
}
|
||||
}
|
||||
|
||||
return slotMsgListList;
|
||||
}
|
||||
|
||||
#MakeSlotSection(slotIdx, msgList)
|
||||
{
|
||||
let section = new CollapsableTitleBox();
|
||||
section.SetTitle(`Slot ${slotIdx} (click to open/collapse)`);
|
||||
section.SetMinWidth("0px");
|
||||
section.Show();
|
||||
let sectionUi = section.GetUI();
|
||||
let sectionBody = section.GetContentContainer();
|
||||
|
||||
let table = document.createElement("table");
|
||||
table.style.borderCollapse = "collapse";
|
||||
table.style.backgroundColor = "white";
|
||||
table.style.width = "100%";
|
||||
table.style.tableLayout = "fixed";
|
||||
this.#AppendColGroup(table);
|
||||
|
||||
let thead = document.createElement("thead");
|
||||
let htr = document.createElement("tr");
|
||||
this.#AppendCell(htr, "Type", true);
|
||||
this.#AppendCell(htr, "Message", true);
|
||||
this.#AppendCell(htr, "Count", true);
|
||||
this.#AppendCell(htr, "Status", true);
|
||||
this.#AppendCell(htr, "Details", true);
|
||||
thead.appendChild(htr);
|
||||
table.appendChild(thead);
|
||||
|
||||
let tbody = document.createElement("tbody");
|
||||
let msgGroupList = this.#GetMsgGroupList(msgList);
|
||||
if (msgGroupList.length == 0)
|
||||
{
|
||||
let tr = document.createElement("tr");
|
||||
this.#AppendCell(tr, "-", false);
|
||||
this.#AppendCell(tr, "-", false);
|
||||
this.#AppendCell(tr, "0", false);
|
||||
this.#AppendCell(tr, "-", false);
|
||||
this.#SetRowBackground(tr, "#ffecec");
|
||||
let tdDetails = this.#MakeDetailsCell();
|
||||
this.#SetDetailsPlaceholder(tdDetails, "No messages in this slot.", false);
|
||||
tr.appendChild(tdDetails);
|
||||
tbody.appendChild(tr);
|
||||
}
|
||||
else
|
||||
{
|
||||
let candidateRowCount = 0;
|
||||
for (const g of msgGroupList)
|
||||
{
|
||||
candidateRowCount += g.isActive ? 1 : 0;
|
||||
}
|
||||
|
||||
let tdDetails = this.#MakeDetailsCell();
|
||||
tdDetails.rowSpan = msgGroupList.length;
|
||||
this.#SetDetailsPlaceholder(tdDetails, "Click row to show/hide RX details.", true);
|
||||
let activeRowIdx = -1;
|
||||
let activeTr = null;
|
||||
|
||||
for (let idx = 0; idx < msgGroupList.length; ++idx)
|
||||
{
|
||||
let g = msgGroupList[idx];
|
||||
let tr = document.createElement("tr");
|
||||
tr.style.cursor = "pointer";
|
||||
let suppressClickForSelection = false;
|
||||
|
||||
this.#AppendCell(tr, g.msgTypeDisplay, false);
|
||||
this.#AppendCell(tr, g.msgDisplay, false);
|
||||
this.#AppendCell(tr, g.rxCount.toString(), false);
|
||||
this.#AppendCell(tr, g.rejectReason, false);
|
||||
let isSingleCandidateRow = candidateRowCount == 1 && g.isActive;
|
||||
this.#SetRowBackground(tr, isSingleCandidateRow ? "#ecffec" : "#ffecec");
|
||||
|
||||
if (idx == 0)
|
||||
{
|
||||
tr.appendChild(tdDetails);
|
||||
}
|
||||
|
||||
tr.addEventListener("mousedown", (e) => {
|
||||
if (e.detail > 1)
|
||||
{
|
||||
e.preventDefault();
|
||||
window.getSelection?.()?.removeAllRanges?.();
|
||||
suppressClickForSelection = false;
|
||||
return;
|
||||
}
|
||||
|
||||
suppressClickForSelection = this.#SelectionIntersectsNode(tr);
|
||||
});
|
||||
|
||||
tr.addEventListener("dblclick", (e) => {
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
tr.addEventListener("click", () => {
|
||||
if (suppressClickForSelection || this.#SelectionIntersectsNode(tr))
|
||||
{
|
||||
suppressClickForSelection = false;
|
||||
return;
|
||||
}
|
||||
|
||||
suppressClickForSelection = false;
|
||||
|
||||
if (activeTr)
|
||||
{
|
||||
this.#SetRowActiveStyle(activeTr, tdDetails, false);
|
||||
}
|
||||
|
||||
if (activeRowIdx == idx)
|
||||
{
|
||||
activeRowIdx = -1;
|
||||
activeTr = null;
|
||||
this.#SetDetailsPlaceholder(tdDetails, "Click row to show/hide RX details.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
activeRowIdx = idx;
|
||||
activeTr = tr;
|
||||
tdDetails.textContent = "";
|
||||
tdDetails.appendChild(this.#MakeRxDetailsUi(g.rxRecordList, slotIdx, g));
|
||||
this.#SetRowActiveStyle(activeTr, tdDetails, true);
|
||||
});
|
||||
|
||||
tbody.appendChild(tr);
|
||||
}
|
||||
}
|
||||
|
||||
table.appendChild(tbody);
|
||||
sectionBody.appendChild(table);
|
||||
|
||||
return sectionUi;
|
||||
}
|
||||
|
||||
#AppendCell(tr, text, isHeader)
|
||||
{
|
||||
let td = document.createElement(isHeader ? "th" : "td");
|
||||
td.textContent = text;
|
||||
td.style.border = "1px solid #999";
|
||||
td.style.padding = "3px 6px";
|
||||
td.style.verticalAlign = "top";
|
||||
td.style.whiteSpace = "normal";
|
||||
td.style.overflowWrap = "anywhere";
|
||||
if (isHeader)
|
||||
{
|
||||
td.style.backgroundColor = "#ddd";
|
||||
td.style.textAlign = "left";
|
||||
}
|
||||
tr.appendChild(td);
|
||||
}
|
||||
|
||||
#AppendColGroup(table)
|
||||
{
|
||||
// Keep a consistent width profile across every slot table.
|
||||
let cg = document.createElement("colgroup");
|
||||
for (const w of this.colWidthList)
|
||||
{
|
||||
let col = document.createElement("col");
|
||||
col.style.width = w;
|
||||
cg.appendChild(col);
|
||||
}
|
||||
table.appendChild(cg);
|
||||
}
|
||||
|
||||
#MakeDetailsCell()
|
||||
{
|
||||
let td = document.createElement("td");
|
||||
td.style.border = "1px solid #999";
|
||||
td.style.padding = "3px 6px";
|
||||
td.style.verticalAlign = "top";
|
||||
td.style.minWidth = "0";
|
||||
td.style.whiteSpace = "pre-wrap";
|
||||
td.style.fontFamily = "monospace";
|
||||
td.style.userSelect = "text";
|
||||
td.style.cursor = "text";
|
||||
|
||||
// Keep details text selectable/copyable without toggling row state.
|
||||
td.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
});
|
||||
return td;
|
||||
}
|
||||
|
||||
#SelectionIntersectsNode(node)
|
||||
{
|
||||
let selection = window.getSelection?.();
|
||||
if (!selection || selection.rangeCount == 0 || selection.isCollapsed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
let range = selection.getRangeAt(0);
|
||||
let commonAncestor = range.commonAncestorContainer;
|
||||
return node.contains(commonAncestor);
|
||||
}
|
||||
|
||||
#SetDetailsPlaceholder(td, text, useDim)
|
||||
{
|
||||
td.textContent = "";
|
||||
let span = document.createElement("span");
|
||||
span.textContent = text;
|
||||
if (useDim)
|
||||
{
|
||||
span.style.color = "#666";
|
||||
}
|
||||
td.appendChild(span);
|
||||
}
|
||||
|
||||
#SetRowBackground(tr, color)
|
||||
{
|
||||
for (const cell of tr.cells)
|
||||
{
|
||||
if (cell.tagName == "TD")
|
||||
{
|
||||
cell.style.backgroundColor = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#SetRowActiveStyle(tr, tdDetails, active)
|
||||
{
|
||||
// Reset row/detail cell active effect only (do not mutate border geometry).
|
||||
for (const cell of tr.cells)
|
||||
{
|
||||
if (cell.tagName == "TD")
|
||||
{
|
||||
cell.style.boxShadow = "none";
|
||||
}
|
||||
}
|
||||
tdDetails.style.boxShadow = "none";
|
||||
|
||||
if (!active)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Active row: emphasize inward (inset) so layout does not expand outward.
|
||||
for (let i = 0; i < tr.cells.length; ++i)
|
||||
{
|
||||
let cell = tr.cells[i];
|
||||
if (cell.tagName != "TD")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let insetTopBottom = "inset 0 2px 0 #444, inset 0 -2px 0 #444";
|
||||
let insetLeft = i == 0 ? ", inset 2px 0 0 #444" : "";
|
||||
cell.style.boxShadow = insetTopBottom + insetLeft;
|
||||
}
|
||||
|
||||
// Keep details visually tied to active row with inward emphasis.
|
||||
tdDetails.style.boxShadow = "inset 0 2px 0 #444, inset 0 -2px 0 #444, inset 2px 0 0 #444, inset -2px 0 0 #444";
|
||||
}
|
||||
|
||||
#GetMsgGroupList(msgList)
|
||||
{
|
||||
let keyToGroup = new Map();
|
||||
|
||||
for (const msg of msgList)
|
||||
{
|
||||
if (!msg)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let key = this.#GetMsgKey(msg);
|
||||
if (!keyToGroup.has(key))
|
||||
{
|
||||
keyToGroup.set(key, {
|
||||
msgTypeDisplay: this.#GetMsgTypeDisplay(msg),
|
||||
msgDisplay: this.#GetMsgDisplay(msg),
|
||||
rejectReason: this.#GetRejectReason(msg),
|
||||
isCandidate: false,
|
||||
isConfirmed: false,
|
||||
isActive: false,
|
||||
msgList: [],
|
||||
rxRecordList: [],
|
||||
});
|
||||
}
|
||||
|
||||
let g = keyToGroup.get(key);
|
||||
g.isCandidate = g.isCandidate || (msg.IsCandidate && msg.IsCandidate());
|
||||
g.isConfirmed = g.isConfirmed || (msg.IsConfirmed && msg.IsConfirmed());
|
||||
g.isActive = g.isActive || (msg.IsNotRejected && msg.IsNotRejected());
|
||||
g.msgList.push(msg);
|
||||
if (Array.isArray(msg.rxRecordList))
|
||||
{
|
||||
g.rxRecordList.push(...msg.rxRecordList);
|
||||
}
|
||||
}
|
||||
|
||||
let out = Array.from(keyToGroup.values());
|
||||
for (const g of out)
|
||||
{
|
||||
g.rxCount = g.rxRecordList.length;
|
||||
}
|
||||
|
||||
out.sort((a, b) => {
|
||||
if (b.rxCount != a.rxCount)
|
||||
{
|
||||
return b.rxCount - a.rxCount;
|
||||
}
|
||||
return a.msgDisplay.localeCompare(b.msgDisplay);
|
||||
});
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
#GetMsgKey(msg)
|
||||
{
|
||||
let f = msg.fields || {};
|
||||
let decodeType = msg.decodeDetails?.type || "";
|
||||
return JSON.stringify([
|
||||
msg.type || "",
|
||||
decodeType,
|
||||
f.callsign || "",
|
||||
f.grid4 || "",
|
||||
f.powerDbm || "",
|
||||
]);
|
||||
}
|
||||
|
||||
#GetMsgDisplay(msg)
|
||||
{
|
||||
let f = msg.fields || {};
|
||||
return `${f.callsign || ""} ${f.grid4 || ""} ${f.powerDbm || ""}`.trim();
|
||||
}
|
||||
|
||||
#GetMsgTypeDisplay(msg)
|
||||
{
|
||||
let type = msg.type || "";
|
||||
if (type == "telemetry")
|
||||
{
|
||||
let decodeType = msg.decodeDetails?.type || "?";
|
||||
if (decodeType == "extended")
|
||||
{
|
||||
let prettyType = msg.decodeDetails?.extended?.prettyType || "";
|
||||
if (prettyType != "")
|
||||
{
|
||||
return `telemetry/${prettyType}`;
|
||||
}
|
||||
}
|
||||
return `telemetry/${decodeType}`;
|
||||
}
|
||||
|
||||
if (type == "regular")
|
||||
{
|
||||
return "regular";
|
||||
}
|
||||
|
||||
return type || "-";
|
||||
}
|
||||
|
||||
#GetRejectReason(msg)
|
||||
{
|
||||
let reason = "";
|
||||
|
||||
if (msg.IsConfirmed && msg.IsConfirmed())
|
||||
{
|
||||
reason = "Confirmed";
|
||||
}
|
||||
else if (msg.IsCandidate && msg.IsCandidate())
|
||||
{
|
||||
reason = "Candidate";
|
||||
}
|
||||
else
|
||||
{
|
||||
let audit = msg.candidateFilterAuditList?.[0];
|
||||
if (!audit)
|
||||
{
|
||||
reason = "Rejected";
|
||||
}
|
||||
else if (audit.note)
|
||||
{
|
||||
reason = `${audit.type || "Rejected"}: ${audit.note}`;
|
||||
}
|
||||
else
|
||||
{
|
||||
reason = audit.type || "Rejected";
|
||||
}
|
||||
}
|
||||
|
||||
if (this.#IsFingerprintReferenceMsg(msg))
|
||||
{
|
||||
reason += reason != "" ? " (frequency reference)" : "Frequency reference";
|
||||
}
|
||||
|
||||
return reason;
|
||||
}
|
||||
|
||||
#IsFingerprintReferenceMsg(msg)
|
||||
{
|
||||
let referenceMsg = this.data.wsprSearch
|
||||
?.time__windowData
|
||||
?.get?.(this.data.dt?.GetRowMetaData(this.data.rowIdx)?.time)
|
||||
?.fingerprintingData
|
||||
?.referenceAudit
|
||||
?.referenceMsg;
|
||||
|
||||
return referenceMsg === msg;
|
||||
}
|
||||
|
||||
#MakeRxDetailsUi(rxRecordList, slotIdx, msgGroup)
|
||||
{
|
||||
let expectedFreqHz = this.#GetExpectedFreqHz();
|
||||
let rowList = [];
|
||||
for (const rx of rxRecordList)
|
||||
{
|
||||
let freq = "";
|
||||
let freqSort = Number.NaN;
|
||||
let offset = null;
|
||||
if (rx?.frequency != undefined && rx?.frequency !== "")
|
||||
{
|
||||
let n = Number(rx.frequency);
|
||||
if (Number.isFinite(n))
|
||||
{
|
||||
freqSort = n;
|
||||
freq = n.toLocaleString("en-US");
|
||||
if (expectedFreqHz != null)
|
||||
{
|
||||
offset = Math.round(n - expectedFreqHz);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
freq = `${rx.frequency}`;
|
||||
}
|
||||
}
|
||||
|
||||
rowList.push({
|
||||
rxRecord: rx,
|
||||
station: rx?.rxCallsign ? rx.rxCallsign : "",
|
||||
freq: freq,
|
||||
freqSort: freqSort,
|
||||
offset: offset,
|
||||
});
|
||||
}
|
||||
|
||||
rowList.sort((a, b) => {
|
||||
let aOffsetValid = a.offset != null;
|
||||
let bOffsetValid = b.offset != null;
|
||||
if (aOffsetValid && bOffsetValid && a.offset != b.offset)
|
||||
{
|
||||
return b.offset - a.offset;
|
||||
}
|
||||
if (aOffsetValid != bOffsetValid)
|
||||
{
|
||||
return aOffsetValid ? -1 : 1;
|
||||
}
|
||||
|
||||
let c1 = a.station.localeCompare(b.station);
|
||||
if (c1 != 0) { return c1; }
|
||||
|
||||
let aFreqValid = Number.isFinite(a.freqSort);
|
||||
let bFreqValid = Number.isFinite(b.freqSort);
|
||||
if (aFreqValid && bFreqValid && a.freqSort != b.freqSort)
|
||||
{
|
||||
return a.freqSort - b.freqSort;
|
||||
}
|
||||
|
||||
return a.freq.localeCompare(b.freq);
|
||||
});
|
||||
|
||||
let hdr = {
|
||||
station: "RX Station",
|
||||
freq: "RX Freq",
|
||||
offset: "+/-LaneHz",
|
||||
};
|
||||
|
||||
let wStation = hdr.station.length;
|
||||
let wFreq = hdr.freq.length;
|
||||
let wOffsetValue = 3;
|
||||
for (const r of rowList)
|
||||
{
|
||||
wStation = Math.max(wStation, r.station.length);
|
||||
wFreq = Math.max(wFreq, r.freq.length);
|
||||
if (r.offset != null)
|
||||
{
|
||||
wOffsetValue = Math.max(wOffsetValue, Math.abs(r.offset).toString().length);
|
||||
}
|
||||
}
|
||||
let wOffset = Math.max(hdr.offset.length, 1 + wOffsetValue);
|
||||
|
||||
let wrapper = document.createElement("div");
|
||||
wrapper.style.whiteSpace = "pre";
|
||||
wrapper.style.userSelect = "text";
|
||||
wrapper.style.fontFamily = "monospace";
|
||||
wrapper.style.fontSize = "12px";
|
||||
|
||||
let makeLine = (station, freq, offset) =>
|
||||
`${station.padEnd(wStation)} ${freq.padStart(wFreq)} ${offset.padStart(wOffset)}`;
|
||||
|
||||
let headerLine = this.#MakeDetailsTextRow(makeLine(hdr.station, hdr.freq, hdr.offset), false);
|
||||
wrapper.appendChild(headerLine);
|
||||
wrapper.appendChild(this.#MakeDetailsTextRow(makeLine("-".repeat(wStation), "-".repeat(wFreq), "-".repeat(wOffset)), false));
|
||||
|
||||
if (rowList.length == 0)
|
||||
{
|
||||
wrapper.appendChild(this.#MakeDetailsTextRow("(no receiving stations)", false));
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const r of rowList)
|
||||
{
|
||||
let line = makeLine(r.station, r.freq, this.#FormatLaneOffset(r.offset, wOffsetValue));
|
||||
let rowNode = this.#MakeDetailsTextRow(line, true);
|
||||
|
||||
this.#RegisterRxRecordNode(r.rxRecord, rowNode);
|
||||
rowNode.addEventListener("mouseenter", () => {
|
||||
this.#ApplyFingerprintHover(slotIdx, msgGroup, r.rxRecord);
|
||||
});
|
||||
rowNode.addEventListener("mouseleave", () => {
|
||||
this.#ClearFingerprintHover();
|
||||
});
|
||||
|
||||
wrapper.appendChild(rowNode);
|
||||
}
|
||||
}
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
#GetExpectedFreqHz()
|
||||
{
|
||||
let band = this.data.band || "";
|
||||
let channel = this.data.channel;
|
||||
if (band == "" || channel == "" || channel == undefined)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
let channelDetails = WSPR.GetChannelDetails(band, channel);
|
||||
if (!channelDetails || !Number.isFinite(channelDetails.freq))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return channelDetails.freq;
|
||||
}
|
||||
|
||||
#FormatLaneOffset(offset, width)
|
||||
{
|
||||
if (offset == null)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
let sign = " ";
|
||||
if (offset > 0)
|
||||
{
|
||||
sign = "+";
|
||||
}
|
||||
else if (offset < 0)
|
||||
{
|
||||
sign = "-";
|
||||
}
|
||||
|
||||
return `${sign}${Math.abs(offset).toString().padStart(width)}`;
|
||||
}
|
||||
|
||||
#MakeDetailsTextRow(text, interactive)
|
||||
{
|
||||
let row = document.createElement("div");
|
||||
row.textContent = text;
|
||||
row.style.whiteSpace = "pre";
|
||||
row.style.cursor = interactive ? "default" : "text";
|
||||
return row;
|
||||
}
|
||||
|
||||
#RegisterRxRecordNode(rxRecord, node)
|
||||
{
|
||||
if (!this.rxRecord__nodeSet.has(rxRecord))
|
||||
{
|
||||
this.rxRecord__nodeSet.set(rxRecord, new Set());
|
||||
}
|
||||
|
||||
this.rxRecord__nodeSet.get(rxRecord).add(node);
|
||||
}
|
||||
|
||||
#ApplyFingerprintHover(slotIdx, msgGroup, rxRecord)
|
||||
{
|
||||
this.#ClearFingerprintHover();
|
||||
|
||||
let rxRecordSet = this.#GetFingerprintHoverRxRecordSet(slotIdx, msgGroup, rxRecord);
|
||||
if (rxRecordSet.size == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (const rxRecordHighlighted of rxRecordSet)
|
||||
{
|
||||
let nodeSet = this.rxRecord__nodeSet.get(rxRecordHighlighted);
|
||||
if (!nodeSet)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const node of nodeSet)
|
||||
{
|
||||
node.style.backgroundColor = "#eefbe7";
|
||||
this.rxRecordHighlightNodeSet.add(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ClearFingerprintHover()
|
||||
{
|
||||
for (const node of this.rxRecordHighlightNodeSet)
|
||||
{
|
||||
node.style.backgroundColor = "";
|
||||
}
|
||||
this.rxRecordHighlightNodeSet.clear();
|
||||
}
|
||||
|
||||
#GetFingerprintHoverRxRecordSet(slotIdx, msgGroup, rxRecord)
|
||||
{
|
||||
let rxRecordSet = new Set();
|
||||
let referenceAudit = this.data.wsprSearch
|
||||
?.time__windowData
|
||||
?.get?.(this.data.dt?.GetRowMetaData(this.data.rowIdx)?.time)
|
||||
?.fingerprintingData
|
||||
?.referenceAudit;
|
||||
|
||||
if (!referenceAudit)
|
||||
{
|
||||
return rxRecordSet;
|
||||
}
|
||||
|
||||
if (slotIdx == referenceAudit.referenceSlot)
|
||||
{
|
||||
for (const slotAudit of referenceAudit.slotAuditList)
|
||||
{
|
||||
if (!slotAudit || slotAudit.slot == referenceAudit.referenceSlot)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
this.#AddFingerprintMatchesForRecord(rxRecordSet, slotAudit.msgAuditList, rxRecord, "rxRecordListA");
|
||||
}
|
||||
|
||||
return rxRecordSet;
|
||||
}
|
||||
|
||||
let slotAudit = referenceAudit.slotAuditList[slotIdx];
|
||||
if (!slotAudit)
|
||||
{
|
||||
return rxRecordSet;
|
||||
}
|
||||
|
||||
let msgSet = new Set(msgGroup.msgList || []);
|
||||
let msgAuditList = (slotAudit.msgAuditList || []).filter(msgAudit => msgSet.has(msgAudit.msg));
|
||||
this.#AddFingerprintMatchesForRecord(rxRecordSet, msgAuditList, rxRecord, "rxRecordListB");
|
||||
|
||||
return rxRecordSet;
|
||||
}
|
||||
|
||||
#AddFingerprintMatchesForRecord(rxRecordSet, msgAuditList, rxRecord, primaryListName)
|
||||
{
|
||||
for (const msgAudit of msgAuditList || [])
|
||||
{
|
||||
for (const rxCallMatch of msgAudit.rxCallMatchList || [])
|
||||
{
|
||||
let primaryList = rxCallMatch[primaryListName] || [];
|
||||
if (!primaryList.includes(rxRecord))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const rxRecordA of rxCallMatch.rxRecordListA || [])
|
||||
{
|
||||
rxRecordSet.add(rxRecordA);
|
||||
}
|
||||
for (const rxRecordB of rxCallMatch.rxRecordListB || [])
|
||||
{
|
||||
rxRecordSet.add(rxRecordB);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user