791 lines
23 KiB
JavaScript
791 lines
23 KiB
JavaScript
/*
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|