mirror of
https://github.com/l5yth/potato-mesh.git
synced 2026-08-07 01:13:01 +02:00
fix: get meshcore protocol icon displaying correctly (#681)
This commit is contained in:
@@ -144,8 +144,8 @@ test('padTwo handles zero', () => {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test('formatCompactDate returns two-digit day of month', () => {
|
||||
// 2025-01-05 UTC
|
||||
const ts = Date.UTC(2025, 0, 5);
|
||||
// Local calendar date (formatCompactDate uses getDate(), not UTC).
|
||||
const ts = new Date(2025, 0, 5).getTime();
|
||||
assert.equal(formatCompactDate(ts), '05');
|
||||
});
|
||||
|
||||
|
||||
@@ -132,9 +132,10 @@ test('renderNodeLongNameLink renders meshtastic icon when protocol is absent', (
|
||||
assert.ok(html.includes('meshtastic.svg'));
|
||||
});
|
||||
|
||||
test('renderNodeLongNameLink omits meshtastic icon for meshcore protocol', () => {
|
||||
test('renderNodeLongNameLink uses meshcore icon for meshcore protocol', () => {
|
||||
const html = renderNodeLongNameLink('Eve', '!aabbccdd', { protocol: 'meshcore' });
|
||||
assert.ok(!html.includes('meshtastic.svg'), 'no meshtastic icon for meshcore protocol');
|
||||
assert.ok(html.includes('meshcore.svg'), 'meshcore icon should be shown');
|
||||
});
|
||||
|
||||
test('renderNodeLongNameLink renders plain text when identifier is null', () => {
|
||||
|
||||
@@ -99,7 +99,14 @@ import {
|
||||
roleColors,
|
||||
roleRenderOrder,
|
||||
} from './role-helpers.js';
|
||||
import { isMeshtasticProtocol, meshtasticIconHtml, MESHTASTIC_ICON_SRC } from './protocol-helpers.js';
|
||||
import {
|
||||
isMeshtasticProtocol,
|
||||
isMeshcoreProtocol,
|
||||
meshtasticIconHtml,
|
||||
MESHTASTIC_ICON_SRC,
|
||||
MESHCORE_ICON_SRC,
|
||||
protocolIconPrefixHtml,
|
||||
} from './protocol-helpers.js';
|
||||
|
||||
/**
|
||||
* Entry point for the interactive dashboard. Wires up event listeners,
|
||||
@@ -2247,7 +2254,9 @@ export function initializeApp(config) {
|
||||
overlayInfo.role = 'CLIENT';
|
||||
}
|
||||
const lines = [];
|
||||
const longNameLink = renderNodeLongNameLink(overlayInfo.longName, overlayInfo.nodeId);
|
||||
const longNameLink = renderNodeLongNameLink(overlayInfo.longName, overlayInfo.nodeId, {
|
||||
protocol: overlayInfo.protocol,
|
||||
});
|
||||
if (longNameLink) {
|
||||
lines.push(`<strong>${longNameLink}</strong>`);
|
||||
} else {
|
||||
@@ -2382,7 +2391,8 @@ export function initializeApp(config) {
|
||||
const fallbackId = nodeIdRaw || 'Unknown node';
|
||||
const longNameRaw = pickFirstProperty([node], ['long_name', 'longName']);
|
||||
const longNameDisplay = longNameRaw ? String(longNameRaw) : fallbackId;
|
||||
const longNameLink = renderNodeLongNameLink(longNameRaw, nodeIdRaw);
|
||||
const nodeProtocol = pickFirstProperty([node], ['protocol']);
|
||||
const longNameLink = renderNodeLongNameLink(longNameRaw, nodeIdRaw, { protocol: nodeProtocol });
|
||||
const announcementName = longNameLink || escapeHtml(longNameDisplay);
|
||||
const shortNameRaw = pickFirstProperty([node], ['short_name', 'shortName']);
|
||||
const shortNameDisplay = shortNameRaw ? String(shortNameRaw) : (nodeIdRaw ? nodeIdRaw.slice(-4) : null);
|
||||
@@ -2397,6 +2407,7 @@ export function initializeApp(config) {
|
||||
role: roleDisplay,
|
||||
metadataSource: node,
|
||||
nodeData: node,
|
||||
protocol: nodeProtocol,
|
||||
messageHtml: `${renderEmojiHtml('☀️')} ${renderAnnouncementCopy('New node:', ` ${announcementName}`)}`
|
||||
});
|
||||
}
|
||||
@@ -2472,6 +2483,7 @@ export function initializeApp(config) {
|
||||
role: context.role,
|
||||
metadataSource: context.metadataSource,
|
||||
nodeData: context.nodeData,
|
||||
protocol: context.protocol,
|
||||
messageHtml: `${renderEmojiHtml('💾')} ${renderAnnouncementCopy('Updated node info')}`
|
||||
});
|
||||
}
|
||||
@@ -2486,6 +2498,7 @@ export function initializeApp(config) {
|
||||
role: context.role,
|
||||
metadataSource: context.metadataSource,
|
||||
nodeData: context.nodeData,
|
||||
protocol: context.protocol,
|
||||
messageHtml: `${renderEmojiHtml('🔋')} ${renderAnnouncementCopy('Broadcasted telemetry', highlightSuffix)}`
|
||||
});
|
||||
}
|
||||
@@ -2500,6 +2513,7 @@ export function initializeApp(config) {
|
||||
role: context.role,
|
||||
metadataSource: context.metadataSource,
|
||||
nodeData: context.nodeData,
|
||||
protocol: context.protocol,
|
||||
messageHtml: `${renderEmojiHtml('📍')} ${renderAnnouncementCopy('Broadcasted position info', highlightSuffix)}`
|
||||
});
|
||||
}
|
||||
@@ -2525,6 +2539,7 @@ export function initializeApp(config) {
|
||||
role: context.role,
|
||||
metadataSource: context.metadataSource,
|
||||
nodeData: context.nodeData,
|
||||
protocol: context.protocol,
|
||||
messageHtml: `${renderEmojiHtml('🏘️')} ${renderAnnouncementCopy('Broadcasted neighbor info', detail)}`
|
||||
});
|
||||
}
|
||||
@@ -2563,7 +2578,8 @@ export function initializeApp(config) {
|
||||
* role: ?string,
|
||||
* metadataSource: Object|null,
|
||||
* nodeData: Object|null,
|
||||
* messageHtml: string
|
||||
* messageHtml: string,
|
||||
* protocol: ?string
|
||||
* }} params Rendering parameters.
|
||||
* @returns {HTMLElement} Chat log element.
|
||||
*/
|
||||
@@ -2574,7 +2590,8 @@ export function initializeApp(config) {
|
||||
role,
|
||||
metadataSource,
|
||||
nodeData,
|
||||
messageHtml
|
||||
messageHtml,
|
||||
protocol: protocolHint = null
|
||||
}) {
|
||||
const div = document.createElement('div');
|
||||
const tsDate = timestampSeconds != null ? new Date(timestampSeconds * 1000) : null;
|
||||
@@ -2587,8 +2604,9 @@ export function initializeApp(config) {
|
||||
const presetTag = formatChatPresetTag({ presetCode: metadata.presetCode });
|
||||
const longNameDisplay = longName != null ? String(longName) : '';
|
||||
const shortHtml = renderShortHtml(shortName, role, longNameDisplay, nodeData || metadataSource || {});
|
||||
const announcementProtocol = (nodeData || metadataSource || {}).protocol;
|
||||
const announcementIconPrefix = isMeshtasticProtocol(announcementProtocol) ? `${meshtasticIconHtml()} ` : '';
|
||||
const announcementProtocol =
|
||||
protocolHint ?? pickFirstProperty([nodeData, metadataSource], ['protocol']);
|
||||
const announcementIconPrefix = protocolIconPrefixHtml(announcementProtocol);
|
||||
div.className = 'chat-entry-node';
|
||||
div.innerHTML = `${prefix}${presetTag} ${announcementIconPrefix}${shortHtml} ${messageHtml}`;
|
||||
return div;
|
||||
@@ -2632,6 +2650,7 @@ export function initializeApp(config) {
|
||||
role: context.role,
|
||||
metadataSource: sourceNode || context.metadataSource,
|
||||
nodeData: sourceNode || context.nodeData,
|
||||
protocol: context.protocol,
|
||||
messageHtml: `${renderEmojiHtml('👣')} ${renderAnnouncementCopy('Caught trace', labelSuffix)}`
|
||||
});
|
||||
}
|
||||
@@ -2737,13 +2756,20 @@ export function initializeApp(config) {
|
||||
* longName: ?string,
|
||||
* role: ?string,
|
||||
* metadataSource: Object|null,
|
||||
* nodeData: Object|null
|
||||
* nodeData: Object|null,
|
||||
* protocol: ?string
|
||||
* }} Normalised display metadata.
|
||||
*/
|
||||
function buildDisplayContext(entry) {
|
||||
const resolvedNode = resolveNodeForLogEntry(entry);
|
||||
const candidateSources = [resolvedNode, entry?.node, entry?.telemetry, entry?.position, entry?.neighbor]
|
||||
.filter(source => source && typeof source === 'object');
|
||||
const candidateSources = [
|
||||
resolvedNode,
|
||||
entry?.node,
|
||||
entry?.telemetry,
|
||||
entry?.position,
|
||||
entry?.neighbor,
|
||||
entry?.trace,
|
||||
].filter(source => source && typeof source === 'object');
|
||||
const nodeId = typeof entry?.nodeId === 'string' && entry.nodeId.trim().length
|
||||
? entry.nodeId.trim()
|
||||
: pickFirstProperty(candidateSources, ['node_id', 'nodeId']);
|
||||
@@ -2761,7 +2787,8 @@ export function initializeApp(config) {
|
||||
const role = pickFirstProperty(candidateSources, ['role']);
|
||||
const metadataSource = resolvedNode || candidateSources[0] || {};
|
||||
const nodeData = resolvedNode || candidateSources[0] || {};
|
||||
return { nodeId, nodeNum, shortName, longName, role, metadataSource, nodeData };
|
||||
const protocol = pickFirstProperty(candidateSources, ['protocol']);
|
||||
return { nodeId, nodeNum, shortName, longName, role, metadataSource, nodeData, protocol };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2979,7 +3006,8 @@ export function initializeApp(config) {
|
||||
const tsDate = tsSeconds != null ? new Date(tsSeconds * 1000) : null;
|
||||
const ts = tsDate ? formatTime(tsDate) : '--:--:--';
|
||||
const short = renderShortHtml(m.node?.short_name, m.node?.role, m.node?.long_name, m.node);
|
||||
const nodeProtocolPrefix = isMeshtasticProtocol(m.node?.protocol) ? `${meshtasticIconHtml()} ` : '';
|
||||
const messageProtocol = pickFirstProperty([m, m?.node], ['protocol']);
|
||||
const nodeProtocolPrefix = protocolIconPrefixHtml(messageProtocol);
|
||||
const replyPrefix = resolveReplyPrefix({
|
||||
message: m,
|
||||
messagesById,
|
||||
@@ -3149,7 +3177,11 @@ export function initializeApp(config) {
|
||||
const channelTabs = filteredChannels.map(channel => ({
|
||||
id: channel.id || `channel-${channel.index}`,
|
||||
label: channel.label,
|
||||
iconSrc: isMeshtasticProtocol(channel.protocol) ? MESHTASTIC_ICON_SRC : null,
|
||||
iconSrc: isMeshtasticProtocol(channel.protocol)
|
||||
? MESHTASTIC_ICON_SRC
|
||||
: isMeshcoreProtocol(channel.protocol)
|
||||
? MESHCORE_ICON_SRC
|
||||
: null,
|
||||
content: buildChatFragment({
|
||||
entries: channel.entries.map(e => ({ ts: e.ts, item: e.message })),
|
||||
renderEntry: entry => createMessageChatEntry(entry.item),
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import { refreshNodeInformation } from './node-details.js';
|
||||
import { isMeshtasticProtocol, meshtasticIconHtml } from './protocol-helpers.js';
|
||||
import { protocolIconPrefixHtml } from './protocol-helpers.js';
|
||||
import {
|
||||
extractChatMessageMetadata,
|
||||
formatChatChannelTag,
|
||||
@@ -114,48 +114,6 @@ function numberOrNull(value) {
|
||||
const num = Number(value);
|
||||
return Number.isFinite(num) ? num : null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Render the telemetry charts for the supplied node when telemetry snapshots
|
||||
@@ -1014,7 +972,7 @@ function renderMessages(messages, renderShortHtml, node) {
|
||||
|
||||
const messageNode = message.node && typeof message.node === 'object' ? message.node : null;
|
||||
const messageProtocol = stringOrNull(messageNode?.protocol ?? fallbackNode?.protocol) ?? null;
|
||||
const protocolIconHtml = isMeshtasticProtocol(messageProtocol) ? `${meshtasticIconHtml()} ` : '';
|
||||
const protocolIconHtml = protocolIconPrefixHtml(messageProtocol);
|
||||
const badgeHtml = renderRoleAwareBadge(renderShortHtml, {
|
||||
shortName: messageNode?.short_name ?? messageNode?.shortName ?? fallbackNode?.shortName ?? fallbackNode?.short_name,
|
||||
longName: messageNode?.long_name ?? messageNode?.longName ?? fallbackNode?.longName ?? fallbackNode?.long_name,
|
||||
@@ -1267,7 +1225,7 @@ function renderNodeDetailHtml(node, {
|
||||
}
|
||||
|
||||
const identifierHtml = identifier ? `<span class="node-detail__identifier">[${escapeHtml(identifier)}]</span>` : '';
|
||||
const iconPrefix = isMeshtasticProtocol(nodeProtocol) ? `${meshtasticIconHtml()} ` : '';
|
||||
const iconPrefix = protocolIconPrefixHtml(nodeProtocol);
|
||||
const nameHtml = longName ? `<span class="node-detail__name">${iconPrefix}${escapeHtml(longName)}</span>` : '';
|
||||
const badgeHtml = `<span class="node-detail__badge">${roleAwareBadge}</span>`;
|
||||
const tableSection = tableHtml ? `<div class="node-detail__table">${tableHtml}</div>` : '';
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
*/
|
||||
|
||||
import { escapeHtml } from './utils.js';
|
||||
import { isMeshtasticProtocol, meshtasticIconHtml } from './protocol-helpers.js';
|
||||
import { protocolIconPrefixHtml } from './protocol-helpers.js';
|
||||
|
||||
/**
|
||||
* Normalise node name fields by trimming whitespace.
|
||||
@@ -79,8 +79,9 @@ export function canonicalNodeIdentifier(identifier) {
|
||||
/**
|
||||
* Render a linked long name pointing to the node detail view.
|
||||
*
|
||||
* When ``protocol`` is ``"meshtastic"`` (or absent) the Meshtastic logo is
|
||||
* prepended to the displayed name. An anchor element is only emitted when
|
||||
* When ``protocol`` is Meshtastic (including null/empty per
|
||||
* {@link module:protocol-helpers~isMeshtasticProtocol}) or ``"meshcore"``, the
|
||||
* matching protocol icon is prepended. An anchor element is only emitted when
|
||||
* ``identifier`` resolves to a non-null detail path.
|
||||
*
|
||||
* @param {string|null} longName Display name.
|
||||
@@ -91,7 +92,7 @@ export function canonicalNodeIdentifier(identifier) {
|
||||
export function renderNodeLongNameLink(longName, identifier, { className = 'node-long-link', protocol = null } = {}) {
|
||||
const text = normalizeNodeNameValue(longName);
|
||||
if (!text) return '';
|
||||
const iconPrefix = isMeshtasticProtocol(protocol) ? `${meshtasticIconHtml()} ` : '';
|
||||
const iconPrefix = protocolIconPrefixHtml(protocol);
|
||||
const href = buildNodeDetailHref(identifier);
|
||||
if (!href) {
|
||||
return `${iconPrefix}${escapeHtml(text)}`;
|
||||
|
||||
@@ -80,3 +80,24 @@ export function meshcoreIconHtml() {
|
||||
' aria-hidden="true">';
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an HTML prefix (protocol icon plus a trailing space) for inline UI.
|
||||
*
|
||||
* Meshtastic — including null, undefined, empty, or whitespace-only values per
|
||||
* {@link isMeshtasticProtocol} — uses the Meshtastic icon. The literal
|
||||
* ``"meshcore"`` uses the MeshCore icon. Any other protocol string yields an
|
||||
* empty prefix (same as the pre-MeshCore behaviour for unknown stacks).
|
||||
*
|
||||
* @param {string|null|undefined} protocol Protocol string from the API.
|
||||
* @returns {string} HTML fragment safe to concatenate before visible text.
|
||||
*/
|
||||
export function protocolIconPrefixHtml(protocol) {
|
||||
if (isMeshcoreProtocol(protocol)) {
|
||||
return `${meshcoreIconHtml()} `;
|
||||
}
|
||||
if (isMeshtasticProtocol(protocol)) {
|
||||
return `${meshtasticIconHtml()} `;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user