393 files

This commit is contained in:
Home Assistant Version Control
2026-09-27 17:16:54 +00:00
parent b05b7a897e
commit 39d97ac2db
404 changed files with 54006 additions and 11034 deletions
@@ -15,7 +15,9 @@ import { LitElement, html, css, nothing } from "lit";
import { property, state } from "lit/decorators.js";
import { t, ensureLocale, langOf, syncLocaleFromHass } from "../styles";
import { registerCustomCard } from "../helpers/register-card";
import { describeWsError } from "../ws-errors";
import { runWs } from "../helpers/ws-run";
import { VACATION_BUFFER_DAYS_RANGE } from "../helpers/setting-ranges";
import { confirmAction } from "../helpers/confirm";
import { sectionCardSharedStyles } from "./section-card-shared-styles";
import "./ms-date-field";
import type { HomeAssistant } from "../types";
@@ -43,7 +45,8 @@ export class MaintenanceVacationSectionCard extends LitElement {
@state() private _error = "";
@state() private _localStart = "";
@state() private _localEnd = "";
@state() private _localBuffer = 7;
/** Typed text — validated against VACATION_BUFFER_DAYS_RANGE on Save. */
@state() private _localBuffer = "3";
@state() private _dirty = false;
private _loaded = false;
@@ -76,72 +79,68 @@ export class MaintenanceVacationSectionCard extends LitElement {
}
private async _load(): Promise<void> {
try {
const r = await this.hass.connection.sendMessagePromise<VacationState>({
type: "maintenance_supporter/vacation/state",
});
this._state = r;
this._localStart = r.start || "";
this._localEnd = r.end || "";
this._localBuffer = r.buffer_days ?? 7;
this._dirty = false;
} catch (e) {
this._error = describeWsError(e, this._lang);
}
const r = await runWs<VacationState>(this, { type: "maintenance_supporter/vacation/state" }, {
onError: (m) => { this._error = m; },
});
if (!r) return;
this._state = r;
this._localStart = r.start || "";
this._localEnd = r.end || "";
this._localBuffer = String(r.buffer_days ?? 3);
this._dirty = false;
}
/** One vacation write — helpers/ws-run runWs with the busy flag and the
* error line; resolves the new state (undefined when refused). */
private _act(payload: Record<string, unknown>): Promise<VacationState | null | undefined> {
this._error = "";
return runWs<VacationState>(this, payload, {
busy: (b) => { this._busy = b; },
onError: (m) => { this._error = m; },
});
}
private async _toggleEnabled(on: boolean): Promise<void> {
this._busy = true;
this._error = "";
try {
const r = await this.hass.connection.sendMessagePromise<VacationState>({
type: "maintenance_supporter/vacation/update",
enabled: on,
});
this._state = r;
} catch (e) {
this._error = describeWsError(e, this._lang);
} finally {
this._busy = false;
}
const r = await this._act({ type: "maintenance_supporter/vacation/update", enabled: on });
if (r) this._state = r;
}
private async _save(): Promise<void> {
if (!this._isAdmin) return;
this._busy = true;
this._error = "";
try {
const r = await this.hass.connection.sendMessagePromise<VacationState>({
type: "maintenance_supporter/vacation/update",
start: this._localStart || null,
end: this._localEnd || null,
buffer_days: this._localBuffer,
});
this._state = r;
this._dirty = false;
} catch (e) {
this._error = describeWsError(e, this._lang);
} finally {
this._busy = false;
// Same range + message as the settings view (DRY audit 2026-09-26): an
// out-of-range buffer is named up front instead of round-tripping into
// a voluptuous error.
const [min, max] = VACATION_BUFFER_DAYS_RANGE;
const buffer = Number(this._localBuffer);
if (this._localBuffer.trim() === "" || !Number.isInteger(buffer) || buffer < min || buffer > max) {
this._error = t("settings_value_out_of_range", this._lang).replace("{min}", String(min)).replace("{max}", String(max));
return;
}
const r = await this._act({
type: "maintenance_supporter/vacation/update",
start: this._localStart || null,
end: this._localEnd || null,
buffer_days: buffer,
});
if (!r) return;
this._state = r;
this._dirty = false;
}
private async _endNow(): Promise<void> {
if (!this._isAdmin) return;
if (!window.confirm(t("vacation_end_now_confirm", this._lang))) return;
this._busy = true;
try {
const r = await this.hass.connection.sendMessagePromise<VacationState>({
type: "maintenance_supporter/vacation/end_now",
});
this._state = r;
this._localStart = r.start || "";
this._localEnd = r.end || "";
} catch (e) {
this._error = describeWsError(e, this._lang);
} finally {
this._busy = false;
}
// The confirm dialog, not the browser's native modal (DRY audit 2026-09-26).
const confirmed = await confirmAction(this.hass, {
title: t("vacation_end_now", this._lang),
message: t("vacation_end_now_confirm", this._lang),
confirmText: t("vacation_end_now", this._lang),
});
if (!confirmed) return;
const r = await this._act({ type: "maintenance_supporter/vacation/end_now" });
if (!r) return;
this._state = r;
this._localStart = r.start || "";
this._localEnd = r.end || "";
}
private _onDeepLink(): void {
@@ -224,12 +223,11 @@ export class MaintenanceVacationSectionCard extends LitElement {
</div>
<div class="date-field buffer">
<label>${t("vacation_buffer", L)}</label>
<input type="number" min="0" max="14"
.value=${String(this._localBuffer)}
<input type="number" min=${VACATION_BUFFER_DAYS_RANGE[0]} max=${VACATION_BUFFER_DAYS_RANGE[1]}
.value=${this._localBuffer}
?disabled=${this._busy}
@input=${(e: Event) => {
this._localBuffer = parseInt(
(e.target as HTMLInputElement).value, 10) || 0;
this._localBuffer = (e.target as HTMLInputElement).value;
this._dirty = true;
}} />
</div>