Compare commits

..

18 Commits

Author SHA1 Message Date
Squazel
05f4419a92
Fix picture-glance card icon styling for unavailable/unknown entities (#26352) 2025-08-04 08:16:11 +02:00
renovate[bot]
5ea8feb86b
Update rspack monorepo to v1.4.11 (#26365)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-04 06:52:21 +02:00
Jan-Philipp Benecke
8fd70b3ae6
Improve Z-Wave JS config dashboard styling (#26368) 2025-08-04 06:50:35 +02:00
renovate[bot]
343aa40bc8
Update dependency @types/luxon to v3.7.1 (#26351)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-03 08:10:32 +02:00
Jan-Philipp Benecke
6022f9a77e
Do not show AI suggestion button when no inputs in save dialog (#26357) 2025-08-03 08:10:06 +02:00
renovate[bot]
bd9de0680e
Update dependency @types/luxon to v3.7.0 (#26342)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-01 17:09:44 +02:00
Simon Lamon
b8000d5bc1
Fix diagnostic download on integration level (#26341) 2025-08-01 13:16:09 +02:00
Wendelin
c6efa1127f
Fix dialog secondary button design (#26344) 2025-08-01 13:13:42 +02:00
Bram Kragten
688a3d91d3
Add support for sub config flows in conversation agent picker (#26336) 2025-08-01 13:13:28 +02:00
Timothy
68151a2a70
Add Bruno and Timo as codeowners of the external_app folders (#26345) 2025-08-01 11:49:47 +02:00
Wendelin
c2ca556151
Fix line-height, fix script editor buttons (#26337)
* Fix line-height

* Fix script root buttons
2025-07-31 16:52:36 +02:00
Wendelin
df86b27af4
Use tilecard button feature editor (#26335)
Use button feature editor
2025-07-31 13:27:40 +02:00
Wendelin
eba1f401cc
Fix ha-button with missing label and links (#26332) 2025-07-31 12:40:17 +02:00
Wendelin
19c2f9c9e8
Revert "Use query params instead of path for media browser navigate ids" (#26333) 2025-07-31 12:38:52 +02:00
Bram Kragten
4250447d14
Fix area picker text alignment in voice wizard (#26330) 2025-07-31 11:52:33 +02:00
Joost Lekkerkerker
4666197f28
Use underscores in AI task name (#26327) 2025-07-30 21:52:09 +02:00
Franck Nijhof
a5ca36c93f
Add weekdays to time trigger (#25908)
* Add weekdays to time trigger

* Update src/translations/en.json

Co-authored-by: Norbert Rittel <norbert@rittel.de>

* Localization changes

---------

Co-authored-by: Norbert Rittel <norbert@rittel.de>
Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com>
2025-07-30 19:45:10 +02:00
Norbert Rittel
a88950e16c
Correct the setup steps for Matter sharing in Google Home app (#26322)
Correct the setup steps in the Google Home app
2025-07-30 19:40:41 +02:00
108 changed files with 1081 additions and 987 deletions

View File

@ -310,7 +310,11 @@ export class DialogMyFeature
.heading=${createCloseHeading(this.hass, this._params.title)}
>
<!-- Dialog content -->
<ha-button @click=${this.closeDialog} slot="secondaryAction">
<ha-button
appearance="plain"
@click=${this.closeDialog}
slot="secondaryAction"
>
${this.hass.localize("ui.common.cancel")}
</ha-button>
<ha-button @click=${this._submit} slot="primaryAction">

8
CODEOWNERS Normal file
View File

@ -0,0 +1,8 @@
# People marked here will be automatically requested for a review
# when the code that they own is touched.
# https://github.com/blog/2392-introducing-code-owners
# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
# Part of the frontend that mobile developper should review
src/external_app/ @bgoncal @TimoPtr
test/external_app/ @bgoncal @TimoPtr

View File

@ -89,11 +89,14 @@ export class HADemoCard extends LitElement implements LovelaceCard {
)}
</div>
<div class="actions small-hidden">
<a href="https://www.home-assistant.io" target="_blank">
<ha-button>
${this.hass.localize("ui.panel.page-demo.cards.demo.learn_more")}
</ha-button>
</a>
<ha-button
appearance="plain"
size="small"
href="https://www.home-assistant.io"
target="_blank"
>
${this.hass.localize("ui.panel.page-demo.cards.demo.learn_more")}
</ha-button>
</div>
</ha-card>
`;

View File

@ -147,13 +147,13 @@ The `title ` option should not be used without a description.
<ha-alert alert-type="success">
This is a success alert — check it out!
<ha-button slot="action" label="Undo"></ha-button>
<ha-button slot="action">Undo</ha-button>
</ha-alert>
```html
<ha-alert alert-type="success">
This is a success alert — check it out!
<ha-button slot="action" label="Undo"></ha-button>
<ha-button slot="action">Undo</ha-button>
</ha-alert>
```

View File

@ -78,21 +78,13 @@ const alerts: {
title: "Error with action",
description: "This is a test error alert with action",
type: "error",
actionSlot: html`<ha-button
size="small"
slot="action"
label="restart"
></ha-button>`,
actionSlot: html`<ha-button size="small" slot="action">restart</ha-button>`,
},
{
title: "Unsaved data",
description: "You have unsaved data",
type: "warning",
actionSlot: html`<ha-button
size="small"
slot="action"
label="save"
></ha-button>`,
actionSlot: html`<ha-button size="small" slot="action">save</ha-button>`,
},
{
title: "Slotted icon",
@ -116,7 +108,7 @@ const alerts: {
title: "Slotted action",
description: "Alert with slotted action",
type: "info",
actionSlot: html`<ha-button slot="action" label="action"></ha-button>`,
actionSlot: html`<ha-button slot="action">action</ha-button>`,
},
{
description: "Dismissable information (RTL)",
@ -128,7 +120,7 @@ const alerts: {
title: "Error with action",
description: "This is a test error alert with action (RTL)",
type: "error",
actionSlot: html`<ha-button slot="action" label="restart"></ha-button>`,
actionSlot: html`<ha-button slot="action">restart</ha-button>`,
rtl: true,
},
{

View File

@ -53,12 +53,13 @@ Check the [webawesome documentation](https://webawesome.com/docs/components/butt
**Properties/Attributes**
| Name | Type | Default | Description |
| ----------- | ---------------------------------------------- | -------- | -------------------------------------------------- |
| appearance | "accent"/"filled"/"plain" | "accent" | Sets the button appearance. |
| variants | "brand"/"danger"/"neutral"/"warning"/"success" | "brand" | Sets the button color variant. "brand" is default. |
| size | "small"/"medium" | "medium" | Sets the button size. |
| hideContent | Boolean | false | Hides the button content (for overlays) |
| Name | Type | Default | Description |
| ---------- | ---------------------------------------------- | -------- | --------------------------------------------------------------------------------- |
| appearance | "accent"/"filled"/"plain" | "accent" | Sets the button appearance. |
| variants | "brand"/"danger"/"neutral"/"warning"/"success" | "brand" | Sets the button color variant. "brand" is default. |
| size | "small"/"medium" | "medium" | Sets the button size. |
| loading | Boolean | false | Shows a loading indicator instead of the buttons label and disable buttons click. |
| disabled | Boolean | false | Disables the button and prevents user interaction. |
**CSS Custom Properties**

View File

@ -604,8 +604,8 @@ export class DialogHassioNetwork
var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12));
display: flex;
justify-content: space-between;
padding: 8px;
padding-bottom: max(var(--safe-area-inset-bottom), 8px);
padding: 16px;
padding-bottom: max(var(--safe-area-inset-bottom), 16px);
background-color: var(--mdc-theme-surface, #fff);
}
.warning {

View File

@ -208,14 +208,16 @@ class UpdateAvailableCard extends LitElement {
<div class="card-actions">
${changelog
? html`
<a href=${changelog} target="_blank" rel="noreferrer">
<ha-button
.label=${this.supervisor.localize(
"update_available.open_release_notes"
)}
>
</ha-button>
</a>
<ha-button
href=${changelog}
target="_blank"
rel="noreferrer"
appearance="plain"
>
${this.supervisor.localize(
"update_available.open_release_notes"
)}
</ha-button>
`
: nothing}
<span></span>

View File

@ -110,12 +110,9 @@ class LandingPageLogs extends LitElement {
})}"
@click=${this._scrollToBottom}
>
<ha-svg-icon .path=${mdiArrowCollapseDown} slot="icon"></ha-svg-icon>
<ha-svg-icon .path=${mdiArrowCollapseDown} slot="start"></ha-svg-icon>
${this.localize("logs.scroll_down_button")}
<ha-svg-icon
.path=${mdiArrowCollapseDown}
slot="trailingIcon"
></ha-svg-icon>
<ha-svg-icon .path=${mdiArrowCollapseDown} slot="end"></ha-svg-icon>
</ha-button>
`;
}

View File

@ -160,8 +160,8 @@
"@octokit/plugin-retry": "8.0.1",
"@octokit/rest": "22.0.0",
"@rsdoctor/rspack-plugin": "1.1.10",
"@rspack/cli": "1.4.10",
"@rspack/core": "1.4.10",
"@rspack/cli": "1.4.11",
"@rspack/core": "1.4.11",
"@types/babel__plugin-transform-runtime": "7.9.5",
"@types/chromecast-caf-receiver": "6.0.22",
"@types/chromecast-caf-sender": "1.0.11",
@ -173,7 +173,7 @@
"@types/leaflet-draw": "1.0.12",
"@types/leaflet.markercluster": "1.5.5",
"@types/lodash.merge": "4.6.9",
"@types/luxon": "3.6.2",
"@types/luxon": "3.7.1",
"@types/mocha": "10.0.10",
"@types/qrcode": "1.5.5",
"@types/sortablejs": "1.15.8",

View File

@ -32,7 +32,8 @@ export type Appearance = "accent" | "filled" | "outlined" | "plain";
* @attr {("small"|"medium")} size - Sets the button size.
* @attr {("brand"|"neutral"|"danger"|"warning"|"success")} variant - Sets the button color variant. "primary" is default.
* @attr {("accent"|"filled"|"plain")} appearance - Sets the button appearance.
* @attr {boolean} hideContent - Hides the button content (for overlays).
* @attr {boolean} loading - shows a loading indicator instead of the buttons label and disable buttons click.
* @attr {boolean} disabled - Disables the button and prevents user interaction.
*/
@customElement("ha-button")
export class HaButton extends Button {
@ -66,6 +67,7 @@ export class HaButton extends Button {
);
font-size: var(--ha-font-size-m);
line-height: 1;
}
:host([size="small"]) .button {

View File

@ -5,8 +5,8 @@ import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event";
import { stopPropagation } from "../common/dom/stop_propagation";
import { debounce } from "../common/util/debounce";
import type { ConfigEntry } from "../data/config_entries";
import { getConfigEntry } from "../data/config_entries";
import type { ConfigEntry, SubEntry } from "../data/config_entries";
import { getConfigEntry, getSubEntries } from "../data/config_entries";
import type { Agent } from "../data/conversation";
import { listAgents } from "../data/conversation";
import { fetchIntegrationManifest } from "../data/integration";
@ -16,6 +16,7 @@ import "./ha-list-item";
import "./ha-select";
import type { HaSelect } from "./ha-select";
import { getExtendedEntityRegistryEntry } from "../data/entity_registry";
import { showSubConfigFlowDialog } from "../dialogs/config-flow/show-dialog-sub-config-flow";
const NONE = "__NONE_OPTION__";
@ -37,6 +38,8 @@ export class HaConversationAgentPicker extends LitElement {
@state() private _configEntry?: ConfigEntry;
@state() private _subConfigEntry?: SubEntry;
protected render() {
if (!this._agents) {
return nothing;
@ -101,7 +104,11 @@ export class HaConversationAgentPicker extends LitElement {
${agent.name}
</ha-list-item>`
)}</ha-select
>${this._configEntry?.supports_options
>${(this._subConfigEntry &&
this._configEntry?.supported_subentry_types[
this._subConfigEntry.subentry_type
]?.supports_reconfigure) ||
this._configEntry?.supports_options
? html`<ha-icon-button
.path=${mdiCog}
@click=${this._openOptionsFlow}
@ -142,8 +149,17 @@ export class HaConversationAgentPicker extends LitElement {
this._configEntry = (
await getConfigEntry(this.hass, regEntry.config_entry_id)
).config_entry;
if (!regEntry.config_subentry_id) {
this._subConfigEntry = undefined;
} else {
this._subConfigEntry = (
await getSubEntries(this.hass, regEntry.config_entry_id)
).find((entry) => entry.subentry_id === regEntry.config_subentry_id);
}
} catch (_err) {
this._configEntry = undefined;
this._subConfigEntry = undefined;
}
}
@ -182,6 +198,25 @@ export class HaConversationAgentPicker extends LitElement {
if (!this._configEntry) {
return;
}
if (
this._subConfigEntry &&
this._configEntry.supported_subentry_types[
this._subConfigEntry.subentry_type
]?.supports_reconfigure
) {
showSubConfigFlowDialog(
this,
this._configEntry,
this._subConfigEntry.subentry_type,
{
startFlowHandler: this._configEntry.entry_id,
subEntryId: this._subConfigEntry.subentry_id,
}
);
return;
}
showOptionsFlowDialog(this, this._configEntry, {
manifest: await fetchIntegrationManifest(
this.hass,

View File

@ -92,11 +92,12 @@ export class HaPictureUpload extends LitElement {
/>
<div>
<ha-button
appearance="plain"
size="small"
variant="danger"
@click=${this._handleChangeClick}
.label=${this.hass.localize(
"ui.components.picture-upload.clear_picture"
)}
>
${this.hass.localize("ui.components.picture-upload.clear_picture")}
</ha-button>
</div>
</div>

View File

@ -103,7 +103,7 @@ class HaQrScanner extends LitElement {
>
${this._error || this._warning}
${this._error
? html` <ha-button @click=${this._retry} slot="action">
? html`<ha-button @click=${this._retry} slot="action">
${this.hass.localize("ui.components.qr-scanner.retry")}
</ha-button>`
: nothing}

View File

@ -130,7 +130,7 @@ export class HaYamlEditor extends LitElement {
<div class="card-actions">
${this.copyClipboard
? html`
<ha-button @click=${this._copyYaml}>
<ha-button appearance="plain" @click=${this._copyYaml}>
${this.hass.localize(
"ui.components.yaml-editor.copy_to_clipboard"
)}

View File

@ -119,15 +119,15 @@ class DialogMediaManage extends LitElement {
class="danger"
slot="navigationIcon"
.disabled=${this._deleting}
.label=${this.hass.localize(
@click=${this._handleDelete}
>
<ha-svg-icon .path=${mdiDelete} slot="start"></ha-svg-icon>
${this.hass.localize(
`ui.components.media-browser.file_management.${
this._deleting ? "deleting" : "delete"
}`,
{ count: this._selected.size }
)}
@click=${this._handleDelete}
>
<ha-svg-icon .path=${mdiDelete} slot="start"></ha-svg-icon>
</ha-button>
${this._deleting
@ -135,15 +135,15 @@ class DialogMediaManage extends LitElement {
: html`
<ha-button
slot="actionItems"
.label=${this.hass.localize(
`ui.components.media-browser.file_management.deselect_all`
)}
@click=${this._handleDeselectAll}
>
<ha-svg-icon
.path=${mdiClose}
slot="icon"
slot="start"
></ha-svg-icon>
${this.hass.localize(
`ui.components.media-browser.file_management.deselect_all`
)}
</ha-button>
`}
`}
@ -331,20 +331,10 @@ class DialogMediaManage extends LitElement {
--mdc-theme-primary: var(--error-color);
}
ha-svg-icon[slot="icon"] {
vertical-align: middle;
}
ha-tip {
margin: 16px;
}
ha-svg-icon[slot="icon"] {
margin-inline-start: 0px !important;
margin-inline-end: 8px !important;
direction: var(--direction);
}
.refresh {
display: flex;
height: 200px;

View File

@ -1,5 +1,5 @@
import { mdiFolderEdit } from "@mdi/js";
import { css, html, LitElement, nothing } from "lit";
import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event";
import type { MediaPlayerItem } from "../../data/media-player";
@ -53,18 +53,6 @@ class MediaManageButton extends LitElement {
onClose: () => fireEvent(this, "media-refresh"),
});
}
static styles = css`
ha-svg-icon[slot="icon"] {
vertical-align: middle;
}
ha-svg-icon[slot="icon"] {
margin-inline-start: 0px;
margin-inline-end: 8px;
direction: var(--direction);
}
`;
}
declare global {

View File

@ -169,6 +169,7 @@ export interface TagTrigger extends BaseTrigger {
export interface TimeTrigger extends BaseTrigger {
trigger: "time";
at: string | { entity_id: string; offset?: string };
weekday?: string | string[];
}
export interface TemplateTrigger extends BaseTrigger {

View File

@ -400,8 +400,23 @@ const tryDescribeTrigger = (
return `${entityStr}${offsetStr}`;
});
// Handle weekday information if present
let weekdays: string[] = [];
if (trigger.weekday) {
const weekdayArray = ensureArray(trigger.weekday);
if (weekdayArray.length > 0) {
weekdays = weekdayArray.map((day) =>
hass.localize(
`ui.panel.config.automation.editor.triggers.type.time.weekdays.${day}` as any
)
);
}
}
return hass.localize(`${triggerTranslationBaseKey}.time.description.full`, {
time: formatListWithOrs(hass.locale, result),
hasWeekdays: weekdays.length > 0 ? "true" : "false",
weekdays: formatListWithOrs(hass.locale, weekdays),
});
}

View File

@ -104,22 +104,12 @@ class StepFlowForm extends LitElement {
</div>`
: nothing}
<div class="buttons">
${this._loading
? html`
<div class="submit-spinner">
<ha-spinner size="small"></ha-spinner>
</div>
`
: html`
<div>
<ha-button @click=${this._submitStep}>
${this.flowConfig.renderShowFormStepSubmitButton(
this.hass,
this.step
)}
</ha-button>
</div>
`}
<ha-button @click=${this._submitStep} .loading=${this._loading}>
${this.flowConfig.renderShowFormStepSubmitButton(
this.hass,
this.step
)}
</ha-button>
</div>
`;
}
@ -304,15 +294,6 @@ class StepFlowForm extends LitElement {
color: red;
}
.submit-spinner {
height: 36px;
display: flex;
align-items: center;
margin-right: 16px;
margin-inline-end: 16px;
margin-inline-start: initial;
}
ha-alert,
ha-form {
margin-top: 24px;
@ -320,7 +301,7 @@ class StepFlowForm extends LitElement {
}
.buttons {
padding: 8px;
padding: 16px;
}
`,
];

View File

@ -34,7 +34,7 @@ export const configFlowContentStyles = css`
.buttons {
position: relative;
padding: 8px 16px 8px 24px;
padding: 16px;
margin: 8px 0 0;
color: var(--primary-color);
display: flex;

View File

@ -228,7 +228,7 @@ class DialogLightColorFavorite extends LitElement {
</div>
</div>
<div slot="actions">
<ha-button @click=${this._cancelDialog}>
<ha-button appearance="plain" @click=${this._cancelDialog}>
${this.hass.localize("ui.common.cancel")}
</ha-button>
<ha-button @click=${this._save} .disabled=${!this._color}

View File

@ -1,13 +1,13 @@
import { css, html, LitElement, nothing } from "lit";
import { property, state } from "lit/decorators";
import { slugify } from "../../../common/string/slugify";
import "../../../components/buttons/ha-progress-button";
import "../../../components/ha-camera-stream";
import type { CameraEntity } from "../../../data/camera";
import type { HomeAssistant } from "../../../types";
import "../../../components/buttons/ha-progress-button";
import { UNAVAILABLE } from "../../../data/entity";
import type { HomeAssistant } from "../../../types";
import { fileDownload } from "../../../util/file_download";
import { showToast } from "../../../util/toast";
import { slugify } from "../../../common/string/slugify";
class MoreInfoCamera extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@ -46,6 +46,7 @@ class MoreInfoCamera extends LitElement {
@click=${this._downloadSnapshot}
.progress=${this._waiting}
.disabled=${this.stateObj.state === UNAVAILABLE}
appearance="filled"
>
${this.hass.localize(
"ui.dialogs.more_info_control.camera.download_snapshot"
@ -104,7 +105,7 @@ class MoreInfoCamera extends LitElement {
flex-wrap: wrap;
justify-content: flex-end;
box-sizing: border-box;
padding: 12px;
padding: 16px;
z-index: 1;
gap: 8px;
}

View File

@ -51,7 +51,11 @@ class MoreInfoSiren extends LitElement {
.iconPathOff=${mdiVolumeOff}
></ha-state-control-toggle>
${allowAdvanced
? html`<ha-button @click=${this._showAdvancedControlsDialog}>
? html`<ha-button
appearance="plain"
size="small"
@click=${this._showAdvancedControlsDialog}
>
${this.hass.localize("ui.components.siren.advanced_controls")}
</ha-button>`
: nothing}

View File

@ -508,7 +508,7 @@ class MoreInfoUpdate extends LitElement {
flex-wrap: wrap;
justify-content: flex-end;
box-sizing: border-box;
padding: 12px;
padding: 16px;
z-index: 1;
gap: 8px;
}

View File

@ -38,7 +38,7 @@ export class HuiNotificationItemTemplate extends LitElement {
.actions {
border-top: 1px solid var(--divider-color, #e8e8e8);
padding: 5px 16px;
padding: 8px;
display: flex;
justify-content: flex-end;
}

View File

@ -32,7 +32,7 @@ class DialogBox extends LitElement {
)}
>
<p>${this.hass.localize("ui.dialogs.update_backup.text")}</p>
<ha-button @click=${this._no} slot="secondaryAction">
<ha-button appearance="plain" @click=${this._no} slot="secondaryAction">
${this.hass!.localize("ui.common.no")}
</ha-button>
<ha-button @click=${this._yes} slot="primaryAction">

View File

@ -76,7 +76,6 @@ export class CloudStepSignin extends LitElement {
</div>
<div class="footer">
<ha-button
unelevated
@click=${this._handleLogin}
.disabled=${this._requestInProgress}
>${this.hass.localize(

View File

@ -90,11 +90,11 @@ export class CloudStepSignup extends LitElement {
? html`<ha-button
@click=${this._handleResendVerifyEmail}
.disabled=${this._requestInProgress}
appearance="plain"
>${this.hass.localize(
"ui.panel.config.cloud.register.resend_confirm_email"
)}</ha-button
><ha-button
unelevated
@click=${this._login}
.disabled=${this._requestInProgress}
>${this.hass.localize(
@ -104,12 +104,12 @@ export class CloudStepSignup extends LitElement {
: html`<ha-button
@click=${this._signIn}
.disabled=${this._requestInProgress}
appearance="plain"
>${this.hass.localize(
"ui.panel.config.cloud.login.sign_in"
)}</ha-button
>
<ha-button
unelevated
@click=${this._handleRegister}
.disabled=${this._requestInProgress}
>${this.hass.localize("ui.common.next")}</ha-button

View File

@ -70,6 +70,7 @@ export class HaVoiceAssistantSetupStepArea extends LitElement {
display: block;
width: 100%;
margin-bottom: 24px;
text-align: initial;
}
`,
];

View File

@ -51,16 +51,16 @@ export class HaVoiceAssistantSetupStepCheck extends LitElement {
)}
</p>
<div class="footer">
<a
<ha-button
appearance="plain"
href=${documentationUrl(
this.hass,
"/voice_control/troubleshooting/#i-dont-get-a-voice-response"
)}
><ha-button
>${this.hass.localize(
"ui.panel.config.voice_assistants.satellite_wizard.check.help"
)}</ha-button
></a
>
>${this.hass.localize(
"ui.panel.config.voice_assistants.satellite_wizard.check.help"
)}</ha-button
>
<ha-button @click=${this._testConnection}
>${this.hass.localize(

View File

@ -80,7 +80,7 @@ export class HaVoiceAssistantSetupStepLocal extends LitElement {
${this._detailState || "Installation can take several minutes"}
</p>`
: this._state === "ERROR"
? html` <img
? html`<img
src="/static/images/voice-assistant/error.png"
alt="Casita Home Assistant error logo"
/>
@ -95,24 +95,27 @@ export class HaVoiceAssistantSetupStepLocal extends LitElement {
"ui.panel.config.voice_assistants.satellite_wizard.local.failed_secondary"
)}
</p>
<ha-button @click=${this._prevStep}
<ha-button
appearance="plain"
size="small"
@click=${this._prevStep}
>${this.hass.localize("ui.common.back")}</ha-button
>
<a
<ha-button
href=${documentationUrl(
this.hass,
"/voice_control/voice_remote_local_assistant/"
)}
target="_blank"
rel="noreferrer noopener"
size="small"
appearance="plain"
>
<ha-button>
<ha-svg-icon .path=${mdiOpenInNew} slot="icon"></ha-svg-icon>
${this.hass.localize(
"ui.panel.config.common.learn_more"
)}</ha-button
>
</a>`
<ha-svg-icon .path=${mdiOpenInNew} slot="start"></ha-svg-icon>
${this.hass.localize(
"ui.panel.config.common.learn_more"
)}</ha-button
>`
: this._state === "NOT_SUPPORTED"
? html`<img
src="/static/images/voice-assistant/error.png"
@ -128,27 +131,27 @@ export class HaVoiceAssistantSetupStepLocal extends LitElement {
"ui.panel.config.voice_assistants.satellite_wizard.local.not_supported_secondary"
)}
</p>
<ha-button @click=${this._prevStep}
<ha-button
appearance="plain"
size="small"
@click=${this._prevStep}
>${this.hass.localize("ui.common.back")}</ha-button
>
<a
<ha-button
href=${documentationUrl(
this.hass,
"/voice_control/voice_remote_local_assistant/"
)}
target="_blank"
rel="noreferrer noopener"
appearance="plain"
size="small"
>
<ha-button>
<ha-svg-icon
.path=${mdiOpenInNew}
slot="icon"
></ha-svg-icon>
${this.hass.localize(
"ui.panel.config.common.learn_more"
)}</ha-button
>
</a>`
<ha-svg-icon .path=${mdiOpenInNew} slot="start"></ha-svg-icon>
${this.hass.localize(
"ui.panel.config.common.learn_more"
)}</ha-button
>`
: nothing}
</div>`;
}

View File

@ -235,10 +235,7 @@ export class HaVoiceAssistantSetupStepPipeline extends LitElement {
: nothing}
</div>
<div class="footer">
<ha-button
@click=${this._createPipeline}
unelevated
.disabled=${!this._value}
<ha-button @click=${this._createPipeline} .disabled=${!this._value}
>${this.hass.localize("ui.common.next")}</ha-button
>
</div>`;

View File

@ -126,8 +126,15 @@ export class HaVoiceAssistantSetupStepSuccess extends LitElement {
</ha-list-item>`
)}
</ha-select>
<ha-button @click=${this._testWakeWord}>
<ha-svg-icon slot="icon" .path=${mdiMicrophone}></ha-svg-icon>
<ha-button
appearance="plain"
size="small"
@click=${this._testWakeWord}
>
<ha-svg-icon
slot="start"
.path=${mdiMicrophone}
></ha-svg-icon>
${this.hass.localize(
"ui.panel.config.voice_assistants.satellite_wizard.success.test_wakeword"
)}
@ -151,8 +158,12 @@ export class HaVoiceAssistantSetupStepSuccess extends LitElement {
</ha-list-item>`
)}
</ha-select>
<ha-button @click=${this._openPipeline}>
<ha-svg-icon slot="icon" .path=${mdiCog}></ha-svg-icon>
<ha-button
appearance="plain"
size="small"
@click=${this._openPipeline}
>
<ha-svg-icon slot="start" .path=${mdiCog}></ha-svg-icon>
${this.hass.localize(
"ui.panel.config.voice_assistants.satellite_wizard.success.edit_pipeline"
)}
@ -169,8 +180,12 @@ export class HaVoiceAssistantSetupStepSuccess extends LitElement {
@value-changed=${this._voicePicked}
@closed=${stopPropagation}
></ha-tts-voice-picker>
<ha-button @click=${this._testTts}>
<ha-svg-icon slot="icon" .path=${mdiPlay}></ha-svg-icon>
<ha-button
appearance="plain"
size="small"
@click=${this._testTts}
>
<ha-svg-icon slot="start" .path=${mdiPlay}></ha-svg-icon>
${this.hass.localize(
"ui.panel.config.voice_assistants.satellite_wizard.success.try_tts"
)}

View File

@ -148,7 +148,10 @@ export class HaVoiceAssistantSetupStepWakeWord extends LitElement {
${this.assistConfiguration &&
this.assistConfiguration.available_wake_words.length > 1
? html`<div class="footer centered">
<ha-button @click=${this._changeWakeWord}
<ha-button
appearance="plain"
size="small"
@click=${this._changeWakeWord}
>${this.hass.localize(
"ui.panel.config.voice_assistants.satellite_wizard.wake_word.change_wake_word"
)}</ha-button

View File

@ -271,7 +271,6 @@ export class HaVoiceCommandDialog extends LitElement {
margin-inline-start: -8px;
}
ha-button-menu ha-button {
--ha-font-size-l: var(--ha-font-size-m);
--ha-button-height: 20px;
}
ha-button-menu ha-button::part(base) {

View File

@ -22,7 +22,9 @@ class HaInitPage extends LitElement {
<p class="retry-text">
Retrying in ${this._retryInSeconds} seconds...
</p>
<ha-button @click=${this._retry}>Retry now</ha-button>
<ha-button size="small" appearance="plain" @click=${this._retry}
>Retry now</ha-button
>
${location.host.includes("ui.nabu.casa")
? html`
<p>

View File

@ -69,10 +69,13 @@ class NotificationManager extends LitElement {
${this._parameters?.action
? html`
<ha-button
appearance="plain"
size="small"
slot="action"
.label=${this._parameters?.action.text}
@click=${this._buttonClicked}
></ha-button>
>
${this._parameters?.action.text}
</ha-button>
`
: nothing}
${this._parameters?.dismissable

View File

@ -32,17 +32,16 @@ class OnboardingRestoreBackupNoCloudBackup extends LitElement {
<ha-button @click=${this._signOut}>
${this.localize("ui.panel.page-onboarding.restore.ha-cloud.sign_out")}
</ha-button>
<a
<ha-button
href="https://www.nabucasa.com/config/backups/"
target="_blank"
rel="noreferrer noopener"
appearance="plain"
>
<ha-button>
${this.localize(
"ui.panel.page-onboarding.restore.ha-cloud.learn_more"
)}
</ha-button>
</a>
${this.localize(
"ui.panel.page-onboarding.restore.ha-cloud.learn_more"
)}
</ha-button>
</div>
`;
}

View File

@ -131,18 +131,17 @@ class OnboardingRestoreBackupRestore extends LitElement {
${this.localize(
"ui.panel.page-onboarding.restore.details.addons_unsupported"
)}
<a
<ha-button
slot="action"
href="https://www.home-assistant.io/installation/#advanced-installation-methods"
target="_blank"
rel="noreferrer noopener"
size="small"
>
${this.localize(
"ui.panel.page-onboarding.restore.ha-cloud.learn_more"
)}</ha-button
>
<ha-button
>${this.localize(
"ui.panel.page-onboarding.restore.ha-cloud.learn_more"
)}</ha-button
>
</a>
</ha-alert>`
: nothing}
${!onlyHomeAssistantBackup
@ -191,14 +190,13 @@ class OnboardingRestoreBackupRestore extends LitElement {
<div class="actions${this.mode === "cloud" ? " cloud" : ""}">
${this.mode === "cloud"
? html`<ha-button @click=${this._signOut}>
? html`<ha-button appearance="plain" @click=${this._signOut}>
${this.localize(
"ui.panel.page-onboarding.restore.ha-cloud.sign_out"
)}
</ha-button>`
: nothing}
<ha-progress-button
unelevated
.progress=${this._loading}
.disabled=${this._loading ||
(backupProtected && this._encryptionKey === "") ||

View File

@ -40,7 +40,11 @@ class ConfirmEventDialogBox extends LitElement {
<div>
<p>${this._params.text}</p>
</div>
<ha-button @click=${this._dismiss} slot="secondaryAction">
<ha-button
appearance="plain"
@click=${this._dismiss}
slot="secondaryAction"
>
${this.hass.localize("ui.common.cancel")}
</ha-button>
<ha-button

View File

@ -1,4 +1,3 @@
import { formatInTimeZone, toDate } from "date-fns-tz";
import {
addDays,
addHours,
@ -6,6 +5,7 @@ import {
differenceInMilliseconds,
startOfHour,
} from "date-fns";
import { formatInTimeZone, toDate } from "date-fns-tz";
import type { HassEntity } from "home-assistant-js-websocket";
import type { CSSResultGroup } from "lit";
import { LitElement, css, html, nothing } from "lit";
@ -18,11 +18,11 @@ import { supportsFeature } from "../../common/entity/supports-feature";
import { isDate } from "../../common/string/is_date";
import "../../components/entity/ha-entity-picker";
import "../../components/ha-alert";
import "../../components/ha-button";
import "../../components/ha-date-input";
import { createCloseHeading } from "../../components/ha-dialog";
import "../../components/ha-formfield";
import "../../components/ha-switch";
import "../../components/ha-button";
import "../../components/ha-textarea";
import "../../components/ha-textfield";
import "../../components/ha-time-input";
@ -282,6 +282,7 @@ class DialogCalendarEventEditor extends LitElement {
? html`
<ha-button
slot="secondaryAction"
appearance="plain"
variant="danger"
@click=${this._deleteEvent}
.disabled=${this._submitting}

View File

@ -135,10 +135,7 @@ class PanelCalendar extends LitElement {
>
<ha-button slot="trigger">
${this.hass.localize("ui.components.calendar.my_calendars")}
<ha-svg-icon
slot="trailingIcon"
.path=${mdiChevronDown}
></ha-svg-icon>
<ha-svg-icon slot="end" .path=${mdiChevronDown}></ha-svg-icon>
</ha-button>
${calendarItems}
${this.hass.user?.is_admin
@ -303,25 +300,7 @@ class PanelCalendar extends LitElement {
--calendar-border-width: 1px 0;
}
ha-button-menu ha-button {
--mdc-theme-primary: currentColor;
--mdc-typography-button-text-transform: none;
--mdc-typography-button-font-size: var(
--mdc-typography-headline6-font-size,
var(--ha-font-size-l)
);
--mdc-typography-button-font-weight: var(
--mdc-typography-headline6-font-weight,
var(--ha-font-weight-medium)
);
--mdc-typography-button-letter-spacing: var(
--mdc-typography-headline6-letter-spacing,
0.0125em
);
--mdc-typography-button-line-height: var(
--mdc-typography-headline6-line-height,
var(--ha-line-height-expanded)
);
--button-height: 40px;
--ha-font-size-m: var(--ha-font-size-l);
}
:host([mobile]) .lists {
--mdc-menu-min-width: 100vw;

View File

@ -220,32 +220,25 @@ export class DialogAddApplicationCredential extends LitElement {
helperPersistent
></ha-password-field>
</div>
${this._loading
? html`
<div slot="primaryAction" class="submit-spinner">
<ha-spinner></ha-spinner>
</div>
`
: html`
<ha-button
appearance="plain"
slot="secondaryAction"
@click=${this._abortDialog}
>
${this.hass.localize("ui.common.cancel")}
</ha-button>
<ha-button
slot="primaryAction"
.disabled=${!this._domain ||
!this._clientId ||
!this._clientSecret}
@click=${this._addApplicationCredential}
>
${this.hass.localize(
"ui.panel.config.application_credentials.editor.add"
)}
</ha-button>
`}
<ha-button
appearance="plain"
slot="secondaryAction"
@click=${this._abortDialog}
.disabled=${this._loading}
>
${this.hass.localize("ui.common.cancel")}
</ha-button>
<ha-button
slot="primaryAction"
.disabled=${!this._domain || !this._clientId || !this._clientSecret}
@click=${this._addApplicationCredential}
.loading=${this._loading}
>
${this.hass.localize(
"ui.panel.config.application_credentials.editor.add"
)}
</ha-button>
</ha-dialog>
`;
}

View File

@ -30,6 +30,8 @@ export default class HaAutomationAction extends LitElement {
@property({ type: Boolean }) public disabled = false;
@property({ type: Boolean }) public root = false;
@property({ attribute: false }) public actions!: Action[];
@property({ attribute: false }) public highlightedActions?: Action[];
@ -110,6 +112,8 @@ export default class HaAutomationAction extends LitElement {
<ha-button
.disabled=${this.disabled}
@click=${this._addActionDialog}
.appearance=${this.root ? "accent" : "filled"}
.size=${this.root ? "medium" : "small"}
>
<ha-svg-icon .path=${mdiPlus} slot="start"></ha-svg-icon>
${this.hass.localize(
@ -117,9 +121,10 @@ export default class HaAutomationAction extends LitElement {
)}
</ha-button>
<ha-button
appearance="plain"
.disabled=${this.disabled}
@click=${this._addActionBuildingBlockDialog}
appearance="plain"
.size=${this.root ? "medium" : "small"}
>
<ha-svg-icon .path=${mdiPlus} slot="start"></ha-svg-icon>
${this.hass.localize(

View File

@ -260,12 +260,14 @@ class DialogAutomationSave extends LitElement implements HassDialog {
.path=${mdiClose}
></ha-icon-button>
<span slot="title">${this._params.title || title}</span>
<ha-suggest-with-ai-button
slot="actionItems"
.hass=${this.hass}
.generateTask=${this._generateTask}
@suggestion=${this._handleSuggestion}
></ha-suggest-with-ai-button>
${this._params.hideInputs
? nothing
: html` <ha-suggest-with-ai-button
slot="actionItems"
.hass=${this.hass}
.generateTask=${this._generateTask}
@suggestion=${this._handleSuggestion}
></ha-suggest-with-ai-button>`}
</ha-dialog-header>
${this._error
? html`<ha-alert alert-type="error"
@ -381,7 +383,7 @@ class DialogAutomationSave extends LitElement implements HassDialog {
return {
type: "data",
task: {
task_name: `frontend:${term}:save`,
task_name: `frontend__${term}__save`,
instructions: `Suggest in language "${this.hass.language}" a name, description, category and labels for the following Home Assistant ${term}.
The name should be relevant to the ${term}'s purpose.

View File

@ -34,6 +34,8 @@ export default class HaAutomationCondition extends LitElement {
@property({ type: Boolean }) public disabled = false;
@property({ type: Boolean }) public root = false;
@state() private _showReorder = false;
@state()
@ -159,6 +161,8 @@ export default class HaAutomationCondition extends LitElement {
<ha-button
.disabled=${this.disabled}
@click=${this._addConditionDialog}
.appearance=${this.root ? "accent" : "filled"}
.size=${this.root ? "medium" : "small"}
>
<ha-svg-icon .path=${mdiPlus} slot="start"></ha-svg-icon>
${this.hass.localize(
@ -168,6 +172,7 @@ export default class HaAutomationCondition extends LitElement {
<ha-button
.disabled=${this.disabled}
appearance="plain"
.size=${this.root ? "medium" : "small"}
@click=${this._addConditionBuildingBlockDialog}
>
<ha-svg-icon .path=${mdiPlus} slot="start"></ha-svg-icon>

View File

@ -228,6 +228,7 @@ export class HaManualAutomationEditor extends LitElement {
@value-changed=${this._conditionChanged}
.hass=${this.hass}
.disabled=${this.disabled}
root
></ha-automation-condition>
<div class="header">
@ -269,6 +270,7 @@ export class HaManualAutomationEditor extends LitElement {
.hass=${this.hass}
.narrow=${this.narrow}
.disabled=${this.disabled}
root
></ha-automation-action>
`;
}

View File

@ -100,14 +100,14 @@ export default class HaAutomationOption extends LitElement {
)}
<div class="buttons">
<ha-button
outlined
appearance="filled"
.disabled=${this.disabled}
.label=${this.hass.localize(
"ui.panel.config.automation.editor.actions.type.choose.add_option"
)}
@click=${this._addOption}
>
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
<ha-svg-icon .path=${mdiPlus} slot="start"></ha-svg-icon>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.type.choose.add_option"
)}
</ha-button>
</div>
</div>

View File

@ -58,7 +58,7 @@ class DialogPasteReplace extends LitElement implements HassDialog {
></ha-yaml-editor>
<div slot="primaryAction">
<ha-button @click=${this._handleAppend}>
<ha-button appearance="plain" @click=${this._handleAppend}>
${this.hass.localize("ui.common.append")}
</ha-button>
<ha-button @click=${this._handleReplace}>
@ -89,6 +89,10 @@ class DialogPasteReplace extends LitElement implements HassDialog {
font-size: inherit;
font-weight: inherit;
}
div[slot="primaryAction"] {
display: flex;
gap: 8px;
}
`,
];
}

View File

@ -2,11 +2,13 @@ import type { PropertyValues } from "lit";
import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { firstWeekdayIndex } from "../../../../../common/datetime/first_weekday";
import { fireEvent } from "../../../../../common/dom/fire_event";
import type { LocalizeFunc } from "../../../../../common/translations/localize";
import "../../../../../components/ha-form/ha-form";
import type { SchemaUnion } from "../../../../../components/ha-form/types";
import type { TimeTrigger } from "../../../../../data/automation";
import type { FrontendLocaleData } from "../../../../../data/translation";
import type { HomeAssistant } from "../../../../../types";
import type { TriggerElement } from "../ha-automation-trigger-row";
import { computeDomain } from "../../../../../common/entity/compute_domain";
@ -14,6 +16,7 @@ import { computeDomain } from "../../../../../common/entity/compute_domain";
const MODE_TIME = "time";
const MODE_ENTITY = "entity";
const VALID_DOMAINS = ["sensor", "input_datetime"];
const DAYS = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"] as const;
@customElement("ha-automation-trigger-time")
export class HaTimeTrigger extends LitElement implements TriggerElement {
@ -35,9 +38,14 @@ export class HaTimeTrigger extends LitElement implements TriggerElement {
private _schema = memoizeOne(
(
localize: LocalizeFunc,
locale: FrontendLocaleData,
inputMode: typeof MODE_TIME | typeof MODE_ENTITY
) =>
[
) => {
const dayIndex = firstWeekdayIndex(locale);
const sortedDays = DAYS.slice(dayIndex, DAYS.length).concat(
DAYS.slice(0, dayIndex)
);
return [
{
name: "mode",
type: "select",
@ -73,7 +81,21 @@ export class HaTimeTrigger extends LitElement implements TriggerElement {
},
{ name: "offset", selector: { text: {} } },
] as const)),
] as const
{
type: "multi_select",
name: "weekday",
options: sortedDays.map(
(day) =>
[
day,
localize(
`ui.panel.config.automation.editor.triggers.type.time.weekdays.${day}`
),
] as const
),
},
] as const;
}
);
public willUpdate(changedProperties: PropertyValues) {
@ -95,12 +117,14 @@ export class HaTimeTrigger extends LitElement implements TriggerElement {
inputMode: undefined | typeof MODE_ENTITY | typeof MODE_TIME,
at:
| string
| { entity_id: string | undefined; offset?: string | undefined }
| { entity_id: string | undefined; offset?: string | undefined },
weekday: string | string[] | undefined
): {
mode: typeof MODE_TIME | typeof MODE_ENTITY;
entity: string | undefined;
time: string | undefined;
offset: string | undefined;
weekday: string | string[] | undefined;
} => {
const entity =
typeof at === "object"
@ -116,6 +140,7 @@ export class HaTimeTrigger extends LitElement implements TriggerElement {
entity,
time,
offset,
weekday,
};
}
);
@ -127,8 +152,12 @@ export class HaTimeTrigger extends LitElement implements TriggerElement {
return nothing;
}
const data = this._data(this._inputMode, at);
const schema = this._schema(this.hass.localize, data.mode);
const data = this._data(this._inputMode, at, this.trigger.weekday);
const schema = this._schema(
this.hass.localize,
this.hass.locale,
data.mode
);
return html`
<ha-form
@ -146,22 +175,36 @@ export class HaTimeTrigger extends LitElement implements TriggerElement {
ev.stopPropagation();
const newValue = { ...ev.detail.value };
this._inputMode = newValue.mode;
const weekday = newValue.weekday;
delete newValue.weekday;
if (newValue.mode === MODE_TIME) {
delete newValue.entity;
delete newValue.offset;
} else {
delete newValue.time;
}
const triggerUpdate: TimeTrigger = {
...this.trigger,
at: newValue.offset
? {
entity_id: newValue.entity,
offset: newValue.offset,
}
: newValue.entity || newValue.time,
};
// Only include weekday if it has a value
if (weekday && weekday.length > 0) {
triggerUpdate.weekday = weekday;
} else {
delete triggerUpdate.weekday;
}
fireEvent(this, "value-changed", {
value: {
...this.trigger,
at: newValue.offset
? {
entity_id: newValue.entity,
offset: newValue.offset,
}
: newValue.entity || newValue.time,
},
value: triggerUpdate,
});
}
@ -173,6 +216,10 @@ export class HaTimeTrigger extends LitElement implements TriggerElement {
return this.hass.localize(
`ui.panel.config.automation.editor.triggers.type.time.at`
);
case "weekday":
return this.hass.localize(
`ui.panel.config.automation.editor.triggers.type.time.weekday`
);
}
return this.hass.localize(
`ui.panel.config.automation.editor.triggers.type.time.${schema.name}`

View File

@ -99,13 +99,11 @@ class HaBackupOverviewBackups extends LitElement {
</ha-md-list>
</div>
<div class="card-actions">
<a href="/config/backup/backups?type=all">
<ha-button appearance="filled">
${this.hass.localize(
"ui.panel.config.backup.overview.backups.show_all"
)}
</ha-button>
</a>
<ha-button appearance="filled" href="/config/backup/backups?type=all">
${this.hass.localize(
"ui.panel.config.backup.overview.backups.show_all"
)}
</ha-button>
</div>
</ha-card>
`;

View File

@ -375,8 +375,13 @@ class DialogBackupOnboarding extends LitElement implements HassDialog {
"ui.panel.config.backup.encryption_key.download_emergency_kit_description"
)}
</span>
<ha-button slot="end" @click=${this._downloadKey}>
<ha-svg-icon .path=${mdiDownload} slot="icon"></ha-svg-icon>
<ha-button
size="small"
appearance="plain"
slot="end"
@click=${this._downloadKey}
>
<ha-svg-icon .path=${mdiDownload} slot="start"></ha-svg-icon>
${this.hass.localize(
"ui.panel.config.backup.encryption_key.download_emergency_kit_action"
)}

View File

@ -128,7 +128,7 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
<ha-button
@click=${this._submit}
.disabled=${!this._newEncryptionKey}
class="danger"
variant="danger"
>
${this.hass.localize(
"ui.panel.config.backup.dialogs.change_encryption_key.actions.change"
@ -176,7 +176,7 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
)}
</span>
<ha-button slot="end" @click=${this._downloadOld}>
<ha-svg-icon .path=${mdiDownload} slot="icon"></ha-svg-icon>
<ha-svg-icon .path=${mdiDownload} slot="start"></ha-svg-icon>
${this.hass.localize(
"ui.panel.config.backup.encryption_key.download_old_emergency_kit_action"
)}
@ -211,7 +211,7 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
)}
</span>
<ha-button slot="end" @click=${this._downloadNew}>
<ha-svg-icon .path=${mdiDownload} slot="icon"></ha-svg-icon>
<ha-svg-icon .path=${mdiDownload} slot="start"></ha-svg-icon>
${this.hass.localize(
"ui.panel.config.backup.encryption_key.download_emergency_kit_action"
)}
@ -297,9 +297,6 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
--md-list-item-leading-space: 0;
--md-list-item-trailing-space: 0;
}
ha-button.danger {
--mdc-theme-primary: var(--error-color);
}
.encryption-key {
border: 1px solid var(--divider-color);
background-color: var(--primary-background-color);

View File

@ -112,7 +112,7 @@ class DialogDownloadDecryptedBackup extends LitElement implements HassDialog {
: nothing}
</div>
<div slot="actions">
<ha-button @click=${this._cancel}>
<ha-button appearance="plain" @click=${this._cancel}>
${this.hass.localize("ui.common.cancel")}
</ha-button>

View File

@ -2,10 +2,10 @@ import type { CSSResultGroup } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-alert";
import "../../../../components/ha-button";
import { createCloseHeading } from "../../../../components/ha-dialog";
import "../../../../components/ha-form/ha-form";
import "../../../../components/ha-alert";
import type {
HaFormSchema,
SchemaUnion,
@ -91,6 +91,7 @@ class LocalBackupLocationDialog extends LitElement {
</ha-alert>
<ha-button
slot="secondaryAction"
appearance="plain"
@click=${this.closeDialog}
dialogInitialFocus
>

View File

@ -227,7 +227,7 @@ class DialogRestoreBackup extends LitElement implements HassDialog {
private _renderConfirmActions() {
return html`
<ha-button @click=${this.closeDialog}>
<ha-button appearance="plain" @click=${this.closeDialog}>
${this.hass.localize("ui.common.cancel")}
</ha-button>
<ha-button @click=${this._restoreBackup} variant="danger">

View File

@ -151,8 +151,13 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog {
"ui.panel.config.backup.encryption_key.download_emergency_kit_description"
)}
</span>
<ha-button slot="end" @click=${this._download}>
<ha-svg-icon .path=${mdiDownload} slot="icon"></ha-svg-icon>
<ha-button
size="small"
appearance="plain"
slot="end"
@click=${this._download}
>
<ha-svg-icon .path=${mdiDownload} slot="start"></ha-svg-icon>
${this.hass.localize(
"ui.panel.config.backup.encryption_key.download_emergency_kit_action"
)}

View File

@ -87,8 +87,13 @@ class DialogShowBackupEncryptionKey extends LitElement implements HassDialog {
"ui.panel.config.backup.encryption_key.download_emergency_kit_description"
)}
</span>
<ha-button slot="end" @click=${this._download}>
<ha-svg-icon .path=${mdiDownload} slot="icon"></ha-svg-icon>
<ha-button
size="small"
appearance="plain"
slot="end"
@click=${this._download}
>
<ha-svg-icon .path=${mdiDownload} slot="start"></ha-svg-icon>
${this.hass.localize(
"ui.panel.config.backup.encryption_key.download_emergency_kit_action"
)}

View File

@ -113,7 +113,10 @@ export class DialogUploadBackup
></ha-file-upload>
</div>
<div slot="actions">
<ha-button @click=${this.closeDialog} .disabled=${this._uploading}
<ha-button
appearance="plain"
@click=${this.closeDialog}
.disabled=${this._uploading}
>${this.hass.localize("ui.common.cancel")}</ha-button
>
<ha-button

View File

@ -417,7 +417,11 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
<div slot="selection-bar">
${!this.narrow
? html`
<ha-button @click=${this._deleteSelected} class="warning">
<ha-button
appearance="plain"
@click=${this._deleteSelected}
variant="danger"
>
${this.hass.localize(
"ui.panel.config.backup.backups.delete_selected"
)}

View File

@ -158,18 +158,18 @@ class HaConfigBackupDetails extends LitElement {
"ui.panel.config.backup.location.encryption.location_encrypted_cloud_description"
)}
</span>
<a
<ha-button
href="https://www.nabucasa.com/config/backups/"
target="_blank"
slot="end"
rel="noreferrer noopener"
appearance="plain"
size="small"
>
<ha-button>
${this.hass.localize(
"ui.panel.config.backup.location.encryption.location_encrypted_cloud_learn_more"
)}
</ha-button>
</a>
${this.hass.localize(
"ui.panel.config.backup.location.encryption.location_encrypted_cloud_learn_more"
)}
</ha-button>
</ha-md-list-item>
`
: encrypted

View File

@ -187,7 +187,11 @@ export class CloudRemotePref extends LitElement {
)
: nothing}</span
>
<ha-button @click=${this._openCertInfo}>
<ha-button
appearance="plain"
size="small"
@click=${this._openCertInfo}
>
${this.hass.localize(
"ui.panel.config.cloud.account.remote.more_info"
)}

View File

@ -126,7 +126,7 @@ export class CloudTTSPref extends LitElement {
`}
</div>
<div class="flex"></div>
<ha-button @click=${this._openTryDialog}>
<ha-button appearance="plain" @click=${this._openTryDialog}>
${this.hass.localize("ui.panel.config.cloud.account.tts.try")}
</ha-button>
</div>

View File

@ -77,7 +77,9 @@ export class DialogSupportPackage extends LitElement {
</ha-alert>
<hr />
<div class="actions">
<ha-button @click=${this.closeDialog}>Close</ha-button>
<ha-button appearance="plain" @click=${this.closeDialog}
>Close</ha-button
>
<ha-button @click=${this._download}>Download</ha-button>
</div>
</div>

View File

@ -1442,10 +1442,9 @@ export class HaConfigDevicePage extends LitElement {
}
private async _signUrl(ev) {
const anchor = ev.currentTarget.closest("a");
const signedUrl = await getSignedPath(
this.hass,
anchor.getAttribute("href")
ev.currentTarget.getAttribute("href")
);
fileDownload(signedUrl.path);
}

View File

@ -202,7 +202,7 @@ export class EntitySettingsHelperTab extends LitElement {
box-sizing: border-box;
display: flex;
justify-content: space-between;
padding: 0 24px 24px 24px;
padding: 16px;
background-color: var(--mdc-theme-surface, #fff);
}
.error {

View File

@ -249,9 +249,9 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
.buttons {
box-sizing: border-box;
display: flex;
padding: 8px;
padding: 16px;
justify-content: space-between;
padding-bottom: max(var(--safe-area-inset-bottom), 8px);
padding-bottom: max(var(--safe-area-inset-bottom), 16px);
background-color: var(--mdc-theme-surface, #fff);
border-top: 1px solid var(--divider-color);
position: sticky;

View File

@ -92,8 +92,9 @@ class DialogScheduleBlockInfo extends LitElement {
</div>
<ha-button
slot="secondaryAction"
class="warning"
@click=${this._deleteBlock}
appearance="plain"
variant="danger"
>
${this.hass!.localize("ui.common.delete")}
</ha-button>

View File

@ -145,7 +145,7 @@ class HaInputSelectForm extends LitElement {
)}
@keydown=${this._handleKeyAdd}
></ha-textfield>
<ha-button @click=${this._addOption}
<ha-button size="small" appearance="plain" @click=${this._addOption}
>${this.hass!.localize(
"ui.dialogs.helper_settings.input_select.add"
)}</ha-button

View File

@ -207,7 +207,7 @@ class HaConfigEntryRow extends LitElement {
: nothing}
</div>
${item.disabled_by === "user"
? html`<ha-button unelevated slot="end" @click=${this._handleEnable}>
? html`<ha-button slot="end" @click=${this._handleEnable}>
${this.hass.localize("ui.common.enable")}
</ha-button>`
: configPanel &&

View File

@ -45,10 +45,9 @@ export class HaDisabledConfigEntryCard extends LitElement {
>
<ha-icon-button .path=${mdiCog}></ha-icon-button>
</a>
<ha-button
@click=${this._handleEnable}
.label=${this.hass.localize("ui.common.enable")}
></ha-button>
<ha-button @click=${this._handleEnable} appearance="filled">
${this.hass.localize("ui.common.enable")}
</ha-button>
</ha-integration-action-card>
`;
}

View File

@ -34,12 +34,11 @@ export class HaIgnoredConfigEntryCard extends LitElement {
this.entry.localized_domain_name
: this.entry.title}
>
<ha-button
@click=${this._removeIgnoredIntegration}
.label=${this.hass.localize(
<ha-button appearance="plain" @click=${this._removeIgnoredIntegration}>
${this.hass.localize(
"ui.panel.config.integrations.ignore.stop_ignore"
)}
></ha-button>
</ha-button>
</ha-integration-action-card>
`;
}

View File

@ -112,7 +112,8 @@ export class HaIntegrationCard extends LitElement {
return html`
<div class="card-actions">
${devices.length > 0
? html`<a
? html`<ha-button
appearance="plain"
href=${devices.length === 1 &&
// Always link to device page for protocol integrations to show Add Device button
// @ts-expect-error
@ -120,40 +121,36 @@ export class HaIntegrationCard extends LitElement {
? `/config/devices/device/${devices[0].id}`
: `/config/devices/dashboard?historyBack=1&domain=${this.domain}`}
>
<ha-button appearance="plain">
${this.hass.localize(
`ui.panel.config.integrations.config_entry.${
services ? "services" : "devices"
}`,
{ count: devices.length }
)}
</ha-button>
</a>`
${this.hass.localize(
`ui.panel.config.integrations.config_entry.${
services ? "services" : "devices"
}`,
{ count: devices.length }
)}
</ha-button>`
: entitiesCount > 0
? html`<a
? html`<ha-button
appearance="plain"
href=${`/config/entities?historyBack=1&domain=${this.domain}`}
>
<ha-button appearance="plain">
${this.hass.localize(
`ui.panel.config.integrations.config_entry.entities`,
{ count: entitiesCount }
)}
</ha-button>
</a>`
${this.hass.localize(
`ui.panel.config.integrations.config_entry.entities`,
{ count: entitiesCount }
)}
</ha-button>`
: this.items.find((itm) => itm.source !== "yaml")
? html`<a
? html`<ha-button
appearance="plain"
href=${`/config/integrations/integration/${this.domain}`}
>
<ha-button appearance="plain">
${this.hass.localize(
`ui.panel.config.integrations.config_entry.entries`,
{
count: this.items.filter((itm) => itm.source !== "yaml")
.length,
}
)}
</ha-button>
</a>`
${this.hass.localize(
`ui.panel.config.integrations.config_entry.entries`,
{
count: this.items.filter((itm) => itm.source !== "yaml")
.length,
}
)}
</ha-button>`
: html`<div class="spacer"></div>`}
<div class="icons">
${this.manifest && !this.manifest.is_built_in

View File

@ -114,7 +114,11 @@ class DialogSSDPDiscoveryInfo extends LitElement implements HassDialog {
</tbody>
</table>
<ha-button slot="secondaryAction" @click=${this._copyToClipboard}>
<ha-button
appearance="plain"
slot="secondaryAction"
@click=${this._copyToClipboard}
>
${this.hass.localize("ui.panel.config.ssdp.copy_to_clipboard")}
</ha-button>
</ha-dialog>

View File

@ -3,16 +3,16 @@ import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../../../common/dom/fire_event";
import { stopPropagation } from "../../../../../common/dom/stop_propagation";
import type { HassDialog } from "../../../../../dialogs/make-dialog-manager";
import { changeZHANetworkChannel } from "../../../../../data/zha";
import { showAlertDialog } from "../../../../../dialogs/generic/show-dialog-box";
import { createCloseHeading } from "../../../../../components/ha-dialog";
import type { HomeAssistant } from "../../../../../types";
import "../../../../../components/buttons/ha-progress-button";
import "../../../../../components/ha-alert";
import "../../../../../components/ha-button";
import "../../../../../components/ha-select";
import { createCloseHeading } from "../../../../../components/ha-dialog";
import "../../../../../components/ha-list-item";
import "../../../../../components/ha-select";
import { changeZHANetworkChannel } from "../../../../../data/zha";
import { showAlertDialog } from "../../../../../dialogs/generic/show-dialog-box";
import type { HassDialog } from "../../../../../dialogs/make-dialog-manager";
import type { HomeAssistant } from "../../../../../types";
import type { ZHAChangeChannelDialogParams } from "./show-dialog-zha-change-channel";
const VALID_CHANNELS = [
@ -128,6 +128,7 @@ class DialogZHAChangeChannel extends LitElement implements HassDialog {
<ha-button
slot="secondaryAction"
appearance="plain"
@click=${this.closeDialog}
.disabled=${this._migrationInProgress}
>${this.hass.localize("ui.common.cancel")}</ha-button

View File

@ -160,7 +160,11 @@ class DialogZHAReconfigureDevice extends LitElement {
<ha-button slot="primaryAction" @click=${this.closeDialog}>
${this.hass.localize("ui.common.close")}
</ha-button>
<ha-button slot="secondaryAction" @click=${this._toggleDetails}>
<ha-button
appearance="plain"
slot="secondaryAction"
@click=${this._toggleDetails}
>
${this._showDetails
? this.hass.localize(
`ui.dialogs.zha_reconfigure_device.button_hide`
@ -189,7 +193,11 @@ class DialogZHAReconfigureDevice extends LitElement {
<ha-button slot="primaryAction" @click=${this.closeDialog}>
${this.hass.localize("ui.common.close")}
</ha-button>
<ha-button slot="secondaryAction" @click=${this._toggleDetails}>
<ha-button
appearance="plain"
slot="secondaryAction"
@click=${this._toggleDetails}
>
${this._showDetails
? this.hass.localize(
`ui.dialogs.zha_reconfigure_device.button_hide`

View File

@ -176,7 +176,7 @@ export class ZHAAddGroupPage extends LitElement {
}
.buttons {
align-items: flex-end;
padding: 8px;
padding: 16px;
}
.buttons .warning {
--mdc-theme-primary: var(--error-color);

View File

@ -305,7 +305,7 @@ export class ZHAGroupPage extends LitElement {
}
.buttons {
align-items: flex-end;
padding: 8px;
padding: 16px;
}
.buttons .warning {
--mdc-theme-primary: var(--error-color);

View File

@ -33,13 +33,11 @@ export class ZWaveJsAddNodeFailed extends LitElement {
</div>`
: nothing}
${this.device?.id
? html`<a href=${`/config/devices/device/${this.device.id}`}>
<ha-button>
${this.hass.localize(
"ui.panel.config.zwave_js.add_node.view_device"
)}
</ha-button>
</a>`
? html`<ha-button href=${`/config/devices/device/${this.device.id}`}>
${this.hass.localize(
"ui.panel.config.zwave_js.add_node.view_device"
)}
</ha-button>`
: nothing}
`;
}

View File

@ -219,7 +219,11 @@ class DialogZWaveJSRemoveNode extends LitElement {
if (this._step === "start_removal") {
return html`
<ha-button slot="secondaryAction" @click=${this.closeDialog}>
<ha-button
appearance="plain"
slot="secondaryAction"
@click=${this.closeDialog}
>
${this.hass.localize("ui.common.cancel")}
</ha-button>
<ha-button
@ -234,7 +238,11 @@ class DialogZWaveJSRemoveNode extends LitElement {
if (this._step === "start_exclusion") {
return html`
<ha-button slot="secondaryAction" @click=${this.closeDialog}>
<ha-button
appearance="plain"
slot="secondaryAction"
@click=${this.closeDialog}
>
${this.hass.localize("ui.common.cancel")}
</ha-button>
<ha-button

View File

@ -157,380 +157,382 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
.path=${mdiRefresh}
.label=${this.hass!.localize("ui.common.refresh")}
></ha-icon-button>
${this._network
? html`
<ha-card class="content network-status">
<div class="card-content">
<div class="heading">
<div class="icon">
${this._status === "disconnected"
? html`<ha-spinner></ha-spinner>`
: html`
<ha-svg-icon
.path=${this._icon}
class="network-status-icon ${classMap({
[this._status!]: true,
})}"
slot="item-icon"
></ha-svg-icon>
`}
</div>
${this._status !== "disconnected"
? html`
<div class="details">
Z-Wave
${this.hass.localize(
"ui.panel.config.zwave_js.common.network"
)}
${this.hass.localize(
`ui.panel.config.zwave_js.network_status.${this._status}`
)}<br />
<small>
<div class="container">
${this._network
? html`
<ha-card class="content network-status">
<div class="card-content">
<div class="heading">
<div class="icon">
${this._status === "disconnected"
? html`<ha-spinner></ha-spinner>`
: html`
<ha-svg-icon
.path=${this._icon}
class="network-status-icon ${classMap({
[this._status!]: true,
})}"
slot="item-icon"
></ha-svg-icon>
`}
</div>
${this._status !== "disconnected"
? html`
<div class="details">
Z-Wave
${this.hass.localize(
`ui.panel.config.zwave_js.dashboard.devices`,
{
count:
this._network.controller.nodes.length +
provisioningDevices,
}
"ui.panel.config.zwave_js.common.network"
)}
${notReadyDevices > 0
? html`(${this.hass.localize(
`ui.panel.config.zwave_js.dashboard.not_ready`,
{ count: notReadyDevices }
)})`
: nothing}
</small>
</div>
`
${this.hass.localize(
`ui.panel.config.zwave_js.network_status.${this._status}`
)}<br />
<small>
${this.hass.localize(
`ui.panel.config.zwave_js.dashboard.devices`,
{
count:
this._network.controller.nodes.length +
provisioningDevices,
}
)}
${notReadyDevices > 0
? html`(${this.hass.localize(
`ui.panel.config.zwave_js.dashboard.not_ready`,
{ count: notReadyDevices }
)})`
: nothing}
</small>
</div>
`
: nothing}
</div>
</div>
<div class="card-actions">
<ha-button
appearance="plain"
href=${`/config/devices/dashboard?historyBack=1&config_entry=${this.configEntryId}`}
>
${this.hass.localize("ui.panel.config.devices.caption")}
</ha-button>
<ha-button
appearance="plain"
href=${`/config/entities/dashboard?historyBack=1&config_entry=${this.configEntryId}`}
>
${this.hass.localize("ui.panel.config.entities.caption")}
</ha-button>
${this._provisioningEntries?.length
? html`<ha-button
appearance="plain"
href=${`provisioned?config_entry=${this.configEntryId}`}
>
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.provisioned_devices"
)}
</ha-button>`
: nothing}
</div>
</div>
<div class="card-actions">
<ha-button
appearance="plain"
href=${`/config/devices/dashboard?historyBack=1&config_entry=${this.configEntryId}`}
>
${this.hass.localize("ui.panel.config.devices.caption")}
</ha-button>
<ha-button
appearance="plain"
href=${`/config/entities/dashboard?historyBack=1&config_entry=${this.configEntryId}`}
>
${this.hass.localize("ui.panel.config.entities.caption")}
</ha-button>
${this._provisioningEntries?.length
? html`<ha-button
appearance="plain"
href=${`provisioned?config_entry=${this.configEntryId}`}
>
</ha-card>
<ha-card header="Diagnostics">
<div class="card-content">
<div class="row">
<span>
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.provisioned_devices"
)}
</ha-button>`
: nothing}
</div>
</ha-card>
<ha-card header="Diagnostics">
<div class="card-content">
<div class="row">
<span>
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.driver_version"
)}:
</span>
<span>${this._network.client.driver_version}</span>
</div>
<div class="row">
<span>
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.server_version"
)}:
</span>
<span>${this._network.client.server_version}</span>
</div>
<div class="row">
<span>
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.home_id"
)}:
</span>
<span>${this._network.controller.home_id}</span>
</div>
<div class="row">
<span>
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.server_url"
)}:
</span>
<span>${this._network.client.ws_server_url}</span>
</div>
<br />
<ha-expansion-panel
.header=${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.statistics.title"
)}
>
<ha-list noninteractive>
<ha-list-item twoline hasmeta>
<span>
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.statistics.messages_tx.label"
)}
</span>
<span slot="secondary">
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.statistics.messages_tx.tooltip"
)}
</span>
<span slot="meta"
>${this._statistics?.messages_tx ?? 0}</span
>
</ha-list-item>
<ha-list-item twoline hasmeta>
<span>
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.statistics.messages_rx.label"
)}
</span>
<span slot="secondary">
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.statistics.messages_rx.tooltip"
)}
</span>
<span slot="meta"
>${this._statistics?.messages_rx ?? 0}</span
>
</ha-list-item>
<ha-list-item twoline hasmeta>
<span>
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.statistics.messages_dropped_tx.label"
)}
</span>
<span slot="secondary">
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.statistics.messages_dropped_tx.tooltip"
)}
</span>
<span slot="meta"
>${this._statistics?.messages_dropped_tx ?? 0}</span
>
</ha-list-item>
<ha-list-item twoline hasmeta>
<span>
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.statistics.messages_dropped_rx.label"
)}
</span>
<span slot="secondary">
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.statistics.messages_dropped_rx.tooltip"
)}
</span>
<span slot="meta"
>${this._statistics?.messages_dropped_rx ?? 0}</span
>
</ha-list-item>
<ha-list-item twoline hasmeta>
<span>
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.statistics.nak.label"
)}
</span>
<span slot="secondary">
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.statistics.nak.tooltip"
)}
</span>
<span slot="meta">${this._statistics?.nak ?? 0}</span>
</ha-list-item>
<ha-list-item twoline hasmeta>
<span>
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.statistics.can.label"
)}
</span>
<span slot="secondary">
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.statistics.can.tooltip"
)}
</span>
<span slot="meta">${this._statistics?.can ?? 0}</span>
</ha-list-item>
<ha-list-item twoline hasmeta>
<span>
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.statistics.timeout_ack.label"
)}
</span>
<span slot="secondary">
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.statistics.timeout_ack.tooltip"
)}
</span>
<span slot="meta"
>${this._statistics?.timeout_ack ?? 0}</span
>
</ha-list-item>
<ha-list-item twoline hasmeta>
<span>
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.statistics.timeout_response.label"
)}
</span>
<span slot="secondary">
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.statistics.timeout_response.tooltip"
)}
</span>
<span slot="meta"
>${this._statistics?.timeout_response ?? 0}</span
>
</ha-list-item>
<ha-list-item twoline hasmeta>
<span>
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.statistics.timeout_callback.label"
)}
</span>
<span slot="secondary">
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.statistics.timeout_callback.tooltip"
)}
</span>
<span slot="meta"
>${this._statistics?.timeout_callback ?? 0}</span
>
</ha-list-item>
</ha-list>
</ha-expansion-panel>
</div>
<div class="card-actions">
<ha-button
appearance="plain"
@click=${this._removeNodeClicked}
.disabled=${this._status !== "connected" ||
(this._network?.controller.inclusion_state !==
InclusionState.Idle &&
this._network?.controller.inclusion_state !==
InclusionState.SmartStart)}
>
${this.hass.localize(
"ui.panel.config.zwave_js.common.remove_a_node"
)}
</ha-button>
<ha-button
appearance="plain"
@click=${this._rebuildNetworkRoutesClicked}
.disabled=${this._status === "disconnected"}
>
${this.hass.localize(
"ui.panel.config.zwave_js.common.rebuild_network_routes"
)}
</ha-button>
</div>
</ha-card>
<ha-card>
<div class="card-header">
<h1>Third-party data reporting</h1>
${this._dataCollectionOptIn !== undefined
? html`
<ha-switch
.checked=${this._dataCollectionOptIn === true}
@change=${this._dataCollectionToggled}
></ha-switch>
`
: html` <ha-spinner size="small"></ha-spinner> `}
</div>
<div class="card-content">
<p>
Enable the reporting of anonymized telemetry and statistics
to the <em>Z-Wave JS organization</em>. This data will be
used to focus development efforts and improve the user
experience. Information about the data that is collected and
how it is used, including an example of the data collected,
can be found in the
<a
target="_blank"
href="https://zwave-js.github.io/node-zwave-js/#/data-collection/data-collection"
>Z-Wave JS data collection documentation</a
>.
</p>
</div>
</ha-card>
<ha-card
.header=${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.nvm_backup.title"
)}
>
<div class="card-content">
<p>
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.nvm_backup.description"
)}
</p>
</div>
<div class="card-actions">
${this._backupProgress !== undefined
? html`<ha-progress-ring
size="small"
.value=${this._backupProgress}
></ha-progress-ring>
"ui.panel.config.zwave_js.dashboard.driver_version"
)}:
</span>
<span>${this._network.client.driver_version}</span>
</div>
<div class="row">
<span>
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.nvm_backup.creating"
)}
${this._backupProgress}%`
: this._restoreProgress !== undefined
"ui.panel.config.zwave_js.dashboard.server_version"
)}:
</span>
<span>${this._network.client.server_version}</span>
</div>
<div class="row">
<span>
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.home_id"
)}:
</span>
<span>${this._network.controller.home_id}</span>
</div>
<div class="row">
<span>
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.server_url"
)}:
</span>
<span>${this._network.client.ws_server_url}</span>
</div>
<br />
<ha-expansion-panel
.header=${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.statistics.title"
)}
>
<ha-list noninteractive>
<ha-list-item twoline hasmeta>
<span>
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.statistics.messages_tx.label"
)}
</span>
<span slot="secondary">
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.statistics.messages_tx.tooltip"
)}
</span>
<span slot="meta"
>${this._statistics?.messages_tx ?? 0}</span
>
</ha-list-item>
<ha-list-item twoline hasmeta>
<span>
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.statistics.messages_rx.label"
)}
</span>
<span slot="secondary">
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.statistics.messages_rx.tooltip"
)}
</span>
<span slot="meta"
>${this._statistics?.messages_rx ?? 0}</span
>
</ha-list-item>
<ha-list-item twoline hasmeta>
<span>
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.statistics.messages_dropped_tx.label"
)}
</span>
<span slot="secondary">
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.statistics.messages_dropped_tx.tooltip"
)}
</span>
<span slot="meta"
>${this._statistics?.messages_dropped_tx ?? 0}</span
>
</ha-list-item>
<ha-list-item twoline hasmeta>
<span>
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.statistics.messages_dropped_rx.label"
)}
</span>
<span slot="secondary">
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.statistics.messages_dropped_rx.tooltip"
)}
</span>
<span slot="meta"
>${this._statistics?.messages_dropped_rx ?? 0}</span
>
</ha-list-item>
<ha-list-item twoline hasmeta>
<span>
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.statistics.nak.label"
)}
</span>
<span slot="secondary">
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.statistics.nak.tooltip"
)}
</span>
<span slot="meta">${this._statistics?.nak ?? 0}</span>
</ha-list-item>
<ha-list-item twoline hasmeta>
<span>
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.statistics.can.label"
)}
</span>
<span slot="secondary">
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.statistics.can.tooltip"
)}
</span>
<span slot="meta">${this._statistics?.can ?? 0}</span>
</ha-list-item>
<ha-list-item twoline hasmeta>
<span>
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.statistics.timeout_ack.label"
)}
</span>
<span slot="secondary">
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.statistics.timeout_ack.tooltip"
)}
</span>
<span slot="meta"
>${this._statistics?.timeout_ack ?? 0}</span
>
</ha-list-item>
<ha-list-item twoline hasmeta>
<span>
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.statistics.timeout_response.label"
)}
</span>
<span slot="secondary">
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.statistics.timeout_response.tooltip"
)}
</span>
<span slot="meta"
>${this._statistics?.timeout_response ?? 0}</span
>
</ha-list-item>
<ha-list-item twoline hasmeta>
<span>
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.statistics.timeout_callback.label"
)}
</span>
<span slot="secondary">
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.statistics.timeout_callback.tooltip"
)}
</span>
<span slot="meta"
>${this._statistics?.timeout_callback ?? 0}</span
>
</ha-list-item>
</ha-list>
</ha-expansion-panel>
</div>
<div class="card-actions">
<ha-button
appearance="plain"
@click=${this._removeNodeClicked}
.disabled=${this._status !== "connected" ||
(this._network?.controller.inclusion_state !==
InclusionState.Idle &&
this._network?.controller.inclusion_state !==
InclusionState.SmartStart)}
>
${this.hass.localize(
"ui.panel.config.zwave_js.common.remove_a_node"
)}
</ha-button>
<ha-button
appearance="plain"
@click=${this._rebuildNetworkRoutesClicked}
.disabled=${this._status === "disconnected"}
>
${this.hass.localize(
"ui.panel.config.zwave_js.common.rebuild_network_routes"
)}
</ha-button>
</div>
</ha-card>
<ha-card>
<div class="card-header">
<h1>Third-party data reporting</h1>
${this._dataCollectionOptIn !== undefined
? html`
<ha-switch
.checked=${this._dataCollectionOptIn === true}
@change=${this._dataCollectionToggled}
></ha-switch>
`
: html` <ha-spinner size="small"></ha-spinner> `}
</div>
<div class="card-content">
<p>
Enable the reporting of anonymized telemetry and
statistics to the <em>Z-Wave JS organization</em>. This
data will be used to focus development efforts and improve
the user experience. Information about the data that is
collected and how it is used, including an example of the
data collected, can be found in the
<a
target="_blank"
href="https://zwave-js.github.io/node-zwave-js/#/data-collection/data-collection"
>Z-Wave JS data collection documentation</a
>.
</p>
</div>
</ha-card>
<ha-card
.header=${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.nvm_backup.title"
)}
>
<div class="card-content">
<p>
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.nvm_backup.description"
)}
</p>
</div>
<div class="card-actions">
${this._backupProgress !== undefined
? html`<ha-progress-ring
size="small"
.value=${this._restoreProgress}
.value=${this._backupProgress}
></ha-progress-ring>
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.nvm_backup.restoring"
"ui.panel.config.zwave_js.dashboard.nvm_backup.creating"
)}
${this._restoreProgress}%`
: html`<ha-button
appearance="plain"
@click=${this._downloadBackup}
>
${this._backupProgress}%`
: this._restoreProgress !== undefined
? html`<ha-progress-ring
size="small"
.value=${this._restoreProgress}
></ha-progress-ring>
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.nvm_backup.download_backup"
"ui.panel.config.zwave_js.dashboard.nvm_backup.restoring"
)}
</ha-button>
<div class="upload-button">
<ha-button
${this._restoreProgress}%`
: html`<ha-button
appearance="plain"
@click=${this._restoreButtonClick}
variant="danger"
@click=${this._downloadBackup}
>
<span class="button-content">
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.nvm_backup.restore_backup"
)}
</span>
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.nvm_backup.download_backup"
)}
</ha-button>
<input
type="file"
id="nvm-restore-file"
accept=".bin"
@change=${this._handleRestoreFileSelected}
style="display: none"
/>
</div>
<ha-button
variant="danger"
@click=${this._openConfigFlow}
class="migrate-button"
>
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.nvm_backup.migrate"
)}
</ha-button>`}
</div>
</ha-card>
`
: nothing}
<div class="upload-button">
<ha-button
appearance="plain"
@click=${this._restoreButtonClick}
variant="danger"
>
<span class="button-content">
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.nvm_backup.restore_backup"
)}
</span>
</ha-button>
<input
type="file"
id="nvm-restore-file"
accept=".bin"
@change=${this._handleRestoreFileSelected}
style="display: none"
/>
</div>
<ha-button
variant="danger"
@click=${this._openConfigFlow}
class="migrate-button"
>
${this.hass.localize(
"ui.panel.config.zwave_js.dashboard.nvm_backup.migrate"
)}
</ha-button>`}
</div>
</ha-card>
`
: nothing}
</div>
<ha-fab
slot="fab"
.label=${this.hass.localize(
@ -957,6 +959,7 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
.card-actions {
display: flex;
align-items: center;
flex-wrap: wrap;
}
.card-actions ha-progress-ring {
@ -984,6 +987,10 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
.migrate-button {
margin-left: auto;
}
.container {
padding: 8px 16px 16px;
}
`,
];
}

