Skip to content
Merged
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
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ This page is the summary. What changed in each version is on the [releases page]
- **Limits from anywhere.** Minimum, maximum and target can each be a number or another
entity.
- **In your language.** The card and its editor follow the language in your Home Assistant
profile: English, German, Spanish, French, Dutch or Portuguese.
profile: English, Arabic, Bengali, Chinese (Simplified and Traditional), Dutch, French,
German, Hindi, Indonesian, Italian, Japanese, Korean, Polish, Portuguese, Russian, Spanish,
Turkish, Urdu or Vietnamese.

## Styles

Expand Down
2 changes: 2 additions & 0 deletions dist/chartdeck-card.ar-DrXvGSpY.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/chartdeck-card.ar-DrXvGSpY.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions dist/chartdeck-card.bn-BYSJ2K3y.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/chartdeck-card.bn-BYSJ2K3y.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions dist/chartdeck-card.de-D9vPFUer.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/chartdeck-card.de-D9vPFUer.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions dist/chartdeck-card.de-D_CUoG8R.js

This file was deleted.

1 change: 0 additions & 1 deletion dist/chartdeck-card.de-D_CUoG8R.js.map

This file was deleted.

2 changes: 0 additions & 2 deletions dist/chartdeck-card.es-CcVmNGgz.js

This file was deleted.

1 change: 0 additions & 1 deletion dist/chartdeck-card.es-CcVmNGgz.js.map

This file was deleted.

2 changes: 2 additions & 0 deletions dist/chartdeck-card.es-xxb6zZjz.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/chartdeck-card.es-xxb6zZjz.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions dist/chartdeck-card.fr-F88aZHdy.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/chartdeck-card.fr-F88aZHdy.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions dist/chartdeck-card.fr-WBanCOES.js

This file was deleted.

1 change: 0 additions & 1 deletion dist/chartdeck-card.fr-WBanCOES.js.map

This file was deleted.

2 changes: 2 additions & 0 deletions dist/chartdeck-card.hi-Bykn0yAD.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/chartdeck-card.hi-Bykn0yAD.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions dist/chartdeck-card.id-CXFAlL1k.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/chartdeck-card.id-CXFAlL1k.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions dist/chartdeck-card.it-BLLdra11.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/chartdeck-card.it-BLLdra11.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions dist/chartdeck-card.ja-DiJOj-UU.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/chartdeck-card.ja-DiJOj-UU.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1,293 changes: 698 additions & 595 deletions dist/chartdeck-card.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/chartdeck-card.js.map

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions dist/chartdeck-card.ko-BVW9kg0e.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/chartdeck-card.ko-BVW9kg0e.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions dist/chartdeck-card.nl-BQI5YeYB.js

This file was deleted.

1 change: 0 additions & 1 deletion dist/chartdeck-card.nl-BQI5YeYB.js.map

This file was deleted.

2 changes: 2 additions & 0 deletions dist/chartdeck-card.nl-x6Zobyim.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/chartdeck-card.nl-x6Zobyim.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions dist/chartdeck-card.pl-BzjGmyaS.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/chartdeck-card.pl-BzjGmyaS.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions dist/chartdeck-card.pt-BfJaynxl.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/chartdeck-card.pt-BfJaynxl.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions dist/chartdeck-card.pt-DlX1t9a_.js

This file was deleted.

1 change: 0 additions & 1 deletion dist/chartdeck-card.pt-DlX1t9a_.js.map

This file was deleted.

2 changes: 2 additions & 0 deletions dist/chartdeck-card.ru-CCFK_DM0.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/chartdeck-card.ru-CCFK_DM0.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions dist/chartdeck-card.tr-XvAn79hR.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/chartdeck-card.tr-XvAn79hR.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions dist/chartdeck-card.ur-DNPzwt2k.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/chartdeck-card.ur-DNPzwt2k.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions dist/chartdeck-card.vi-jLOMeR3H.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/chartdeck-card.vi-jLOMeR3H.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions dist/chartdeck-card.zh-Hans-BCjZNu4w.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/chartdeck-card.zh-Hans-BCjZNu4w.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions dist/chartdeck-card.zh-Hant-DvFx-VpB.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/chartdeck-card.zh-Hant-DvFx-VpB.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions src/editor/annotations-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { languageOf, localize } from '../localize';
import type { ChartEditorContext } from '../styles/index';
import type { Band, CardConfig, HomeAssistant } from '../types';
import './bands-editor';

const DELETE_ICON = 'M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z';
import { ICON_DELETE } from './icons';
import { computeHelper } from './helper';

