From 0d446c593918a70dd25a9037cd6348a55bce0632 Mon Sep 17 00:00:00 2001
From: Henrik <15855905+high3eam@users.noreply.github.com>
Date: Fri, 11 Jul 2025 08:27:40 +0200
Subject: [PATCH] Create custom.js
---
custom.js | 890 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 890 insertions(+)
create mode 100644 custom.js
diff --git a/custom.js b/custom.js
new file mode 100644
index 0000000..367f4a6
--- /dev/null
+++ b/custom.js
@@ -0,0 +1,890 @@
+(function() {
+
+ if (!localStorage.getItem('disable_advanced_search')) {
+ function addAdvancedSearchLink() {
+ let link = document.createElement("a");
+ link.href = "/library/advancedsearch";
+ link.target = "_blank";
+ link.textContent = "Advanced Search";
+ link.className = "multiellipsis my-2 flex h-[150px] overflow-hidden p-2 text-center text-lg focus:outline-none focus:ring focus:ring-gray-400 border-2 border-solid border-black bg-white text-black";
+ link.setAttribute("q:link", "");
+ link.setAttribute("data-prefetch", "");
+ link.style.display = "inline-block";
+ link.style.maxWidth = "200px";
+ link.style.height = "auto";
+ link.style.marginRight = "10px";
+
+ let targetDiv = document.querySelector("body > div > main > div.flex.flex-1.flex-col > div.mx-auto.w-full.max-w-7xl > div.relative.my-2.flex.flex-col");
+ if (targetDiv) {
+ targetDiv.style.flexDirection = "row";
+ targetDiv.style.alignItems = "center";
+ let inputField = targetDiv.querySelector('input[id^="text-search-rs70xc-"]');
+ if (inputField) {
+ inputField.style.flex = "1";
+ inputField.style.width = "auto";
+ targetDiv.insertBefore(link, inputField);
+ }
+ let searchIcon = targetDiv.querySelector("svg.feather.feather-search");
+ if (searchIcon) {
+ searchIcon.remove();
+ }
+ }
+ }
+
+ function checkAndAddLink(mutations, observer) {
+ let targetDiv = document.querySelector("body > div > main > div.flex.flex-1.flex-col > div.mx-auto.w-full.max-w-7xl > div.relative.my-2.flex.flex-col");
+ if (targetDiv) {
+ let advancedSearchExists = targetDiv.querySelector('a[href="/library/advancedsearch"]');
+ if (!advancedSearchExists) {
+ addAdvancedSearchLink();
+ observer.disconnect();
+ }
+ }
+ }
+
+ function initObserver() {
+ if (document.body) {
+ const observer = new MutationObserver(checkAndAddLink);
+ observer.observe(document.body, {
+ childList: true,
+ subtree: true
+ });
+ checkAndAddLink(null, observer);
+ } else {
+ setTimeout(initObserver, 100);
+ }
+ }
+
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', initObserver);
+ } else {
+ initObserver();
+ }
+ }
+
+ if (!localStorage.getItem('disable_uecaps_parsing')) {
+ function createFeaturesSection() {
+ const targetElement = document.querySelector("body > div > main > div.flex.flex-1.flex-col > div.flex.flex-1.flex-col > div:nth-child(2)");
+ if (targetElement && !document.getElementById('featuresSection')) {
+ const featuresSection = document.createElement('div');
+ featuresSection.id = 'featuresSection';
+ featuresSection.className = 'mx-auto w-full max-w-7xl overflow-x-auto';
+ featuresSection.innerHTML = `
+
+ `;
+ targetElement.insertAdjacentElement('afterend', featuresSection);
+
+ const toggleButton = document.getElementById('toggleRowVisibilityButton');
+ toggleButton.addEventListener('click', toggleRowVisibility);
+ }
+ }
+
+ function toggleRowVisibility() {
+ const tableBody = document.getElementById('featuresTableBody');
+ const button = document.getElementById('toggleRowVisibilityButton');
+
+ if (tableBody && button) {
+ const rows = tableBody.querySelectorAll("tr");
+ const currentState = button.getAttribute("data-state");
+ let newState;
+
+ switch (currentState) {
+ case "all":
+ newState = "unsupported";
+ button.textContent = "Show supported";
+ break;
+ case "unsupported":
+ newState = "supported";
+ button.textContent = "Show all";
+ break;
+ case "supported":
+ newState = "all";
+ button.textContent = "Show unsupported";
+ break;
+ }
+
+ button.setAttribute("data-state", newState);
+
+ rows.forEach(row => {
+ const cell = row.querySelector("td");
+ if (cell) {
+ const cellText = cell.textContent.trim();
+ if (cell.id.startsWith("custom-cell-")) {
+ switch (newState) {
+ case "all":
+ row.style.display = "table-row";
+ break;
+ case "unsupported":
+ row.style.display = cellText.startsWith("❌") ? "table-row" : "none";
+ break;
+ case "supported":
+ row.style.display = cellText.startsWith("✅") || cellText.startsWith("ℹ️") ? "table-row" : "none";
+ break;
+ }
+ } else {
+ row.style.display = "table-row";
+ }
+ }
+ });
+ }
+ }
+
+ function addTableRow(content, headerText = "Lowband EN-DC Combos") {
+ const tableBody = document.getElementById('featuresTableBody');
+ if (tableBody) {
+ const sanitizedHeaderText = headerText.replace(/[^a-z0-9]/gi, '-').toLowerCase();
+ let newRow = document.querySelector(`#custom-row-${sanitizedHeaderText}`);
+ let newCell;
+ if (!newRow) {
+ newRow = document.createElement("tr");
+ newRow.id = `custom-row-${sanitizedHeaderText}`;
+
+ const newHeader = document.createElement("th");
+ newHeader.className = "border-collapse border border-gray-500 p-1.5";
+ newHeader.style.color = "dodgerblue";
+ newHeader.textContent = headerText;
+ newRow.appendChild(newHeader);
+
+ newCell = document.createElement("td");
+ newCell.id = `custom-cell-${sanitizedHeaderText}`;
+ newCell.className = "border-collapse border border-gray-500 p-1.5";
+ newCell.style.whiteSpace = 'break-spaces';
+ if (content.startsWith("Log misses")) {
+ newCell.style.color = "darkorange";
+ }
+ if (content.startsWith("❌")) {
+ newCell.style.color = "red";
+ }
+ newCell.textContent = content;
+ newRow.appendChild(newCell);
+
+ tableBody.appendChild(newRow);
+ } else {
+ newCell = document.querySelector(`#custom-cell-${sanitizedHeaderText}`);
+ if (newCell.textContent === "No Lowband EN-DC Support" || newCell.textContent === "No NR-SA UL-MIMO Support" || newCell.textContent === "No NR-SA UL-CA Support" || newCell.textContent === "Log misses NR-CA capabilities." || newCell.textContent === "Log misses EN-DC capabilities." || newCell.textContent === "Log misses LTE-CA capabilities.") {
+ newCell.textContent = content;
+ } else {
+ newCell.textContent += `, ${content}`;
+ }
+ newCell.style.whiteSpace = 'break-spaces';
+ }
+ } else {
+ console.error('Table body element not found');
+ }
+ }
+
+ function waitForElement(selector, callback) {
+ const interval = setInterval(() => {
+ const element = document.getElementById('featuresTableBody');
+ if (element) {
+ clearInterval(interval);
+ callback(element);
+ }
+ }, 500);
+ }
+
+ function compareModulation(current, newMod) {
+ const modOrder = {
+ 'qam16': 1,
+ 'qam64': 2,
+ 'qam256': 3,
+ 'qam1024': 4
+ };
+
+ if (!current) return newMod;
+ if (!newMod) return current;
+
+ return modOrder[newMod.toLowerCase()] > modOrder[current.toLowerCase()] ? newMod : current;
+ }
+
+ function formatModulation(modString) {
+ if (!modString) return '';
+ return modString.toLowerCase().replace('qam', '').trim() + 'QAM';
+ }
+
+ function generateCombinations(arr) {
+ const combinations = [];
+ for (let i = 0; i < arr.length; i++) {
+ for (let j = i + 1; j < arr.length; j++) {
+ combinations.push([arr[i], arr[j]]);
+ }
+ }
+ return combinations;
+ }
+
+ function generateEndcCombinations(lteBands, nrBands) {
+ const combinations = [];
+ lteBands.forEach(lteBand => {
+ nrBands.forEach(nrBand => {
+ combinations.push({
+ lteBand: lteBand,
+ nrBand: nrBand
+ });
+ });
+ });
+ return combinations;
+ }
+
+ function sortCombinations(combinations) {
+ return combinations.sort((a, b) => {
+ const extractNumbers = (combo) => {
+ return combo.match(/\d+/g).map(Number);
+ };
+
+ const numsA = extractNumbers(a);
+ const numsB = extractNumbers(b);
+
+ if (numsA[0] !== numsB[0]) {
+ return numsA[0] - numsB[0];
+ }
+ return numsA[1] - numsB[1];
+ });
+ }
+
+ const lteLowbands = [5, 6, 8, 12, 13, 14, 17, 18, 19, 20, 26, 27, 28, 29, 31, 44, 67, 68, 71, 72, 73, 85, 87, 88, 103, 106, 107, 108];
+ const nrLowbands = [5, 8, 12, 13, 14, 18, 20, 26, 28, 29, 31, 67, 71, 72, 81, 82, 83, 85, 89, 91, 92, 93, 94, 100, 105, 106, 109];
+
+ const lteCombosToCheck = generateCombinations(lteLowbands);
+ const nrCombosToCheck = generateCombinations(nrLowbands);
+
+ const combinationsToCheck = generateEndcCombinations(lteLowbands, nrLowbands);
+
+ function checkCapabilities(data, isMultiOutput = true) {
+ const loggedCombinations = new Set();
+ const lteLowbandsToCheck = [5, 6, 8, 12, 13, 14, 17, 18, 19, 20, 26, 27, 28, 29, 31, 44, 67, 68, 71, 72, 73, 85, 87, 88, 103, 106, 107, 108];
+ const nrLowbandsToCheck = [5, 8, 12, 13, 14, 18, 20, 26, 28, 29, 31, 67, 71, 72, 81, 82, 83, 85, 89, 91, 92, 93, 94, 100, 105, 106, 109];
+ const nrBandsToCheck = [1, 2, 3, 5, 7, 8, 20, 25, 28, 30, 66, 71, 75];
+ const nrMmwaveBandsToCheck = [257, 258, 259, 260, 261, 262];
+ const nrMmwaveBandsExclude = new Set([257, 258, 259, 260, 261, 262]);
+ const nrTddBands = new Set([34, 38, 39, 40, 41, 46, 47, 48, 50, 53, 77, 78, 79, 90, 96, 101, 102, 104]);
+ const nrFddBands = new Set([1, 2, 3, 5, 7, 8, 12, 13, 14, 18, 20, 24, 25, 26, 28, 30, 31, 65, 66, 70, 71, 72, 74, 85, 91, 92, 93, 94, 100, 105, 106, 109]);
+ const nrSulBandsToCheck = [80, 81, 82, 83, 84, 86, 89, 95, 97, 98, 99];
+ const nrMmwaveBandwidths = new Set();
+ const validMmwaveBandwidths = [50, 100, 200, 400, 800, 1600, 2000];
+
+ let foundLowbandSupport = false;
+ let foundLteCaSupport = false;
+ let foundNrCaSupport = false;
+ let foundNrMmwaveSupport = false;
+ let foundNrTxSwitchingSupport = false;
+ let nrUlmimoBands = new Set();
+ let nrUlcaCombos = new Set();
+ let lteCaCombos = new Set();
+ let nrCaCombos = new Set();
+ let nrMmwaveBands = new Set();
+ let nrTxSwitchingCombos = new Set();
+ let foundNrUlMimo = false;
+ let foundNrUlCa = false;
+ let hasNrca = false;
+ let hasEndc = false;
+ let hasNrdc = false;
+ let lte4RxBands = new Set();
+ let nr4RxBands = new Set();
+ let foundLte4Rx = false;
+ let foundNr4Rx = false;
+ let nr6RxBands = new Set();
+ let foundNr6Rx = false;
+ let nrMaxBwSupport = {};
+ let foundNrMaxBwSupport = false;
+ let foundNrUlMimoInEndc = false;
+ let nrUlmimoBandsEndc = new Set();
+ let maxNrTddBandwidth = 0;
+ let maxNrFddBandwidth = 0;
+ let maxTddBandwidth = {
+ 2: 0,
+ 3: 0,
+ 4: 0,
+ 5: 0,
+ 6: 0,
+ 7: 0,
+ 8: 0,
+ 9: 0,
+ 10: 0
+ };
+ let maxSupportedBands = 0;
+ let maxLteMimo = 0;
+ let maxNrMimo = 0;
+ let nrSulBandsSupported = new Set();
+ let maxSupportedComboSize = 0;
+ let has100MHzOrMoreInHigherCombo = false;
+ let maxLteCaMimoStreams = 0;
+ let maxEndcMimoStreams = 0;
+ let maxNrCaMimoStreams = 0;
+ let maxLteDlModulation = '';
+ let maxLteUlModulation = '';
+ let maxNrDlModulation = '';
+ let maxNrUlModulation = '';
+ let maxNrFr2DlModulation = '';
+ let maxNrFr2UlModulation = '';
+ let hasLteCaData = false;
+ let hasFoundLteCaCombos = false;
+
+ function checkMmwaveBandwidth(component) {
+ if (nrMmwaveBandsToCheck.includes(component.band) && component.maxBwDl && component.maxBwDl.value) {
+ const bandwidth = component.maxBwDl.value;
+ if (validMmwaveBandwidths.includes(bandwidth)) {
+ nrMmwaveBandwidths.add(bandwidth);
+ }
+ }
+ }
+
+ function calculateMimoStreams(component) {
+ if (component.mimoDl && component.mimoDl.value && !isNaN(component.mimoDl.value)) {
+ let multiplier = 1;
+ if (component.bwClassDl === 'B' || component.bwClassDl === 'C') {
+ multiplier = 2;
+ } else if (component.bwClassDl === 'D') {
+ multiplier = 3;
+ } else if (component.bwClassDl === 'E') {
+ multiplier = 4;
+ }
+ return component.mimoDl.value * multiplier;
+ }
+ return 0;
+ }
+
+ function updateMaxMimo(mimoValue, currentMax) {
+ if (typeof mimoValue === 'number' && !isNaN(mimoValue)) {
+ return Math.max(currentMax, mimoValue);
+ }
+ return currentMax;
+ }
+
+ function calculateBandwidth(component) {
+ if (!component.maxBwDl) return 0;
+
+ let bandwidth = 0;
+ const multiplier = {
+ 'A': 1,
+ 'B': 2,
+ 'C': 2,
+ 'D': 3,
+ 'E': 4
+ };
+
+ if (component.maxBwDl.type === "single") {
+ bandwidth = component.maxBwDl.value * (multiplier[component.bwClassDl] || 1);
+ } else if (component.maxBwDl.type === "mixed" && Array.isArray(component.maxBwDl.value)) {
+ bandwidth = component.maxBwDl.value.reduce((sum, bw) => sum + bw, 0);
+ }
+
+ return Math.min(bandwidth, 400);
+ }
+
+ function calculateVirtualBands(component) {
+ const multiplier = {
+ 'A': 1,
+ 'B': 2,
+ 'C': 2,
+ 'D': 3,
+ 'E': 4
+ };
+ return multiplier[component.bwClassDl] || 1;
+ }
+
+ const capabilitiesList = isMultiOutput ? data.capabilitiesList : [data];
+
+ if (capabilitiesList && Array.isArray(capabilitiesList)) {
+ capabilitiesList.forEach(capability => {
+ if (capability.endc && Array.isArray(capability.endc)) {
+ hasEndc = true;
+ capability.endc.forEach(endcItem => {
+
+ let totalMimoStreams = 0;
+
+ if (endcItem.componentsLte && Array.isArray(endcItem.componentsLte)) {
+
+ totalMimoStreams += endcItem.componentsLte.reduce((sum, component) =>
+ sum + calculateMimoStreams(component), 0);
+
+ endcItem.componentsLte.forEach(component => {
+ if (lteLowbandsToCheck.includes(component.band) && component.mimoDl && component.mimoDl.value === 4) {
+ lte4RxBands.add(component.band);
+ foundLte4Rx = true;
+ }
+ });
+
+ lteCombosToCheck.forEach(combo => {
+ const [band1, band2] = combo;
+ const hasBand1 = endcItem.componentsLte.some(component =>
+ component.band === band1 && component.bwClassDl === "A");
+ const hasBand2 = endcItem.componentsLte.some(component =>
+ component.band === band2 && component.bwClassDl === "A");
+
+ if (hasBand1 && hasBand2) {
+ const comboString = `${band1}+${band2}`;
+ if (!lteCaCombos.has(comboString)) {
+ lteCaCombos.add(comboString);
+ hasFoundLteCaCombos = true;
+ }
+ }
+ });
+
+ }
+
+ if (endcItem.componentsNr && Array.isArray(endcItem.componentsNr)) {
+
+ endcItem.componentsNr.forEach(checkMmwaveBandwidth);
+
+ totalMimoStreams += endcItem.componentsNr.reduce((sum, component) =>
+ sum + calculateMimoStreams(component), 0);
+
+ endcItem.componentsNr.forEach(component => {
+
+ if (nrMmwaveBandsToCheck.includes(component.band)) {
+ if (component.modulationDl && component.modulationDl.value) {
+ maxNrFr2DlModulation = compareModulation(maxNrFr2DlModulation,
+ component.modulationDl.value);
+ }
+ if (component.modulationUl && component.modulationUl.value) {
+ maxNrFr2UlModulation = compareModulation(maxNrFr2UlModulation,
+ component.modulationUl.value);
+ }
+ }
+
+ if (nrSulBandsToCheck.includes(component.band)) {
+ nrSulBandsSupported.add(component.band);
+ }
+
+ if (component.mimoDl && component.mimoDl.value) {
+ maxNrMimo = updateMaxMimo(component.mimoDl.value, maxNrMimo);
+ }
+
+ if (component.mimoUl && component.mimoUl.value === 2) {
+ nrUlmimoBandsEndc.add(`n${component.band}`);
+ foundNrUlMimoInEndc = true;
+ }
+
+ if (component.modulationDl && component.modulationDl.value) {
+ maxNrDlModulation = compareModulation(maxNrDlModulation, component.modulationDl.value);
+ }
+ if (component.modulationUl && component.modulationUl.value) {
+ maxNrUlModulation = compareModulation(maxNrUlModulation, component.modulationUl.value);
+ }
+
+ });
+ }
+
+ maxEndcMimoStreams = Math.max(maxEndcMimoStreams, totalMimoStreams);
+
+ combinationsToCheck.forEach(combination => {
+ const {
+ lteBand,
+ nrBand
+ } = combination;
+
+ const lteComponent = endcItem.componentsLte && endcItem.componentsLte.find(component => component.band === lteBand && component.bwClassUl === "A");
+ const nrComponent = endcItem.componentsNr && endcItem.componentsNr.find(component => component.band === nrBand && component.bwClassUl === "A");
+
+ if (lteComponent && nrComponent) {
+ const combinationString = `${lteBand}_n${nrBand}`;
+ if (!loggedCombinations.has(combinationString)) {
+ loggedCombinations.add(combinationString);
+ foundLowbandSupport = true;
+ }
+ }
+ });
+
+ nrLowbandsToCheck.forEach(band => {
+ const nr4RxComponent = endcItem.componentsNr && endcItem.componentsNr.find(component => component.band === band && component.mimoDl && component.mimoDl.value === 4);
+ if (nr4RxComponent) {
+ nr4RxBands.add(band);
+ foundNr4Rx = true;
+ }
+ });
+
+ [...nrTddBands, ...nrFddBands].forEach(band => {
+ const nr6RxComponent = endcItem.componentsNr && endcItem.componentsNr.find(component => component.band === band && component.mimoDl && component.mimoDl.value === 6);
+ if (nr6RxComponent) {
+ nr6RxBands.add(band);
+ foundNr6Rx = true;
+ }
+ });
+
+ nrBandsToCheck.forEach(band => {
+ const nrComponent = endcItem.componentsNr && endcItem.componentsNr.find(component => component.band === band && component.maxBwDl && component.maxBwDl.value > 20);
+ if (nrComponent) {
+ if (!nrMaxBwSupport[band] || nrMaxBwSupport[band] < nrComponent.maxBwDl.value) {
+ nrMaxBwSupport[band] = nrComponent.maxBwDl.value;
+ foundNrMaxBwSupport = true;
+ }
+ }
+ });
+
+ nrMmwaveBandsToCheck.forEach(band => {
+ const nrMmwaveComponent = endcItem.componentsNr && endcItem.componentsNr.find(component => component.band === band);
+ if (nrMmwaveComponent) {
+ nrMmwaveBands.add(`n${band}`);
+ foundNrMmwaveSupport = true;
+ }
+ });
+ });
+ }
+
+ if (capability.nrca && Array.isArray(capability.nrca)) {
+ hasNrca = true;
+ capability.nrca.forEach(nrcaItem => {
+ if (nrcaItem.components && Array.isArray(nrcaItem.components)) {
+ let ulMimoBandsInComponent = new Set();
+ let ulCaBandsInComponent = [];
+
+ let totalFddBandwidth = 0;
+ let hasFddBand = false;
+ let totalTddBandwidth = 0;
+ let totalVirtualBands = 0;
+ let hasTddBand = false;
+ let tddBandwidths = [];
+
+ nrcaItem.components.forEach(checkMmwaveBandwidth);
+
+ const totalMimoStreams = nrcaItem.components.reduce((sum, component) =>
+ sum + calculateMimoStreams(component), 0);
+ maxNrCaMimoStreams = Math.max(maxNrCaMimoStreams, totalMimoStreams);
+
+ nrcaItem.components.forEach(component => {
+
+ if (nrSulBandsToCheck.includes(component.band)) {
+ nrSulBandsSupported.add(component.band);
+ }
+
+ if (component.mimoDl && component.mimoDl.value) {
+ maxNrMimo = updateMaxMimo(component.mimoDl.value, maxNrMimo);
+ }
+
+ if (nrTddBands.has(component.band)) {
+ hasTddBand = true;
+ const bandwidth = calculateBandwidth(component);
+ totalTddBandwidth += bandwidth;
+ tddBandwidths.push(bandwidth);
+ totalVirtualBands += calculateVirtualBands(component);
+ } else if (nrFddBands.has(component.band)) {
+ hasFddBand = true;
+ const bandwidth = calculateBandwidth(component);
+ totalFddBandwidth += bandwidth;
+ totalVirtualBands += calculateVirtualBands(component);
+ }
+
+ if (component.modulationDl && component.modulationDl.value) {
+ maxNrDlModulation = compareModulation(maxNrDlModulation, component.modulationDl.value);
+ }
+ if (component.modulationUl && component.modulationUl.value) {
+ maxNrUlModulation = compareModulation(maxNrUlModulation, component.modulationUl.value);
+ }
+
+ });
+
+ if (hasTddBand) {
+ maxNrTddBandwidth = Math.max(maxNrTddBandwidth, totalTddBandwidth);
+ }
+ if (hasFddBand) {
+ maxNrFddBandwidth = Math.max(maxNrFddBandwidth, totalFddBandwidth);
+ }
+
+ if (hasTddBand && hasFddBand) {
+ const comboSize = totalVirtualBands;
+ if (comboSize >= 2 && comboSize <= 10) {
+ maxTddBandwidth[comboSize] = Math.max(
+ maxTddBandwidth[comboSize],
+ totalTddBandwidth
+ );
+ maxSupportedComboSize = Math.max(maxSupportedComboSize, comboSize);
+
+ if (comboSize > 2 && totalTddBandwidth >= 100) {
+ has100MHzOrMoreInHigherCombo = true;
+ }
+ }
+ }
+
+ nrcaItem.components.forEach(component => {
+ if (component.mimoUl && component.mimoUl.value === 2) {
+ nrUlmimoBands.add(`n${component.band}`);
+ foundNrUlMimo = true;
+ }
+
+ if (component.bwClassUl) {
+ if (component.bwClassUl === 'A') {
+ ulCaBandsInComponent.push(`n${component.band}`);
+ } else {
+ const bandConfig = `n${component.band}${component.bwClassUl}`;
+ ulCaBandsInComponent.push(bandConfig);
+ if (!nrUlcaCombos.has(bandConfig)) {
+ nrUlcaCombos.add(bandConfig);
+ foundNrUlCa = true;
+ }
+ }
+ }
+
+ if (nrLowbandsToCheck.includes(component.band) && component.mimoDl && component.mimoDl.value === 4) {
+ nr4RxBands.add(component.band);
+ foundNr4Rx = true;
+ }
+
+ if ((nrTddBands.has(component.band) || nrFddBands.has(component.band)) && component.mimoDl && component.mimoDl.value === 6) {
+ nr6RxBands.add(component.band);
+ foundNr6Rx = true;
+ }
+
+ if (nrBandsToCheck.includes(component.band) && component.maxBwDl && component.maxBwDl.value > 20) {
+ if (!nrMaxBwSupport[component.band] || nrMaxBwSupport[component.band] < component.maxBwDl.value) {
+ nrMaxBwSupport[component.band] = component.maxBwDl.value;
+ foundNrMaxBwSupport = true;
+ }
+ }
+
+ if (nrMmwaveBandsToCheck.includes(component.band)) {
+ nrMmwaveBands.add(`n${component.band}`);
+ foundNrMmwaveSupport = true;
+ }
+ });
+
+ if (ulCaBandsInComponent.length > 1) {
+ const combo = ulCaBandsInComponent.join('+');
+ if (!nrUlcaCombos.has(combo)) {
+ nrUlcaCombos.add(combo);
+ foundNrUlCa = true;
+ }
+ }
+
+ nrCombosToCheck.forEach(combo => {
+ const [band1, band2] = combo;
+ const hasBand1 = nrcaItem.components.some(component => component.band === band1 && component.bwClassDl === "A");
+ const hasBand2 = nrcaItem.components.some(component => component.band === band2 && component.bwClassDl === "A");
+
+ if (hasBand1 && hasBand2) {
+ const comboString = `n${band1}+n${band2}`;
+ if (!nrCaCombos.has(comboString)) {
+ nrCaCombos.add(comboString);
+ foundNrCaSupport = true;
+ }
+ }
+ });
+
+ if (nrcaItem.uplinkTxSwitch && Array.isArray(nrcaItem.uplinkTxSwitch)) {
+ const hasValidTxSwitching = nrcaItem.uplinkTxSwitch.some(txSwitch =>
+ txSwitch.option === "BOTH" ||
+ txSwitch.option === "DUAL_UL" ||
+ txSwitch.option === "SWITCHED_UL"
+ );
+
+ if (hasValidTxSwitching) {
+ const ulBands = nrcaItem.components
+ .filter(component => component.bwClassUl)
+ .map(component => component.band);
+
+ if (ulBands.length > 1) {
+ const sortedBands = ulBands.sort((a, b) => a - b);
+ const comboString = sortedBands.map(band => `n${band}`).join('+');
+
+ if (!nrTxSwitchingCombos.has(comboString)) {
+ nrTxSwitchingCombos.add(comboString);
+ foundNrTxSwitchingSupport = true;
+ }
+ }
+ }
+ }
+ }
+ });
+ }
+
+ if (has100MHzOrMoreInHigherCombo && maxTddBandwidth[2] < 100) {
+ maxTddBandwidth[2] = 100;
+ }
+
+ if (capability.nrdc && Array.isArray(capability.nrdc)) {
+ hasNrdc = true;
+ capability.nrdc.forEach(nrdcItem => {
+ if (nrdcItem.componentsFr2 && Array.isArray(nrdcItem.componentsFr2)) {
+
+ nrdcItem.componentsFr2.forEach(checkMmwaveBandwidth);
+
+ nrdcItem.componentsFr2.forEach(component => {
+
+ if (component.modulationDl && component.modulationDl.value) {
+ maxNrFr2DlModulation = compareModulation(maxNrFr2DlModulation,
+ component.modulationDl.value);
+ }
+ if (component.modulationUl && component.modulationUl.value) {
+ maxNrFr2UlModulation = compareModulation(maxNrFr2UlModulation,
+ component.modulationUl.value);
+ }
+
+ if (nrMmwaveBandsToCheck.includes(component.band)) {
+ nrMmwaveBands.add(`n${component.band}`);
+ foundNrMmwaveSupport = true;
+ }
+ });
+ }
+ });
+ }
+
+ if (capability.lteca && Array.isArray(capability.lteca)) {
+ hasLteCaData = true;
+ capability.lteca.forEach(ltecaItem => {
+ if (ltecaItem.components && Array.isArray(ltecaItem.components)) {
+
+ const totalMimoStreams = ltecaItem.components.reduce((sum, component) =>
+ sum + calculateMimoStreams(component), 0);
+ maxLteCaMimoStreams = Math.max(maxLteCaMimoStreams, totalMimoStreams);
+
+ ltecaItem.components.forEach(component => {
+
+ if (component.mimoDl && component.mimoDl.value) {
+ maxLteMimo = updateMaxMimo(component.mimoDl.value, maxLteMimo);
+ }
+
+ if (component.modulationDl && component.modulationDl.value) {
+ maxLteDlModulation = compareModulation(maxLteDlModulation, component.modulationDl.value);
+ }
+ if (component.modulationUl && component.modulationUl.value) {
+ maxLteUlModulation = compareModulation(maxLteUlModulation, component.modulationUl.value);
+ }
+
+ });
+
+ lteCombosToCheck.forEach(combo => {
+ const [band1, band2] = combo;
+ const hasBand1 = ltecaItem.components.some(component => component.band === band1 && component.bwClassDl === "A");
+ const hasBand2 = ltecaItem.components.some(component => component.band === band2 && component.bwClassDl === "A");
+
+ if (hasBand1 && hasBand2) {
+ const comboString = `${band1}+${band2}`;
+ if (!lteCaCombos.has(comboString)) {
+ lteCaCombos.add(comboString);
+ hasFoundLteCaCombos = true;
+ }
+ }
+ });
+
+ lteLowbandsToCheck.forEach(band => {
+ const lte4RxComponent = ltecaItem.components.find(component => component.band === band && component.mimoDl && component.mimoDl.value === 4);
+ if (lte4RxComponent) {
+ lte4RxBands.add(band);
+ foundLte4Rx = true;
+ }
+ });
+ }
+ });
+ }
+ });
+ }
+
+ const addSupportRow = (condition, foundSupport, foundSupportText, notFoundSupportText, headerText, logMissesText = '', useInfoIcon = false) => {
+ waitForElement('featuresTableBody', function() {
+ if (condition) {
+ if (foundSupport) {
+ const icon = useInfoIcon ? 'ℹ️' : '✅';
+ addTableRow(`${icon} ${foundSupportText}`, headerText);
+ } else {
+ addTableRow(`❌ ${notFoundSupportText}`, headerText);
+ }
+ } else {
+ addTableRow(logMissesText, headerText);
+ }
+ });
+ };
+
+ addSupportRow(hasLteCaData || hasFoundLteCaCombos, hasFoundLteCaCombos, sortCombinations([...lteCaCombos]).join(', '), "No lowband LTE-CA combo support", "Lowband LTE-CA combos", "Log misses LTE-CA capabilities.");
+ addSupportRow(hasEndc, foundLowbandSupport, sortCombinations([...loggedCombinations]).join(', '), "No lowband EN-DC support", "Lowband EN-DC combos", "Log misses EN-DC capabilities.");
+ addSupportRow(hasNrca, foundNrCaSupport, sortCombinations([...nrCaCombos]).join(', '), "No lowband NR-CA combo support", "Lowband NR-CA combos", "Log misses NR-CA capabilities.");
+ addSupportRow(hasLteCaData || hasEndc, foundLte4Rx, [...lte4RxBands].sort((a, b) => a - b).join(', '), "No lowband LTE 4Rx support", "Lowband LTE 4Rx support", "Log misses LTE-CA capabilities.");
+ addSupportRow(hasEndc || hasNrca, foundNr4Rx, [...nr4RxBands].sort((a, b) => a - b).map(band => `n${band}`).join(', '), "No lowband NR 4Rx support", "Lowband NR 4Rx support", "Log misses EN-DC and NR-CA capabilities.");
+ addSupportRow(hasEndc || hasNrca, foundNr6Rx, [...nr6RxBands].sort((a, b) => a - b).map(band => `n${band}`).join(', '), "No NR 6Rx support", "NR 6Rx bands", "Log misses EN-DC and NR-CA capabilities.");
+ addSupportRow(hasLteCaData, maxLteMimo > 0, `${maxLteMimo} Rx`, "No LTE MIMO support found", "LTE max MIMO Rx", "Log misses LTE-CA capabilities.", true);
+ addSupportRow(hasEndc || hasNrca, maxNrMimo > 0, `${maxNrMimo} Rx`, "No NR MIMO support found", "NR max MIMO Rx", "Log misses EN-DC and NR-CA capabilities.", true);
+ addSupportRow(hasLteCaData, maxLteCaMimoStreams > 0, `${maxLteCaMimoStreams} Streams`, "No LTE CA MIMO stream information found", "LTE max DL streams", "Log misses LTE-CA capabilities.", true);
+ addSupportRow(hasEndc, maxEndcMimoStreams > 0, `${maxEndcMimoStreams} Streams`, "No ENDC MIMO stream information found", "NR-NSA max DL streams", "Log misses EN-DC capabilities.", true);
+ addSupportRow(hasNrca, maxNrCaMimoStreams > 0, `${maxNrCaMimoStreams} Streams`, "No NR-SA CA MIMO stream information found", "NR-SA max DL streams", "Log misses NR-CA capabilities.", true);
+ addSupportRow(hasLteCaData, maxLteDlModulation, formatModulation(maxLteDlModulation), "No LTE DL modulation information found", "LTE max DL modulation", "Log misses LTE-CA capabilities.", true);
+ addSupportRow(hasLteCaData, maxLteUlModulation, formatModulation(maxLteUlModulation), "No LTE UL modulation information found", "LTE max UL modulation", "Log misses LTE-CA capabilities.", true);
+ addSupportRow(hasEndc || hasNrca, maxNrDlModulation, formatModulation(maxNrDlModulation), "No NR DL modulation information found", "NR max DL modulation", "Log misses EN-DC and NR-CA capabilities.", true);
+ addSupportRow(hasEndc || hasNrca, maxNrUlModulation, formatModulation(maxNrUlModulation), "No NR UL modulation information found", "NR max UL modulation", "Log misses EN-DC and NR-CA capabilities.", true);
+ addSupportRow(hasEndc || hasNrdc, maxNrFr2DlModulation, formatModulation(maxNrFr2DlModulation), "No NR FR2 DL modulation information found", "NR FR2 max DL modulation", "Log misses EN-DC and NR-DC capabilities.", true);
+ addSupportRow(hasEndc || hasNrdc, maxNrFr2UlModulation, formatModulation(maxNrFr2UlModulation), "No NR FR2 UL modulation information found", "NR FR2 max UL modulation", "Log misses EN-DC and NR-DC capabilities.", true);
+ addSupportRow(hasEndc || hasNrca, foundNrMaxBwSupport, Object.entries(nrMaxBwSupport).filter(([band, bw]) => bw > 20).map(([band, bw]) => `n${band}@${bw}MHz`).join(', '), "No extended NR FDD bandwidth support", "NR FDD extended bw support", "Log misses EN-DC and NR-CA capabilities.");
+ addSupportRow(hasEndc || hasNrca, nrSulBandsSupported.size > 0, [...nrSulBandsSupported].sort((a, b) => a - b).map(band => `n${band}`).join(', '), "No NR SUL bands support", "NR SUL bands", "Log misses EN-DC and NR-CA capabilities.");
+ addSupportRow(hasEndc, foundNrUlMimoInEndc, [...nrUlmimoBandsEndc].map(band => band.replace('n', '')).sort((a, b) => a - b).map(band => `n${band}`).join(', '), "No NR-NSA UL-MIMO support", "NR-NSA UL MIMO", "Log misses EN-DC capabilities.");
+ addSupportRow(hasNrca, foundNrUlMimo, [...nrUlmimoBands].map(band => band.replace('n', '')).sort((a, b) => a - b).map(band => `n${band}`).join(', '), "No NR-SA UL-MIMO support", "NR-SA UL MIMO", "Log misses NR-CA capabilities.");
+ addSupportRow(hasNrca, foundNrUlCa, sortCombinations([...nrUlcaCombos]).join(', '), "No NR-SA UL-CA support", "NR-SA ULCA", "Log misses NR-CA capabilities.");
+ addSupportRow(hasNrca, foundNrTxSwitchingSupport, sortCombinations([...nrTxSwitchingCombos]).join(', '), "No NR-SA uplink TX switching support", "NR-SA uplink TX switching", "Log misses NR-CA capabilities.");
+ addSupportRow(hasNrca, maxNrFddBandwidth > 0, `${maxNrFddBandwidth}MHz`, "No NR-SA max overall FR1 FDD bandwidth support", "NR-SA max overall FR1 FDD bandwidth", "Log misses NR-CA capabilities.");
+ addSupportRow(hasNrca, maxNrTddBandwidth > 0, `${maxNrTddBandwidth}MHz`, "No NR-SA max overall FR1 TDD bandwidth support", "NR-SA max overall FR1 TDD bandwidth", "Log misses NR-CA capabilities.");
+ for (let i = 2; i <= maxSupportedComboSize; i++) {
+ addSupportRow(hasNrca, maxTddBandwidth[i] > 0, `${maxTddBandwidth[i]}MHz`, `No ${i}xCA NR-CA with mixed TDD/FDD support`, `NR-SA max TDD bandwidth (FR1 ${i}xCA F+T)`, "Log misses NR-CA capabilities.");
+ }
+ addSupportRow(hasEndc || hasNrca, foundNrMmwaveSupport, [...nrMmwaveBands].map(band => band.replace('n', '')).sort((a, b) => a - b).map(band => `n${band}`).join(', '), "No NR mmWave support", "NR mmWave bands", "Log misses EN-DC and NR-CA capabilities.");
+ addSupportRow(hasEndc || hasNrca || hasNrdc, nrMmwaveBandwidths.size > 0, [...nrMmwaveBandwidths].sort((a, b) => a - b).map(bw => `${bw}MHz`).join(', '), "No NR mmWave bandwidth support found", "NR mmWave supported bandwidth per CC", "Log misses EN-DC, NR-CA and NR-DC capabilities.");
+
+ }
+
+ function observeTableCreation() {
+ const config = {
+ childList: true,
+ subtree: true
+ };
+
+ const callback = function(mutationsList, observer) {
+ for (let mutation of mutationsList) {
+ if (mutation.type === 'childList') {
+ const targetElement = document.querySelector("body > div > main > div.flex.flex-1.flex-col > div.flex.flex-1.flex-col > div:nth-child(2)");
+ if (targetElement) {
+ createFeaturesSection();
+ observer.disconnect();
+ break;
+ }
+ }
+ }
+ };
+
+ const observer = new MutationObserver(callback);
+
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', () => {
+ if (document.body) {
+ observer.observe(document.body, config);
+ }
+ });
+ } else {
+ if (document.body) {
+ observer.observe(document.body, config);
+ }
+ }
+ }
+
+ observeTableCreation();
+
+ const originalXhrSend = XMLHttpRequest.prototype.send;
+ XMLHttpRequest.prototype.send = function(...args) {
+ this.addEventListener("load", function() {
+ const url = this.responseURL;
+ if (url.includes("getMultiOutput") || url.includes("getOutput")) {
+ try {
+ const data = JSON.parse(this.responseText);
+ if (url.includes("getMultiOutput")) {
+ checkCapabilities(data, true);
+ } else if (url.includes("getOutput")) {
+ checkCapabilities(data, false);
+ }
+ } catch (err) {
+ console.error('Failed to parse JSON response:', err);
+ }
+ }
+ });
+
+ return originalXhrSend.apply(this, args);
+ };
+ }
+})();