diff --git a/rollup.config.js b/rollup.config.js
index bd5201b..3e26bad 100644
--- a/rollup.config.js
+++ b/rollup.config.js
@@ -36,11 +36,37 @@ const plugins = [
export default [
{
- input: 'src/energy-period-selector-plus.ts',
+ input: ['src/energy-period-selector-plus.ts'],
output: {
dir: 'dist',
format: 'es',
+ inlineDynamicImports: true,
+ },
+ plugins: [
+ minifyHTML(),
+ terser({ output: { comments: false } }),
+ typescript({
+ declaration: false,
+ }),
+ nodeResolve(),
+ json({
+ compact: true,
+ }),
+ commonjs(),
+ babel({
+ exclude: "node_modules/**",
+ babelHelpers: "bundled",
+ }),
+ ...(dev ? [serve(serveOptions)] : [terser()]),
+ ],
+ moduleContext: (id) => {
+ const thisAsWindowForModules = [
+ "node_modules/@formatjs/intl-utils/lib/src/diff.js",
+ "node_modules/@formatjs/intl-utils/lib/src/resolve-locale.js",
+ ];
+ if (thisAsWindowForModules.some((id_) => id.trimRight().endsWith(id_))) {
+ return "window";
+ }
},
- plugins: [...plugins],
},
];
diff --git a/src/energy-period-selector-plus-base.ts b/src/energy-period-selector-plus-base.ts
index 09d61fd..d96b6da 100644
--- a/src/energy-period-selector-plus-base.ts
+++ b/src/energy-period-selector-plus-base.ts
@@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
-import { mdiCompare, mdiCompareRemove } from '@mdi/js';
+import { mdiCompare, mdiCompareRemove, mdiCalendarToday } from '@mdi/js';
import {
addDays,
addMonths,
@@ -17,7 +17,6 @@ import {
startOfToday,
startOfWeek,
startOfYear,
- format,
} from 'date-fns/esm';
import { UnsubscribeFunc } from 'home-assistant-js-websocket';
import { html, LitElement, nothing } from 'lit';
@@ -77,7 +76,7 @@ export class EnergyPeriodSelectorBase extends SubscribeMixin(LitElement) {
return this.hass.localize(`ui.panel.lovelace.components.energy_period_selector.${period}`) || localize(`toggleButtons.${period}`);
};
- const viewButtons: ToggleButton[] = !this._config?.period_buttons
+ const periodButtons: ToggleButton[] = !this._config?.period_buttons
? [
{
label: this.hass.localize('ui.panel.lovelace.components.energy_period_selector.day'),
@@ -127,6 +126,22 @@ export class EnergyPeriodSelectorBase extends SubscribeMixin(LitElement) {
`;
+
+ const todayButtonText = html`
+ ${this.hass.localize('ui.panel.lovelace.components.energy_period_selector.today')}
+ `;
+
+ const todayButtonIcon = html`
+ `;
+
+ const todayButton =
+ this._config?.today_button_type === false ? nothing : this._config?.today_button_type === 'icon' ? todayButtonIcon : todayButtonText;
+
return html`
${this._period === 'custom'
@@ -152,22 +167,18 @@ export class EnergyPeriodSelectorBase extends SubscribeMixin(LitElement) {
>
`
: nothing}
- ${this._config?.today_button !== false
- ? html`
- ${this.hass.localize('ui.panel.lovelace.components.energy_period_selector.today')}
- `
- : nothing}
+ ${todayButton}
`}
- ${this._config?.compare_button === 'icon'
+ ${this._config?.compare_button_type === 'icon'
? html`
- ${this.hass.localize('ui.panel.lovelace.components.energy_period_selector.compare')}
+ ${this._config.compare_button_label ?? this.hass.localize('ui.panel.lovelace.components.energy_period_selector.compare')}
`
- : this._config?.compare_button === 'text'
+ : this._config?.compare_button_type === 'text'
? html`
- ${this.hass.localize('ui.panel.lovelace.components.energy_period_selector.compare')}
+ ${this._config.compare_button_label ?? this.hass.localize('ui.panel.lovelace.components.energy_period_selector.compare')}
`
: nothing}
diff --git a/src/energy-period-selector-plus-config.ts b/src/energy-period-selector-plus-config.ts
index b1afe61..3106983 100644
--- a/src/energy-period-selector-plus-config.ts
+++ b/src/energy-period-selector-plus-config.ts
@@ -3,9 +3,10 @@ import { EnergyCardBaseConfig } from './type/energy-card-base-config';
export interface EnergyPeriodSelectorPlusConfig extends LovelaceCardConfig, EnergyCardBaseConfig {
card_background?: boolean;
- today_button?: boolean;
prev_next_buttons?: boolean;
- compare_button?: string;
+ compare_button_type?: string;
+ compare_button_label?: string;
+ today_button_type?: string | boolean;
period_buttons?: string[];
custom_period_label?: string;
}
diff --git a/src/energy-period-selector-plus.ts b/src/energy-period-selector-plus.ts
index 84d9d8a..4b7e665 100644
--- a/src/energy-period-selector-plus.ts
+++ b/src/energy-period-selector-plus.ts
@@ -9,6 +9,7 @@ import './energy-period-selector-plus-base';
import { localize } from './localize/localize';
import { logError } from './logging';
import { styles } from './style';
+import { LovelaceCardEditor } from 'custom-card-helpers';
registerCustomCard({
type: 'energy-period-selector-plus',
@@ -25,13 +26,18 @@ export class EnergyPeriodSelectorPlus extends LitElement implements LovelaceCard
return 1;
}
+ public static async getConfigElement(): Promise {
+ await import('./ui-editor/ui-editor');
+ return document.createElement('energy-period-selector-editor');
+ }
+
public setConfig(config: EnergyPeriodSelectorPlusConfig): void {
this._config = config;
}
protected render() {
if (!this.hass || !this._config) {
- logError(localize('common.invalid_configuration'));
+ logError(localize('common.invalid_configuration') || 'Invalid configuration');
return nothing;
}
const EnergyPeriodSelectorBase = html`
diff --git a/src/localize/languages/en.json b/src/localize/languages/en.json
index 484c5aa..3bc341b 100644
--- a/src/localize/languages/en.json
+++ b/src/localize/languages/en.json
@@ -20,33 +20,17 @@
"entity_editor": "Entity editor",
"decimals": "decimals",
"fields": {
- "autoconfig": "Autoconfig",
- "print_yaml": "Print auto generated config yaml",
- "show_names": "Show names",
- "show_icons": "Show icons",
- "show_states": "Show states",
- "show_units": "Show units",
- "energy_date_selection": "Sync with energy_date_selection component",
- "height": "Height",
- "wide": "Wide",
- "min_box_height": "Min box height",
- "min_box_distance": "Min box distance",
- "min_state": "Min state",
- "round": "Round",
- "throttle": "Throttle",
- "unit_prefix": "Unit prefix",
- "entity": "Entity",
- "type": "Type",
- "children": "Children",
- "name": "Name",
- "icon": "Icon",
- "color": "Color",
- "unit_of_measurement": "Unit of measurement",
- "tap_action": "Tap action",
- "color_on_state": "Change color based on state",
- "color_limit": "State limit for color change",
- "color_above": "Color above limit",
- "color_below": "Color below limit"
+ "card_background": "Card Background",
+ "prev_next_buttons": "Show Previous/Next Buttons",
+ "compare_button_type": "Compare Button Type",
+ "custom_period_label": "Custom Period Label",
+ "compare_button_options": {
+ "icon": "Icon",
+ "text": "Text"
+ },
+ "period_buttons": "Period Buttons",
+ "today_button_type": "Today Button Type",
+ "compare_button_label": "Compare Button Label"
},
"entity_types": {
"entity": "Entity",
diff --git a/src/localize/localize.ts b/src/localize/localize.ts
index c6c1660..2b70399 100644
--- a/src/localize/localize.ts
+++ b/src/localize/localize.ts
@@ -9,10 +9,10 @@ const languages: any = {
'pt-PT': pt_PT,
};
-export function localize(string: string, search = '', replace = ''): string {
+export function localize(string: string, search = '', replace = '') {
const lang = (localStorage.getItem('selectedLanguage') || 'en').replace(/['"]+/g, '').replace('-', '_');
- let translated: string;
+ let translated: string | undefined;
try {
translated = string.split('.').reduce((o, i) => o[i], languages[lang]);
@@ -23,7 +23,7 @@ export function localize(string: string, search = '', replace = ''): string {
if (translated === undefined) translated = string.split('.').reduce((o, i) => o && o[i], languages['en']);
if (search !== '' && replace !== '') {
- translated = translated.replace(search, replace);
+ translated = translated?.replace(search, replace);
}
- return translated || string;
+ return translated;
}
diff --git a/src/style.ts b/src/style.ts
index 91767ad..05182cf 100644
--- a/src/style.ts
+++ b/src/style.ts
@@ -48,7 +48,7 @@ export const stylesBase = css`
mwc-button {
margin-left: 8px;
}
- ha-icon-button {
+ ha-icon-button:not(.today-icon) {
margin-left: 4px;
--mdc-icon-size: 20px;
}
@@ -81,7 +81,7 @@ export const stylesBase = css`
--mdc-button-disabled-ink-color: var(--disabled-text-color);
--mdc-icon-button-ripple-opacity: 0.2;
}
- ha-icon-button {
+ ha-icon-button:not(.today-icon) {
--mdc-icon-button-size: 28px;
}
ha-button-toggle-group {
diff --git a/src/ui-editor/types/schema-union.ts b/src/ui-editor/types/schema-union.ts
new file mode 100644
index 0000000..a64a96e
--- /dev/null
+++ b/src/ui-editor/types/schema-union.ts
@@ -0,0 +1,132 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import type { LitElement } from "lit";
+
+
+
+export type HaFormSchema =
+ | HaFormConstantSchema
+ | HaFormStringSchema
+ | HaFormIntegerSchema
+ | HaFormFloatSchema
+ | HaFormBooleanSchema
+ | HaFormSelectSchema
+ | HaFormMultiSelectSchema
+ | HaFormTimeSchema
+ | HaFormSelector
+ | HaFormGridSchema
+ | HaFormExpandableSchema;
+
+export interface HaFormBaseSchema {
+ name: string;
+ // This value is applied if no data is submitted for this field
+ default?: HaFormData;
+ required?: boolean;
+ disabled?: boolean;
+ description?: {
+ suffix?: string;
+ // This value will be set initially when form is loaded
+ suggested_value?: HaFormData;
+ };
+ context?: Record;
+}
+
+export interface HaFormGridSchema extends HaFormBaseSchema {
+ type: "grid";
+ name: string;
+ column_min_width?: string;
+ schema: readonly HaFormSchema[];
+}
+
+export interface HaFormExpandableSchema extends HaFormBaseSchema {
+ type: "expandable";
+ name: "";
+ title: string;
+ icon?: string;
+ iconPath?: string;
+ expanded?: boolean;
+ headingLevel?: 1 | 2 | 3 | 4 | 5 | 6;
+ schema: readonly HaFormSchema[];
+}
+
+export interface HaFormSelector extends HaFormBaseSchema {
+ type?: never;
+ selector: any;
+}
+
+export interface HaFormConstantSchema extends HaFormBaseSchema {
+ type: "constant";
+ value?: string;
+}
+
+export interface HaFormIntegerSchema extends HaFormBaseSchema {
+ type: "integer";
+ default?: HaFormIntegerData;
+ valueMin?: number;
+ valueMax?: number;
+}
+
+export interface HaFormSelectSchema extends HaFormBaseSchema {
+ type: "select";
+ options: ReadonlyArray;
+}
+
+export interface HaFormMultiSelectSchema extends HaFormBaseSchema {
+ type: "multi_select";
+ options:
+ | Record
+ | readonly string[]
+ | ReadonlyArray;
+}
+
+export interface HaFormFloatSchema extends HaFormBaseSchema {
+ type: "float";
+}
+
+export interface HaFormStringSchema extends HaFormBaseSchema {
+ type: "string";
+ format?: string;
+ autocomplete?: string;
+}
+
+export interface HaFormBooleanSchema extends HaFormBaseSchema {
+ type: "boolean";
+}
+
+export interface HaFormTimeSchema extends HaFormBaseSchema {
+ type: "positive_time_period_dict";
+}
+
+// Type utility to unionize a schema array by flattening any grid schemas
+export type SchemaUnion<
+ SchemaArray extends readonly HaFormSchema[],
+ Schema = SchemaArray[number]
+> = Schema extends HaFormGridSchema | HaFormExpandableSchema
+ ? SchemaUnion
+ : Schema;
+
+export interface HaFormDataContainer {
+ [key: string]: HaFormData;
+}
+
+export type HaFormData =
+ | HaFormStringData
+ | HaFormIntegerData
+ | HaFormFloatData
+ | HaFormBooleanData
+ | HaFormSelectData
+ | HaFormMultiSelectData
+ | HaFormTimeData;
+
+export type HaFormStringData = string;
+export type HaFormIntegerData = number;
+export type HaFormFloatData = number;
+export type HaFormBooleanData = boolean;
+export type HaFormSelectData = string;
+export type HaFormMultiSelectData = string[];
+export type HaFormTimeData = any;
+
+export interface HaFormElement extends LitElement {
+ schema: HaFormSchema | readonly HaFormSchema[];
+ data?: HaFormDataContainer | HaFormData;
+ label?: string;
+}
diff --git a/src/ui-editor/ui-editor.ts b/src/ui-editor/ui-editor.ts
new file mode 100644
index 0000000..896b0df
--- /dev/null
+++ b/src/ui-editor/ui-editor.ts
@@ -0,0 +1,224 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+/* eslint-disable no-use-before-define */
+
+import { LitElement, css, html, nothing } from 'lit';
+import { customElement, property, state } from 'lit/decorators.js';
+import { fireEvent, HomeAssistant, LovelaceCardEditor } from 'custom-card-helpers';
+import { EnergyCardBaseConfig } from '../type/energy-card-base-config';
+import { any, assert, assign, boolean, integer, number, object, optional, string } from 'superstruct';
+import { localize } from '../localize/localize';
+import memoizeOne from 'memoize-one';
+import { EnergyPeriodSelectorPlusConfig } from '../energy-period-selector-plus-config';
+import { SchemaUnion } from './types/schema-union';
+
+export const loadHaForm = async () => {
+ if (customElements.get('ha-form')) return;
+
+ const helpers = await (window as any).loadCardHelpers?.();
+ if (!helpers) return;
+ const card = await helpers.createCardElement({ type: 'entity' });
+ if (!card) return;
+ await card.getConfigElement();
+};
+
+@customElement('energy-period-selector-editor')
+export class EnergyPeriodSelectorEditor extends LitElement implements LovelaceCardEditor {
+ @property({ attribute: false }) public hass!: HomeAssistant;
+ @state() private _config?: EnergyPeriodSelectorPlusConfig;
+
+ public async setConfig(config: EnergyPeriodSelectorPlusConfig): Promise {
+ assert(
+ config,
+ assign(
+ object({
+ type: string(),
+ view_layout: optional(string()),
+ }),
+ object({
+ card_background: optional(boolean()),
+ today_button: optional(boolean()),
+ prev_next_buttons: optional(boolean()),
+ compare_button_type: optional(string()),
+ today_button_type: optional(any()),
+ period_buttons: optional(any()),
+ custom_period_label: optional(string()),
+ compare_button_label: optional(string()),
+ }),
+ ),
+ );
+ this._config = config;
+ }
+
+ connectedCallback(): void {
+ super.connectedCallback();
+ loadHaForm();
+ }
+
+ private _schema = memoizeOne(
+ (showCompareLabel, showCustomPeriodLabel) =>
+ [
+ {
+ type: 'grid',
+ name: '',
+ schema: [
+ {
+ name: 'card_background',
+ selector: { boolean: {} },
+ },
+ {
+ name: 'prev_next_buttons',
+ selector: { boolean: {} },
+ },
+ ],
+ },
+ {
+ type: 'grid',
+ name: '',
+ schema: [
+ {
+ name: 'compare_button_type',
+ selector: {
+ select: {
+ options: [
+ { value: '', label: '' },
+ { value: 'icon', label: localize('editor.fields.compare_button_options.icon') },
+ { value: 'text', label: localize('editor.fields.compare_button_options.text') },
+ ],
+ mode: 'dropdown',
+ },
+ },
+ },
+ ...(showCompareLabel
+ ? ([
+ {
+ name: 'compare_button_label',
+ selector: { text: {} },
+ },
+ ] as const)
+ : []),
+ ],
+ },
+ {
+ name: 'today_button_type',
+ selector: {
+ select: {
+ options: [
+ { value: false, label: '' },
+ { value: 'icon', label: localize('editor.fields.compare_button_options.icon') },
+ { value: 'text', label: localize('editor.fields.compare_button_options.text') },
+ ],
+ mode: 'dropdown',
+ },
+ },
+ },
+ {
+ type: 'grid',
+ name: '',
+ schema: [
+ {
+ type: 'multi_select',
+ options: {
+ day: 'day',
+ week: 'week',
+ month: 'month',
+ year: 'year',
+ custom: 'custom',
+ },
+ name: 'period_buttons',
+ default: ['day', 'week', 'month', 'year'],
+ },
+ ...(showCustomPeriodLabel
+ ? ([
+ {
+ name: 'custom_period_label',
+ selector: { text: {} },
+ },
+ ] as const)
+ : []),
+ ],
+ },
+ ] as const,
+ );
+
+ protected render() {
+ if (!this.hass || !this._config) {
+ return nothing;
+ }
+ const data = {
+ ...this._config,
+ card_background: this._config.card_background ?? false,
+ today_button: this._config.today_button ?? true,
+ prev_next_buttons: this._config.prev_next_buttons ?? true,
+ compare_button_type: this._config.compare_button_type ?? '',
+ today_button_type: this._config.today_button_type ?? 'text',
+ period_buttons: this._config.period_buttons ?? ['day', 'week', 'month', 'year'],
+ custom_period_label: this._config.custom_period_label ?? localize(`toggleButtons.custom`),
+ compare_button_label: this._config.compare_button_label ?? this.hass.localize('ui.panel.lovelace.components.energy_period_selector.compare'),
+ };
+
+ const schema = this._schema(data.compare_button_type === 'text', data.period_buttons.includes('custom'));
+ return html`
+
+ `;
+ }
+
+ private _valueChanged(ev: CustomEvent): void {
+ const config = ev?.detail.value;
+ fireEvent(this, 'config-changed', { config });
+ }
+
+ private _computeLabelCallback = schema => {
+ return localize(`editor.fields.${schema.name}`) || `not found: ${schema.name}`;
+ };
+
+ static get styles() {
+ return css`
+ ha-form {
+ width: 100%;
+ }
+
+ ha-icon-button {
+ align-self: center;
+ }
+
+ .card-config {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 10px;
+ }
+
+ .config-header {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+ width: 100%;
+ }
+
+ .config-header.sub-header {
+ margin-top: 24px;
+ }
+
+ ha-icon {
+ padding-bottom: 2px;
+ position: relative;
+ top: -4px;
+ right: 1px;
+ }
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'energy-period-selector-editor': EnergyPeriodSelectorEditor;
+ }
+}