/** The fields each kind of annotation has, after its kind; label and colour come last. */
const FIELDS: Record<AnnotationType, string[]> = {
Expand Down Expand Up @@ -195,6 +195,7 @@ export function annotationsEditor(ctx: ChartEditorContext): TemplateResult {
.data=${annotationData(a)}
.schema=${annotationSchema(ANNOTATION_TYPES.includes(a.type) ? a.type : 'line', ctx.hass, entities)}
.computeLabel=${(s: { name: string }) => t(`editor_annotation_${s.name}`)}
.computeHelper=${(s: { name: string }) => computeHelper(ctx.hass, `editor_annotation_${s.name}`)}
@value-changed=${(ev: CustomEvent) => {
ev.stopPropagation();
replace(i, annotationFromData((ev.detail?.value ?? {}) as Record<string, unknown>, a));
Expand All @@ -218,7 +219,7 @@ export function annotationsEditor(ctx: ChartEditorContext): TemplateResult {
</div>
<ha-icon-button
class="remove"
.path=${DELETE_ICON}
.path=${ICON_DELETE}
.label=${t('editor_annotation_remove')}
@click=${() => replace(i)}
></ha-icon-button>
Expand Down
2 changes: 2 additions & 0 deletions src/editor/badge-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { setOption } from './edit';
import { loadHaComponents } from './chartdeck-card-editor';
import { applyFormData, formData } from './schemas';
import type { FieldBinding } from './schemas';
import { computeHelper } from './helper';

/*
* The badge's visual editor: entity and look first, then what it reads and how it is labelled,
Expand Down Expand Up @@ -156,6 +157,7 @@ export class ChartdeckBadgeEditor extends LitElement {
.data=${formData(this._config!, fields)}
.schema=${schema}
.computeLabel=${this._label}
.computeHelper=${(s: { name: string }) => computeHelper(this.hass, `editor_badge_${s.name}`)}
@value-changed=${(ev: CustomEvent) => this._formChanged(fields, ev)}
></ha-form>`;
}
Expand Down
44 changes: 40 additions & 4 deletions src/editor/bands-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import { languageOf, localize } from '../localize';
import type { Band, HomeAssistant } from '../types';
import { deepEqual } from './schemas';
import { LanguageController } from '../language-controller';
import { ICON_DELETE } from './icons';

export type PresetName = keyof typeof BAND_PRESETS;

const PRESET_NAMES = Object.keys(BAND_PRESETS) as PresetName[];
const COLOR_NAMES = Object.keys(PALETTE);
const CUSTOM = 'custom';
const DELETE_ICON = 'M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z';
const TEXT_SELECTOR = { text: {} };
const ENTITY_SELECTOR = { entity: { domain: ['sensor', 'input_number', 'number'] } };
const ICON_ENTITY =
Expand Down Expand Up @@ -51,6 +51,11 @@ export class CdBandsEditor extends LitElement {
static styles = css`
:host {
display: block;
container-type: inline-size;
}
.warning {
display: block;
margin-top: 12px;
}
/* Two lines per band: where it starts and its colour, then its label and icon. */
.band {
Expand Down Expand Up @@ -88,6 +93,16 @@ export class CdBandsEditor extends LitElement {
.remove {
grid-area: remove;
}
/* Narrow: the start keeps its buttons; the colour, label and icon each get a line wide enough to read. */
@container (max-width: 420px) {
.band {
grid-template-areas:
'swatch from from mode remove'
'. color color color color'
'. label label label label'
'. icon icon icon icon';
}
}
.swatch {
width: 20px;
height: 20px;
Expand Down Expand Up @@ -279,12 +294,33 @@ export class CdBandsEditor extends LitElement {
this._emit(bands);
}

/** Two bands starting at the same number: the card keeps one, so the other never shows. */
private _duplicateWarning() {
const seen = new Set<number>();
for (const band of this.bands ?? []) {
const from = typeof band.from === 'string' && !isEntityStart(band.from) ? Number(band.from) : band.from;
if (typeof from !== 'number' || !Number.isFinite(from)) continue;
if (seen.has(from)) {
return html`<ha-alert class="warning" data-warning="bands" alert-type="warning"
>${localize(this.hass, 'editor_warn_band_same_start', { from: String(from) })}</ha-alert
>`;
}
seen.add(from);
}
return nothing;
}

private _renderBand(band: Band, index: number, selectors: ReturnType<CdBandsEditor['_getSelectors']>) {
const custom = !isPaletteColor(band.color) || this._customOpen.has(index);
const entity = isEntityStart(band.from) ? band.from : undefined;
const entityMode = entity !== undefined || this._entityOpen.has(index);
return html`<div class="band">
<span class="swatch" style=${styleMap({ '--swatch': baseColor(band.color) })}></span>
<span
class="swatch"
role="img"
aria-label=${isPaletteColor(band.color) ? this._t(`editor_color_${band.color}`) : band.color}
style=${styleMap({ '--swatch': baseColor(band.color) })}
></span>
<ha-selector
.required=${false}
data-role="from"
Expand Down Expand Up @@ -331,7 +367,7 @@ export class CdBandsEditor extends LitElement {
></ha-icon-button>
<ha-icon-button
class="remove"
.path=${DELETE_ICON}
.path=${ICON_DELETE}
.label=${this._t('editor_remove_band')}
@click=${() => this._removeBand(index)}
></ha-icon-button>
Expand Down Expand Up @@ -378,7 +414,7 @@ export class CdBandsEditor extends LitElement {
.value=${preset}
@value-changed=${this._presetChanged}
></ha-selector>
${(this.bands ?? []).map((band, i) => this._renderBand(band, i, selectors))}
${(this.bands ?? []).map((band, i) => this._renderBand(band, i, selectors))} ${this._duplicateWarning()}
<button type="button" class="add-band" @click=${this._addBand}>${this._t('editor_add_band')}</button>`;
}
}
Expand Down
110 changes: 109 additions & 1 deletion src/editor/chart-editors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { languageOf, localize } from '../localize';
import type { ChartEditorContext } from '../styles/index';
import type { CardConfig, HomeAssistant } from '../types';
import { annotationsEditor } from './annotations-editor';
import { computeHelper } from './helper';
import { NOW_MARKER_FIELDS, nowMarkerSchema } from './now-marker-editor';
import { LINE_WIDTH_SELECTOR, applyFormData, formData } from './schemas';
import type { FieldBinding } from './schemas';
Expand Down Expand Up @@ -322,6 +323,112 @@ export function cachedSchema(id: string, hass: HomeAssistant | undefined, build:
return schema;
}

/** The folding sections a long chart form is split into, in the order they appear. */
export const CHART_GROUPS = ['data', 'look', 'axes', 'extras', 'layout'] as const;
export type ChartGroup = (typeof CHART_GROUPS)[number];

/** Field names that belong to a section other than `look` (prefix matches end in `_`). */
const GROUP_MATCH: Record<Exclude<ChartGroup, 'look'>, string[]> = {
data: [
'period_',
'source',
'energy_',
'group_by',
'aggregate',
'compare',
'readings',
'bucket',
'bins',
'bin_width',
'days',
'hours',
'points_per_hour',
'cycle_plot_',
'series_aggregate',
'entity_aggregate',
'under_aggregate',
'measure',
],
axes: [
'x_axis',
'y_axis',
'axis_labels',
'grid_horizontal',
'grid_vertical',
'y_min',
'y_max',
'x_min',
'x_max',
'min_range',
'y_scale',
'value_axis',
'value_grid',
'value_min',
'value_max',
'tooltip',
'trend_y_',
'trend_tooltip',
],
extras: ['zoom', 'overview', 'forecast_', 'zones', 'now_', 'show_now', 'extremes', 'anomalies', 'extrema', 'average'],
layout: ['sync', 'header', 'compact', 'legend_toggle', 'multiples'],
};

/** Below this many fields a chart form stays one flat list: folding a short form only hides it. */
const GROUP_MIN_FIELDS = 9;

type SchemaItem = { name?: string; type?: string; schema?: SchemaItem[] };

function matches(name: string, pattern: string): boolean {
return pattern.endsWith('_') ? name.startsWith(pattern) : name === pattern || name.startsWith(`${pattern}_`);
}

/** The section a schema item goes in: a grid goes with its first named field. */
export function chartGroupOf(item: SchemaItem): ChartGroup {
const name = item.name || item.schema?.find((child) => child.name)?.name || '';
for (const group of CHART_GROUPS) {
if (group === 'look') continue;
if (GROUP_MATCH[group].some((pattern) => matches(name, pattern))) return group;
}
return 'look';
}

function countFields(items: SchemaItem[]): number {
return items.reduce((n, item) => n + (item.schema ? countFields(item.schema) : 1), 0);
}

const grouped = new WeakMap<unknown[], unknown[]>();

/**
* A long chart form folded into sections (time span and readings, look, axes, extras, layout),
* the first open. Short forms, and forms whose fields all land in one section, come back as they
* are. Kept per schema, so ha-form is handed the same object each render.
*/
export function groupChartSchema(hass: HomeAssistant | undefined, schema: unknown[]): unknown[] {
const known = grouped.get(schema);
if (known) return known;
const items = schema as SchemaItem[];
let result: unknown[] = schema;
if (countFields(items) >= GROUP_MIN_FIELDS && !items.some((item) => item.type === 'expandable')) {
const buckets = new Map<ChartGroup, SchemaItem[]>();
for (const item of items) {
const group = chartGroupOf(item);
buckets.set(group, [...(buckets.get(group) ?? []), item]);
}
if (buckets.size > 1) {
result = CHART_GROUPS.filter((group) => buckets.has(group)).map((group, i) => ({
type: 'expandable',
name: '',
flatten: true,
expanded: i === 0,
title: localize(hass, `editor_chart_group_${group}`),
schema: buckets.get(group),
}));
}
}
grouped.set(schema, result);
return result;
}

/** An ha-form bound to card-config paths; a value set back to its default removes the key. */
export function chartForm(
ctx: ChartEditorContext,
Expand All @@ -335,8 +442,9 @@ export function chartForm(
data-section=${section}
.hass=${ctx.hass}
.data=${formData(ctx.config, fields)}
.schema=${schema}
.schema=${section === 'chart' ? groupChartSchema(ctx.hass, schema) : schema}
.computeLabel=${(s: { name: string }) => localize(ctx.hass, labels[s.name] ?? `editor_${s.name}`)}
.computeHelper=${(s: { name: string }) => computeHelper(ctx.hass, labels[s.name] ?? `editor_${s.name}`)}
@value-changed=${(ev: CustomEvent) => {
ev.stopPropagation();
const next = withEnergyDefaults(
Expand Down
Loading
Loading