View File

@ -92,12 +92,12 @@ class ZWaveJSCustomParam extends LitElement {
</div>
<div class="custom-config-buttons">
${this._isLoading ? html`<ha-spinner></ha-spinner>` : nothing}
<ha-button @click=${this._getCustomConfigValue}>
<ha-button appearance="plain" @click=${this._getCustomConfigValue}>
${this.hass.localize(
"ui.panel.config.zwave_js.node_config.get_value"
)}
</ha-button>
<ha-button @click=${this._setCustomConfigValue}>
<ha-button appearance="plain" @click=${this._setCustomConfigValue}>
${this.hass.localize(
"ui.panel.config.zwave_js.node_config.set_value"
)}

View File

@ -94,7 +94,7 @@ class DownloadLogsDialog extends LitElement {
</ha-md-select>
</div>
<div slot="actions">
<ha-button @click=${this.closeDialog}>
<ha-button appearance="plain" @click=${this.closeDialog}>
${this.hass.localize("ui.common.cancel")}
</ha-button>
<ha-button @click=${this._downloadLogs}>

View File

@ -312,17 +312,16 @@ class ErrorLogCard extends LitElement {
!this._scrolledToBottomController.value) ||
false,
})}"
size="small"
appearance="filled"
@click=${this._scrollToBottom}
>
<ha-svg-icon
.path=${mdiArrowCollapseDown}
slot="icon"
slot="start"
></ha-svg-icon>
${localize("ui.panel.config.logs.scroll_down_button")}
<ha-svg-icon
.path=${mdiArrowCollapseDown}
slot="trailingIcon"
></ha-svg-icon>
<ha-svg-icon .path=${mdiArrowCollapseDown} slot="end"></ha-svg-icon>
</ha-button>
${streaming && this._boot === 0 && !this._error
? html`<div class="live-indicator">
@ -823,25 +822,15 @@ class ErrorLogCard extends LitElement {
}
.new-logs-indicator {
--mdc-theme-primary: var(--text-primary-color);
overflow: hidden;
position: absolute;
bottom: 0;
left: 0;
right: 0;
bottom: 4px;
height: 0;
background-color: var(--primary-color);
border-radius: 8px;
transition: height 0.4s ease-out;
display: flex;
justify-content: space-between;
align-items: center;
}
.new-logs-indicator.visible {
height: 24px;
height: 32px;
}
.error {

View File

@ -284,7 +284,7 @@ class ConfigUrlForm extends SubscribeMixin(LitElement) {
.url=${internalUrl}
@click=${this._copyURL}
>
<ha-svg-icon slot="icon" .path=${mdiContentCopy}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon>
${this.hass.localize("ui.panel.config.common.copy_link")}
</ha-button>
</div>

View File

@ -145,13 +145,12 @@ export class HassioNetwork extends LitElement {
class="scan"
@click=${this._scanForAP}
.disabled=${this._scanning}
.loading=${this._scanning}
>
${this._scanning
? html`<ha-spinner size="small"> </ha-spinner>`
: this.hass.localize(
"ui.panel.config.network.supervisor.scan_ap"
)}
<ha-svg-icon slot="icon" .path=${mdiWifi}></ha-svg-icon>
${this.hass.localize(
"ui.panel.config.network.supervisor.scan_ap"
)}
<ha-svg-icon slot="start" .path=${mdiWifi}></ha-svg-icon>
</ha-button>
${this._accessPoints.length
? html`
@ -261,6 +260,9 @@ export class HassioNetwork extends LitElement {
: nothing}
</div>
<div class="card-actions">
<ha-button appearance="plain" @click=${this._clear}>
${this.hass.localize("ui.panel.config.network.supervisor.reset")}
</ha-button>
<ha-button
.loading=${this._processing}
@click=${this._updateNetwork}
@ -268,9 +270,6 @@ export class HassioNetwork extends LitElement {
>
${this.hass.localize("ui.common.save")}
</ha-button>
<ha-button appearance="plain" @click=${this._clear}>
${this.hass.localize("ui.panel.config.network.supervisor.reset")}
</ha-button>
</div>`;
}

View File

@ -196,6 +196,7 @@ export class HaManualScriptEditor extends LitElement {
.hass=${this.hass}
.narrow=${this.narrow}
.disabled=${this.disabled}
root
></ha-automation-action>
`;
}

View File

@ -91,12 +91,10 @@ export class AssistPipelineDetailTTS extends LitElement {
${
this.data?.tts_engine
? html`<div class="footer">
<ha-button
.label=${this.hass.localize(
<ha-button @click=${this._preview}>
${this.hass.localize(
"ui.panel.config.voice_assistants.assistants.pipeline.detail.try_tts"
)}
@click=${this._preview}
>
</ha-button>
</div>`
: nothing

View File

@ -61,10 +61,12 @@ export class AssistPipelineRunDebug extends LitElement {
slot="toolbar-icon"
@click=${this._clearConversation}
.disabled=${!this._finished}
appearance="plain"
>
${this.hass.localize("ui.common.clear")}
</ha-button>
<ha-button
appearance="plain"
slot="toolbar-icon"
@click=${this._downloadConversation}
>
@ -83,13 +85,16 @@ export class AssistPipelineRunDebug extends LitElement {
@value-changed=${this._pipelinePicked}
></ha-assist-pipeline-picker>
<div class="start-buttons">
<ha-button raised @click=${this._runTextPipeline}>
<ha-button
appearance="filled"
@click=${this._runTextPipeline}
>
${this.hass.localize(
"ui.panel.config.voice_assistants.debug.pipeline.run_text_pipeline"
)}
</ha-button>
<ha-button
raised
appearance="filled"
@click=${this._runAudioPipeline}
.disabled=${!window.isSecureContext ||
// @ts-ignore-next-line
@ -100,7 +105,7 @@ export class AssistPipelineRunDebug extends LitElement {
)}
</ha-button>
<ha-button
raised
appearance="filled"
@click=${this._runAudioWakeWordPipeline}
.disabled=${!window.isSecureContext ||
// @ts-ignore-next-line
@ -135,13 +140,19 @@ export class AssistPipelineRunDebug extends LitElement {
? this._pipelineRuns[0].init_options!.start_stage ===
"wake_word"
? html`
<ha-button @click=${this._runAudioWakeWordPipeline}>
<ha-button
appearance="filled"
@click=${this._runAudioWakeWordPipeline}
>
${this.hass.localize(
"ui.panel.config.voice_assistants.debug.pipeline.continue_listening"
)}
</ha-button>
`
: html`<ha-button @click=${this._runAudioPipeline}>
: html`<ha-button
appearance="filled"
@click=${this._runAudioPipeline}
>
${this.hass.localize(
"ui.panel.config.voice_assistants.debug.pipeline.continue_talking"
)}

View File

@ -182,13 +182,11 @@ export class DialogVoiceAssistantPipelineDetail extends LitElement {
${this.hass.localize(
"ui.panel.config.voice_assistants.assistants.pipeline.detail.no_cloud_message"
)}
<a href="/config/cloud" slot="action">
<ha-button>
${this.hass.localize(
"ui.panel.config.voice_assistants.assistants.pipeline.detail.no_cloud_action"
)}
</ha-button>
</a>
<ha-button size="small" href="/config/cloud" slot="action">
${this.hass.localize(
"ui.panel.config.voice_assistants.assistants.pipeline.detail.no_cloud_action"
)}
</ha-button>
</ha-alert>
`
: nothing}

View File

@ -164,12 +164,16 @@ class HaPanelDevAssist extends SubscribeMixin(LitElement) {
${this._results.length
? html`
<div class="result-toolbar">
<ha-button outlined @click=${this._clear} variant="danger">
<ha-svg-icon slot="icon" .path=${mdiTrashCan}></ha-svg-icon>
<ha-button
appearance="filled"
@click=${this._clear}
variant="danger"
>
<ha-svg-icon slot="start" .path=${mdiTrashCan}></ha-svg-icon>
${this.hass.localize("ui.common.clear")}
</ha-button>
<ha-button outlined @click=${this._download}>
<ha-svg-icon slot="icon" .path=${mdiDownload}></ha-svg-icon>
<ha-button appearance="filled" @click=${this._download}>
<ha-svg-icon slot="start" .path=${mdiDownload}></ha-svg-icon>
${this.hass.localize(
"ui.panel.developer-tools.tabs.assist.download_results"
)}

View File

@ -287,6 +287,7 @@ export class DialogStatisticsFixUnsupportedUnitMetadata extends LitElement {
slot="secondaryAction"
.disabled=${this._busy}
@click=${this._clearChosenStatistic}
appearance="plain"
>
${this.hass.localize("ui.common.back")}</ha-button
>

View File

@ -1,15 +1,15 @@
import { html, LitElement, nothing } from "lit";
import type { HassEntity } from "home-assistant-js-websocket";
import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { computeDomain } from "../../../common/entity/compute_domain";
import "../../../components/ha-control-button";
import "../../../components/ha-control-button-group";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature } from "../types";
import type { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import type {
LovelaceCardFeatureContext,
ButtonCardFeatureConfig,
LovelaceCardFeatureContext,
} from "./types";
export const supportsButtonCardFeature = (
@ -89,6 +89,11 @@ class HuiButtonCardFeature extends LitElement implements LovelaceCardFeature {
}
static styles = cardFeatureStyles;
public static async getConfigElement(): Promise<LovelaceCardFeatureEditor> {
await import("../editor/config-elements/hui-button-card-feature-editor");
return document.createElement("hui-button-card-feature-editor");
}
}
declare global {

View File

@ -30,7 +30,14 @@ import type {
} from "./types";
import type { PersonEntity } from "../../../data/person";
const STATES_OFF = new Set(["closed", "locked", "not_home", "off"]);
const STATES_OFF = new Set([
"closed",
"locked",
"not_home",
"off",
"unavailable",
"unknown",
]);
@customElement("hui-picture-glance-card")
class HuiPictureGlanceCard extends LitElement implements LovelaceCard {

View File

@ -154,14 +154,11 @@ export class HuiDialogSuggestCard extends LitElement {
slot="primaryAction"
.disabled=${this._saving}
@click=${this._save}
.loading=${this._saving}
>
${this._saving
? html`
<ha-spinner aria-label="Saving" size="small"></ha-spinner>
`
: this.hass!.localize(
"ui.panel.lovelace.editor.suggest_card.add"
)}
${this.hass!.localize(
"ui.panel.lovelace.editor.suggest_card.add"
)}
</ha-button>
`
: nothing}

View File

@ -1,10 +1,11 @@
import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import type { LocalizeFunc } from "../../../../common/translations/localize";
import "../../../../components/ha-form/ha-form";
import type { HomeAssistant } from "../../../../types";
import type { ButtonCardFeatureConfig } from "../../card-features/types";
import type { LovelaceCardFeatureEditor } from "../../types";
import "../../../../components/ha-form/ha-form";
import type { HaFormSchema } from "../../../../components/ha-form/types";
@customElement("hui-button-card-feature-editor")
export class HuiButtonCardFeatureEditor
@ -19,14 +20,15 @@ export class HuiButtonCardFeatureEditor
this._config = config;
}
private _schema: HaFormSchema[] = [
private _schema = memoizeOne((localize: LocalizeFunc) => [
{
name: "action_name",
default: localize("ui.card.button.press"),
selector: {
text: {},
},
},
];
]);
protected render() {
if (!this.hass || !this._config) {
@ -37,12 +39,15 @@ export class HuiButtonCardFeatureEditor
<ha-form
.hass=${this.hass}
.data=${this._config}
.schema=${this._schema}
.schema=${this._schema(this.hass.localize)}
.computeLabel=${this._computeLabel}
@value-changed=${this._valueChanged}
></ha-form>
`;
}
private _computeLabel = () => this.hass.localize("ui.common.name");
private _valueChanged(ev: CustomEvent) {
ev.stopPropagation();
this.dispatchEvent(

View File

@ -372,14 +372,9 @@ export class HuiCardFeaturesEditor extends LitElement {
@action=${this._addFeature}
@closed=${stopPropagation}
>
<ha-button
slot="trigger"
outlined
.label=${this.hass!.localize(
`ui.panel.lovelace.editor.features.add`
)}
>
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
<ha-button slot="trigger" appearance="filled" size="small">
<ha-svg-icon .path=${mdiPlus} slot="start"></ha-svg-icon>
${this.hass!.localize(`ui.panel.lovelace.editor.features.add`)}
</ha-button>
${types.map(
(type) => html`

View File

@ -120,11 +120,11 @@ export class HuiHeadingBadgesEditor extends LitElement {
<div class="add-container">
<ha-button
data-add-entity
outlined
.label=${this.hass!.localize(`ui.panel.lovelace.editor.entities.add`)}
appearance="filled"
@click=${this._addEntity}
>
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
<ha-svg-icon .path=${mdiPlus} slot="start"></ha-svg-icon>
${this.hass!.localize(`ui.panel.lovelace.editor.entities.add`)}
</ha-button>
${this._renderPicker()}
</div>

View File

@ -110,7 +110,11 @@ export class HuiDialogSelectDashboard extends LitElement {
</div>`}
</div>
<div slot="actions">
<ha-button @click=${this.closeDialog} .disabled=${this._saving}>
<ha-button
appearance="plain"
@click=${this.closeDialog}
.disabled=${this._saving}
>
${this.hass!.localize("ui.common.cancel")}
</ha-button>
<ha-button

Some files were not shown because too many files have changed in this diff Show More