v_2.1
This commit is contained in:
parent
d5a4eb33ef
commit
181b64c590
6 changed files with 1242 additions and 1046 deletions
125
dist/power-flux-card.js
vendored
125
dist/power-flux-card.js
vendored
|
|
@ -14,6 +14,7 @@ const lang_de = {
|
|||
"editor.consumers_section": "Zusätzliche Verbraucher",
|
||||
"editor.options_section": "Darstellung & Optionen",
|
||||
"editor.flow_rate_title": "Flussraten (W) an Röhren anzeigen",
|
||||
"editor.invert_battery": "Wert umkehren (+/-)",
|
||||
"editor.label_toggle": "Label im Kreis anzeigen",
|
||||
"editor.compact_view": "Kompakte Ansicht (evcc)",
|
||||
"editor.hide_inactive": "Inaktive Röhren ausblenden",
|
||||
|
|
@ -42,6 +43,7 @@ const lang_en = {
|
|||
"editor.consumers_section": "Additional Consumers",
|
||||
"editor.options_section": "Appearance & Options",
|
||||
"editor.flow_rate_title": "Show Flow Rates (W) on pipes",
|
||||
"editor.invert_battery": "Invert Power Value (+/-)",
|
||||
"editor.label_toggle": "Show Label in Bubble",
|
||||
"editor.compact_view": "Compact View (evcc)",
|
||||
"editor.hide_inactive": "Hide Inactive Pipes",
|
||||
|
|
@ -69,6 +71,19 @@ cardTranslations['en'] = lang_en.card;
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const editorTranslations = {
|
||||
"en": lang_en.editor,
|
||||
"de": lang_de.editor
|
||||
};
|
||||
|
||||
const cardTranslations = {
|
||||
"en": lang_en.card,
|
||||
"de": lang_de.card
|
||||
};
|
||||
|
||||
const fireEvent = (node, type, detail, options) => {
|
||||
options = options || {};
|
||||
detail = detail === null || detail === undefined ? {} : detail;
|
||||
|
|
@ -129,6 +144,7 @@ class PowerFluxCardEditor extends LitElement {
|
|||
const entityKeys = [
|
||||
'solar', 'grid', 'grid_export',
|
||||
'battery', 'battery_soc',
|
||||
'house',
|
||||
'consumer_1', 'consumer_2', 'consumer_3'
|
||||
];
|
||||
|
||||
|
|
@ -440,6 +456,15 @@ class PowerFluxCardEditor extends LitElement {
|
|||
></ha-switch>
|
||||
<div class="switch-label">${this._localize('editor.flow_rate_title')}</div>
|
||||
</div>
|
||||
|
||||
<div class="switch-row">
|
||||
<ha-switch
|
||||
.checked=${this._config.invert_battery === true}
|
||||
.configValue=${'invert_battery'}
|
||||
@change=${this._valueChanged}
|
||||
></ha-switch>
|
||||
<div class="switch-label">${this._localize('editor.invert_battery')}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
|
|
@ -452,6 +477,21 @@ class PowerFluxCardEditor extends LitElement {
|
|||
<h2>${this._localize('editor.consumers_section')}</h2>
|
||||
</div>
|
||||
|
||||
<div class="consumer-group">
|
||||
<div class="consumer-title">🏠 Gesamthausverbrauch (Optional)</div>
|
||||
<ha-selector
|
||||
.hass=${this.hass}
|
||||
.selector=${entitySelectorSchema}
|
||||
.value=${entities.house || ""}
|
||||
.configValue=${'house'}
|
||||
.label=${'Sensor für Hausverbrauch (Optional)'}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-selector>
|
||||
<div style="font-size: 0.8em; color: var(--secondary-text-color); margin-top: 4px;">
|
||||
Wird benötigt, damit das Haus-Icon anklickbar ist.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="consumer-group">
|
||||
<div class="consumer-title" style="color: #a855f7;">🚗 Links (Lila)</div>
|
||||
<ha-selector
|
||||
|
|
@ -681,13 +721,26 @@ class PowerFluxCardEditor extends LitElement {
|
|||
customElements.define("power-flux-card-editor", PowerFluxCardEditor);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
console.log(
|
||||
"%c⚡ Power-Flux-Card v_2.0 ready",
|
||||
"background: #2ecc71; color: #000; padding: 2px 6px; border-radius: 4px; font-weight: bold;"
|
||||
"%c⚡ Power Flux Card v_2.1 ready",
|
||||
"background: #d19525ff; color: #000; padding: 2px 6px; border-radius: 4px; font-weight: bold;"
|
||||
);
|
||||
|
||||
class PowerFluxCard extends LitElement {
|
||||
(function () {
|
||||
const cardTranslations = {
|
||||
"en": lang_en.card,
|
||||
"de": lang_de.card
|
||||
};
|
||||
|
||||
const LitElement = customElements.get("ha-lit-element") || Object.getPrototypeOf(customElements.get("home-assistant-main"));
|
||||
const html = LitElement.prototype.html;
|
||||
const css = LitElement.prototype.css;
|
||||
|
||||
class PowerFluxCard extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
hass: {},
|
||||
|
|
@ -731,6 +784,7 @@ class PowerFluxCard extends LitElement {
|
|||
grid_export: "",
|
||||
battery: "",
|
||||
battery_soc: "",
|
||||
house: "",
|
||||
consumer_1: "",
|
||||
consumer_2: "",
|
||||
consumer_3: ""
|
||||
|
|
@ -738,6 +792,16 @@ class PowerFluxCard extends LitElement {
|
|||
};
|
||||
}
|
||||
|
||||
_handleClick(entityId) {
|
||||
if (!entityId) return;
|
||||
const event = new Event("hass-more-info", {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
});
|
||||
event.detail = { entityId };
|
||||
this.dispatchEvent(event);
|
||||
}
|
||||
|
||||
setConfig(config) {
|
||||
if (!config.entities) {
|
||||
// Init allow
|
||||
|
|
@ -948,7 +1012,7 @@ class PowerFluxCard extends LitElement {
|
|||
.node-c2 { top: 370px; left: 165px; }
|
||||
.node-c3 { top: 370px; left: 325px; }
|
||||
|
||||
svg { position: absolute; top: 7; left: 25; width: 100%; height: 100%; z-index: 1; pointer-events: none; }
|
||||
svg { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1; pointer-events: none; }
|
||||
|
||||
.bg-path { fill: none; stroke-width: 6; transition: opacity 0.3s ease; }
|
||||
.bg-solar { stroke: var(--neon-yellow); }
|
||||
|
|
@ -1081,7 +1145,10 @@ class PowerFluxCard extends LitElement {
|
|||
const solar = entities.solar ? Math.max(0, getVal(entities.solar)) : 0;
|
||||
const gridMain = entities.grid ? getVal(entities.grid) : 0;
|
||||
const gridExportSensor = entities.grid_export ? getVal(entities.grid_export) : 0;
|
||||
const battery = entities.battery ? getVal(entities.battery) : 0;
|
||||
let battery = entities.battery ? getVal(entities.battery) : 0;
|
||||
if (this.config.invert_battery) {
|
||||
battery *= -1;
|
||||
}
|
||||
const c1Val = entities.consumer_1 ? getVal(entities.consumer_1) : 0; // EV Value
|
||||
|
||||
// 2. Logic Calculation
|
||||
|
|
@ -1159,7 +1226,7 @@ class PowerFluxCard extends LitElement {
|
|||
const barSegments = [];
|
||||
let currentX = 0;
|
||||
|
||||
const addSegment = (val, color, type, label) => {
|
||||
const addSegment = (val, color, type, label, entityId) => {
|
||||
if (val <= threshold) return;
|
||||
const pct = val / totalFlux;
|
||||
const width = pct * fullWidth;
|
||||
|
|
@ -1170,14 +1237,15 @@ class PowerFluxCard extends LitElement {
|
|||
widthPx: width,
|
||||
startPx: currentX,
|
||||
type,
|
||||
label
|
||||
label,
|
||||
entityId
|
||||
});
|
||||
currentX += width;
|
||||
}
|
||||
|
||||
addSegment(srcBattery, 'var(--neon-yellow)', 'battery', 'battery');
|
||||
addSegment(srcSolar, 'var(--neon-green)', 'solar', 'solar');
|
||||
addSegment(srcGrid, 'var(--grid-grey)', 'grid', 'grid');
|
||||
addSegment(srcBattery, 'var(--neon-yellow)', 'battery', 'battery', entities.battery);
|
||||
addSegment(srcSolar, 'var(--neon-green)', 'solar', 'solar', entities.solar);
|
||||
addSegment(srcGrid, 'var(--grid-grey)', 'grid', 'grid', entities.grid);
|
||||
|
||||
// --- GENERATE TOP BRACKETS (Based on Bar Segments) ---
|
||||
const topBrackets = barSegments.map(s => {
|
||||
|
|
@ -1188,7 +1256,7 @@ class PowerFluxCard extends LitElement {
|
|||
if (s.type === 'grid') { icon = 'mdi:transmission-tower'; iconColor = 'var(--grid-grey)'; }
|
||||
if (s.type === 'battery') { icon = 'mdi:battery-high'; iconColor = 'var(--neon-yellow)'; }
|
||||
|
||||
return { path, width: s.widthPx, center: s.startPx + (s.widthPx/2), icon, iconColor };
|
||||
return { path, width: s.widthPx, center: s.startPx + (s.widthPx / 2), icon, iconColor, val: s.val, entityId: s.entityId };
|
||||
});
|
||||
|
||||
// --- GENERATE BOTTOM BRACKETS (Independent Calculation) ---
|
||||
|
|
@ -1196,7 +1264,7 @@ class PowerFluxCard extends LitElement {
|
|||
const bottomBrackets = [];
|
||||
let bottomX = 0;
|
||||
|
||||
const addBottomBracket = (val, type) => {
|
||||
const addBottomBracket = (val, type, entityId = null) => {
|
||||
if (val <= threshold) return;
|
||||
const pct = val / totalFlux;
|
||||
const width = pct * fullWidth;
|
||||
|
|
@ -1207,6 +1275,7 @@ class PowerFluxCard extends LitElement {
|
|||
if (type === 'house') { icon = 'mdi:home'; iconColor = 'var(--primary-text-color)'; }
|
||||
if (type === 'car') { icon = 'mdi:car-electric'; iconColor = '#a855f7'; }
|
||||
if (type === 'export') { icon = 'mdi:arrow-right-box'; iconColor = 'var(--export-purple)'; }
|
||||
if (type === 'battery') { icon = 'mdi:battery-charging-high'; iconColor = 'var(--neon-green)'; }
|
||||
|
||||
const path = this._createBracketPath(bottomX, width, 'up');
|
||||
bottomBrackets.push({
|
||||
|
|
@ -1214,14 +1283,17 @@ class PowerFluxCard extends LitElement {
|
|||
width: width,
|
||||
center: bottomX + (width / 2),
|
||||
icon,
|
||||
iconColor
|
||||
iconColor,
|
||||
val,
|
||||
entityId
|
||||
});
|
||||
bottomX += width;
|
||||
};
|
||||
|
||||
addBottomBracket(destHouse, 'house');
|
||||
addBottomBracket(destEV, 'car');
|
||||
addBottomBracket(destExport, 'export');
|
||||
addBottomBracket(destHouse, 'house', entities.house);
|
||||
addBottomBracket(destEV, 'car', entities.consumer_1);
|
||||
addBottomBracket(destExport, 'export', entities.grid_export || entities.grid);
|
||||
addBottomBracket(batteryCharge, 'battery', entities.battery);
|
||||
|
||||
// Note: If there is Battery Charging happening, bottomX will not reach fullWidth.
|
||||
// This leaves a gap at the end (or between segments depending on logic), which is visually correct
|
||||
|
|
@ -1236,7 +1308,10 @@ class PowerFluxCard extends LitElement {
|
|||
${topBrackets.map(b => this._renderSVGPath(b.path, b.iconColor))}
|
||||
</svg>
|
||||
${topBrackets.map(b => b.width > 20 ? html`
|
||||
<div class="compact-icon-wrapper" style="left: ${b.center}px; transform: translateX(-50%); top: 4px;">
|
||||
<div class="compact-icon-wrapper"
|
||||
style="left: ${b.center}px; transform: translateX(-50%); top: 4px; cursor: ${b.entityId ? 'pointer' : 'default'};"
|
||||
title="${this._formatPower(b.val)}"
|
||||
@click=${() => b.entityId && this._handleClick(b.entityId)}>
|
||||
<ha-icon icon="${b.icon}" class="compact-icon" style="color: ${b.iconColor};"></ha-icon>
|
||||
</div>` : '')}
|
||||
</div>
|
||||
|
|
@ -1244,7 +1319,10 @@ class PowerFluxCard extends LitElement {
|
|||
<!-- MAIN BAR -->
|
||||
<div class="compact-bar-wrapper">
|
||||
${barSegments.map(s => html`
|
||||
<div class="bar-segment" style="width: ${s.widthPct}%; background: ${s.color}; color: ${s.color === 'var(--export-purple)' ? 'white' : 'black'};">
|
||||
<div class="bar-segment"
|
||||
style="width: ${s.widthPct}%; background: ${s.color}; color: ${s.color === 'var(--export-purple)' ? 'white' : 'black'}; cursor: ${s.entityId ? 'pointer' : 'default'};"
|
||||
title="${this._formatPower(s.val)}"
|
||||
@click=${() => s.entityId && this._handleClick(s.entityId)}>
|
||||
${s.widthPx > 35 ? this._formatPower(s.val) : ''}
|
||||
</div>
|
||||
`)}
|
||||
|
|
@ -1256,7 +1334,10 @@ class PowerFluxCard extends LitElement {
|
|||
${bottomBrackets.map(b => this._renderSVGPath(b.path, b.iconColor))}
|
||||
</svg>
|
||||
${bottomBrackets.map(b => b.width > 20 ? html`
|
||||
<div class="compact-icon-wrapper" style="left: ${b.center}px; transform: translateX(-50%); top: -3px;">
|
||||
<div class="compact-icon-wrapper"
|
||||
style="left: ${b.center}px; transform: translateX(-50%); top: -3px; cursor: ${b.entityId ? 'pointer' : 'default'};"
|
||||
title="${this._formatPower(b.val)}"
|
||||
@click=${() => b.entityId && this._handleClick(b.entityId)}>
|
||||
<ha-icon icon="${b.icon}" class="compact-icon" style="color: ${b.iconColor};"></ha-icon>
|
||||
</div>` : '')}
|
||||
</div>
|
||||
|
|
@ -1337,7 +1418,10 @@ class PowerFluxCard extends LitElement {
|
|||
const solar = hasSolar ? getVal(entities.solar) : 0;
|
||||
const gridMain = hasGrid ? getVal(entities.grid) : 0;
|
||||
const gridExpSensor = (hasGrid && entities.grid_export) ? getVal(entities.grid_export) : 0;
|
||||
const battery = hasBattery ? getVal(entities.battery) : 0;
|
||||
let battery = hasBattery ? getVal(entities.battery) : 0;
|
||||
if (this.config.invert_battery) {
|
||||
battery *= -1;
|
||||
}
|
||||
const battSoc = (hasBattery && entities.battery_soc) ? getVal(entities.battery_soc) : 0;
|
||||
|
||||
const solarVal = Math.max(0, solar);
|
||||
|
|
@ -1655,6 +1739,7 @@ class PowerFluxCard extends LitElement {
|
|||
}
|
||||
|
||||
customElements.define("power-flux-card", PowerFluxCard);
|
||||
})();
|
||||
|
||||
window.customCards = window.customCards || [];
|
||||
window.customCards.push({
|
||||
|
|
|
|||
|
|
@ -9,12 +9,28 @@ export default {
|
|||
"editor.consumers_section": "Zusätzliche Verbraucher",
|
||||
"editor.options_section": "Darstellung & Optionen",
|
||||
"editor.flow_rate_title": "Flussraten (W) an Röhren anzeigen",
|
||||
"editor.invert_battery": "Wert umkehren (+/-)",
|
||||
"editor.label_toggle": "Label im Kreis anzeigen",
|
||||
"editor.compact_view": "Kompakte Ansicht (evcc)",
|
||||
"editor.hide_inactive": "Inaktive Röhren ausblenden",
|
||||
"editor.entity": "Entität (Watt)",
|
||||
"editor.label": "Beschriftung",
|
||||
"editor.icon": "Icon",
|
||||
"editor.back": "Zurück",
|
||||
"editor.battery_soc_label": "Ladestand (%)",
|
||||
"editor.house_total_title": "🏠 Gesamtverbrauch (optional)",
|
||||
"editor.house_sensor_label": "Sensor für Hausverbrauch (optional)",
|
||||
"editor.house_sensor_hint": "Wird benötigt, damit das Haus-Icon anklickbar ist.",
|
||||
"editor.consumer_1_title": "🚗 Links (Lila)",
|
||||
"editor.consumer_2_title": "♨️ Mitte (Orange)",
|
||||
"editor.consumer_3_title": "🏊 Rechts (Türkis)",
|
||||
"editor.zoom_label": "🔍 Zoom (Standard View)",
|
||||
"editor.neon_glow": "Neon Glow",
|
||||
"editor.donut_chart": "Donut Chart (Grid/Haus)",
|
||||
"editor.comet_tail": "Comet Tail Effect",
|
||||
"editor.dashed_line": "Dashed Line Effect",
|
||||
"editor.colored_values": "Farbige Textwerte",
|
||||
"editor.hide_consumer_icons": "Icons unten ausblenden",
|
||||
},
|
||||
card: {
|
||||
"card.label_solar": "Solar",
|
||||
|
|
|
|||
|
|
@ -9,12 +9,28 @@ export default {
|
|||
"editor.consumers_section": "Additional Consumers",
|
||||
"editor.options_section": "Appearance & Options",
|
||||
"editor.flow_rate_title": "Show Flow Rates (W) on pipes",
|
||||
"editor.invert_battery": "Invert Power Value (+/-)",
|
||||
"editor.label_toggle": "Show Label in Bubble",
|
||||
"editor.compact_view": "Compact View (evcc)",
|
||||
"editor.hide_inactive": "Hide Inactive Pipes",
|
||||
"editor.entity": "Entity (Watt)",
|
||||
"editor.label": "Label",
|
||||
"editor.icon": "Icon",
|
||||
"editor.back": "Back",
|
||||
"editor.battery_soc_label": "State of Charge (%)",
|
||||
"editor.house_total_title": "🏠 Total Consumption (optional)",
|
||||
"editor.house_sensor_label": "Sensor for House Consumption (optional)",
|
||||
"editor.house_sensor_hint": "Required to make the house icon clickable.",
|
||||
"editor.consumer_1_title": "🚗 Left (Purple)",
|
||||
"editor.consumer_2_title": "♨️ Center (Orange)",
|
||||
"editor.consumer_3_title": "🏊 Right (Cyan)",
|
||||
"editor.zoom_label": "🔍 Zoom (Standard View)",
|
||||
"editor.neon_glow": "Neon Glow",
|
||||
"editor.donut_chart": "Donut Chart (Grid/House)",
|
||||
"editor.comet_tail": "Comet Tail Effect",
|
||||
"editor.dashed_line": "Dashed Line Effect",
|
||||
"editor.colored_values": "Colored Text Values",
|
||||
"editor.hide_consumer_icons": "Hide Consumer Icons",
|
||||
},
|
||||
card: {
|
||||
"card.label_solar": "Solar",
|
||||
|
|
|
|||
|
|
@ -1,3 +1,12 @@
|
|||
import lang_en from "./lang-en.js";
|
||||
import lang_de from "./lang-de.js";
|
||||
|
||||
const editorTranslations = {
|
||||
"en": lang_en.editor,
|
||||
"de": lang_de.editor
|
||||
};
|
||||
|
||||
|
||||
const fireEvent = (node, type, detail, options) => {
|
||||
options = options || {};
|
||||
detail = detail === null || detail === undefined ? {} : detail;
|
||||
|
|
@ -58,6 +67,7 @@ class PowerFluxCardEditor extends LitElement {
|
|||
const entityKeys = [
|
||||
'solar', 'grid', 'grid_export',
|
||||
'battery', 'battery_soc',
|
||||
'house',
|
||||
'consumer_1', 'consumer_2', 'consumer_3'
|
||||
];
|
||||
|
||||
|
|
@ -178,7 +188,7 @@ class PowerFluxCardEditor extends LitElement {
|
|||
return html`
|
||||
<div class="header">
|
||||
<div class="back-btn" @click=${this._goBack}>
|
||||
<ha-icon icon="mdi:arrow-left"></ha-icon> Zurück
|
||||
<ha-icon icon="mdi:arrow-left"></ha-icon> ${this._localize('editor.back')}
|
||||
</div>
|
||||
<h2>${this._localize('editor.solar_section')}</h2>
|
||||
</div>
|
||||
|
|
@ -216,7 +226,7 @@ class PowerFluxCardEditor extends LitElement {
|
|||
|
||||
<div class="switch-row">
|
||||
<ha-switch
|
||||
.checked=${this._config.show_label_solar !== false}
|
||||
.checked=${this._config.show_label_solar === true}
|
||||
.configValue=${'show_label_solar'}
|
||||
@change=${this._valueChanged}
|
||||
></ha-switch>
|
||||
|
|
@ -238,7 +248,7 @@ class PowerFluxCardEditor extends LitElement {
|
|||
return html`
|
||||
<div class="header">
|
||||
<div class="back-btn" @click=${this._goBack}>
|
||||
<ha-icon icon="mdi:arrow-left"></ha-icon> Zurück
|
||||
<ha-icon icon="mdi:arrow-left"></ha-icon> ${this._localize('editor.back')}
|
||||
</div>
|
||||
<h2>${this._localize('editor.grid_section')}</h2>
|
||||
</div>
|
||||
|
|
@ -285,7 +295,7 @@ class PowerFluxCardEditor extends LitElement {
|
|||
|
||||
<div class="switch-row">
|
||||
<ha-switch
|
||||
.checked=${this._config.show_label_grid !== false}
|
||||
.checked=${this._config.show_label_grid === true}
|
||||
.configValue=${'show_label_grid'}
|
||||
@change=${this._valueChanged}
|
||||
></ha-switch>
|
||||
|
|
@ -307,7 +317,7 @@ class PowerFluxCardEditor extends LitElement {
|
|||
return html`
|
||||
<div class="header">
|
||||
<div class="back-btn" @click=${this._goBack}>
|
||||
<ha-icon icon="mdi:arrow-left"></ha-icon> Zurück
|
||||
<ha-icon icon="mdi:arrow-left"></ha-icon> ${this._localize('editor.back')}
|
||||
</div>
|
||||
<h2>${this._localize('editor.battery_section')}</h2>
|
||||
</div>
|
||||
|
|
@ -326,7 +336,7 @@ class PowerFluxCardEditor extends LitElement {
|
|||
.selector=${entitySelectorSchema}
|
||||
.value=${entities.battery_soc}
|
||||
.configValue=${'battery_soc'}
|
||||
.label=${"Ladestand (%)"}
|
||||
.label=${this._localize('editor.battery_soc_label')}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-selector>
|
||||
|
||||
|
|
@ -354,7 +364,7 @@ class PowerFluxCardEditor extends LitElement {
|
|||
|
||||
<div class="switch-row">
|
||||
<ha-switch
|
||||
.checked=${this._config.show_label_battery !== false}
|
||||
.checked=${this._config.show_label_battery === true}
|
||||
.configValue=${'show_label_battery'}
|
||||
@change=${this._valueChanged}
|
||||
></ha-switch>
|
||||
|
|
@ -369,6 +379,15 @@ class PowerFluxCardEditor extends LitElement {
|
|||
></ha-switch>
|
||||
<div class="switch-label">${this._localize('editor.flow_rate_title')}</div>
|
||||
</div>
|
||||
|
||||
<div class="switch-row">
|
||||
<ha-switch
|
||||
.checked=${this._config.invert_battery === true}
|
||||
.configValue=${'invert_battery'}
|
||||
@change=${this._valueChanged}
|
||||
></ha-switch>
|
||||
<div class="switch-label">${this._localize('editor.invert_battery')}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
|
|
@ -376,13 +395,28 @@ class PowerFluxCardEditor extends LitElement {
|
|||
return html`
|
||||
<div class="header">
|
||||
<div class="back-btn" @click=${this._goBack}>
|
||||
<ha-icon icon="mdi:arrow-left"></ha-icon> Zurück
|
||||
<ha-icon icon="mdi:arrow-left"></ha-icon> ${this._localize('editor.back')}
|
||||
</div>
|
||||
<h2>${this._localize('editor.consumers_section')}</h2>
|
||||
</div>
|
||||
|
||||
<div class="consumer-group">
|
||||
<div class="consumer-title" style="color: #a855f7;">🚗 Links (Lila)</div>
|
||||
<div class="consumer-title">${this._localize('editor.house_total_title')}</div>
|
||||
<ha-selector
|
||||
.hass=${this.hass}
|
||||
.selector=${entitySelectorSchema}
|
||||
.value=${entities.house || ""}
|
||||
.configValue=${'house'}
|
||||
.label=${this._localize('editor.house_sensor_label')}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-selector>
|
||||
<div style="font-size: 0.8em; color: var(--secondary-text-color); margin-top: 4px;">
|
||||
${this._localize('editor.house_sensor_hint')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="consumer-group">
|
||||
<div class="consumer-title" style="color: #a855f7;">${this._localize('editor.consumer_1_title')}</div>
|
||||
<ha-selector
|
||||
.hass=${this.hass}
|
||||
.selector=${entitySelectorSchema}
|
||||
|
|
@ -412,7 +446,7 @@ class PowerFluxCardEditor extends LitElement {
|
|||
</div>
|
||||
|
||||
<div class="consumer-group">
|
||||
<div class="consumer-title" style="color: #f97316;">♨️ Mitte (Orange)</div>
|
||||
<div class="consumer-title" style="color: #f97316;">${this._localize('editor.consumer_2_title')}</div>
|
||||
<ha-selector
|
||||
.hass=${this.hass}
|
||||
.selector=${entitySelectorSchema}
|
||||
|
|
@ -442,7 +476,7 @@ class PowerFluxCardEditor extends LitElement {
|
|||
</div>
|
||||
|
||||
<div class="consumer-group">
|
||||
<div class="consumer-title" style="color: #06b6d4;">🏊 Rechts (Türkis)</div>
|
||||
<div class="consumer-title" style="color: #06b6d4;">${this._localize('editor.consumer_3_title')}</div>
|
||||
<ha-selector
|
||||
.hass=${this.hass}
|
||||
.selector=${entitySelectorSchema}
|
||||
|
|
@ -525,7 +559,7 @@ class PowerFluxCardEditor extends LitElement {
|
|||
.selector=${{ number: { min: 0.5, max: 1.5, step: 0.05, mode: "slider" } }}
|
||||
.value=${this._config.zoom !== undefined ? this._config.zoom : 0.9}
|
||||
.configValue=${'zoom'}
|
||||
.label=${"🔍 Zoom (Standard View)"}
|
||||
.label=${this._localize('editor.zoom_label')}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-selector>
|
||||
</div>
|
||||
|
|
@ -536,7 +570,7 @@ class PowerFluxCardEditor extends LitElement {
|
|||
.configValue=${'show_neon_glow'}
|
||||
@change=${this._valueChanged}
|
||||
></ha-switch>
|
||||
<div class="switch-label">Neon Glow</div>
|
||||
<div class="switch-label">${this._localize('editor.neon_glow')}</div>
|
||||
</div>
|
||||
|
||||
<div class="switch-row">
|
||||
|
|
@ -545,7 +579,7 @@ class PowerFluxCardEditor extends LitElement {
|
|||
.configValue=${'show_donut_border'}
|
||||
@change=${this._valueChanged}
|
||||
></ha-switch>
|
||||
<div class="switch-label">Donut Chart (Grid/Haus)</div>
|
||||
<div class="switch-label">${this._localize('editor.donut_chart')}</div>
|
||||
</div>
|
||||
|
||||
<div class="switch-row">
|
||||
|
|
@ -554,7 +588,7 @@ class PowerFluxCardEditor extends LitElement {
|
|||
.configValue=${'show_comet_tail'}
|
||||
@change=${this._valueChanged}
|
||||
></ha-switch>
|
||||
<div class="switch-label">Comet Tail Effect</div>
|
||||
<div class="switch-label">${this._localize('editor.comet_tail')}</div>
|
||||
</div>
|
||||
|
||||
<div class="switch-row">
|
||||
|
|
@ -563,7 +597,7 @@ class PowerFluxCardEditor extends LitElement {
|
|||
.configValue=${'show_dashed_line'}
|
||||
@change=${this._valueChanged}
|
||||
></ha-switch>
|
||||
<div class="switch-label">Dashed Line Animation</div>
|
||||
<div class="switch-label">${this._localize('editor.dashed_line')}</div>
|
||||
</div>
|
||||
|
||||
<div class="switch-row">
|
||||
|
|
@ -572,7 +606,7 @@ class PowerFluxCardEditor extends LitElement {
|
|||
.configValue=${'use_colored_values'}
|
||||
@change=${this._valueChanged}
|
||||
></ha-switch>
|
||||
<div class="switch-label">Farbige Textwerte</div>
|
||||
<div class="switch-label">${this._localize('editor.colored_values')}</div>
|
||||
</div>
|
||||
|
||||
<div class="switch-row">
|
||||
|
|
@ -581,7 +615,7 @@ class PowerFluxCardEditor extends LitElement {
|
|||
.configValue=${'hide_consumer_icons'}
|
||||
@change=${this._valueChanged}
|
||||
></ha-switch>
|
||||
<div class="switch-label">Icons unten ausblenden</div>
|
||||
<div class="switch-label">${this._localize('editor.hide_consumer_icons')}</div>
|
||||
</div>
|
||||
|
||||
<div class="switch-row">
|
||||
|
|
|
|||
|
|
@ -1,10 +1,23 @@
|
|||
import { } from "./power-flux-card-editor.js";
|
||||
import lang_en from "./lang-en.js";
|
||||
import lang_de from "./lang-de.js";
|
||||
|
||||
console.log(
|
||||
"%c⚡ Power-Flux-Card v_2.0 ready",
|
||||
"background: #2ecc71; color: #000; padding: 2px 6px; border-radius: 4px; font-weight: bold;"
|
||||
"%c⚡ Power Flux Card v_2.1 ready",
|
||||
"background: #d19525ff; color: #000; padding: 2px 6px; border-radius: 4px; font-weight: bold;"
|
||||
);
|
||||
|
||||
class PowerFluxCard extends LitElement {
|
||||
(function (lang_en, lang_de) {
|
||||
const cardTranslations = {
|
||||
"en": lang_en.card,
|
||||
"de": lang_de.card
|
||||
};
|
||||
|
||||
const LitElement = customElements.get("ha-lit-element") || Object.getPrototypeOf(customElements.get("home-assistant-main"));
|
||||
const html = LitElement.prototype.html;
|
||||
const css = LitElement.prototype.css;
|
||||
|
||||
class PowerFluxCard extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
hass: {},
|
||||
|
|
@ -48,6 +61,7 @@ class PowerFluxCard extends LitElement {
|
|||
grid_export: "",
|
||||
battery: "",
|
||||
battery_soc: "",
|
||||
house: "",
|
||||
consumer_1: "",
|
||||
consumer_2: "",
|
||||
consumer_3: ""
|
||||
|
|
@ -55,6 +69,16 @@ class PowerFluxCard extends LitElement {
|
|||
};
|
||||
}
|
||||
|
||||
_handleClick(entityId) {
|
||||
if (!entityId) return;
|
||||
const event = new Event("hass-more-info", {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
});
|
||||
event.detail = { entityId };
|
||||
this.dispatchEvent(event);
|
||||
}
|
||||
|
||||
setConfig(config) {
|
||||
if (!config.entities) {
|
||||
// Init allow
|
||||
|
|
@ -265,7 +289,7 @@ class PowerFluxCard extends LitElement {
|
|||
.node-c2 { top: 370px; left: 165px; }
|
||||
.node-c3 { top: 370px; left: 325px; }
|
||||
|
||||
svg { position: absolute; top: 7; left: 25; width: 100%; height: 100%; z-index: 1; pointer-events: none; }
|
||||
svg { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1; pointer-events: none; }
|
||||
|
||||
.bg-path { fill: none; stroke-width: 6; transition: opacity 0.3s ease; }
|
||||
.bg-solar { stroke: var(--neon-yellow); }
|
||||
|
|
@ -398,7 +422,10 @@ class PowerFluxCard extends LitElement {
|
|||
const solar = entities.solar ? Math.max(0, getVal(entities.solar)) : 0;
|
||||
const gridMain = entities.grid ? getVal(entities.grid) : 0;
|
||||
const gridExportSensor = entities.grid_export ? getVal(entities.grid_export) : 0;
|
||||
const battery = entities.battery ? getVal(entities.battery) : 0;
|
||||
let battery = entities.battery ? getVal(entities.battery) : 0;
|
||||
if (this.config.invert_battery) {
|
||||
battery *= -1;
|
||||
}
|
||||
const c1Val = entities.consumer_1 ? getVal(entities.consumer_1) : 0; // EV Value
|
||||
|
||||
// 2. Logic Calculation
|
||||
|
|
@ -476,7 +503,7 @@ class PowerFluxCard extends LitElement {
|
|||
const barSegments = [];
|
||||
let currentX = 0;
|
||||
|
||||
const addSegment = (val, color, type, label) => {
|
||||
const addSegment = (val, color, type, label, entityId) => {
|
||||
if (val <= threshold) return;
|
||||
const pct = val / totalFlux;
|
||||
const width = pct * fullWidth;
|
||||
|
|
@ -487,14 +514,15 @@ class PowerFluxCard extends LitElement {
|
|||
widthPx: width,
|
||||
startPx: currentX,
|
||||
type,
|
||||
label
|
||||
label,
|
||||
entityId
|
||||
});
|
||||
currentX += width;
|
||||
}
|
||||
|
||||
addSegment(srcBattery, 'var(--neon-yellow)', 'battery', 'battery');
|
||||
addSegment(srcSolar, 'var(--neon-green)', 'solar', 'solar');
|
||||
addSegment(srcGrid, 'var(--grid-grey)', 'grid', 'grid');
|
||||
addSegment(srcBattery, 'var(--neon-yellow)', 'battery', 'battery', entities.battery);
|
||||
addSegment(srcSolar, 'var(--neon-green)', 'solar', 'solar', entities.solar);
|
||||
addSegment(srcGrid, 'var(--grid-grey)', 'grid', 'grid', entities.grid);
|
||||
|
||||
// --- GENERATE TOP BRACKETS (Based on Bar Segments) ---
|
||||
const topBrackets = barSegments.map(s => {
|
||||
|
|
@ -505,7 +533,7 @@ class PowerFluxCard extends LitElement {
|
|||
if (s.type === 'grid') { icon = 'mdi:transmission-tower'; iconColor = 'var(--grid-grey)'; }
|
||||
if (s.type === 'battery') { icon = 'mdi:battery-high'; iconColor = 'var(--neon-yellow)'; }
|
||||
|
||||
return { path, width: s.widthPx, center: s.startPx + (s.widthPx/2), icon, iconColor };
|
||||
return { path, width: s.widthPx, center: s.startPx + (s.widthPx / 2), icon, iconColor, val: s.val, entityId: s.entityId };
|
||||
});
|
||||
|
||||
// --- GENERATE BOTTOM BRACKETS (Independent Calculation) ---
|
||||
|
|
@ -513,7 +541,7 @@ class PowerFluxCard extends LitElement {
|
|||
const bottomBrackets = [];
|
||||
let bottomX = 0;
|
||||
|
||||
const addBottomBracket = (val, type) => {
|
||||
const addBottomBracket = (val, type, entityId = null) => {
|
||||
if (val <= threshold) return;
|
||||
const pct = val / totalFlux;
|
||||
const width = pct * fullWidth;
|
||||
|
|
@ -524,6 +552,7 @@ class PowerFluxCard extends LitElement {
|
|||
if (type === 'house') { icon = 'mdi:home'; iconColor = 'var(--primary-text-color)'; }
|
||||
if (type === 'car') { icon = 'mdi:car-electric'; iconColor = '#a855f7'; }
|
||||
if (type === 'export') { icon = 'mdi:arrow-right-box'; iconColor = 'var(--export-purple)'; }
|
||||
if (type === 'battery') { icon = 'mdi:battery-charging-high'; iconColor = 'var(--neon-green)'; }
|
||||
|
||||
const path = this._createBracketPath(bottomX, width, 'up');
|
||||
bottomBrackets.push({
|
||||
|
|
@ -531,14 +560,17 @@ class PowerFluxCard extends LitElement {
|
|||
width: width,
|
||||
center: bottomX + (width / 2),
|
||||
icon,
|
||||
iconColor
|
||||
iconColor,
|
||||
val,
|
||||
entityId
|
||||
});
|
||||
bottomX += width;
|
||||
};
|
||||
|
||||
addBottomBracket(destHouse, 'house');
|
||||
addBottomBracket(destEV, 'car');
|
||||
addBottomBracket(destExport, 'export');
|
||||
addBottomBracket(destHouse, 'house', entities.house);
|
||||
addBottomBracket(destEV, 'car', entities.consumer_1);
|
||||
addBottomBracket(destExport, 'export', entities.grid_export || entities.grid);
|
||||
addBottomBracket(batteryCharge, 'battery', entities.battery);
|
||||
|
||||
// Note: If there is Battery Charging happening, bottomX will not reach fullWidth.
|
||||
// This leaves a gap at the end (or between segments depending on logic), which is visually correct
|
||||
|
|
@ -553,7 +585,10 @@ class PowerFluxCard extends LitElement {
|
|||
${topBrackets.map(b => this._renderSVGPath(b.path, b.iconColor))}
|
||||
</svg>
|
||||
${topBrackets.map(b => b.width > 20 ? html`
|
||||
<div class="compact-icon-wrapper" style="left: ${b.center}px; transform: translateX(-50%); top: 4px;">
|
||||
<div class="compact-icon-wrapper"
|
||||
style="left: ${b.center}px; transform: translateX(-50%); top: 4px; cursor: ${b.entityId ? 'pointer' : 'default'};"
|
||||
title="${this._formatPower(b.val)}"
|
||||
@click=${() => b.entityId && this._handleClick(b.entityId)}>
|
||||
<ha-icon icon="${b.icon}" class="compact-icon" style="color: ${b.iconColor};"></ha-icon>
|
||||
</div>` : '')}
|
||||
</div>
|
||||
|
|
@ -561,7 +596,10 @@ class PowerFluxCard extends LitElement {
|
|||
<!-- MAIN BAR -->
|
||||
<div class="compact-bar-wrapper">
|
||||
${barSegments.map(s => html`
|
||||
<div class="bar-segment" style="width: ${s.widthPct}%; background: ${s.color}; color: ${s.color === 'var(--export-purple)' ? 'white' : 'black'};">
|
||||
<div class="bar-segment"
|
||||
style="width: ${s.widthPct}%; background: ${s.color}; color: ${s.color === 'var(--export-purple)' ? 'white' : 'black'}; cursor: ${s.entityId ? 'pointer' : 'default'};"
|
||||
title="${this._formatPower(s.val)}"
|
||||
@click=${() => s.entityId && this._handleClick(s.entityId)}>
|
||||
${s.widthPx > 35 ? this._formatPower(s.val) : ''}
|
||||
</div>
|
||||
`)}
|
||||
|
|
@ -573,7 +611,10 @@ class PowerFluxCard extends LitElement {
|
|||
${bottomBrackets.map(b => this._renderSVGPath(b.path, b.iconColor))}
|
||||
</svg>
|
||||
${bottomBrackets.map(b => b.width > 20 ? html`
|
||||
<div class="compact-icon-wrapper" style="left: ${b.center}px; transform: translateX(-50%); top: -3px;">
|
||||
<div class="compact-icon-wrapper"
|
||||
style="left: ${b.center}px; transform: translateX(-50%); top: -3px; cursor: ${b.entityId ? 'pointer' : 'default'};"
|
||||
title="${this._formatPower(b.val)}"
|
||||
@click=${() => b.entityId && this._handleClick(b.entityId)}>
|
||||
<ha-icon icon="${b.icon}" class="compact-icon" style="color: ${b.iconColor};"></ha-icon>
|
||||
</div>` : '')}
|
||||
</div>
|
||||
|
|
@ -654,7 +695,10 @@ class PowerFluxCard extends LitElement {
|
|||
const solar = hasSolar ? getVal(entities.solar) : 0;
|
||||
const gridMain = hasGrid ? getVal(entities.grid) : 0;
|
||||
const gridExpSensor = (hasGrid && entities.grid_export) ? getVal(entities.grid_export) : 0;
|
||||
const battery = hasBattery ? getVal(entities.battery) : 0;
|
||||
let battery = hasBattery ? getVal(entities.battery) : 0;
|
||||
if (this.config.invert_battery) {
|
||||
battery *= -1;
|
||||
}
|
||||
const battSoc = (hasBattery && entities.battery_soc) ? getVal(entities.battery_soc) : 0;
|
||||
|
||||
const solarVal = Math.max(0, solar);
|
||||
|
|
@ -972,6 +1016,7 @@ class PowerFluxCard extends LitElement {
|
|||
}
|
||||
|
||||
customElements.define("power-flux-card", PowerFluxCard);
|
||||
})(lang_en, lang_de);
|
||||
|
||||
window.customCards = window.customCards || [];
|
||||
window.customCards.push({
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue