Add copy button for firmware information

This commit is contained in:
Alain Carlucci
2025-12-06 16:42:58 +01:00
parent 420648d851
commit 6bea7dfec2
26 changed files with 85 additions and 94 deletions

View File

@@ -178,7 +178,7 @@ class DS4Controller extends BaseController {
if(!is_clone) {
// Add Board Model (UI will append the info icon)
infoItems.push({ key: l("Board Model"), value: this.hwToBoardModel(hw_ver_minor), cat: "hw", addInfoIcon: 'board' });
infoItems.push({ key: l("Board Model"), value: this.hwToBoardModel(hw_ver_minor), cat: "hw", addInfoIcon: 'board', copyable: true });
const bd_addr = await this.getBdAddr();
infoItems.push({ key: l("Bluetooth Address"), value: bd_addr, cat: "hw" });

View File

@@ -298,16 +298,16 @@ class DS5Controller extends BaseController {
const serial_number = await this.getSystemInfo(1, 19, 17);
const color = ds5_color(serial_number);
const infoItems = [
{ key: l("Serial Number"), value: serial_number, cat: "hw" },
{ key: l("MCU Unique ID"), value: await this.getSystemInfo(1, 9, 9, false), cat: "hw", isExtra: true },
{ key: l("Serial Number"), value: serial_number, cat: "hw", copyable: true },
{ key: l("MCU Unique ID"), value: await this.getSystemInfo(1, 9, 9, false), cat: "hw", isExtra: true, copyable: true },
{ key: l("PCBA ID"), value: reverse_str(await this.getSystemInfo(1, 17, 14)), cat: "hw", isExtra: true },
{ key: l("Battery Barcode"), value: await this.getSystemInfo(1, 24, 23), cat: "hw", isExtra: true },
{ key: l("VCM Left Barcode"), value: await this.getSystemInfo(1, 26, 16), cat: "hw", isExtra: true },
{ key: l("VCM Right Barcode"), value: await this.getSystemInfo(1, 28, 16), cat: "hw", isExtra: true },
{ key: l("Battery Barcode"), value: await this.getSystemInfo(1, 24, 23), cat: "hw", isExtra: true, copyable: true },
{ key: l("VCM Left Barcode"), value: await this.getSystemInfo(1, 26, 16), cat: "hw", isExtra: true, copyable: true },
{ key: l("VCM Right Barcode"), value: await this.getSystemInfo(1, 28, 16), cat: "hw", isExtra: true, copyable: true },
{ key: l("Color"), value: l(color), cat: "hw", addInfoIcon: 'color' },
{ key: l("Color"), value: l(color), cat: "hw", addInfoIcon: 'color', copyable: true },
...(is_edge ? [] : [{ key: l("Board Model"), value: this.hwToBoardModel(hwinfo), cat: "hw", addInfoIcon: 'board' }]),
...(is_edge ? [] : [{ key: l("Board Model"), value: this.hwToBoardModel(hwinfo), cat: "hw", addInfoIcon: 'board', copyable: true }]),
{ key: l("FW Build Date"), value: build_date + " " + build_time, cat: "fw" },
{ key: l("FW Type"), value: "0x" + dec2hex(fwtype), cat: "fw", isExtra: true },
@@ -320,7 +320,7 @@ class DS5Controller extends BaseController {
{ key: l("Venom FW Version"), value: "0x" + dec2hex32(fwversion2), cat: "fw", isExtra: true },
{ key: l("Spider FW Version"), value: "0x" + dec2hex32(fwversion3), cat: "fw", isExtra: true },
{ key: l("Touchpad ID"), value: await this.getSystemInfo(5, 2, 8, false), cat: "hw", isExtra: true },
{ key: l("Touchpad ID"), value: await this.getSystemInfo(5, 2, 8, false), cat: "hw", isExtra: true, copyable: true },
{ key: l("Touchpad FW Version"), value: await this.getSystemInfo(5, 4, 8, false), cat: "fw", isExtra: true },
];

View File

@@ -885,20 +885,11 @@ function render_info_to_dom(infoItems) {
if (!Array.isArray(infoItems)) return;
// Add new info items
infoItems.forEach(({key, value, addInfoIcon, severity, isExtra, cat}) => {
infoItems.forEach(({key, value, addInfoIcon, severity, isExtra, cat, copyable}) => {
if (!key) return;
// Compose value with optional info icon
let valueHtml = String(value ?? "");
if (addInfoIcon === 'board') {
const icon = '&nbsp;<a class="link-body-emphasis" href="#" onclick="board_model_info()">' +
'<svg class="bi" width="1.3em" height="1.3em"><use xlink:href="#info"/></svg></a>';
valueHtml += icon;
} else if (addInfoIcon === 'color') {
const icon = '&nbsp;<a class="link-body-emphasis" href="#" onclick="edge_color_info()">' +
'<svg class="bi" width="1.3em" height="1.3em"><use xlink:href="#info"/></svg></a>';
valueHtml += icon;
}
// Apply severity formatting if requested
if (severity) {
@@ -908,25 +899,43 @@ function render_info_to_dom(infoItems) {
}
if (isExtra) {
append_info_extra(key, valueHtml, cat || "hw");
appendInfoExtra(key, valueHtml, cat || "hw", copyable ?? false);
} else {
append_info(key, valueHtml, cat || "hw");
appendInfo(key, valueHtml, cat || "hw", copyable ?? false);
}
});
}
function append_info_extra(key, value, cat) {
function copyValueToClipboard(text) {
navigator.clipboard.writeText(text).then(function() {
infoAlert(l("The item has been copied to the clipboard."), 2000);
}).catch(function(err) {
errorAlert(l("Cannot copy text to the clipboard:") + " " + str(err));
});
}
function genCopyString(value, copyable) {
if(!copyable)
return '';
const cleanStringRegex = value.match(/^[A-Za-z0-9_.-]+/);
const escapedValue = cleanStringRegex ? cleanStringRegex[0] : "";
return '&nbsp;<i style="cursor:pointer;" class="fa-regular fa-copy" onclick=\'copyValueToClipboard("' + escapedValue + '")\'></i>';
}
function appendInfoExtra(key, value, cat, copyable) {
// TODO escape html
const s = '<dt class="text-muted col-sm-4 col-md-6 col-xl-5">' + key + '</dt><dd class="col-sm-8 col-md-6 col-xl-7" style="text-align: right;">' + value + '</dd>';
const s = '<dt class="text-muted col-sm-4 col-md-6 col-xl-5">' + key + '</dt><dd class="col-sm-8 col-md-6 col-xl-7" style="text-align: right;">' + value + genCopyString(value, copyable) + '</dd>';
$("#fwinfoextra-" + cat).html($("#fwinfoextra-" + cat).html() + s);
}
function append_info(key, value, cat) {
function appendInfo(key, value, cat, copyable) {
// TODO escape html
const s = '<dt class="text-muted col-6">' + key + '</dt><dd class="col-6" style="text-align: right;">' + value + '</dd>';
const s = '<dt class="text-muted col-6">' + key + '</dt><dd class="col-6" style="text-align: right;">' + value + genCopyString(value, copyable) + '</dd>';
$("#fwinfo").html($("#fwinfo").html() + s);
append_info_extra(key, value, cat);
appendInfoExtra(key, value, cat, copyable);
}
function show_popup(text, is_html = false) {
@@ -964,25 +973,6 @@ function show_info_tab() {
$('#info-tab').tab('show');
}
function discord_popup() {
la("discord_popup");
show_popup(l("My handle on discord is: the_al"));
}
function edge_color_info() {
la("cm_info");
const text = l("Color detection thanks to") + ' romek77 from Poland.';
show_popup(text, true);
}
function board_model_info() {
la("bm_info");
const l1 = l("This feature is experimental.");
const l2 = l("Please let me know if the board model of your controller is not detected correctly.");
const l3 = l("Board model detection thanks to") + ' <a href="https://battlebeavercustoms.com/">Battle Beaver Customs</a>.';
show_popup(l3 + "<br><br>" + l1 + " " + l2, true);
}
// Alert Management Functions
let alertCounter = 0;
@@ -1068,6 +1058,7 @@ window.connect = connect;
window.disconnect = disconnectSync;
window.show_faq_modal = show_faq_modal;
window.show_info_tab = show_info_tab;
window.copyValueToClipboard = copyValueToClipboard;
window.calibrate_range = () => calibrate_range(
controller,
{ ll_data, rr_data },
@@ -1112,8 +1103,6 @@ window.nvsunlock = nvsunlock;
window.nvslock = nvslock;
window.welcome_accepted = welcome_accepted;
window.show_donate_modal = show_donate_modal;
window.board_model_info = board_model_info;
window.edge_color_info = edge_color_info;
window.show_quick_test_modal = () => {
show_quick_test_modal(controller).catch(error => {
throw new Error("Failed to show quick test modal", { cause: error });