Compare commits

..

12 Commits

Author SHA1 Message Date
ildar170975
c78cfb4012 Picture card: fix default tap_action (#51819)
* fix default tap_action

* fix default tap_action

* Update hui-picture-card.ts

* use hasAnyAction
2026-05-01 09:50:17 +02:00
ildar170975
09e993ffd6 Helpers, Automations, Scenes & Scripts data tables: add a search by a label (#51794)
* allow to search by a label

* allow to search by a label

* allow to search by a label

* allow to search by a label
2026-05-01 09:44:59 +02:00
Aidan Timson
f8f175426d Improve spacing on assist devtools (#51805) 2026-05-01 09:23:05 +02:00
renovate[bot]
89e3687f22 Update dependency typescript-eslint to v8.59.1 (#51818)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-01 09:36:39 +03:00
ildar170975
18a20576a9 hui-picture-header-footer: use hasAnyAction (#51821)
use hasAnyAction
2026-05-01 09:35:45 +03:00
ildar170975
8ee41e5d9b ha-chart-base: fix vertical misalignment in legend (#51816)
vert alignment of value
2026-05-01 09:35:00 +03:00
Brooke Hatton
cac31ac55a Adjust Copy for maintenance summary card and include unavailable device count (#51815)
* Adjust Copy For summary card

* Further tweak copy and include unavailable devices
2026-04-30 16:47:09 -04:00
Matthias de Baat
8f002f2783 Promote backup encryption key and reorganize backup page (#51806)
* Promote backup encryption key and reorganize backup page

* Polish

* More polish

---------

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
2026-04-30 14:29:16 +00:00
Aidan Timson
df754fcd0d Add gap between hui editors and previews on mobile (#51811) 2026-04-30 15:00:03 +03:00
Aidan Timson
bc4437b3b5 Ally: Add aria labels to ha-icon-button and hui-root (#51784)
* Ally: Add aria labels to ha-icon-button and hui-root

* use aria-hidden

* Add hidden content for label to satisfy ally review

* Make fix in button instead (probably should update upstream)

* Aria label (pending wa update)
2026-04-30 09:20:56 +00:00
Wendelin
c99b43dcf3 Use input button slots for a11y (#51801)
Co-authored-by: Copilot <copilot@github.com>
2026-04-30 09:12:23 +00:00
Bram Kragten
8945b917b3 Add tooltips for Jinja editors (#51792)
* Add descriptions to Jinja2 tags, filters, expressions, tests and variables

All standard Jinja2 tags, filters, and expression completions now carry
info and detail strings so the autocomplete info popover shows meaningful
documentation when users browse them — not just HA-specific functions.

* Add keyboard shortcut tip to the template developer tool

A ha-tip below the editor card now shows users that Ctrl+Space triggers
autocomplete, Ctrl+F opens the search panel, and F11 toggles fullscreen,
making the editor's built-in features more discoverable.

* Add hover tooltips for Jinja2 functions, filters and expressions

Hovering over a function, filter, tag, test, or variable name inside a
Jinja2 template shows a tooltip with its signature and description.
Non-tag completions also get a help-circle icon linking to the
corresponding Home Assistant template-functions documentation page.

The tooltip is rendered as a custom Lit element (ha-code-editor-jinja-hover)
that takes the Completion object and an optional docUrl as properties.

The tooltip source (haJinjaHoverSource) is wired into ha-code-editor
via CodeMirror's hoverTooltip extension. The documentationUrl() helper
is used so the link points to the correct subdomain (www / rc / next)
based on the running HA version.

* Add hover tooltips for Jinja2 hover + arg value tooltips for entity/device/area

Wire haJinjaHoverSource into ha-code-editor via CodeMirror hoverTooltip.
Two types of hover are now shown in jinja2/yaml mode:

- Hovering a function/filter/tag/expression name shows its signature,
  description, and a doc-link icon (non-tags only).
- Hovering a string-literal argument of a known HA Jinja function (e.g.
  states(), device_name(), area_entities()) shows the friendly name,
  current state, device, and area for entity_id arguments; the device
  name and area for device_id arguments; and the area name for area_id
  arguments. The same applies to states["entity_id"] subscripts.

The arg-value tooltip reuses CompletionItem / ha-code-editor-completion-items
(the same component used for autocomplete info popovers) via a new
ha-code-editor-jinja-arg-hover element. HA registry data is passed from
ha-code-editor via a HassArgHoverContext interface to keep jinja_ha_completions.ts
free of HomeAssistant type imports.

* only add tip for autocomplete

* review
2026-04-30 12:07:50 +03:00
35 changed files with 2409 additions and 451 deletions

View File

@@ -54,7 +54,7 @@
"@fullcalendar/list": "6.1.20",
"@fullcalendar/luxon3": "6.1.20",
"@fullcalendar/timegrid": "6.1.20",
"@home-assistant/webawesome": "3.3.1-ha.1",
"@home-assistant/webawesome": "3.3.1-ha.2",
"@lezer/highlight": "1.2.3",
"@lit-labs/motion": "1.1.0",
"@lit-labs/observers": "2.1.0",
@@ -201,7 +201,7 @@
"terser-webpack-plugin": "5.5.0",
"ts-lit-plugin": "2.0.2",
"typescript": "6.0.3",
"typescript-eslint": "8.59.0",
"typescript-eslint": "8.59.1",
"vite-tsconfig-paths": "6.1.1",
"vitest": "4.1.5",
"webpack-stats-plugin": "1.1.3",

View File

@@ -1499,6 +1499,7 @@ export class HaChartBase extends LitElement {
margin-inline-start: var(--ha-space-1);
flex-shrink: 0;
white-space: nowrap;
line-height: 1;
}
.chart-legend .legend-toggle {
background: none;

View File

@@ -166,7 +166,6 @@ export class HaEntityToggle extends LitElement {
ha-control-switch {
--control-switch-thickness: 20px;
--control-switch-off-color: var(--state-inactive-color);
--control-switch-touch-area-size: var(--ha-space-2);
}
ha-icon-button {
--ha-icon-button-size: 40px;

View File

@@ -0,0 +1,42 @@
import { css, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import type { CompletionItem } from "./ha-code-editor-completion-items";
import "./ha-code-editor-completion-items";
@customElement("ha-code-editor-jinja-arg-hover")
export class HaCodeEditorJinjaArgHover extends LitElement {
/** Bold heading shown above the items grid (e.g. entity/device/area name). */
@property({ attribute: false }) public heading?: string;
@property({ attribute: false }) public items: CompletionItem[] = [];
render() {
return html`
${this.heading
? html`<div class="heading">${this.heading}</div>`
: nothing}
<ha-code-editor-completion-items
.items=${this.items}
></ha-code-editor-completion-items>
`;
}
static styles = css`
:host {
display: block;
padding: 6px 10px;
max-width: 360px;
}
.heading {
font-weight: var(--ha-font-weight-bold);
margin-bottom: 4px;
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"ha-code-editor-jinja-arg-hover": HaCodeEditorJinjaArgHover;
}
}

View File

@@ -0,0 +1,101 @@
import type { Completion } from "@codemirror/autocomplete";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import { mdiHelpCircleOutline } from "@mdi/js";
import "./ha-svg-icon";
@customElement("ha-code-editor-jinja-hover")
export class HaCodeEditorJinjaHover extends LitElement {
@property({ attribute: false }) public completion!: Completion;
@property({ attribute: false }) public docUrl?: string;
@property({ attribute: false }) public openDocumentation =
"Open documentation";
render() {
const info =
typeof this.completion.info === "string"
? this.completion.info
: undefined;
return html`
<div class="header">
<div class="sig">
<strong>${this.completion.label}</strong>
${this.completion.detail
? html`<span class="detail">(${this.completion.detail})</span>`
: nothing}
</div>
${this.docUrl
? html`<a
class="doc-link"
href=${this.docUrl}
target="_blank"
rel="noreferrer"
title=${this.openDocumentation}
><ha-svg-icon .path=${mdiHelpCircleOutline}></ha-svg-icon
></a>`
: nothing}
</div>
${info ? html`<div class="desc">${info}</div>` : nothing}
`;
}
static styles = css`
:host {
display: block;
padding: 6px 10px;
max-width: 360px;
line-height: 1.5;
}
.header {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 4px;
}
.sig {
font-family: var(--ha-font-family-code);
font-size: 0.9em;
flex: 1;
min-width: 0;
}
.detail {
color: var(--secondary-text-color);
}
.doc-link {
flex-shrink: 0;
display: inline-flex;
align-items: center;
color: var(--secondary-text-color);
opacity: 0.7;
line-height: 1;
}
.doc-link:hover {
opacity: 1;
color: var(--primary-color);
}
.doc-link ha-svg-icon {
width: 16px;
height: 16px;
}
.desc {
font-size: 0.9em;
color: var(--secondary-text-color);
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"ha-code-editor-jinja-hover": HaCodeEditorJinjaHover;
}
}

View File

@@ -36,9 +36,13 @@ import { computeAreaName } from "../common/entity/compute_area_name";
import { computeFloorName } from "../common/entity/compute_floor_name";
import { copyToClipboard } from "../common/util/copy-clipboard";
import { haStyleScrollbar } from "../resources/styles";
import type { JinjaArgType } from "../resources/jinja_ha_completions";
import type {
JinjaArgType,
HassArgHoverContext,
} from "../resources/jinja_ha_completions";
import type { HomeAssistant } from "../types";
import { showToast } from "../util/toast";
import { documentationUrl } from "../util/documentation-url";
import { labelsContext } from "../data/context";
import type { LabelRegistryEntry } from "../data/label/label_registry";
import "./ha-code-editor-completion-items";
@@ -387,6 +391,16 @@ export class HaCodeEditor extends ReactiveElement {
this._loadedCodeMirror.tooltips({
position: "absolute",
}),
this._loadedCodeMirror.hoverTooltip(
(view, pos) =>
this._loadedCodeMirror!.haJinjaHoverSource(
view,
pos,
this.hass ? documentationUrl(this.hass, "") : undefined,
this.hass ? this._hassArgHoverContext() : undefined
),
{ hoverTime: 300 }
),
...(this.placeholder ? [placeholder(this.placeholder)] : []),
];
@@ -636,6 +650,48 @@ export class HaCodeEditor extends ReactiveElement {
}
};
/**
* Builds a HassArgHoverContext from the current hass object so that
* haJinjaHoverSource can resolve entity / device / area friendly names
* without importing the full HomeAssistant type into the resource file.
*/
private _hassArgHoverContext(): HassArgHoverContext {
const hass = this.hass!;
const labelMap: Record<
string,
{ name: string; description?: string | null }
> = {};
for (const label of this._labels ?? []) {
labelMap[label.label_id] = {
name: label.name,
description: label.description,
};
}
return {
states: hass.states as HassArgHoverContext["states"],
devices: hass.devices as HassArgHoverContext["devices"],
areas: hass.areas as HassArgHoverContext["areas"],
floors: hass.floors as HassArgHoverContext["floors"],
entities: hass.entities as HassArgHoverContext["entities"],
labels: labelMap,
formatEntityState: (entityId) =>
hass.formatEntityState(hass.states[entityId]),
formatEntityName: (entityId) => {
const stateObj = hass.states[entityId];
return (
(stateObj?.attributes.friendly_name as string | undefined) ??
hass.entities[entityId]?.name ??
undefined
);
},
formatAttributeName: (entityId, attribute) =>
hass.formatEntityAttributeName(hass.states[entityId], attribute),
formatAttributeValue: (entityId, attribute) =>
hass.formatEntityAttributeValue(hass.states[entityId], attribute),
localize: (key) => hass.localize(key as never),
};
}
private _renderInfo = (completion: Completion): CompletionInfo => {
const key =
typeof completion.apply === "string"

View File

@@ -8,7 +8,7 @@ import {
} from "@egjs/hammerjs";
import type { PropertyValues, TemplateResult } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import { customElement, property, query } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import { fireEvent } from "../common/dom/fire_event";
import { mainWindow } from "../common/dom/get_main_window";
@@ -71,13 +71,16 @@ export class HaControlSwitch extends LitElement {
this.destroyListeners();
}
@query("#switch")
private switch!: HTMLDivElement;
setupSwipeListeners() {
if (this.disabled) {
return;
}
if (!this._mc) {
this._mc = new Manager(this, {
if (this.switch && !this._mc) {
this._mc = new Manager(this.switch, {
touchAction: this.touchAction ?? (this.vertical ? "pan-x" : "pan-y"),
});
this._mc.add(
@@ -188,7 +191,6 @@ export class HaControlSwitch extends LitElement {
static styles = css`
:host {
display: block;
position: relative;
--control-switch-on-color: var(--primary-color);
--control-switch-off-color: var(--disabled-color);
--control-switch-background-opacity: 0.2;
@@ -196,7 +198,6 @@ export class HaControlSwitch extends LitElement {
--control-switch-thickness: 40px;
--control-switch-border-radius: var(--ha-border-radius-lg);
--control-switch-padding: 4px;
--control-switch-touch-area-size: 0px;
--mdc-icon-size: 20px;
height: var(--control-switch-thickness);
width: 100%;
@@ -205,11 +206,6 @@ export class HaControlSwitch extends LitElement {
transition: box-shadow 180ms ease-in-out;
-webkit-tap-highlight-color: transparent;
}
:host::before {
content: "";
position: absolute;
inset: calc(-1 * var(--control-switch-touch-area-size));
}
.switch:not([disabled]):focus-visible {
box-shadow: 0 0 0 2px var(--control-switch-off-color);
}

View File

@@ -53,7 +53,10 @@ export class HaIconButton extends LitElement {
.download=${this.download}
>
${this.path
? html`<ha-svg-icon .path=${this.path}></ha-svg-icon>`
? html`<ha-svg-icon
aria-hidden="true"
.path=${this.path}
></ha-svg-icon>`
: html`<span><slot></slot></span>`}
</ha-button>
`;

View File

@@ -1,8 +1,6 @@
import { consume, type ContextType } from "@lit/context";
import { mdiMagnify } from "@mdi/js";
import { css, html, type PropertyValues } from "lit";
import { customElement, state } from "lit/decorators";
import { internationalizationContext } from "../../data/context";
import { customElement } from "lit/decorators";
import { HaInput } from "./ha-input";
/**
@@ -17,10 +15,6 @@ import { HaInput } from "./ha-input";
*/
@customElement("ha-input-search")
export class HaInputSearch extends HaInput {
@state()
@consume({ context: internationalizationContext, subscribe: true })
private _i18n!: ContextType<typeof internationalizationContext>;
constructor() {
super();
this.withClear = true;
@@ -35,7 +29,7 @@ export class HaInputSearch extends HaInput {
!this.placeholder &&
(!this.hasUpdated || changedProps.has("_i18n"))
) {
this.placeholder = this._i18n.localize("ui.common.search");
this.placeholder = this.i18n?.localize?.("ui.common.search") || "Search";
}
}

View File

@@ -2,19 +2,21 @@ import "@home-assistant/webawesome/dist/components/animation/animation";
import "@home-assistant/webawesome/dist/components/input/input";
import type WaInput from "@home-assistant/webawesome/dist/components/input/input";
import { HasSlotController } from "@home-assistant/webawesome/dist/internal/slot";
import { consume, type ContextType } from "@lit/context";
import { mdiClose, mdiEye, mdiEyeOff } from "@mdi/js";
import {
LitElement,
type PropertyValues,
type TemplateResult,
css,
html,
LitElement,
nothing,
type PropertyValues,
type TemplateResult,
} from "lit";
import { customElement, property, query } from "lit/decorators";
import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { ifDefined } from "lit/directives/if-defined";
import { stopPropagation } from "../../common/dom/stop_propagation";
import { internationalizationContext } from "../../data/context";
import "../ha-icon-button";
import "../ha-svg-icon";
import "../ha-tooltip";
@@ -125,6 +127,10 @@ export class HaInput extends WaInputMixin(LitElement) {
@query("wa-input")
private _input?: WaInput;
@state()
@consume({ context: internationalizationContext, subscribe: true })
protected i18n?: ContextType<typeof internationalizationContext>;
private readonly _hasSlotController = new HasSlotController(
this,
"label",
@@ -233,19 +239,22 @@ export class HaInput extends WaInputMixin(LitElement) {
${this.renderStartDefault()}
</slot>
<slot name="end" slot="end"> ${this.renderEndDefault()} </slot>
<slot name="clear-icon" slot="clear-icon">
<ha-icon-button .path=${mdiClose}></ha-icon-button>
</slot>
<slot name="show-password-icon" slot="show-password-icon">
<slot name="clear-button" slot="clear-button">
<ha-icon-button
@keydown=${stopPropagation}
.path=${mdiEye}
@click=${this._handleClearClick}
.label=${this.i18n?.localize?.("ui.components.input.clear") ||
"Clear"}
.path=${mdiClose}
></ha-icon-button>
</slot>
<slot name="hide-password-icon" slot="hide-password-icon">
<slot name="password-toggle-button" slot="password-toggle-button">
<ha-icon-button
@keydown=${stopPropagation}
.path=${mdiEyeOff}
@click=${this._handlePasswordToggle}
.label=${this.i18n?.localize?.(
`ui.components.input.${this.passwordVisible ? "hide_password" : "show_password"}`
) || (this.passwordVisible ? "Hide password" : "Show password")}
.path=${this.passwordVisible ? mdiEyeOff : mdiEye}
></ha-icon-button>
</slot>
<div
@@ -293,6 +302,14 @@ export class HaInput extends WaInputMixin(LitElement) {
}
};
private _handleClearClick() {
this._input?.clear();
}
private _handlePasswordToggle() {
this.passwordVisible = !this.passwordVisible;
}
static styles = [
waInputStyles,
css`
@@ -414,6 +431,12 @@ export class HaInput extends WaInputMixin(LitElement) {
color: var(--ha-color-text-secondary);
}
ha-icon-button {
display: flex;
align-items: center;
color: var(--ha-color-text-secondary);
}
:host([appearance="outlined"]) wa-input.no-label {
--ha-icon-button-size: 24px;
--mdc-icon-size: 18px;

View File

@@ -189,6 +189,20 @@ export const updateBackupConfig = (
config: BackupMutableConfig
) => hass.callWS({ type: "backup/config/update", ...config });
export const saveBackupConfig = (hass: HomeAssistant, config: BackupConfig) =>
updateBackupConfig(hass, {
create_backup: {
agent_ids: config.create_backup.agent_ids,
include_folders: config.create_backup.include_folders ?? [],
include_database: config.create_backup.include_database,
include_addons: config.create_backup.include_addons ?? [],
include_all_addons: config.create_backup.include_all_addons,
password: config.create_backup.password,
},
retention: config.retention,
schedule: config.schedule,
});
export const getBackupDownloadUrl = (
id: string,
agentId: string,

View File

@@ -134,6 +134,7 @@ type AutomationItem = AutomationEntity & {
formatted_state: string;
category: string | undefined;
label_entries: LabelRegistryEntry[];
labels: string[]; // search only
assistants: string[];
assistants_sortable_key: string | undefined;
};
@@ -256,6 +257,9 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
);
const category = entityRegEntry?.categories.automation;
const labels = labelReg && entityRegEntry?.labels;
const label_entries = (labels || [])
.map((lbl) => labelReg!.find((label) => label.label_id === lbl)!)
.filter(Boolean);
const assistants = getEntityVoiceAssistantsIds(
entityReg,
automation.entity_id
@@ -271,9 +275,8 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
category: category
? categoryReg?.find((cat) => cat.category_id === category)?.name
: undefined,
label_entries: (labels || [])
.map((lbl) => labelReg!.find((label) => label.label_id === lbl)!)
.filter(Boolean),
label_entries,
labels: label_entries.map((lbl) => lbl.name),
assistants,
assistants_sortable_key: getAssistantsSortableKey(assistants),
selectable: entityRegEntry !== undefined,

View File

@@ -0,0 +1,123 @@
import { mdiPuzzle } from "@mdi/js";
import type { CSSResultGroup } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import "../../../../../components/ha-card";
import "../../../../../components/ha-icon-next";
import "../../../../../components/ha-md-list";
import "../../../../../components/ha-md-list-item";
import "../../../../../components/ha-svg-icon";
import {
getSupervisorUpdateConfig,
type SupervisorUpdateConfig,
} from "../../../../../data/supervisor/update";
import { haStyle } from "../../../../../resources/styles";
import type { HomeAssistant } from "../../../../../types";
@customElement("ha-backup-overview-app-update-backup")
class HaBackupOverviewAppUpdateBackup extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _supervisorUpdateConfig?: SupervisorUpdateConfig;
protected firstUpdated() {
this._fetchSupervisorUpdateConfig();
}
public connectedCallback() {
super.connectedCallback();
if (this.hasUpdated) {
this._fetchSupervisorUpdateConfig();
}
}
private async _fetchSupervisorUpdateConfig() {
try {
this._supervisorUpdateConfig = await getSupervisorUpdateConfig(this.hass);
} catch (err) {
// eslint-disable-next-line no-console
console.error(err);
}
}
private _appUpdateBackupDescription() {
if (!this._supervisorUpdateConfig) {
return this.hass.localize(
"ui.panel.config.backup.settings.app_update_backup.local_only"
);
}
if (!this._supervisorUpdateConfig.add_on_backup_before_update) {
return this.hass.localize(
"ui.panel.config.backup.schedule.update_preference.skip_backups"
);
}
const copies =
this._supervisorUpdateConfig.add_on_backup_retain_copies || 1;
return `${this.hass.localize(
"ui.panel.config.backup.schedule.update_preference.backup_before_update"
)} ${this.hass.localize(
"ui.panel.config.backup.overview.settings.schedule_copies_backups",
{ count: copies }
)}`;
}
protected render() {
return html`
<ha-card>
<div class="card-header">
${this.hass.localize(
"ui.panel.config.backup.overview.app_update_backup.title"
)}
</div>
<div class="card-content">
<ha-md-list>
<ha-md-list-item
type="link"
href="/config/backup/app-update-backups"
>
<ha-svg-icon slot="start" .path=${mdiPuzzle}></ha-svg-icon>
<div slot="headline">${this._appUpdateBackupDescription()}</div>
<div slot="supporting-text">
${this.hass.localize(
"ui.panel.config.backup.overview.app_update_backup.description"
)}
</div>
<ha-icon-next slot="end"></ha-icon-next>
</ha-md-list-item>
</ha-md-list>
</div>
</ha-card>
`;
}
static get styles(): CSSResultGroup {
return [
haStyle,
css`
.card-header {
padding-bottom: 8px;
}
.card-content {
padding-left: 0;
padding-right: 0;
padding-top: 0;
}
ha-md-list {
padding-top: 0;
padding-bottom: 0;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-backup-overview-app-update-backup": HaBackupOverviewAppUpdateBackup;
}
}

View File

@@ -0,0 +1,154 @@
import { css, html, LitElement, nothing } from "lit";
import type { PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { debounce } from "../../../common/util/debounce";
import "../../../components/ha-alert";
import "../../../components/ha-card";
import {
getSupervisorUpdateConfig,
updateSupervisorUpdateConfig,
type SupervisorUpdateConfig,
} from "../../../data/supervisor/update";
import "../../../layouts/hass-subpage";
import type { HomeAssistant } from "../../../types";
import "./components/config/ha-backup-config-addon";
@customElement("ha-config-backup-app-update-backups")
class HaConfigBackupAppUpdateBackups extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Boolean }) public narrow = false;
@state() private _supervisorUpdateConfig?: SupervisorUpdateConfig;
@state() private _error?: string;
protected willUpdate(changedProps: PropertyValues<this>): void {
super.willUpdate(changedProps);
if (
!this.hasUpdated &&
this.hass &&
isComponentLoaded(this.hass.config, "hassio")
) {
this._getSupervisorUpdateConfig();
}
}
protected render() {
return html`
<hass-subpage
back-path="/config/backup/overview"
.hass=${this.hass}
.narrow=${this.narrow}
.header=${this.hass.localize(
"ui.panel.config.backup.app_update_backups.header"
)}
>
<div class="content">
<ha-card>
<div class="card-content">
<p>
${this.hass.localize(
"ui.panel.config.backup.settings.app_update_backup.description"
)}
</p>
<p>
${this.hass.localize(
"ui.panel.config.backup.settings.app_update_backup.local_only"
)}
</p>
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: nothing}
<ha-backup-config-addon
.hass=${this.hass}
.supervisorUpdateConfig=${this._supervisorUpdateConfig}
@update-config-changed=${this._supervisorUpdateConfigChanged}
></ha-backup-config-addon>
</div>
</ha-card>
</div>
</hass-subpage>
`;
}
private async _getSupervisorUpdateConfig() {
try {
this._supervisorUpdateConfig = await getSupervisorUpdateConfig(this.hass);
this._error = undefined;
} catch (err: any) {
// eslint-disable-next-line no-console
console.error(err);
this._error = this.hass.localize(
"ui.panel.config.backup.settings.app_update_backup.error_load",
{
error: err?.message || err,
}
);
}
}
private async _supervisorUpdateConfigChanged(ev) {
const config = ev.detail.value as SupervisorUpdateConfig;
this._supervisorUpdateConfig = {
...this._supervisorUpdateConfig,
...config,
} as SupervisorUpdateConfig;
this._debounceSaveSupervisorUpdateConfig();
}
private _debounceSaveSupervisorUpdateConfig = debounce(
() => this._saveSupervisorUpdateConfig(),
500
);
private async _saveSupervisorUpdateConfig() {
if (!this._supervisorUpdateConfig) {
return;
}
try {
await updateSupervisorUpdateConfig(
this.hass,
this._supervisorUpdateConfig
);
this._error = undefined;
} catch (err: any) {
// eslint-disable-next-line no-console
console.error(err);
this._error = this.hass.localize(
"ui.panel.config.backup.settings.app_update_backup.error_save",
{
error: err?.message || err?.toString(),
}
);
}
}
static styles = css`
p {
color: var(--secondary-text-color);
}
.content {
padding: 28px 20px 0;
max-width: 690px;
margin: 0 auto;
gap: var(--ha-space-6);
display: flex;
flex-direction: column;
margin-bottom: 24px;
}
.card-content {
padding-bottom: 0;
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"ha-config-backup-app-update-backups": HaConfigBackupAppUpdateBackups;
}
}

View File

@@ -1,8 +1,9 @@
import { mdiDotsVertical, mdiPlus, mdiUpload } from "@mdi/js";
import type { CSSResultGroup, TemplateResult } from "lit";
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../common/dom/fire_event";
import { debounce } from "../../../common/util/debounce";
import "../../../components/ha-button";
import "../../../components/ha-card";
import "../../../components/ha-dropdown";
@@ -23,6 +24,7 @@ import {
computeBackupAgentName,
generateBackup,
generateBackupWithAutomaticSettings,
saveBackupConfig,
} from "../../../data/backup";
import type { ManagerStateEvent } from "../../../data/backup_manager";
import type { CloudStatus } from "../../../data/cloud";
@@ -32,10 +34,12 @@ import { haStyle } from "../../../resources/styles";
import type { HomeAssistant, Route } from "../../../types";
import { showAlertDialog } from "../../lovelace/custom-card-helpers";
import "./components/overview/ha-backup-overview-backups";
import "./components/overview/ha-backup-overview-app-update-backup";
import "./components/overview/ha-backup-overview-onboarding";
import "./components/overview/ha-backup-overview-progress";
import "./components/overview/ha-backup-overview-settings";
import "./components/overview/ha-backup-overview-summary";
import "./components/config/ha-backup-config-encryption-key";
import { showBackupOnboardingDialog } from "./dialogs/show-dialog-backup_onboarding";
import { showGenerateBackupDialog } from "./dialogs/show-dialog-generate-backup";
import { showNewBackupDialog } from "./dialogs/show-dialog-new-backup";
@@ -68,10 +72,54 @@ class HaConfigBackupOverview extends LitElement {
{ uploaded_bytes: number; total_bytes: number }
> = {};
@state() private _config?: BackupConfig;
protected willUpdate(changedProperties: PropertyValues<this>): void {
super.willUpdate(changedProperties);
if (changedProperties.has("config") && !this._config) {
this._config = this.config;
}
}
public connectedCallback(): void {
super.connectedCallback();
// Update config when the page is displayed (e.g. when coming back from a settings page)
this._config = this.config;
}
private _uploadBackup = async () => {
await showUploadBackupDialog(this, {});
};
private _encryptionKeyChanged(ev) {
if (!this._config) {
return;
}
const password = ev.detail.value as string;
this._config = {
...this._config,
create_backup: {
...this._config.create_backup,
password,
},
};
this._debounceSaveConfig();
}
private _debounceSaveConfig = debounce(() => this._saveConfig(), 500);
private async _saveConfig() {
if (!this._config) {
return;
}
await saveBackupConfig(this.hass, this._config);
fireEvent(this, "ha-refresh-backup-config");
}
private _handleOnboardingButtonClick(ev) {
ev.stopPropagation();
this._setupAutomaticBackup(true);
@@ -234,13 +282,41 @@ class HaConfigBackupOverview extends LitElement {
.backups=${this.backups}
></ha-backup-overview-backups>
${!this._needsOnboarding && this.config
${!this._needsOnboarding && this._config
? html`
<ha-card>
<div class="card-header">
${this.hass.localize(
"ui.panel.config.backup.settings.encryption_key.title"
)}
</div>
<div class="card-content">
<p>
${this.hass.localize(
"ui.panel.config.backup.settings.encryption_key.description"
)}
</p>
<ha-backup-config-encryption-key
.hass=${this.hass}
.value=${this._config.create_backup.password}
@value-changed=${this._encryptionKeyChanged}
></ha-backup-config-encryption-key>
</div>
</ha-card>
<ha-backup-overview-settings
.hass=${this.hass}
.config=${this.config!}
.config=${this._config}
.agents=${this.agents}
></ha-backup-overview-settings>
${this.hass.config.components.includes("hassio")
? html`
<ha-backup-overview-app-update-backup
.hass=${this.hass}
></ha-backup-overview-app-update-backup>
`
: nothing}
`
: nothing}
</div>
@@ -270,6 +346,10 @@ class HaConfigBackupOverview extends LitElement {
return [
haStyle,
css`
p {
color: var(--secondary-text-color);
}
.content {
padding: 28px 20px 0;
max-width: 690px;
@@ -283,10 +363,6 @@ class HaConfigBackupOverview extends LitElement {
display: flex;
justify-content: flex-end;
}
.card-content {
padding-left: 0;
padding-right: 0;
}
.loading {
display: flex;
}

View File

@@ -16,7 +16,7 @@ import "../../../components/ha-icon-button";
import "../../../components/ha-icon-next";
import "../../../components/ha-svg-icon";
import type { BackupAgent, BackupConfig } from "../../../data/backup";
import { updateBackupConfig } from "../../../data/backup";
import { saveBackupConfig } from "../../../data/backup";
import type { CloudStatus } from "../../../data/cloud";
import {
getSupervisorUpdateConfig,
@@ -27,11 +27,9 @@ import "../../../layouts/hass-subpage";
import type { HomeAssistant } from "../../../types";
import { brandsUrl } from "../../../util/brands-url";
import { documentationUrl } from "../../../util/documentation-url";
import "./components/config/ha-backup-config-addon";
import "./components/config/ha-backup-config-agents";
import "./components/config/ha-backup-config-data";
import type { BackupConfigData } from "./components/config/ha-backup-config-data";
import "./components/config/ha-backup-config-encryption-key";
import "./components/config/ha-backup-config-schedule";
import type { BackupConfigSchedule } from "./components/config/ha-backup-config-schedule";
import { showLocalBackupLocationDialog } from "./dialogs/show-dialog-local-backup-location";
@@ -79,7 +77,7 @@ class HaConfigBackupSettings extends LitElement {
// eslint-disable-next-line no-console
console.error(err);
this._supervisorUpdateConfigError = this.hass.localize(
"ui.panel.config.backup.settings.app_update_backup.error_load",
"ui.panel.config.backup.settings.schedule.error_load",
{
error: err?.message || err,
}
@@ -315,57 +313,6 @@ class HaConfigBackupSettings extends LitElement {
: nothing}
</div>
</ha-card>
${supervisor
? html`<ha-card>
<div class="card-header">
${this.hass.localize(
"ui.panel.config.backup.settings.app_update_backup.title"
)}
</div>
<div class="card-content">
<p>
${this.hass.localize(
"ui.panel.config.backup.settings.app_update_backup.description"
)}
</p>
<p>
${this.hass.localize(
"ui.panel.config.backup.settings.app_update_backup.local_only"
)}
</p>
${this._supervisorUpdateConfigError
? html`<ha-alert alert-type="error">
${this._supervisorUpdateConfigError}
</ha-alert>`
: nothing}
<ha-backup-config-addon
.hass=${this.hass}
.supervisorUpdateConfig=${this._supervisorUpdateConfig}
@update-config-changed=${this
._supervisorUpdateConfigChanged}
></ha-backup-config-addon>
</div>
</ha-card>`
: nothing}
<ha-card>
<div class="card-header">
${this.hass.localize(
"ui.panel.config.backup.settings.encryption_key.title"
)}
</div>
<div class="card-content">
<p>
${this.hass.localize(
"ui.panel.config.backup.settings.encryption_key.description"
)}
</p>
<ha-backup-config-encryption-key
.hass=${this.hass}
.value=${this._config.create_backup.password}
@value-changed=${this._encryptionKeyChanged}
></ha-backup-config-encryption-key>
</div>
</ha-card>
</div>
</hass-subpage>
`;
@@ -438,18 +385,6 @@ class HaConfigBackupSettings extends LitElement {
this._debounceSave();
}
private _encryptionKeyChanged(ev) {
const password = ev.detail.value as string;
this._config = {
...this._config!,
create_backup: {
...this._config!.create_backup,
password: password,
},
};
this._debounceSave();
}
private _debounceSaveSupervisorUpdateConfig = debounce(
() => this._saveSupervisorUpdateConfig(),
500
@@ -468,7 +403,7 @@ class HaConfigBackupSettings extends LitElement {
// eslint-disable-next-line no-console
console.error(err);
this._supervisorUpdateConfigError = this.hass.localize(
"ui.panel.config.backup.settings.app_update_backup.error_save",
"ui.panel.config.backup.settings.schedule.error_save",
{
error: err?.message || err?.toString(),
}
@@ -479,18 +414,7 @@ class HaConfigBackupSettings extends LitElement {
private _debounceSave = debounce(() => this._save(), 500);
private async _save() {
await updateBackupConfig(this.hass, {
create_backup: {
agent_ids: this._config!.create_backup.agent_ids,
include_folders: this._config!.create_backup.include_folders ?? [],
include_database: this._config!.create_backup.include_database,
include_addons: this._config!.create_backup.include_addons ?? [],
include_all_addons: this._config!.create_backup.include_all_addons,
password: this._config!.create_backup.password,
},
retention: this._config!.retention,
schedule: this._config!.schedule,
});
await saveBackupConfig(this.hass, this._config!);
fireEvent(this, "ha-refresh-backup-config");
}

View File

@@ -125,6 +125,11 @@ class HaConfigBackup extends SubscribeMixin(HassRouterPage) {
load: () => import("./ha-config-backup-settings"),
cache: true,
},
"app-update-backups": {
tag: "ha-config-backup-app-update-backups",
load: () => import("./ha-config-backup-app-update-backups"),
cache: true,
},
location: {
tag: "ha-config-backup-location",
load: () => import("./ha-config-backup-location"),

View File

@@ -244,9 +244,13 @@ class HaPanelDevAssist extends SubscribeMixin(LitElement) {
max-width: 1040px;
margin: 0 auto;
}
.card-content {
display: flex;
flex-direction: column;
gap: var(--ha-space-4);
}
.description {
margin: 0;
margin-bottom: var(--ha-space-4);
}
ha-textarea {
width: 100%;

View File

@@ -9,6 +9,7 @@ import "../../../../components/ha-button";
import "../../../../components/ha-card";
import "../../../../components/ha-code-editor";
import "../../../../components/ha-spinner";
import "../../../../components/ha-tip";
import type { RenderTemplateResult } from "../../../../data/ws-templates";
import { subscribeRenderTemplate } from "../../../../data/ws-templates";
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
@@ -158,6 +159,14 @@ class HaPanelDevTemplate extends LitElement {
${this.hass.localize("ui.common.clear")}
</ha-button>
</div>
<ha-tip .hass=${this.hass}>
${this.hass.localize(
"ui.panel.config.developer-tools.tabs.templates.keyboard_tip",
{
autocomplete: html`<kbd>Ctrl</kbd>+<kbd>Space</kbd>`,
}
)}
</ha-tip>
</ha-card>
<ha-card
@@ -361,6 +370,22 @@ ${type === "object"
color: var(--warning-color);
}
ha-tip {
padding: 0 var(--ha-space-4) var(--ha-space-4);
display: block;
}
kbd {
display: inline-block;
font-family: var(--ha-font-family-code);
font-size: 0.85em;
padding: 1px 5px;
border: 1px solid var(--divider-color);
border-radius: var(--ha-border-radius-xs);
background-color: var(--secondary-background-color);
white-space: nowrap;
}
@media all and (max-width: 870px) {
.content ha-card {
max-width: 100%;

View File

@@ -146,6 +146,7 @@ interface HelperItem {
category: string | undefined;
area?: string;
label_entries: LabelRegistryEntry[];
labels: string[]; // search only
assistants: string[];
assistants_sortable_key: string | undefined;
disabled?: boolean;
@@ -552,6 +553,9 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
const entityRegEntry =
entityRegistryByEntityId(entityReg)[item.entity_id];
const labels = labelReg && entityRegEntry?.labels;
const label_entries = (labels || []).map(
(lbl) => labelReg!.find((label) => label.label_id === lbl)!
);
const category = entityRegEntry?.categories.helpers;
const deviceId = entityRegEntry?.device_id;
const areaId =
@@ -572,9 +576,8 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
`ui.panel.config.helpers.types.${item.type}` as LocalizeKeys
) ||
item.type,
label_entries: (labels || []).map(
(lbl) => labelReg!.find((label) => label.label_id === lbl)!
),
label_entries,
labels: label_entries.map((lbl) => lbl.name),
category: category
? categoryReg?.find((cat) => cat.category_id === category)?.name
: undefined,

View File

@@ -122,6 +122,7 @@ type SceneItem = SceneEntity & {
area: string | undefined;
category: string | undefined;
label_entries: LabelRegistryEntry[];
labels: string[]; // search only
assistants: string[];
assistants_sortable_key: string | undefined;
editable: boolean;
@@ -239,6 +240,9 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
);
const category = entityRegEntry?.categories.scene;
const labels = labelReg && entityRegEntry?.labels;
const label_entries = (labels || []).map(
(lbl) => labelReg!.find((label) => label.label_id === lbl)!
);
const assistants = getEntityVoiceAssistantsIds(
entityReg,
scene.entity_id
@@ -252,9 +256,8 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
category: category
? categoryReg?.find((cat) => cat.category_id === category)?.name
: undefined,
label_entries: (labels || []).map(
(lbl) => labelReg!.find((label) => label.label_id === lbl)!
),
label_entries,
labels: label_entries.map((lbl) => lbl.name),
assistants,
assistants_sortable_key: getAssistantsSortableKey(assistants),
selectable: entityRegEntry !== undefined,

View File

@@ -127,6 +127,7 @@ type ScriptItem = ScriptEntity & {
last_triggered: string | undefined;
category: string | undefined;
label_entries: LabelRegistryEntry[];
labels: string[]; // search only
assistants: string[];
assistants_sortable_key: string | undefined;
};
@@ -245,6 +246,9 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
);
const category = entityRegEntry?.categories.script;
const labels = labelReg && entityRegEntry?.labels;
const label_entries = (labels || []).map(
(lbl) => labelReg!.find((label) => label.label_id === lbl)!
);
const assistants = getEntityVoiceAssistantsIds(
entityReg,
script.entity_id
@@ -259,9 +263,8 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
category: category
? categoryReg?.find((cat) => cat.category_id === category)?.name
: undefined,
label_entries: (labels || []).map(
(lbl) => labelReg!.find((label) => label.label_id === lbl)!
),
label_entries,
labels: label_entries.map((lbl) => lbl.name),
assistants,
assistants_sortable_key: getAssistantsSortableKey(assistants),
selectable: entityRegEntry !== undefined,

View File

@@ -35,7 +35,10 @@ import {
HOME_SUMMARIES_ICONS,
type HomeSummary,
} from "../strategies/home/helpers/home-summaries";
import { filterNeedsAttentionEntities } from "../../maintenance/strategies/maintenance-view-strategy";
import {
filterLowBatteryEntities,
filterUnavailableBatteryEntities,
} from "../../maintenance/strategies/maintenance-view-strategy";
import type { LovelaceCard, LovelaceGridOptions } from "../types";
import { tileCardStyle } from "./tile/tile-card-style";
import type { HomeSummaryCard } from "./types";
@@ -258,19 +261,48 @@ export class HuiHomeSummaryCard
maintenanceFilters
);
const needsAttentionEntities = filterNeedsAttentionEntities(
const lowBatteryEntities = filterLowBatteryEntities(
this.hass!,
maintenanceEntities
);
if (needsAttentionEntities.length > 0) {
return this.hass.localize(
"ui.card.home-summary.count_maintenance_issues",
{
count: needsAttentionEntities.length,
}
);
const unavailableBatteryEntities = filterUnavailableBatteryEntities(
this.hass!,
maintenanceEntities
);
const lowBatteryText =
lowBatteryEntities.length > 0
? this.hass.localize(
"ui.card.home-summary.count_maintenance_low_battery_issues",
{
count: lowBatteryEntities.length,
}
)
: undefined;
const unavailableText =
unavailableBatteryEntities.length > 0
? this.hass.localize(
"ui.card.home-summary.count_maintenance_issues_unavailable_battery_entities",
{
count: unavailableBatteryEntities.length,
}
)
: undefined;
if (lowBatteryText && unavailableText) {
return `${lowBatteryText}, ${unavailableText}`;
}
if (lowBatteryText) {
return lowBatteryText;
}
if (unavailableText) {
return unavailableText;
}
return this.hass.localize("ui.card.home-summary.all_maintenance_good");
}
case "energy": {

View File

@@ -12,7 +12,7 @@ import type { ActionHandlerEvent } from "../../../data/lovelace/action_handler";
import type { HomeAssistant } from "../../../types";
import { actionHandler } from "../common/directives/action-handler-directive";
import { handleAction } from "../common/handle-action";
import { hasAction } from "../common/has-action";
import { hasAction, hasAnyAction } from "../common/has-action";
import { hasConfigChanged } from "../common/has-changed";
import { createEntityNotFoundWarning } from "../components/hui-warning";
import type { LovelaceCard, LovelaceCardEditor } from "../types";
@@ -52,10 +52,17 @@ export class HuiPictureCard extends LitElement implements LovelaceCard {
throw new Error("Image required");
}
this._config = {
tap_action: { action: "more-info" },
...config,
};
if (config.image_entity) {
this._config = {
tap_action: { action: "more-info" },
...config,
};
} else {
this._config = {
tap_action: { action: "none" },
...config,
};
}
}
protected shouldUpdate(changedProps: PropertyValues): boolean {
@@ -167,6 +174,11 @@ export class HuiPictureCard extends LitElement implements LovelaceCard {
return nothing;
}
const clickable = Boolean(
(this._config.image_entity && !this._config.tap_action) ||
hasAnyAction(this._config)
);
return html`
<ha-card
@action=${this._handleAction}
@@ -180,15 +192,7 @@ export class HuiPictureCard extends LitElement implements LovelaceCard {
: undefined
)}
class=${classMap({
clickable: Boolean(
(this._config.image_entity && !this._config.tap_action) ||
(this._config.tap_action &&
this._config.tap_action.action !== "none") ||
(this._config.hold_action &&
this._config.hold_action.action !== "none") ||
(this._config.double_tap_action &&
this._config.double_tap_action.action !== "none")
),
clickable,
})}
>
<img

View File

@@ -55,7 +55,6 @@ class HuiEntitiesToggle extends LitElement {
ha-control-switch {
--control-switch-thickness: 20px;
--control-switch-off-color: var(--state-inactive-color);
--control-switch-touch-area-size: var(--ha-space-2);
}
`;

View File

@@ -419,6 +419,7 @@ export class HuiDialogEditBadge
.content {
width: 100%;
max-width: 100%;
gap: var(--ha-space-3);
}
}

View File

@@ -419,6 +419,7 @@ export class HuiDialogEditCard
.content {
width: 100%;
max-width: 100%;
gap: var(--ha-space-3);
}
}

View File

@@ -68,9 +68,7 @@ export class HuiPictureCardEditor
{
name: "tap_action",
selector: {
ui_action: {
default_action: "more-info",
},
ui_action: {},
},
context: ACTION_RELATED_CONTEXT,
},

View File

@@ -8,7 +8,7 @@ import type { ActionHandlerEvent } from "../../../data/lovelace/action_handler";
import type { HomeAssistant } from "../../../types";
import { actionHandler } from "../common/directives/action-handler-directive";
import { handleAction } from "../common/handle-action";
import { hasAction } from "../common/has-action";
import { hasAction, hasAnyAction } from "../common/has-action";
import type { LovelaceHeaderFooter } from "../types";
import type { PictureHeaderFooterConfig } from "./types";
@@ -56,9 +56,7 @@ export class HuiPictureHeaderFooter
return nothing;
}
const clickable = Boolean(
this._config.tap_action || this._config.hold_action
);
const clickable = hasAnyAction(this._config);
return html`
<img

View File

@@ -409,6 +409,8 @@ class HUIRoot extends LitElement {
slot="actionItems"
.id="button-${index}"
.path=${item.icon}
.label=${label}
hide-title
@click=${item.buttonAction}
></ha-icon-button>
<ha-tooltip placement="bottom" .for="button-${index}">

View File

@@ -31,7 +31,7 @@ export const maintenanceEntityFilters: EntityFilter[] = [
const LOW_BATTERY_THRESHOLD = 20;
export const filterNeedsAttentionEntities = (
export const filterLowBatteryEntities = (
hass: HomeAssistant,
entityIds: string[]
): string[] =>
@@ -40,6 +40,14 @@ export const filterNeedsAttentionEntities = (
return !isNaN(stateValue) && stateValue <= LOW_BATTERY_THRESHOLD;
});
export const filterUnavailableBatteryEntities = (
hass: HomeAssistant,
entityIds: string[]
): string[] =>
entityIds.filter((entityId) => {
return hass.states[entityId]?.state === "unavailable";
});
const computeBatteryTileCard = (entityId: string): TileCardConfig => ({
type: "tile",
entity: entityId,

View File

@@ -44,6 +44,7 @@ export {
drawSelection,
EditorView,
highlightActiveLine,
hoverTooltip,
keymap,
lineNumbers,
rectangularSelection,
@@ -71,9 +72,10 @@ export const closeBracketsOverride = Prec.highest(
export {
haJinjaCompletionSource,
haJinjaHoverSource,
JINJA_FUNCTION_ARG_TYPES,
} from "./jinja_ha_completions";
export type { JinjaArgType } from "./jinja_ha_completions";
export type { HassArgHoverContext, JinjaArgType } from "./jinja_ha_completions";
export { closePercentBrace };
export const langCompartment = new Compartment();

File diff suppressed because it is too large Load Diff

View File

@@ -218,7 +218,8 @@
"all_secure": "All secure",
"no_media_playing": "No media playing",
"count_media_playing": "{count} {count, plural,\n one {playing}\n other {playing}\n}",
"count_maintenance_issues": "{count} {count, plural,\n one {issue}\n other {issues}\n}",
"count_maintenance_low_battery_issues": "{count} {count, plural,\n one {low battery}\n other {low batteries}\n}",
"count_maintenance_issues_unavailable_battery_entities": "{count} {count, plural,\n one {unavailable device}\n other {unavailable devices}\n}",
"all_maintenance_good": "All good",
"count_persons_home": "{count} {count, plural,\n one {person}\n other {people}\n} home",
"nobody_home": "No one home"
@@ -491,6 +492,11 @@
"markdown": "Markdown"
},
"components": {
"input": {
"clear": "Clear",
"show_password": "Show password",
"hide_password": "Hide password"
},
"selectors": {
"serial_port": {
"enter_manually": "Enter manually",
@@ -1498,6 +1504,9 @@
"area_label": "Area",
"description": "Configure which areas correspond to each vacuum segment"
},
"codemirror": {
"open_documentation": "Open documentation"
},
"safe_mode": {
"title": "Safe mode",
"text": "Home Assistant is running in safe mode, custom integrations and community frontend modules are not available. Restart Home Assistant to exit safe mode."
@@ -3558,7 +3567,7 @@
"show_all": "Show all backups"
},
"settings": {
"title": "Backup settings",
"title": "Automatic backup",
"configure": "Configure backup settings",
"schedule": "Automatic backup schedule and retention",
"schedule_copies_all": "and keep all backups",
@@ -3604,6 +3613,10 @@
"sat": "Sa",
"sun": "Su"
}
},
"app_update_backup": {
"title": "App update backup",
"description": "Backup behavior for app updates"
}
},
"backups": {
@@ -3615,13 +3628,15 @@
"new_backup": "[%key:ui::panel::config::backup::overview::new_backup%]"
},
"settings": {
"header": "Backup settings",
"header": "Automatic backups",
"menu": {
"change_default_location": "Change default action location"
},
"schedule": {
"title": "Automatic backups",
"description": "Let Home Assistant take care of your backups by creating a scheduled backup that also removes older backups."
"title": "Backup cycle",
"description": "Let Home Assistant take care of your backups by creating a scheduled backup that also removes older backups.",
"error_load": "Error loading Supervisor update config: {error}",
"error_save": "Error saving Supervisor update config: {error}"
},
"data": {
"title": "Backup data"
@@ -3649,6 +3664,9 @@
"error_save": "Error saving Supervisor update config: {error}"
}
},
"app_update_backups": {
"header": "App update backups"
},
"details": {
"header": "Backup",
"not_found": "Not found",
@@ -3876,7 +3894,8 @@
"no_listeners": "This template does not listen for any events and will not update automatically.",
"listeners": "This template listens for the following state changed events:",
"entity": "Entity",
"domain": "Domain"
"domain": "Domain",
"keyboard_tip": "Press {autocomplete} to trigger autocomplete, when your cursor is inside a function that supports it."
},
"statistics": {
"title": "Statistics",

156
yarn.lock
View File

@@ -1857,9 +1857,9 @@ __metadata:
languageName: node
linkType: hard
"@home-assistant/webawesome@npm:3.3.1-ha.1":
version: 3.3.1-ha.1
resolution: "@home-assistant/webawesome@npm:3.3.1-ha.1"
"@home-assistant/webawesome@npm:3.3.1-ha.2":
version: 3.3.1-ha.2
resolution: "@home-assistant/webawesome@npm:3.3.1-ha.2"
dependencies:
"@ctrl/tinycolor": "npm:4.1.0"
"@floating-ui/dom": "npm:^1.6.13"
@@ -1870,7 +1870,7 @@ __metadata:
lit: "npm:^3.2.1"
nanoid: "npm:^5.1.5"
qr-creator: "npm:^1.0.0"
checksum: 10/903b7e5e44a9c4afa5120b5fbbf139880889798a82262b10b5c5fa51cd56cd344d8027d69658b15c85efc579688476bbd8e5eff73535f903959bbd9ffd00051f
checksum: 10/c66965544bb34eb9d6d131579c31e4c6d21c8c231bdf565db61195da03a995d17e25d6d997875c902d7d2f3020c70434088efb7299b2626d4a7d1343cb33d726
languageName: node
linkType: hard
@@ -4383,105 +4383,105 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/eslint-plugin@npm:8.59.0":
version: 8.59.0
resolution: "@typescript-eslint/eslint-plugin@npm:8.59.0"
"@typescript-eslint/eslint-plugin@npm:8.59.1":
version: 8.59.1
resolution: "@typescript-eslint/eslint-plugin@npm:8.59.1"
dependencies:
"@eslint-community/regexpp": "npm:^4.12.2"
"@typescript-eslint/scope-manager": "npm:8.59.0"
"@typescript-eslint/type-utils": "npm:8.59.0"
"@typescript-eslint/utils": "npm:8.59.0"
"@typescript-eslint/visitor-keys": "npm:8.59.0"
"@typescript-eslint/scope-manager": "npm:8.59.1"
"@typescript-eslint/type-utils": "npm:8.59.1"
"@typescript-eslint/utils": "npm:8.59.1"
"@typescript-eslint/visitor-keys": "npm:8.59.1"
ignore: "npm:^7.0.5"
natural-compare: "npm:^1.4.0"
ts-api-utils: "npm:^2.5.0"
peerDependencies:
"@typescript-eslint/parser": ^8.59.0
"@typescript-eslint/parser": ^8.59.1
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: ">=4.8.4 <6.1.0"
checksum: 10/fcf2c85cb37d61854d2c03fa11c66ad58d99f4eee731dd09663f20f0395e642b12edeab2a6c368ac1806505b2071a01de01bc30b9011fa309299836e868a293a
checksum: 10/c736ee32211a3751e31151b51dacc8cfa5bf18e086f2a87aba7ee325f7e2fa96d8b9febdbaf4dfa70d14954312b7b9740fbe5d5886b3f8561c4a94a9c7ff7688
languageName: node
linkType: hard
"@typescript-eslint/parser@npm:8.59.0":
version: 8.59.0
resolution: "@typescript-eslint/parser@npm:8.59.0"
"@typescript-eslint/parser@npm:8.59.1":
version: 8.59.1
resolution: "@typescript-eslint/parser@npm:8.59.1"
dependencies:
"@typescript-eslint/scope-manager": "npm:8.59.0"
"@typescript-eslint/types": "npm:8.59.0"
"@typescript-eslint/typescript-estree": "npm:8.59.0"
"@typescript-eslint/visitor-keys": "npm:8.59.0"
"@typescript-eslint/scope-manager": "npm:8.59.1"
"@typescript-eslint/types": "npm:8.59.1"
"@typescript-eslint/typescript-estree": "npm:8.59.1"
"@typescript-eslint/visitor-keys": "npm:8.59.1"
debug: "npm:^4.4.3"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: ">=4.8.4 <6.1.0"
checksum: 10/b8990e1b67e6f55aa4884807e6c3b6bd08c58f96ea4f03f19e7aba3fc1b16f040fe58378490de9bd831c804eb48e633e30e5baf291b8e8dd53960424e5751391
checksum: 10/b014b485e5ec9c7430a87117271836b86fd80083fe6b1d216167313518f26222f45c0ee3f4cbc0616dbd6335cbde50336d8953ca5ffefecc55b2d896ac7645f9
languageName: node
linkType: hard
"@typescript-eslint/project-service@npm:8.59.0":
version: 8.59.0
resolution: "@typescript-eslint/project-service@npm:8.59.0"
"@typescript-eslint/project-service@npm:8.59.1":
version: 8.59.1
resolution: "@typescript-eslint/project-service@npm:8.59.1"
dependencies:
"@typescript-eslint/tsconfig-utils": "npm:^8.59.0"
"@typescript-eslint/types": "npm:^8.59.0"
"@typescript-eslint/tsconfig-utils": "npm:^8.59.1"
"@typescript-eslint/types": "npm:^8.59.1"
debug: "npm:^4.4.3"
peerDependencies:
typescript: ">=4.8.4 <6.1.0"
checksum: 10/b842f1e0623c3a679d21d76c7ca38698787681d40f6a9ce93c8983120fb6795a2395907d530e4f8d89b4ac5bc65e71bbfdf2d8060f210c8487cffdae40baea74
checksum: 10/dd98f49a407cb21999d31ec527a0f8c2c34422dde9fdb21210d66c3cc3d498d9d3678d95c99d76450af68ce3392692902d9ba044718d6c99122655df7afdc0a7
languageName: node
linkType: hard
"@typescript-eslint/scope-manager@npm:8.59.0":
version: 8.59.0
resolution: "@typescript-eslint/scope-manager@npm:8.59.0"
"@typescript-eslint/scope-manager@npm:8.59.1":
version: 8.59.1
resolution: "@typescript-eslint/scope-manager@npm:8.59.1"
dependencies:
"@typescript-eslint/types": "npm:8.59.0"
"@typescript-eslint/visitor-keys": "npm:8.59.0"
checksum: 10/8bb1182559e7676120ba12bdac11edea9fb414bd33d379a1902b035b8b4b68d23ad239d845bfe6943b5da13ecd938ea1482c73e8c6ddb4d7e3e0f8e111467e28
"@typescript-eslint/types": "npm:8.59.1"
"@typescript-eslint/visitor-keys": "npm:8.59.1"
checksum: 10/50c941d1af470d3e67a9bd2247c541a676ae6bb2931440a44458682d61382ba1194ce29d0388dd1e538c5a35d7a542febd9519d8170abe758692d1b6cd196eab
languageName: node
linkType: hard
"@typescript-eslint/tsconfig-utils@npm:8.59.0, @typescript-eslint/tsconfig-utils@npm:^8.59.0":
version: 8.59.0
resolution: "@typescript-eslint/tsconfig-utils@npm:8.59.0"
"@typescript-eslint/tsconfig-utils@npm:8.59.1, @typescript-eslint/tsconfig-utils@npm:^8.59.1":
version: 8.59.1
resolution: "@typescript-eslint/tsconfig-utils@npm:8.59.1"
peerDependencies:
typescript: ">=4.8.4 <6.1.0"
checksum: 10/9c094c199be4803d696dbf7cb5cdb76741876e412bf97ddde0133a75e11bc47345354b3bb188a0ff101b7ce2c582187e758696ab89c1981892a43162f36d0af1
checksum: 10/9e3351bb182bb02f6f140759472f08ce334c7c96f4ebfeec8e9e404fe60b8fe1865e1a6d1b50526f83f41e7224301485e46459df6c3675923f3b657177415cd7
languageName: node
linkType: hard
"@typescript-eslint/type-utils@npm:8.59.0":
version: 8.59.0
resolution: "@typescript-eslint/type-utils@npm:8.59.0"
"@typescript-eslint/type-utils@npm:8.59.1":
version: 8.59.1
resolution: "@typescript-eslint/type-utils@npm:8.59.1"
dependencies:
"@typescript-eslint/types": "npm:8.59.0"
"@typescript-eslint/typescript-estree": "npm:8.59.0"
"@typescript-eslint/utils": "npm:8.59.0"
"@typescript-eslint/types": "npm:8.59.1"
"@typescript-eslint/typescript-estree": "npm:8.59.1"
"@typescript-eslint/utils": "npm:8.59.1"
debug: "npm:^4.4.3"
ts-api-utils: "npm:^2.5.0"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: ">=4.8.4 <6.1.0"
checksum: 10/9c2d34c10676d5726f93b975136295971ac7d2a53f74bfba51ae71deefaa36292adda79d016782196b729429143634b7f90224c27dcdb3a884b9771128be7490
checksum: 10/8a8a71656f8fab446024e55b24f6f6c4b3ee4d4cdcb593ff68ec0ca10530fcb4d451628c03898c929e91445a999cbe980c0cfaec1b53a7c5ddc8ac899ad665fa
languageName: node
linkType: hard
"@typescript-eslint/types@npm:8.59.0, @typescript-eslint/types@npm:^8.56.0, @typescript-eslint/types@npm:^8.59.0":
version: 8.59.0
resolution: "@typescript-eslint/types@npm:8.59.0"
checksum: 10/51a773339c58a350d0ddaecba46ba735696f11829cab1f9b3d5d58a4bbd498693296ae742e3959d32f3bb29676c8e6bd120b970379d749a5a9b419393696930d
"@typescript-eslint/types@npm:8.59.1, @typescript-eslint/types@npm:^8.56.0, @typescript-eslint/types@npm:^8.59.1":
version: 8.59.1
resolution: "@typescript-eslint/types@npm:8.59.1"
checksum: 10/4d324a01c2314d8e196b43b9dc5fe9a4d82c1b65f4915cd2f965879c5565d4453603b6f7b6bcdc436fb629135f07ad0f9d274e4697b02ce8bc1c0310916f7ace
languageName: node
linkType: hard
"@typescript-eslint/typescript-estree@npm:8.59.0":
version: 8.59.0
resolution: "@typescript-eslint/typescript-estree@npm:8.59.0"
"@typescript-eslint/typescript-estree@npm:8.59.1":
version: 8.59.1
resolution: "@typescript-eslint/typescript-estree@npm:8.59.1"
dependencies:
"@typescript-eslint/project-service": "npm:8.59.0"
"@typescript-eslint/tsconfig-utils": "npm:8.59.0"
"@typescript-eslint/types": "npm:8.59.0"
"@typescript-eslint/visitor-keys": "npm:8.59.0"
"@typescript-eslint/project-service": "npm:8.59.1"
"@typescript-eslint/tsconfig-utils": "npm:8.59.1"
"@typescript-eslint/types": "npm:8.59.1"
"@typescript-eslint/visitor-keys": "npm:8.59.1"
debug: "npm:^4.4.3"
minimatch: "npm:^10.2.2"
semver: "npm:^7.7.3"
@@ -4489,32 +4489,32 @@ __metadata:
ts-api-utils: "npm:^2.5.0"
peerDependencies:
typescript: ">=4.8.4 <6.1.0"
checksum: 10/48eba6a117a36c4bf569aa1a728463619b131a45a6891cc0a5d2454828d9d3d07a499e9906de0df31de57761ce1d13aebb635a059782f3cc16563e3e63a29713
checksum: 10/8ede99640ac8b08ac73905bbc66dd06b2c4dc211240a4a9cb532b0fcf5c36ec9e7639ed7e1c17f86a948499279ff93e9dbcdf9170661d9f8347fcb53e8266772
languageName: node
linkType: hard
"@typescript-eslint/utils@npm:8.59.0":
version: 8.59.0
resolution: "@typescript-eslint/utils@npm:8.59.0"
"@typescript-eslint/utils@npm:8.59.1":
version: 8.59.1
resolution: "@typescript-eslint/utils@npm:8.59.1"
dependencies:
"@eslint-community/eslint-utils": "npm:^4.9.1"
"@typescript-eslint/scope-manager": "npm:8.59.0"
"@typescript-eslint/types": "npm:8.59.0"
"@typescript-eslint/typescript-estree": "npm:8.59.0"
"@typescript-eslint/scope-manager": "npm:8.59.1"
"@typescript-eslint/types": "npm:8.59.1"
"@typescript-eslint/typescript-estree": "npm:8.59.1"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: ">=4.8.4 <6.1.0"
checksum: 10/70547510f16459ca29e207584676f7c15626b5f7e2562643144fe037a1a9c4ca7116be99e67b9045f0de60db0022affb58c34c553a5370276ff8f542f7b05732
checksum: 10/26ae39a574e56d92b6fc406113e797c354fce8b377721cc5dd50579a0e9f8c3efe23c7693826ce2c16be96490520dd6ce7e145c4c39c22d8d00f2614791603ba
languageName: node
linkType: hard
"@typescript-eslint/visitor-keys@npm:8.59.0":
version: 8.59.0
resolution: "@typescript-eslint/visitor-keys@npm:8.59.0"
"@typescript-eslint/visitor-keys@npm:8.59.1":
version: 8.59.1
resolution: "@typescript-eslint/visitor-keys@npm:8.59.1"
dependencies:
"@typescript-eslint/types": "npm:8.59.0"
"@typescript-eslint/types": "npm:8.59.1"
eslint-visitor-keys: "npm:^5.0.0"
checksum: 10/b81753b9ddddeb3564e44d1199ba5546028731c7b5b3270938525f1f2b549d1df5fa8f203d9b3eacc120fa6b5af314cb1fb69d3a12d1dcce18a52a0fe316628d
checksum: 10/5343f3424cafdcaf2550fade29eca6b86ad3f6ac953aef6ba1dccd39789a1c38520634fbbc0814419d9227f508789053c1c9f59c2841d72e56431c3fdd93ac65
languageName: node
linkType: hard
@@ -8144,7 +8144,7 @@ __metadata:
"@fullcalendar/list": "npm:6.1.20"
"@fullcalendar/luxon3": "npm:6.1.20"
"@fullcalendar/timegrid": "npm:6.1.20"
"@home-assistant/webawesome": "npm:3.3.1-ha.1"
"@home-assistant/webawesome": "npm:3.3.1-ha.2"
"@html-eslint/eslint-plugin": "npm:0.59.0"
"@lezer/highlight": "npm:1.2.3"
"@lit-labs/motion": "npm:1.1.0"
@@ -8275,7 +8275,7 @@ __metadata:
tinykeys: "npm:3.0.0"
ts-lit-plugin: "npm:2.0.2"
typescript: "npm:6.0.3"
typescript-eslint: "npm:8.59.0"
typescript-eslint: "npm:8.59.1"
vite-tsconfig-paths: "npm:6.1.1"
vitest: "npm:4.1.5"
webpack-stats-plugin: "npm:1.1.3"
@@ -12568,18 +12568,18 @@ __metadata:
languageName: node
linkType: hard
"typescript-eslint@npm:8.59.0":
version: 8.59.0
resolution: "typescript-eslint@npm:8.59.0"
"typescript-eslint@npm:8.59.1":
version: 8.59.1
resolution: "typescript-eslint@npm:8.59.1"
dependencies:
"@typescript-eslint/eslint-plugin": "npm:8.59.0"
"@typescript-eslint/parser": "npm:8.59.0"
"@typescript-eslint/typescript-estree": "npm:8.59.0"
"@typescript-eslint/utils": "npm:8.59.0"
"@typescript-eslint/eslint-plugin": "npm:8.59.1"
"@typescript-eslint/parser": "npm:8.59.1"
"@typescript-eslint/typescript-estree": "npm:8.59.1"
"@typescript-eslint/utils": "npm:8.59.1"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: ">=4.8.4 <6.1.0"
checksum: 10/e015494cae2ae88c291e87d9d8c2c8d9924536f2edfac1a1da5e05f5ee083df7a8d916549f87af8a7b818d01de2bd505e29fdf991a086522a062387b4c2f1f64
checksum: 10/d35d30bdcff5b9644c9bf500d3ad74cebbd41612bf2669c3e3208cd74ee43302941666336acfca65bc44352b9b58c0455afe0a9e7106f12e54789b8c1f16dc11
languageName: node
linkType: hard