mirror of
https://github.com/home-assistant/frontend.git
synced 2026-07-24 04:00:27 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7a6091b5e2 | |||
| 6d12672c3e | |||
| 495562d748 | |||
| 5cf7fb3a3d | |||
| b6813ce060 |
@@ -23,11 +23,10 @@ Dialog implementation requirements:
|
||||
|
||||
- Use `ha-dialog`.
|
||||
- Use `DialogMixin`, which implements `HassDialogNext<T>`, for new dialogs. See `src/dialogs/dialog-mixin.ts`.
|
||||
- Read dialog parameters from the mixin's `params` property and render the dialog open. Return `nothing` while required parameters are absent.
|
||||
- Call the mixin's `closeDialog()` to close a new dialog. The mixin handles the `closed` event, fires `dialog-closed`, and removes the host element.
|
||||
- Existing dialogs may implement the legacy `HassDialog<T>` interface from `src/dialogs/make-dialog-manager.ts`.
|
||||
- Use `@state() private _open = false` to control visibility.
|
||||
- Set `_open = true` in `showDialog()` and `_open = false` in `closeDialog()`.
|
||||
- Return `nothing` while required params are absent.
|
||||
- Fire `dialog-closed` in the close handler.
|
||||
- Preserve the existing `showDialog()`, open-state, and close-event lifecycle when maintaining a legacy dialog; do not copy that lifecycle into a `DialogMixin` dialog.
|
||||
- Use `header-title` and `header-subtitle` for simple header text.
|
||||
- Use slots when standard header attributes are not enough.
|
||||
- Use `ha-dialog-footer` with `primaryAction` and `secondaryAction` slots.
|
||||
@@ -64,7 +63,7 @@ Use `computeLabel`, `computeError`, and `computeHelper` for translated labels, v
|
||||
.data=${this._data}
|
||||
.schema=${this._schema}
|
||||
.error=${this._errors}
|
||||
.computeLabel=${(schema) => this.hass.localize(`ui.panel.${schema.name}`)}
|
||||
.computeLabel=${(schema) => this._localize(`ui.panel.${schema.name}`)}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form>
|
||||
```
|
||||
@@ -78,10 +77,16 @@ Use `ha-alert` for user-visible status messaging.
|
||||
- Slots: `icon` for custom leading icon, `action` for custom action content.
|
||||
- Content is announced by screen readers when dynamically displayed.
|
||||
|
||||
```html
|
||||
<ha-alert alert-type="error">Error message</ha-alert>
|
||||
<ha-alert alert-type="warning" title="Warning">Description</ha-alert>
|
||||
<ha-alert alert-type="success" dismissable>Success message</ha-alert>
|
||||
```ts
|
||||
html`
|
||||
<ha-alert alert-type="error">${this._localize("ui.example.error")}</ha-alert>
|
||||
<ha-alert alert-type="warning" .title=${this._localize("ui.example.warning")}>
|
||||
${this._localize("ui.example.description")}
|
||||
</ha-alert>
|
||||
<ha-alert alert-type="success" dismissable>
|
||||
${this._localize("ui.example.success")}
|
||||
</ha-alert>
|
||||
`;
|
||||
```
|
||||
|
||||
## Shortcuts And Tooltips
|
||||
|
||||
@@ -23,27 +23,27 @@ Container components may keep `hass` when they own it and feed providers. Leaf c
|
||||
|
||||
## Context Selection
|
||||
|
||||
| Context | Replaces |
|
||||
| -------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
|
||||
| `statesContext` | `hass.states` |
|
||||
| `entitiesContext`, `devicesContext`, `areasContext`, `floorsContext` | `hass.entities`, `hass.devices`, `hass.areas`, `hass.floors` |
|
||||
| `registriesContext` | all four registries together |
|
||||
| `servicesContext` | `hass.services` |
|
||||
| `internationalizationContext` | `hass.localize`, `hass.locale`, `hass.language` |
|
||||
| `formattersContext` | entity and attribute formatters |
|
||||
| `configContext` | `hass.config`, `hass.user`, `hass.auth`, `hass.userData` |
|
||||
| `connectionContext` | `hass.connection`, `hass.connected`, `hass.hassUrl` |
|
||||
| `apiContext` | `hass.callService`, `hass.callApi`, `hass.callWS`, `hass.sendWS`, `hass.fetchWithAuth` |
|
||||
| `uiContext` | themes, selected theme, panels, sidebar, and UI state |
|
||||
| `narrowViewportContext` | narrow-layout boolean |
|
||||
| Context | Replaces |
|
||||
| -------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
|
||||
| `statesContext` | `hass.states` |
|
||||
| `entitiesContext`, `devicesContext`, `areasContext`, `floorsContext` | `hass.entities`, `hass.devices`, `hass.areas`, `hass.floors` |
|
||||
| `registriesContext` | all four registries together |
|
||||
| `servicesContext` | `hass.services` |
|
||||
| `internationalizationContext` | `hass.localize`, `hass.locale`, `hass.language` |
|
||||
| `formattersContext` | entity and attribute formatters |
|
||||
| `configContext` | `hass.config`, `hass.user`, `hass.auth`, `hass.userData` |
|
||||
| `connectionContext` | `hass.connection`, `hass.connected`, `hass.debugConnection`, `hass.hassUrl` |
|
||||
| `apiContext` | `hass.callService`, `hass.callApi`, `hass.callApiRaw`, `hass.callWS`, `hass.sendWS`, `hass.fetchWithAuth` |
|
||||
| `uiContext` | themes, selected theme, panels, sidebar, and UI state |
|
||||
| `narrowViewportContext` | narrow-layout boolean |
|
||||
|
||||
Lazy contexts subscribe on first consumer and tear down after the last consumer: `labelsContext`, `fullEntitiesContext`, `configEntriesContext`, and `manifestsContext`.
|
||||
Lazy contexts subscribe on first consumer and tear down after the last consumer: `labelsContext`, `fullEntitiesContext`, `configEntriesContext`, `manifestsContext`, `triggerDescriptionsContext`, and `conditionDescriptionsContext`.
|
||||
|
||||
The single-field contexts such as `localizeContext`, `themesContext`, and `userContext` are deprecated. Use grouped contexts instead.
|
||||
|
||||
## Consumption Patterns
|
||||
|
||||
Use entity-scoped helpers when the component watches an entity id held on the host:
|
||||
Use entity-scoped helpers when the component watches an entity ID held on the host:
|
||||
|
||||
```ts
|
||||
@state() @consumeEntityState({ entityIdPath: ["_config", "entity"] })
|
||||
@@ -56,6 +56,13 @@ private _entity?: EntityRegistryDisplayEntry;
|
||||
private _localize!: LocalizeFunc;
|
||||
```
|
||||
|
||||
Use `consumeEntityStates` when the host property contains one or more entity IDs. It filters missing entities and preserves the previous record when none of the selected entities changed.
|
||||
|
||||
```ts
|
||||
@state() @consumeEntityStates({ entityIdPath: ["_config", "entities"] })
|
||||
private _stateObjs?: Record<string, HassEntity>;
|
||||
```
|
||||
|
||||
For a single field from a grouped context, pair `@consume` with `@transform`:
|
||||
|
||||
```ts
|
||||
@@ -65,7 +72,7 @@ For a single field from a grouped context, pair `@consume` with `@transform`:
|
||||
private _themes!: Themes;
|
||||
```
|
||||
|
||||
Use `@transform` with `watch` when the transformer depends on a host property, such as a computed entity id. `consumeEntityState` only watches the first path segment.
|
||||
Use `@transform` with `watch` when the transformer depends on a host property, such as a computed entity ID. `consumeEntityState` and `consumeEntityStates` only watch the first path segment.
|
||||
|
||||
To consume a whole group untransformed, omit `@transform` and type the field as `ContextType<typeof statesContext>` or the matching context type.
|
||||
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
# Gallery Agent Instructions
|
||||
---
|
||||
name: ha-frontend-gallery
|
||||
description: Home Assistant frontend gallery structure, pages, demos, content, and verification. Use when changing files under gallery/, including gallery markdown, TypeScript demos, sidebar entries, mock data, page generation, or gallery builds.
|
||||
---
|
||||
|
||||
This file applies to all files under `gallery/`. Follow the root `AGENTS.md` for repository-wide Home Assistant frontend, TypeScript, Lit, accessibility, and copy standards. This file adds gallery-specific structure, page, demo, and verification guidance.
|
||||
# HA Frontend Gallery
|
||||
|
||||
Use this skill for all work under `gallery/`. Follow the persistent repository guidance in `AGENTS.md` and load the matching specialist skills alongside this gallery-specific guidance.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
@@ -13,7 +18,7 @@ yarn lint # ESLint, Prettier, TypeScript, and Lit checks
|
||||
yarn lint:types # TypeScript compiler, without file arguments
|
||||
```
|
||||
|
||||
Never run `yarn lint:types` or `tsc` with file arguments. See the root `AGENTS.md` for the generated `.js` file risk.
|
||||
Never run `yarn lint:types` or `tsc` with file arguments. File arguments make `tsc` ignore `tsconfig.json` and can emit `.js` files into `src/`.
|
||||
|
||||
## Purpose
|
||||
|
||||
@@ -26,36 +31,36 @@ The gallery is a developer and designer reference for Home Assistant frontend UI
|
||||
|
||||
## Structure
|
||||
|
||||
- `sidebar.js`: Defines gallery sections, headers, and explicit page ordering.
|
||||
- `script/develop_gallery`: Wrapper for the `develop-gallery` gulp task.
|
||||
- `script/build_gallery`: Wrapper for the `build-gallery` gulp task.
|
||||
- `src/entrypoint.js`: Creates the `<ha-gallery>` shell.
|
||||
- `src/ha-gallery.ts`: Renders the drawer, page routing, markdown descriptions, demos, edit links, and RTL toggle.
|
||||
- `src/html/index.html.template`: HTML template used by the gallery build.
|
||||
- `src/pages/<category>/<page>.markdown`: Optional page description and frontmatter.
|
||||
- `src/pages/<category>/<page>.ts`: Optional live demo module for the same page id.
|
||||
- `src/components/`: Gallery-only demo wrappers like `demo-card`, `demo-cards`, `demo-more-info`, and `page-description`.
|
||||
- `src/data/`: Fake `hass`, demo states, mock traces, and reusable sample data.
|
||||
- `public/`: Static assets copied into the gallery output.
|
||||
- `gallery/sidebar.js`: Defines gallery sections, headers, and explicit page ordering.
|
||||
- `gallery/script/develop_gallery`: Wrapper for the `develop-gallery` gulp task.
|
||||
- `gallery/script/build_gallery`: Wrapper for the `build-gallery` gulp task.
|
||||
- `gallery/src/entrypoint.js`: Creates the `<ha-gallery>` shell.
|
||||
- `gallery/src/ha-gallery.ts`: Renders the drawer, page routing, markdown descriptions, demos, edit links, and RTL toggle.
|
||||
- `gallery/src/html/index.html.template`: HTML template used by the gallery build.
|
||||
- `gallery/src/pages/<category>/<page>.markdown`: Optional page description and frontmatter.
|
||||
- `gallery/src/pages/<category>/<page>.ts`: Optional live demo module for the same page ID.
|
||||
- `gallery/src/components/`: Gallery-only demo wrappers like `demo-card`, `demo-cards`, `demo-more-info`, and `page-description`.
|
||||
- `gallery/src/data/`: Fake `hass`, demo states, mock traces, and reusable sample data.
|
||||
- `gallery/public/`: Static assets copied into the gallery output.
|
||||
|
||||
## Page Model
|
||||
|
||||
Gallery pages are generated by `gather-gallery-pages` in `build-scripts/gulp/gallery.js`.
|
||||
|
||||
- A page id is the path under `src/pages/` without the extension, like `components/ha-button`.
|
||||
- A `.markdown` file and a `.ts` file with the same page id become one gallery page.
|
||||
- A page ID is the path under `gallery/src/pages/` without the extension, like `components/ha-button`.
|
||||
- A `.markdown` file and a `.ts` file with the same page ID become one gallery page.
|
||||
- A page may have only markdown, only a TypeScript demo, or both.
|
||||
- Markdown can contain YAML frontmatter with `title` and optional `subtitle`.
|
||||
- Markdown that contains only frontmatter contributes metadata without rendering a description block.
|
||||
- TypeScript demo modules are dynamically imported for side effects when the page is opened.
|
||||
- A demo module must define a custom element named `demo-${category}-${page}` with slashes replaced by hyphens, like `demo-components-ha-button` for `components/ha-button`.
|
||||
- `ha-gallery.ts` renders that element with `dynamicElement()` based on the current page id.
|
||||
- `gallery/src/ha-gallery.ts` renders that element with `dynamicElement()` based on the current page ID.
|
||||
|
||||
## Sidebar
|
||||
|
||||
Use `sidebar.js` when a page needs a visible section, section header, or deterministic ordering.
|
||||
Use `gallery/sidebar.js` when a page needs a visible section, section header, or deterministic ordering.
|
||||
|
||||
- `category` must match the first directory name under `src/pages/`.
|
||||
- `category` must match the first directory name under `gallery/src/pages/`.
|
||||
- `header` is the section label shown in the drawer.
|
||||
- `pages` is optional. When present, listed pages keep that exact order.
|
||||
- Pages in a category that are not listed are appended alphabetically after the listed pages.
|
||||
@@ -83,20 +88,20 @@ Use markdown pages for explanations, design guidance, API notes, and copy standa
|
||||
- Use fenced code blocks with a language tag for copyable examples.
|
||||
- Keep examples short and focused on the behavior being documented.
|
||||
- Prefer real component names and attributes over prose-only descriptions.
|
||||
- Use Home Assistant terminology from the root `AGENTS.md`.
|
||||
- For remove/delete and add/create wording, follow `src/pages/misc/remove-delete-add-create.markdown`.
|
||||
- Use Home Assistant terminology from `ha-frontend-user-facing-text`.
|
||||
- For remove/delete and add/create wording, follow `gallery/src/pages/misc/remove-delete-add-create.markdown`.
|
||||
|
||||
Gallery markdown is documentation content and is not localized with `localize`. If demo code creates production UI strings, keep those strings aligned with the root localization and copy guidance.
|
||||
Gallery markdown is documentation content and is not localized with `localize`. If demo code creates production UI strings, follow the localization and copy guidance in `ha-frontend-user-facing-text`.
|
||||
|
||||
## Demo Components
|
||||
|
||||
Use TypeScript demo pages for interactive or stateful examples.
|
||||
|
||||
- Import production components from `../../../src/...` or the correct relative path from the demo file.
|
||||
- Import production components from `src/` using the correct relative path from the demo file.
|
||||
- Import reusable gallery helpers from `gallery/src/components/` when they already model the pattern.
|
||||
- Use `demo-card` and `demo-cards` for Lovelace card examples that render YAML card configs.
|
||||
- Use `demo-more-info` and `demo-more-infos` for more-info dialog examples.
|
||||
- Use shared mock data from `src/data/` instead of repeating large fake state objects inline.
|
||||
- Use shared mock data from `gallery/src/data/` instead of repeating large fake state objects inline.
|
||||
- Show meaningful states, such as loading, unavailable, empty, error, active, inactive, and disabled when relevant.
|
||||
- Check responsive behavior and the gallery RTL toggle when layout or direction-sensitive UI changes.
|
||||
- Keep unavoidable casts or loose demo parsing local to the demo helper or demo page.
|
||||
@@ -105,7 +110,7 @@ The gallery ESLint config allows `console` for gallery diagnostics. Do not copy
|
||||
|
||||
## Content Standards
|
||||
|
||||
The root copy standards still apply: use American English, sentence case, active voice, inclusive language, direct user-focused wording, and consistent Home Assistant terminology.
|
||||
Follow the detailed copy standards in `ha-frontend-user-facing-text`: use American English, sentence case, active voice, inclusive language, direct user-focused wording, and consistent Home Assistant terminology.
|
||||
|
||||
- Use `Home Assistant` in full, not `HA` or `HASS`.
|
||||
- Use `integration` instead of `component` for product concepts.
|
||||
@@ -65,9 +65,9 @@ The app suite uses a stripped-down harness for e2e. Demo and gallery use their n
|
||||
|
||||
## Benchmarks
|
||||
|
||||
For chart data transforms such as history, statistics, energy, and downsampling, follow `test/benchmarks/README.md`.
|
||||
For chart data transforms such as history, statistics, energy, and downsampling, read and follow the complete workflow in `test/benchmarks/README.md` before making benchmark or optimization changes.
|
||||
|
||||
Use seeded fixtures, characterization snapshot tests, and `yarn test:bench` before and after optimization. Optimizations must keep output bit-identical.
|
||||
That workflow owns the baseline, noise analysis, guardrails, acceptance thresholds, and reporting requirements. In particular, optimizations must keep output bit-identical; never update snapshots or modify fixtures to make an optimization pass.
|
||||
|
||||
## Verification Selection
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ Use this skill for all user-facing text, translations, labels, buttons, dialog c
|
||||
- Give translators enough context through key naming and placeholders.
|
||||
|
||||
```ts
|
||||
this.hass.localize("ui.panel.config.updates.updates_refreshed", {
|
||||
this._localize("ui.panel.config.updates.updates_refreshed", {
|
||||
count: 5,
|
||||
});
|
||||
```
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
name: Copilot Setup Steps
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- .github/workflows/copilot-setup-steps.yml
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/workflows/copilot-setup-steps.yml
|
||||
env:
|
||||
NODE_OPTIONS: --max_old_space_size=6144
|
||||
|
||||
jobs:
|
||||
copilot-setup-steps:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Node and install
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-modules-cache: true
|
||||
|
||||
- name: Skip fetching nightly translations
|
||||
run: echo "SKIP_FETCH_NIGHTLY_TRANSLATIONS=1" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Build resources
|
||||
run: ./node_modules/.bin/gulp gen-icons-json build-translations build-locale-data gather-gallery-pages
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
You are helping develop the Home Assistant frontend. This repository is a TypeScript application built from Lit-based Web Components for the Home Assistant web UI.
|
||||
|
||||
For gallery-specific documentation, demos, page structure, and examples, read `gallery/AGENTS.md` when working under `gallery/`.
|
||||
|
||||
## Essential Commands
|
||||
|
||||
```bash
|
||||
@@ -46,6 +44,7 @@ Detailed guidance lives in project skills under `.agents/skills/`. Load the matc
|
||||
- `ha-frontend-testing`: lint, typecheck, Vitest, Playwright e2e dev servers, and benchmarks.
|
||||
- `ha-frontend-user-facing-text`: localization, terminology, sentence case, and Home Assistant text style.
|
||||
- `ha-frontend-review`: PR template use, review checklist, and recurring review issues.
|
||||
- `ha-frontend-gallery`: gallery pages, demos, sidebar structure, content, and verification.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
|
||||
+32
-3
@@ -15,10 +15,26 @@ export interface HttpConfig {
|
||||
ssl_profile?: "modern" | "intermediate";
|
||||
}
|
||||
|
||||
// The slot the running HTTP server was actually started with.
|
||||
export type ActiveConfigType = "stable" | "pending" | "default";
|
||||
|
||||
// A stored config slot carries metadata alongside the editable fields:
|
||||
// - created_at: when the slot was staged
|
||||
// - error: null while healthy; set once a slot could not be applied or a
|
||||
// pending trial was not confirmed (then it is kept for display, not retried)
|
||||
export interface HttpConfigWithMeta extends HttpConfig {
|
||||
created_at?: string;
|
||||
error?: string | null;
|
||||
}
|
||||
|
||||
export interface HttpConfigState {
|
||||
stable: HttpConfig;
|
||||
pending: HttpConfig | null;
|
||||
stable: HttpConfigWithMeta;
|
||||
pending: HttpConfigWithMeta | null;
|
||||
revert_at: string | null;
|
||||
// Added in the "active HTTP config slot" backend change; optional so the
|
||||
// frontend keeps working against cores without it.
|
||||
active_config_type?: ActiveConfigType;
|
||||
default?: HttpConfigWithMeta;
|
||||
}
|
||||
|
||||
export const HTTP_CONFIG_FIELDS: (keyof HttpConfig)[] = [
|
||||
@@ -40,6 +56,19 @@ export interface SaveHttpConfigResult {
|
||||
restart: boolean;
|
||||
}
|
||||
|
||||
// Keep only the editable fields; the backend storage schema rejects unknown
|
||||
// keys, so the created_at/error metadata that rides along on a fetched slot
|
||||
// must be dropped before configuring.
|
||||
export const stripHttpConfigMeta = (config: HttpConfig): HttpConfig => {
|
||||
const stripped: Partial<Record<keyof HttpConfig, unknown>> = {};
|
||||
for (const key of HTTP_CONFIG_FIELDS) {
|
||||
if (config[key] !== undefined) {
|
||||
stripped[key] = config[key];
|
||||
}
|
||||
}
|
||||
return stripped as HttpConfig;
|
||||
};
|
||||
|
||||
export const fetchHttpConfig = (hass: HomeAssistant) =>
|
||||
hass.callWS<HttpConfigState>({ type: "http/config" });
|
||||
|
||||
@@ -49,7 +78,7 @@ export const saveHttpConfig = (
|
||||
) =>
|
||||
hass.callWS<SaveHttpConfigResult>({
|
||||
type: "http/config/configure",
|
||||
config,
|
||||
config: config ? stripHttpConfigMeta(config) : null,
|
||||
});
|
||||
|
||||
export const promoteHttpConfig = (hass: HomeAssistant) =>
|
||||
|
||||
@@ -78,18 +78,21 @@ class MoreInfoLawnMower extends LitElement {
|
||||
);
|
||||
}
|
||||
|
||||
private get _startPauseIcon(): string {
|
||||
if (!this.stateObj) return mdiPlay;
|
||||
return isMowing(this.stateObj) &&
|
||||
private get _showPause(): boolean {
|
||||
if (!this.stateObj) return false;
|
||||
return (
|
||||
isMowing(this.stateObj) &&
|
||||
supportsFeature(this.stateObj, LawnMowerEntityFeature.PAUSE)
|
||||
? mdiPause
|
||||
: mdiPlay;
|
||||
);
|
||||
}
|
||||
|
||||
private get _startPauseIcon(): string {
|
||||
return this._showPause ? mdiPause : mdiPlay;
|
||||
}
|
||||
|
||||
private get _startPauseLabel(): string {
|
||||
if (!this.stateObj) return "";
|
||||
return isMowing(this.stateObj) &&
|
||||
supportsFeature(this.stateObj, LawnMowerEntityFeature.PAUSE)
|
||||
return this._showPause
|
||||
? this._i18n.localize("ui.dialogs.more_info_control.lawn_mower.pause")
|
||||
: this._i18n.localize(
|
||||
"ui.dialogs.more_info_control.lawn_mower.start_mowing"
|
||||
@@ -99,8 +102,11 @@ class MoreInfoLawnMower extends LitElement {
|
||||
private get _startPauseDisabled(): boolean {
|
||||
if (!this.stateObj) return true;
|
||||
if (this.stateObj.state === UNAVAILABLE) return true;
|
||||
if (isMowing(this.stateObj)) return false;
|
||||
return !canStartMowing(this.stateObj);
|
||||
if (this._showPause) return false;
|
||||
return (
|
||||
!supportsFeature(this.stateObj, LawnMowerEntityFeature.START_MOWING) ||
|
||||
!canStartMowing(this.stateObj)
|
||||
);
|
||||
}
|
||||
|
||||
private _renderBattery() {
|
||||
@@ -156,7 +162,7 @@ class MoreInfoLawnMower extends LitElement {
|
||||
private _handleStartPause() {
|
||||
if (!this.stateObj) return;
|
||||
forwardHaptic(this, "light");
|
||||
if (isMowing(this.stateObj)) {
|
||||
if (this._showPause) {
|
||||
this._api.callService("lawn_mower", "pause", {
|
||||
entity_id: this.stateObj.entity_id,
|
||||
});
|
||||
|
||||
@@ -269,7 +269,14 @@ export class HomeAssistantAppEl extends QuickBarMixin(HassElement) {
|
||||
// The check re-runs on the next reconnect; ignore transient failures.
|
||||
return;
|
||||
}
|
||||
if (!httpConfig.pending || this._httpPendingDialogOpen) {
|
||||
// Only prompt for an active trial. A pending config with an error was
|
||||
// already reverted/failed and is kept only for display in the config form,
|
||||
// so it must not pop the confirm/revert dialog.
|
||||
if (
|
||||
!httpConfig.pending ||
|
||||
httpConfig.pending.error ||
|
||||
this._httpPendingDialogOpen
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this._httpPendingDialogOpen = true;
|
||||
|
||||
@@ -19,7 +19,11 @@ import {
|
||||
HTTP_CONFIG_FIELDS,
|
||||
saveHttpConfig,
|
||||
} from "../../../data/http";
|
||||
import type { HttpConfig } from "../../../data/http";
|
||||
import type {
|
||||
ActiveConfigType,
|
||||
HttpConfig,
|
||||
HttpConfigWithMeta,
|
||||
} from "../../../data/http";
|
||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
@@ -161,6 +165,11 @@ class HaConfigHttpForm extends LitElement {
|
||||
|
||||
@state() private _showNoChanges = false;
|
||||
|
||||
@state() private _activeConfigType?: ActiveConfigType;
|
||||
|
||||
// A pending config that was reverted/failed and kept only for display.
|
||||
@state() private _revertedPending?: HttpConfigWithMeta;
|
||||
|
||||
@query("ha-form") private _form?: HaForm;
|
||||
|
||||
@query("ha-alert") private _firstAlert?: HTMLElement;
|
||||
@@ -201,6 +210,40 @@ class HaConfigHttpForm extends LitElement {
|
||||
<p class="description">
|
||||
${this.hass.localize("ui.panel.config.network.http.description")}
|
||||
</p>
|
||||
${
|
||||
this._activeConfigType === "default"
|
||||
? html`
|
||||
<ha-alert alert-type="warning">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.network.http.running_default"
|
||||
)}
|
||||
</ha-alert>
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
${
|
||||
this._revertedPending
|
||||
? html`
|
||||
<ha-alert alert-type="warning">
|
||||
${
|
||||
this._revertedPending.error === "not_promoted"
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.network.http.reverted_not_confirmed"
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.network.http.reverted_failed",
|
||||
{ error: this._revertedPending.error ?? "" }
|
||||
)
|
||||
}
|
||||
<ha-button slot="action" @click=${this._reviewReverted}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.network.http.reverted_action"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-alert>
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
${
|
||||
portChanged
|
||||
? html`
|
||||
@@ -266,16 +309,30 @@ class HaConfigHttpForm extends LitElement {
|
||||
|
||||
private async _fetchConfig(): Promise<void> {
|
||||
try {
|
||||
// Pending is exclusively handled by the global confirm/revert dialog, so
|
||||
// the form only ever displays stable.
|
||||
const { stable } = await fetchHttpConfig(this.hass);
|
||||
const { stable, pending, active_config_type } = await fetchHttpConfig(
|
||||
this.hass
|
||||
);
|
||||
this._stable = stable;
|
||||
this._config = { ...stable };
|
||||
this._activeConfigType = active_config_type;
|
||||
// An active trial pending (no error) is handled by the global
|
||||
// confirm/revert dialog. A pending carrying an error was reverted or
|
||||
// failed to apply and is kept only so we can surface it here.
|
||||
this._revertedPending = pending?.error ? pending : undefined;
|
||||
} catch (err: any) {
|
||||
this._error = err.message;
|
||||
}
|
||||
}
|
||||
|
||||
private _reviewReverted(): void {
|
||||
if (!this._revertedPending) {
|
||||
return;
|
||||
}
|
||||
// Load the reverted values into the form so the user can fix and re-save.
|
||||
this._config = { ...this._revertedPending };
|
||||
this._revertedPending = undefined;
|
||||
}
|
||||
|
||||
private _computeLabel = (
|
||||
schema: SchemaUnion<ReturnType<typeof SCHEMA>>
|
||||
): string => {
|
||||
|
||||
@@ -16,7 +16,11 @@ import "../../../components/ha-svg-icon";
|
||||
import { apiContext } from "../../../data/context";
|
||||
import { UNAVAILABLE } from "../../../data/entity/entity";
|
||||
import type { LawnMowerEntity } from "../../../data/lawn_mower";
|
||||
import { LawnMowerEntityFeature, canDock } from "../../../data/lawn_mower";
|
||||
import {
|
||||
LawnMowerEntityFeature,
|
||||
canDock,
|
||||
canStartMowing,
|
||||
} from "../../../data/lawn_mower";
|
||||
import type { HomeAssistant, HomeAssistantApi } from "../../../types";
|
||||
import type { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types";
|
||||
import { cardFeatureStyles } from "./common/card-feature-styles";
|
||||
@@ -72,6 +76,9 @@ export const LAWN_MOWER_COMMANDS_BUTTONS: Record<
|
||||
translationKey: "start",
|
||||
icon: mdiPlay,
|
||||
serviceName: "start_mowing",
|
||||
disabled:
|
||||
!supportsFeature(stateObj, LawnMowerEntityFeature.START_MOWING) ||
|
||||
!canStartMowing(stateObj),
|
||||
};
|
||||
},
|
||||
dock: (stateObj) => ({
|
||||
|
||||
@@ -29,7 +29,7 @@ class StateCardLock extends LitElement {
|
||||
.inDialog=${this.inDialog}
|
||||
></state-info>
|
||||
${
|
||||
!supportsOpen
|
||||
supportsOpen
|
||||
? html`<ha-button
|
||||
appearance="plain"
|
||||
size="s"
|
||||
|
||||
@@ -8693,6 +8693,10 @@
|
||||
"port_warning": "Clients such as the Home Assistant mobile apps will lose their connection until you update the URL in their settings. If Home Assistant is not confirmed reachable on the new port, the change is rolled back automatically after 5 minutes.",
|
||||
"invalid_host": "Enter a valid IP address.",
|
||||
"invalid_network": "Enter a valid IP address or network.",
|
||||
"running_default": "Your saved HTTP configuration could not be applied, so Home Assistant is running on the built-in default configuration.",
|
||||
"reverted_not_confirmed": "The last HTTP configuration change was not confirmed in time and was rolled back. Home Assistant is running on the previous configuration.",
|
||||
"reverted_failed": "The last HTTP configuration change could not be applied and was rolled back. Home Assistant is running on the previous configuration. Reason: {error}",
|
||||
"reverted_action": "Review the change",
|
||||
"save_confirm": {
|
||||
"title": "Restart required",
|
||||
"text": "Saving will restart Home Assistant to apply the new HTTP settings.",
|
||||
|
||||
@@ -9148,9 +9148,9 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"fast-uri@npm:^3.0.1":
|
||||
version: 3.1.3
|
||||
resolution: "fast-uri@npm:3.1.3"
|
||||
checksum: 10/7969a50a327482035d5c8c93faf51b26d7a93ce62bc750c91b3df158550004b5fbb378c95c900fd71f4dded36b5a78d1c666fb8043bb1214efbaebd8518d873e
|
||||
version: 3.1.4
|
||||
resolution: "fast-uri@npm:3.1.4"
|
||||
checksum: 10/1c1ff8e7b6f7b38e997b1528aa2c97e290e0173d51250bfe4e11a303e454fc297a287555b5cb2ded59dbfce7876855fb15994a1a6b439f2497a2b8926513e397
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
Reference in New Issue
Block a user