feat: value source card crosslinks + gradient_map test shows input value
Lint & Test / test (push) Successful in 1m23s

Add navigateToCard crosslinks for ha_entity (→ HA source), gradient_map
(→ input value source + gradient entity), and css_extract (→ color strip).
Gradient map test now charts the input interpolation factor instead of
output luminance, making the 0–1 chart meaningful.
This commit is contained in:
2026-03-30 03:23:05 +03:00
parent f6c25cd15f
commit 4b7a8d75f4
4 changed files with 34 additions and 8 deletions
@@ -813,13 +813,16 @@ export function testValueSource(sourceId: any) {
_testVsWs.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
_testVsLatest = data.value;
_testVsHistory.push(data.value);
// For color sources with an input value (e.g. gradient_map),
// chart the input interpolation factor instead of luminance
const chartValue = data.input_value !== undefined ? data.input_value : data.value;
_testVsLatest = chartValue;
_testVsHistory.push(chartValue);
if (_testVsHistory.length > VS_HISTORY_SIZE) {
_testVsHistory.shift();
}
if (data.value < _testVsMinObserved) _testVsMinObserved = data.value;
if (data.value > _testVsMaxObserved) _testVsMaxObserved = data.value;
if (chartValue < _testVsMinObserved) _testVsMinObserved = chartValue;
if (chartValue > _testVsMaxObserved) _testVsMaxObserved = chartValue;
if (data.raw_value !== undefined) {
_testVsRawLatest = data.raw_value;
if (data.raw_range) _testVsRawRange = data.raw_range;
@@ -1134,8 +1137,11 @@ export function createValueSourceCard(src: ValueSource) {
const haName = haSrc ? haSrc.name : ((src as any).ha_source_id || '-');
const entityId = (src as any).entity_id || '';
const attr = (src as any).attribute;
const haBadge = haSrc
? `<span class="stream-card-prop stream-card-link" title="${escapeHtml(t('value_source.ha_source'))}" onclick="event.stopPropagation(); navigateToCard('streams','home_assistant','ha-sources','data-id','${(src as any).ha_source_id}')">${ICON_HOME} ${escapeHtml(haName)}</span>`
: `<span class="stream-card-prop">${ICON_HOME} ${escapeHtml(haName)}</span>`;
propsHtml = `
<span class="stream-card-prop">${ICON_HOME} ${escapeHtml(haName)}</span>
${haBadge}
<span class="stream-card-prop">${ICON_LINK} ${escapeHtml(entityId)}${attr ? '.' + escapeHtml(attr) : ''}</span>
<span class="stream-card-prop">${ICON_MOVE_VERTICAL} ${(src as any).min_ha_value ?? 0}\u2013${(src as any).max_ha_value ?? 100}</span>
`;
@@ -1149,9 +1155,15 @@ export function createValueSourceCard(src: ValueSource) {
const gradientCss = stops.length >= 2
? `linear-gradient(to right, ${stops.map((s: any) => `rgb(${s.color.join(',')}) ${(s.position * 100).toFixed(0)}%`).join(', ')})`
: '#333';
const inputBadge = inputVs
? `<span class="stream-card-prop stream-card-link" title="${escapeHtml(t('value_source.gradient_map.input'))}" onclick="event.stopPropagation(); navigateToCard('streams','value','value-sources','data-id','${(src as any).value_source_id}')">${ICON_LINK} ${escapeHtml(inputName)}</span>`
: `<span class="stream-card-prop">${ICON_LINK} ${escapeHtml(inputName)}</span>`;
const gradBadge = grad
? `<span class="stream-card-prop stream-card-link" title="${escapeHtml(t('value_source.gradient_map.gradient'))}" onclick="event.stopPropagation(); navigateToCard('streams','gradients','gradients','data-id','${(src as any).gradient_id}')">${ICON_RAINBOW} ${escapeHtml(gradName)}</span>`
: `<span class="stream-card-prop">${ICON_RAINBOW} ${escapeHtml(gradName)}</span>`;
propsHtml = `
<span class="stream-card-prop">${ICON_LINK} ${escapeHtml(inputName)}</span>
<span class="stream-card-prop">${ICON_RAINBOW} ${escapeHtml(gradName)}</span>
${inputBadge}
${gradBadge}
<div style="height:8px;border-radius:4px;margin:4px 0;background:${gradientCss};"></div>
`;
} else if (src.source_type === 'css_extract') {
@@ -1160,8 +1172,11 @@ export function createValueSourceCard(src: ValueSource) {
const ledStart = (src as any).led_start ?? 0;
const ledEnd = (src as any).led_end ?? -1;
const rangeLabel = ledEnd < 0 ? `${ledStart}\u2013all` : `${ledStart}\u2013${ledEnd}`;
const cssBadge = cssSrc
? `<span class="stream-card-prop stream-card-link" title="${escapeHtml(t('value_source.css_extract.source'))}" onclick="event.stopPropagation(); navigateToCard('streams','color_strip','color-strips','data-css-id','${(src as any).color_strip_source_id}')">${ICON_DROPLETS} ${escapeHtml(cssName)}</span>`
: `<span class="stream-card-prop">${ICON_DROPLETS} ${escapeHtml(cssName)}</span>`;
propsHtml = `
<span class="stream-card-prop">${ICON_DROPLETS} ${escapeHtml(cssName)}</span>
${cssBadge}
<span class="stream-card-prop">${ICON_MOVE_VERTICAL} LED ${rangeLabel}</span>
`;
}
@@ -1463,6 +1463,9 @@
"value_source.type.gradient_map.desc": "Maps numeric value through a color gradient",
"value_source.type.css_extract": "Strip Extract",
"value_source.type.css_extract.desc": "Extracts color from a color strip source",
"value_source.gradient_map.input": "Input Value Source",
"value_source.gradient_map.gradient": "Gradient",
"value_source.css_extract.source": "Color Strip Source",
"value_source.ha_source": "HA Connection:",
"value_source.ha_source.hint": "Home Assistant connection to read entities from",
"value_source.entity_id": "Entity:",