Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions InteractiveHtmlBom/web/ibom.css
Original file line number Diff line number Diff line change
Expand Up @@ -885,4 +885,16 @@ a {

::-moz-focus-inner {
padding: 0;
}

.copy-link-button {
font-size: 12px;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use "em" based size

border: none;
background: transparent;
cursor: pointer;
vertical-align: middle;
}

.copy-link-button:hover {
opacity: 0.7;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rather have the transparency be reversed i.e. more opaque when hovered.

}
129 changes: 128 additions & 1 deletion InteractiveHtmlBom/web/ibom.js
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,25 @@ function populateBomBody(placeholderColumn = null, placeHolderElements = null) {
netname = bomentry;
td = document.createElement("TD");
td.innerHTML = highlightFilter(netname ? netname : "<no net>");

// Add copy button for net names in netlist mode
if (settings.bommode === "netlist") {
var copyButton = document.createElement("button");
copyButton.className = "copy-link-button";
copyButton.title = "Copy deep-link URL";
copyButton.innerHTML = "🔗";
copyButton.style.cssText = "margin-left: 5px; font-size: 10pt; border: none; background: transparent; cursor: pointer; height: 100%";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are duplicated and overriding properties here, move everything to css.

(function(currentNetname) {
copyButton.onclick = function(e) {
e.stopPropagation();
var url = new URL(window.location.href);
url.searchParams.set("net", "\"" + currentNetname + "\"");
copyToClipboard(url.toString());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change window location to the url as well, will give more visual clues that the click action worked.

};
})(netname);
td.appendChild(copyButton);
}

tr.appendChild(td);
var color = settings.netColors[netname] || defaultNetColor;
td = document.createElement("TD");
Expand Down Expand Up @@ -721,7 +740,31 @@ function populateBomBody(placeholderColumn = null, placeHolderElements = null) {
}
} else if (column === "References") {
td = document.createElement("TD");
td.innerHTML = highlightFilter(references.map(r => r[0]).join(", "));
var refHtml = highlightFilter(references.map(r => r[0]).join(", "));
td.innerHTML = refHtml;

// Add copy button for component references in ungrouped mode
if (settings.bommode === "ungrouped") {
var copyButton = document.createElement("button");
copyButton.className = "copy-link-button";
copyButton.title = "Copy deep-link URL";
copyButton.innerHTML = "🔗";
copyButton.style.cssText = "margin-left: 5px; font-size: 10pt; border: none; background: transparent; cursor: pointer; height: 100%";
var refName = references[0][0];
(function(currentRefname) {
copyButton.onclick = function(e) {
e.stopPropagation();
// Get the first reference to create URL (since we have multiple refs, we'll use the first one)
var url = new URL(window.location.href);
url.searchParams.set("ref", "\"" + currentRefname + "\"");
copyToClipboard(url.toString());

};
})(refName);
td.appendChild(copyButton);
}
Comment on lines +746 to +765

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lot of code duplication here with the copy button for nets. Extract it into a helper that creates button based on type (net/ref) and value (net name/ref name)



tr.appendChild(td);
} else if (column === "Quantity" && settings.bommode == "grouped") {
// Quantity
Expand Down Expand Up @@ -783,6 +826,26 @@ function populateBomBody(placeholderColumn = null, placeHolderElements = null) {
});
}

function copyToClipboard(text) {
var textArea = document.createElement("textarea");
textArea.className = "clipboard-temp";
textArea.value = text;
document.body.appendChild(textArea);
textArea.select();
try {
var successful = document.execCommand('copy');
if (!successful) {
// Fallback for browsers that don't support execCommand
navigator.clipboard.writeText(text).catch(function(err) {
console.error('Could not copy text: ', err);
});
}
} catch (err) {
console.error('Could not copy text: ', err);
}
document.body.removeChild(textArea);
}

function highlightPreviousRow() {
if (!currentHighlightedRowId) {
highlightHandlers[highlightHandlers.length - 1].handler();
Expand Down Expand Up @@ -1194,6 +1257,47 @@ function updateCheckboxStats(checkbox) {
td.lastChild.innerHTML = checked + "/" + total + " (" + Math.round(percent) + "%)";
}

function selectComponentByReference(ref) {
// Find the footprint with matching reference
var footprintIndex = -1;
for (var i = 0; i < pcbdata.footprints.length; i++) {
if (pcbdata.footprints[i].ref === ref) {
footprintIndex = i;
break;
}
}

// If we found the component, select it
if (footprintIndex !== -1) {
// Use the existing footprintIndexToHandler to trigger selection
if (footprintIndex in footprintIndexToHandler) {
footprintIndexToHandler[footprintIndex]();
// Scroll to the selected row to center it on screen
if (currentHighlightedRowId) {
smoothScrollToRow(currentHighlightedRowId);
}
} else {
// If no handler exists, try to find the row manually
for (var i = 0; i < highlightHandlers.length; i++) {
var handlerInfo = highlightHandlers[i];
if (handlerInfo.handler && handlerInfo.handler.refs) {
// Check if any of the references in this row match our target ref
for (var j = 0; j < handlerInfo.handler.refs.length; j++) {
if (handlerInfo.handler.refs[j][0] === ref) {
handlerInfo.handler();
if (currentHighlightedRowId) {
smoothScrollToRow(currentHighlightedRowId);
}
break;
}
}
}
}
}
}
// If component not found, do nothing (preserve existing behavior)
}

function constrain(number, min, max) {
return Math.min(Math.max(parseInt(number), min), max);
}
Expand Down Expand Up @@ -1320,6 +1424,29 @@ window.onload = function (e) {
// Triggers render
changeBomLayout(settings.bomlayout);

// Parse URL parameters for deep-linking
var urlParams = new URLSearchParams(window.location.search);
var refParam = urlParams.get('ref') || urlParams.get('component');
if (refParam) {
// Change layout to ungrouped
changeBomMode('ungrouped');
// Extract the value from the "" or the '' string
refParam = refParam.replace(/^["']|["']$/g, "");
// Try to find and select the component
selectComponentByReference(refParam);
}

// Handle net parameter for deep-linking to nets
var netParam = urlParams.get('net');
if (netParam && "nets" in pcbdata) {
// Change layout to netlist
changeBomMode('netlist');
// Extract the value from the "" or the '' string
netParam = netParam.replace(/^["']|["']$/g, "");
// Try to find and select the net
netClicked(netParam);
}

// Users may leave fullscreen without touching the checkbox. Uncheck.
document.addEventListener('fullscreenchange', () => {
if (!document.fullscreenElement)
Expand Down