mirror of
https://github.com/home-assistant/frontend.git
synced 2026-06-03 15:02:01 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9536b885c0 | |||
| 04a986cd2c | |||
| 5b09b1475d | |||
| b1b9ac23cf | |||
| aa19ca91a3 | |||
| 1388aa56ea | |||
| 6ca7ac1ca5 | |||
| d4380248c2 | |||
| 14617aaf3c | |||
| 42e1051d9c | |||
| cfe30114f0 | |||
| 288c03c248 | |||
| 8cd9a5adf6 | |||
| 4d3437b491 | |||
| ceb51714be |
@@ -78,13 +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">restart</ha-button>`,
|
||||
actionSlot: html`<ha-button size="s" slot="action">restart</ha-button>`,
|
||||
},
|
||||
{
|
||||
title: "Unsaved data",
|
||||
description: "You have unsaved data",
|
||||
type: "warning",
|
||||
actionSlot: html`<ha-button size="small" slot="action">save</ha-button>`,
|
||||
actionSlot: html`<ha-button size="s" slot="action">save</ha-button>`,
|
||||
},
|
||||
{
|
||||
title: "Slotted icon",
|
||||
|
||||
@@ -26,7 +26,7 @@ title: Button
|
||||
filled button
|
||||
</ha-button>
|
||||
|
||||
<ha-button size="small">
|
||||
<ha-button size="s">
|
||||
small
|
||||
</ha-button>
|
||||
</div>
|
||||
@@ -34,7 +34,7 @@ title: Button
|
||||
```html
|
||||
<ha-button> simple button </ha-button>
|
||||
|
||||
<ha-button size="small"> small </ha-button>
|
||||
<ha-button size="s"> small </ha-button>
|
||||
```
|
||||
|
||||
### API
|
||||
@@ -57,7 +57,7 @@ Check the [webawesome documentation](https://webawesome.com/docs/components/butt
|
||||
| ---------- | ---------------------------------------------- | -------- | --------------------------------------------------------------------------------- |
|
||||
| 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"/"large" | "medium" | Sets the button size. |
|
||||
| size | "xs"/"s"/"m"/"l"/"xl" | "m" | 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. |
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ export class DemoHaButton extends LitElement {
|
||||
<ha-button
|
||||
.appearance=${appearance}
|
||||
.variant=${variant}
|
||||
size="small"
|
||||
size="s"
|
||||
>
|
||||
${titleCase(`${variant} ${appearance}`)}
|
||||
</ha-button>
|
||||
@@ -100,7 +100,7 @@ export class DemoHaButton extends LitElement {
|
||||
<ha-button
|
||||
.variant=${variant}
|
||||
.appearance=${appearance}
|
||||
size="small"
|
||||
size="s"
|
||||
disabled
|
||||
>
|
||||
${titleCase(`${appearance}`)}
|
||||
|
||||
@@ -17,13 +17,13 @@ subtitle: A slider component for selecting a value from a range.
|
||||
### Example Usage
|
||||
|
||||
<div class="wrapper">
|
||||
<ha-slider size="small" with-markers min="0" max="8" value="4"></ha-slider>
|
||||
<ha-slider size="medium"></ha-slider>
|
||||
<ha-slider size="s" with-markers min="0" max="8" value="4"></ha-slider>
|
||||
<ha-slider size="m"></ha-slider>
|
||||
</div>
|
||||
|
||||
```html
|
||||
<ha-slider size="small" with-markers min="0" max="8" value="4"></ha-slider>
|
||||
<ha-slider size="medium"></ha-slider>
|
||||
<ha-slider size="s" with-markers min="0" max="8" value="4"></ha-slider>
|
||||
<ha-slider size="m"></ha-slider>
|
||||
```
|
||||
|
||||
### API
|
||||
|
||||
@@ -29,7 +29,7 @@ export class DemoHaSlider extends LitElement {
|
||||
></ha-slider>
|
||||
<span>Small</span>
|
||||
<ha-slider
|
||||
size="small"
|
||||
size="s"
|
||||
min="0"
|
||||
max="8"
|
||||
value="4"
|
||||
@@ -37,7 +37,7 @@ export class DemoHaSlider extends LitElement {
|
||||
></ha-slider>
|
||||
<span>Medium</span>
|
||||
<ha-slider
|
||||
size="medium"
|
||||
size="m"
|
||||
min="0"
|
||||
max="8"
|
||||
value="4"
|
||||
|
||||
@@ -13,6 +13,28 @@ export interface NetworkInfo {
|
||||
supervisor_internet: boolean;
|
||||
}
|
||||
|
||||
interface SupervisorJob {
|
||||
name: string;
|
||||
reference: string | null;
|
||||
uuid: string;
|
||||
progress: number; // float, 0–100
|
||||
stage: string | null;
|
||||
done: boolean;
|
||||
errors: {
|
||||
type: string;
|
||||
message: string;
|
||||
stage: string | null;
|
||||
}[];
|
||||
created: string; // ISO datetime string
|
||||
extra: Record<string, unknown> | null;
|
||||
child_jobs: SupervisorJob[];
|
||||
}
|
||||
|
||||
export interface SupervisorJobInfo {
|
||||
ignore_conditions: string[];
|
||||
jobs: SupervisorJob[];
|
||||
}
|
||||
|
||||
export const ALTERNATIVE_DNS_SERVERS: {
|
||||
ipv4: string[];
|
||||
ipv6: string[];
|
||||
@@ -57,6 +79,15 @@ export async function getSupervisorNetworkInfo(): Promise<NetworkInfo> {
|
||||
return responseData?.data;
|
||||
}
|
||||
|
||||
export async function getSupervisorJobsInfo(): Promise<
|
||||
HassioResponse<SupervisorJobInfo>
|
||||
> {
|
||||
const responseData = await handleFetchPromise<
|
||||
HassioResponse<SupervisorJobInfo>
|
||||
>(fetch("/supervisor-api/jobs/info"));
|
||||
return responseData;
|
||||
}
|
||||
|
||||
export const setSupervisorNetworkDns = async (
|
||||
dnsServerIndex: number,
|
||||
primaryInterface: string
|
||||
|
||||
@@ -2,9 +2,9 @@ import { mdiOpenInNew } from "@mdi/js";
|
||||
import { css, html, nothing, type PropertyValues } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { extractSearchParam } from "../../src/common/url/search-params";
|
||||
import "../../src/components/animation/ha-fade-in";
|
||||
import "../../src/components/ha-alert";
|
||||
import "../../src/components/ha-button";
|
||||
import "../../src/components/animation/ha-fade-in";
|
||||
import "../../src/components/ha-spinner";
|
||||
import "../../src/components/ha-svg-icon";
|
||||
import "../../src/components/progress/ha-progress-bar";
|
||||
@@ -15,6 +15,7 @@ import { haStyle } from "../../src/resources/styles";
|
||||
import "./components/landing-page-logs";
|
||||
import "./components/landing-page-network";
|
||||
import {
|
||||
getSupervisorJobsInfo,
|
||||
getSupervisorNetworkInfo,
|
||||
pingSupervisor,
|
||||
type NetworkInfo,
|
||||
@@ -24,6 +25,7 @@ import { LandingPageBaseElement } from "./landing-page-base-element";
|
||||
export const ASSUME_CORE_START_SECONDS = 60;
|
||||
const SCHEDULE_CORE_CHECK_SECONDS = 1;
|
||||
const SCHEDULE_FETCH_NETWORK_INFO_SECONDS = 5;
|
||||
const SCHEDULE_FETCH_JOBS_INFO_SECONDS = 2;
|
||||
|
||||
@customElement("ha-landing-page")
|
||||
class HaLandingPage extends LandingPageBaseElement {
|
||||
@@ -39,6 +41,8 @@ class HaLandingPage extends LandingPageBaseElement {
|
||||
|
||||
@state() private _coreCheckActive = false;
|
||||
|
||||
@state() private _progress = -1;
|
||||
|
||||
private _mobileApp =
|
||||
extractSearchParam("redirect_uri") === "homeassistant://auth-callback";
|
||||
|
||||
@@ -60,7 +64,14 @@ class HaLandingPage extends LandingPageBaseElement {
|
||||
${!networkIssue && !this._supervisorError
|
||||
? html`
|
||||
<p>${this.localize("subheader")}</p>
|
||||
<ha-progress-bar indeterminate></ha-progress-bar>
|
||||
<ha-progress-bar
|
||||
.indeterminate=${this._progress <= 0}
|
||||
.value=${this._progress > 0 ? this._progress : undefined}
|
||||
.loading=${this._progress >= 0}
|
||||
>${this._progress > 0
|
||||
? `${Math.round(this._progress)}%`
|
||||
: nothing}</ha-progress-bar
|
||||
>
|
||||
`
|
||||
: nothing}
|
||||
${networkIssue || this._networkInfoError
|
||||
@@ -126,6 +137,7 @@ class HaLandingPage extends LandingPageBaseElement {
|
||||
import("../../src/components/ha-language-picker");
|
||||
|
||||
this._fetchSupervisorInfo(true);
|
||||
this._fetchSupervisorJobsInfo();
|
||||
}
|
||||
|
||||
private _scheduleFetchSupervisorInfo() {
|
||||
@@ -138,6 +150,13 @@ class HaLandingPage extends LandingPageBaseElement {
|
||||
);
|
||||
}
|
||||
|
||||
private _scheduleFetchSupervisorJobsInfo() {
|
||||
setTimeout(
|
||||
() => this._fetchSupervisorJobsInfo(),
|
||||
SCHEDULE_FETCH_JOBS_INFO_SECONDS * 1000
|
||||
);
|
||||
}
|
||||
|
||||
private _scheduleTurnOffCoreCheck() {
|
||||
setTimeout(() => {
|
||||
this._coreCheckActive = false;
|
||||
@@ -165,7 +184,7 @@ class HaLandingPage extends LandingPageBaseElement {
|
||||
// assume supervisor update if ping fails -> don't show an error
|
||||
if (!this._coreCheckActive && err.message !== "ping-failed") {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err);
|
||||
console.error("Failed to fetch supervisor info", err);
|
||||
this._networkInfoError = true;
|
||||
}
|
||||
}
|
||||
@@ -175,6 +194,33 @@ class HaLandingPage extends LandingPageBaseElement {
|
||||
}
|
||||
}
|
||||
|
||||
private async _fetchSupervisorJobsInfo() {
|
||||
try {
|
||||
const jobsInfo = await getSupervisorJobsInfo();
|
||||
const coreInstallJob =
|
||||
jobsInfo.result === "ok"
|
||||
? jobsInfo.data.jobs.find(
|
||||
(job) => job.name === "home_assistant_core_install"
|
||||
)
|
||||
: undefined;
|
||||
if (coreInstallJob) {
|
||||
this._progress = coreInstallJob.progress;
|
||||
} else {
|
||||
this._progress = -1;
|
||||
}
|
||||
} catch (err: any) {
|
||||
await this._checkCoreAvailability();
|
||||
|
||||
if (!this._coreCheckActive) {
|
||||
this._progress = -1;
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Failed to fetch supervisor jobs info", err);
|
||||
}
|
||||
}
|
||||
|
||||
this._scheduleFetchSupervisorJobsInfo();
|
||||
}
|
||||
|
||||
private async _checkCoreAvailability() {
|
||||
try {
|
||||
const response = await fetch("/manifest.json");
|
||||
@@ -222,21 +268,27 @@ class HaLandingPage extends LandingPageBaseElement {
|
||||
flex-direction: column;
|
||||
gap: var(--ha-space-4);
|
||||
}
|
||||
ha-language-picker {
|
||||
min-width: 200px;
|
||||
}
|
||||
ha-alert p {
|
||||
text-align: unset;
|
||||
}
|
||||
.footer ha-svg-icon {
|
||||
--mdc-icon-size: var(--ha-space-5);
|
||||
}
|
||||
ha-language-picker {
|
||||
margin-inline-start: calc(-1 * var(--ha-space-4));
|
||||
}
|
||||
ha-button {
|
||||
margin-inline-end: calc(-1 * var(--ha-space-2));
|
||||
}
|
||||
ha-fade-in {
|
||||
min-height: calc(100vh - 64px - 88px);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
ha-progress-bar {
|
||||
--ha-progress-bar-track-height: 20px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
+4
-4
@@ -70,8 +70,8 @@
|
||||
"@replit/codemirror-indentation-markers": "6.5.3",
|
||||
"@swc/helpers": "0.5.23",
|
||||
"@thomasloven/round-slider": "0.6.0",
|
||||
"@tsparticles/engine": "4.1.0",
|
||||
"@tsparticles/preset-links": "4.1.0",
|
||||
"@tsparticles/engine": "4.1.1",
|
||||
"@tsparticles/preset-links": "4.1.1",
|
||||
"@vibrant/color": "4.0.4",
|
||||
"@webcomponents/scoped-custom-element-registry": "0.0.10",
|
||||
"@webcomponents/webcomponentsjs": "2.8.0",
|
||||
@@ -88,7 +88,7 @@
|
||||
"dialog-polyfill": "0.5.6",
|
||||
"echarts": "6.1.0",
|
||||
"element-internals-polyfill": "3.0.2",
|
||||
"fuse.js": "7.3.0",
|
||||
"fuse.js": "7.4.0",
|
||||
"google-timezones-json": "1.2.0",
|
||||
"gulp-zopfli-green": "7.0.0",
|
||||
"hls.js": "1.6.16",
|
||||
@@ -180,7 +180,7 @@
|
||||
"jsdom": "29.1.1",
|
||||
"jszip": "3.10.1",
|
||||
"license-checker-rseidelsohn": "5.0.1",
|
||||
"lint-staged": "17.0.5",
|
||||
"lint-staged": "17.0.6",
|
||||
"lit-analyzer": "2.0.3",
|
||||
"lodash.merge": "4.6.2",
|
||||
"lodash.template": "4.18.1",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { LitElement, css, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../ha-tooltip";
|
||||
|
||||
export type LiveTestState = "pass" | "fail" | "invalid" | "unknown";
|
||||
|
||||
@@ -13,7 +12,6 @@ export type LiveTestState = "pass" | "fail" | "invalid" | "unknown";
|
||||
*
|
||||
* @attr {"pass"|"fail"|"invalid"|"unknown"} state - The current live-test state. Defaults to `unknown`.
|
||||
* @attr {string} label - Accessible label announced by assistive technology.
|
||||
* @attr {string} message - Optional tooltip body shown on hover/focus.
|
||||
*/
|
||||
@customElement("ha-automation-row-live-test")
|
||||
export class HaAutomationRowLiveTest extends LitElement {
|
||||
@@ -21,8 +19,6 @@ export class HaAutomationRowLiveTest extends LitElement {
|
||||
|
||||
@property() public label = "";
|
||||
|
||||
@property() public message?: string;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<div
|
||||
@@ -31,39 +27,38 @@ export class HaAutomationRowLiveTest extends LitElement {
|
||||
tabindex="0"
|
||||
aria-label=${this.label}
|
||||
></div>
|
||||
${this.message
|
||||
? html`<ha-tooltip for="indicator">${this.message}</ha-tooltip>`
|
||||
: nothing}
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
inset-inline-end: -6px;
|
||||
display: inline-block;
|
||||
}
|
||||
#indicator {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: var(--ha-border-radius-circle);
|
||||
border: 3px solid;
|
||||
border: var(--ha-border-width-md) solid;
|
||||
box-sizing: border-box;
|
||||
background-color: var(--card-background-color);
|
||||
box-shadow: 0 0 0 2px var(--card-background-color);
|
||||
transition: all var(--ha-animation-duration-normal) ease-in-out;
|
||||
}
|
||||
:host([state="pass"]) #indicator {
|
||||
background-color: var(--ha-color-fill-success-loud-resting);
|
||||
border-color: var(--ha-color-fill-success-loud-resting);
|
||||
background-color: var(--ha-color-green-60);
|
||||
border-color: var(--ha-color-green-60);
|
||||
}
|
||||
:host([state="fail"]) #indicator {
|
||||
border-color: var(--ha-color-fill-warning-loud-resting);
|
||||
border-color: var(--ha-color-orange-60);
|
||||
}
|
||||
:host([state="invalid"]) #indicator {
|
||||
border-color: var(--ha-color-fill-danger-loud-resting);
|
||||
border-color: var(--ha-color-red-60);
|
||||
}
|
||||
:host([state="unknown"]) #indicator {
|
||||
border-color: var(--ha-color-fill-neutral-loud-resting);
|
||||
border-color: var(--ha-color-neutral-60);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -165,7 +165,8 @@ export class HaAutomationRow extends LitElement {
|
||||
::slotted([slot="leading-icon"]) {
|
||||
color: var(--ha-color-on-neutral-quiet);
|
||||
}
|
||||
:host([building-block]) ::slotted([slot="leading-icon"]) {
|
||||
:host([building-block]) ::slotted([slot="leading-icon"].action-icon),
|
||||
:host([building-block]) ::slotted(#condition-icon) {
|
||||
--mdc-icon-size: var(--ha-space-5);
|
||||
color: var(--white-color);
|
||||
transform: rotate(-45deg);
|
||||
|
||||
@@ -101,18 +101,22 @@ export class HaSankeyChart extends LitElement {
|
||||
const value = this.valueFormatter
|
||||
? this.valueFormatter(data.value)
|
||||
: data.value;
|
||||
// Keep numbers and units left-to-right, even in RTL locales.
|
||||
const formattedValue = html`<div style="direction:ltr; display: inline;">
|
||||
${value}
|
||||
</div>`;
|
||||
if (data.id) {
|
||||
const node = this.data.nodes.find((n) => n.id === data.id);
|
||||
return html`<ha-chart-tooltip-marker
|
||||
.color=${String(params.color ?? "")}
|
||||
></ha-chart-tooltip-marker>
|
||||
${node?.label ?? data.id}<br />${value}`;
|
||||
${node?.label ?? data.id}<br />${formattedValue}`;
|
||||
}
|
||||
if (data.source && data.target) {
|
||||
const source = this.data.nodes.find((n) => n.id === data.source);
|
||||
const target = this.data.nodes.find((n) => n.id === data.target);
|
||||
return html`${source?.label ?? data.source} →
|
||||
${target?.label ?? data.target}<br />${value}`;
|
||||
${target?.label ?? data.target}<br />${formattedValue}`;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -167,7 +167,7 @@ export class StateHistoryCharts extends LitElement {
|
||||
)}`}
|
||||
${this.syncCharts && this._hasZoomedCharts
|
||||
? html`<ha-button
|
||||
size="large"
|
||||
size="l"
|
||||
class="reset-button"
|
||||
@click=${this._handleGlobalZoomReset}
|
||||
>
|
||||
|
||||
@@ -117,7 +117,7 @@ export class HaEntityNamePicker extends LitElement {
|
||||
<div class="header">
|
||||
${this.label ? html`<label>${this.label}</label>` : nothing}
|
||||
<ha-button-toggle-group
|
||||
size="small"
|
||||
size="s"
|
||||
.buttons=${modeButtons}
|
||||
.active=${this._mode}
|
||||
.disabled=${this.disabled}
|
||||
|
||||
@@ -15,7 +15,7 @@ import "./ha-svg-icon";
|
||||
*
|
||||
* @attr {ToggleButton[]} buttons - the button config
|
||||
* @attr {string} active - The value of the currently active button.
|
||||
* @attr {("small"|"medium")} size - The size of the buttons in the group.
|
||||
* @attr {("s"|"m")} size - The size of the buttons in the group.
|
||||
* @attr {("brand"|"neutral"|"success"|"warning"|"danger")} variant - The variant of the buttons in the group.
|
||||
*
|
||||
* @fires value-changed - Dispatched when the active button changes.
|
||||
@@ -26,7 +26,7 @@ export class HaButtonToggleGroup extends LitElement {
|
||||
|
||||
@property() public active?: string;
|
||||
|
||||
@property({ reflect: true }) size: "small" | "medium" = "medium";
|
||||
@property({ reflect: true }) size: "s" | "m" = "m";
|
||||
|
||||
@property({ type: Boolean, reflect: true, attribute: "no-wrap" })
|
||||
public nowrap = false;
|
||||
|
||||
@@ -27,7 +27,7 @@ export type Appearance = "accent" | "filled" | "outlined" | "plain";
|
||||
* @cssprop --ha-button-height - The height of the button.
|
||||
* @cssprop --ha-button-border-radius - The border radius of the button. defaults to `var(--ha-border-radius-pill)`.
|
||||
*
|
||||
* @attr {("small"|"medium"|"large")} size - Sets the button size.
|
||||
* @attr {("xs"|"s"|"m"|"l"|"xl")} 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} loading - shows a loading indicator instead of the buttons label and disable buttons click.
|
||||
@@ -65,7 +65,7 @@ export class HaButton extends Button {
|
||||
box-shadow: var(--ha-button-box-shadow);
|
||||
}
|
||||
|
||||
:host([size="small"]) .button {
|
||||
:host([size="s"]) .button {
|
||||
--wa-form-control-height: var(
|
||||
--ha-button-height,
|
||||
var(--button-height, 32px)
|
||||
@@ -74,7 +74,7 @@ export class HaButton extends Button {
|
||||
--wa-form-control-padding-inline: var(--ha-space-3);
|
||||
}
|
||||
|
||||
:host([size="large"]) .button {
|
||||
:host([size="l"]) .button {
|
||||
--wa-form-control-height: var(
|
||||
--ha-button-height,
|
||||
var(--button-height, 48px)
|
||||
|
||||
@@ -61,7 +61,7 @@ export class HaDurationInput extends LitElement {
|
||||
${this.allowNegative
|
||||
? html`
|
||||
<ha-button-toggle-group
|
||||
size="small"
|
||||
size="s"
|
||||
.buttons=${[
|
||||
{ label: "+", iconPath: mdiPlusThick, value: "+" },
|
||||
{ label: "-", iconPath: mdiMinusThick, value: "-" },
|
||||
|
||||
@@ -120,7 +120,7 @@ export class HaFileUpload extends LitElement {
|
||||
@dragend=${this._handleDragEnd}
|
||||
>${!this.value
|
||||
? html`<ha-button
|
||||
size="small"
|
||||
size="s"
|
||||
appearance="filled"
|
||||
@click=${this._openFilePicker}
|
||||
>
|
||||
|
||||
@@ -129,7 +129,7 @@ export class HaFormOptionalActions extends LitElement implements HaFormElement {
|
||||
@wa-select=${this._handleAddAction}
|
||||
@closed=${stopPropagation}
|
||||
>
|
||||
<ha-button slot="trigger" appearance="filled" size="small">
|
||||
<ha-button slot="trigger" appearance="filled" size="s">
|
||||
<ha-svg-icon .path=${mdiPlus} slot="start"></ha-svg-icon>
|
||||
${this.localize?.("ui.components.form-optional-actions.add") ||
|
||||
"Add interaction"}
|
||||
|
||||
@@ -174,7 +174,7 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
|
||||
<slot name="field">
|
||||
${this.addButtonLabel && !this.value
|
||||
? html`<ha-button
|
||||
size="small"
|
||||
size="s"
|
||||
appearance="filled"
|
||||
@click=${this.open}
|
||||
.disabled=${this.disabled}
|
||||
|
||||
@@ -3,13 +3,12 @@ import type { TemplateResult } from "lit";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { mainWindow } from "../common/dom/get_main_window";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./ha-icon-button";
|
||||
import { consumeLocalize } from "../common/decorators/consume-context-entry";
|
||||
import type { LocalizeFunc } from "../common/translations/localize";
|
||||
|
||||
@customElement("ha-icon-button-arrow-prev")
|
||||
export class HaIconButtonArrowPrev extends LitElement {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property() public label?: string;
|
||||
@@ -25,11 +24,15 @@ export class HaIconButtonArrowPrev extends LitElement {
|
||||
@state() private _icon =
|
||||
mainWindow.document.dir === "rtl" ? mdiArrowRight : mdiArrowLeft;
|
||||
|
||||
@state()
|
||||
@consumeLocalize()
|
||||
private _localize!: LocalizeFunc;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-icon-button
|
||||
.disabled=${this.disabled}
|
||||
.label=${this.label || this.hass?.localize("ui.common.back") || "Back"}
|
||||
.label=${this.label || this._localize("ui.common.back") || "Back"}
|
||||
.path=${this._icon}
|
||||
.href=${this.href}
|
||||
.target=${this.target}
|
||||
|
||||
@@ -171,7 +171,7 @@ export class HaLabelsPicker extends LitElement {
|
||||
: nothing}
|
||||
<ha-button
|
||||
id="picker"
|
||||
size="small"
|
||||
size="s"
|
||||
appearance="filled"
|
||||
@click=${this._openPicker}
|
||||
.disabled=${this.disabled}
|
||||
|
||||
@@ -53,7 +53,7 @@ class HaLawnMowerActionButton extends LitElement {
|
||||
appearance="plain"
|
||||
@click=${this.callService}
|
||||
.service=${action.service}
|
||||
size="small"
|
||||
size="s"
|
||||
>
|
||||
${this.hass.localize(`ui.card.lawn_mower.actions.${action.action}`)}
|
||||
</ha-button>
|
||||
|
||||
@@ -102,7 +102,7 @@ export class HaPictureUpload extends LitElement {
|
||||
<div>
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
size="small"
|
||||
size="s"
|
||||
variant="danger"
|
||||
@click=${this._handleChangeClick}
|
||||
>
|
||||
|
||||
@@ -63,7 +63,7 @@ export class HaChooseSelector extends LitElement {
|
||||
return html`<div class="multi-header">
|
||||
<span>${this.label}${this.required ? "*" : ""}</span>
|
||||
<ha-button-toggle-group
|
||||
size="small"
|
||||
size="s"
|
||||
.buttons=${this._toggleButtons(
|
||||
this.selector.choose.choices,
|
||||
this.selector.choose.translation_key,
|
||||
|
||||
@@ -190,7 +190,7 @@ export class HaMediaSelector extends LitElement {
|
||||
? html`<div>
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
size="small"
|
||||
size="s"
|
||||
variant="danger"
|
||||
@click=${this._clearValue}
|
||||
>
|
||||
|
||||
@@ -307,7 +307,7 @@ export class HaNumericThresholdSelector extends LitElement {
|
||||
>`
|
||||
: nothing}
|
||||
<ha-button-toggle-group
|
||||
size="small"
|
||||
size="s"
|
||||
.buttons=${choiceToggleButtons}
|
||||
.active=${activeChoice}
|
||||
.disabled=${this.disabled}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { mainWindow } from "../common/dom/get_main_window";
|
||||
|
||||
@customElement("ha-slider")
|
||||
export class HaSlider extends Slider {
|
||||
@property({ reflect: true }) size: "small" | "medium" = "small";
|
||||
@property({ reflect: true }) size: "s" | "m" = "s";
|
||||
|
||||
@property({ type: Boolean, attribute: "with-tooltip" }) withTooltip = true;
|
||||
|
||||
@@ -110,12 +110,12 @@ export class HaSlider extends Slider {
|
||||
);
|
||||
}
|
||||
|
||||
:host([size="medium"]) {
|
||||
:host([size="m"]) {
|
||||
--thumb-width: 20px;
|
||||
--thumb-height: 20px;
|
||||
}
|
||||
|
||||
:host([size="small"]) {
|
||||
:host([size="s"]) {
|
||||
--thumb-width: 16px;
|
||||
--thumb-height: 16px;
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ class HaTracePicker extends LitElement {
|
||||
slot="field"
|
||||
appearance="filled"
|
||||
variant="neutral"
|
||||
size="small"
|
||||
size="s"
|
||||
@click=${this._openPicker}
|
||||
>
|
||||
${this._renderTracePickerValue(this.value!)}
|
||||
|
||||
@@ -58,7 +58,7 @@ export class HaVacuumState extends LitElement {
|
||||
return html`
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
size="small"
|
||||
size="s"
|
||||
@click=${this._callService}
|
||||
.disabled=${!interceptable}
|
||||
>
|
||||
|
||||
@@ -99,7 +99,7 @@ export class HaInputCopy extends LitElement {
|
||||
: nothing}
|
||||
</ha-input>
|
||||
</div>
|
||||
<ha-button @click=${this._copy} appearance="plain" size="small">
|
||||
<ha-button @click=${this._copy} appearance="plain" size="s">
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon>
|
||||
${this.label || this._i18n.localize("ui.common.copy")}
|
||||
</ha-button>
|
||||
|
||||
@@ -130,7 +130,7 @@ class HaInputMulti extends LitElement {
|
||||
</ha-sortable>
|
||||
<div class="layout horizontal">
|
||||
<ha-button
|
||||
size="small"
|
||||
size="s"
|
||||
appearance="filled"
|
||||
@click=${this._addItem}
|
||||
.disabled=${this.disabled ||
|
||||
|
||||
@@ -38,7 +38,7 @@ class MediaManageButton extends LitElement {
|
||||
return nothing;
|
||||
}
|
||||
return html`
|
||||
<ha-button appearance="filled" size="small" @click=${this._manage}>
|
||||
<ha-button appearance="filled" size="s" @click=${this._manage}>
|
||||
<ha-svg-icon .path=${mdiFolderEdit} slot="start"></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.components.media-browser.file_management.manage"
|
||||
|
||||
@@ -485,6 +485,17 @@ export const migrateAutomationTrigger = (
|
||||
}
|
||||
delete trigger.platform;
|
||||
}
|
||||
|
||||
if ("options" in trigger) {
|
||||
if (trigger.options && "behavior" in trigger.options) {
|
||||
if (trigger.options.behavior === "any") {
|
||||
trigger.options.behavior = "each";
|
||||
} else if (trigger.options.behavior === "last") {
|
||||
trigger.options.behavior = "all";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return trigger;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import { createContext } from "@lit/context";
|
||||
|
||||
export interface DirtyStateContext<State = unknown> {
|
||||
/** Whether current state differs from the initial snapshot */
|
||||
isDirty: boolean;
|
||||
/** Current tracked state */
|
||||
state: State;
|
||||
/** Update the tracked state — triggers dirty comparison */
|
||||
setState: (state: State) => void;
|
||||
/** Reset initial snapshot to current state (marks clean) */
|
||||
markClean: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Singleton context key for dirty-state tracking.
|
||||
*
|
||||
* Because Lit context keys are singletons, the value type is
|
||||
* `DirtyStateContext<unknown>`. The provider mixin and consumer controller
|
||||
* supply type-safe APIs on top of this boundary.
|
||||
*/
|
||||
export const dirtyStateContext = createContext<DirtyStateContext>("dirtyState");
|
||||
@@ -114,7 +114,7 @@ class EntityPreviewRow extends LitElement {
|
||||
|
||||
if (domain === "button") {
|
||||
return html`
|
||||
<ha-button appearance="plain" size="small" .disabled=${disabled}>
|
||||
<ha-button appearance="plain" size="s" .disabled=${disabled}>
|
||||
${this.hass.localize("ui.card.button.press")}
|
||||
</ha-button>
|
||||
`;
|
||||
@@ -237,7 +237,7 @@ class EntityPreviewRow extends LitElement {
|
||||
.disabled=${disabled}
|
||||
class="text-content"
|
||||
appearance="plain"
|
||||
size="small"
|
||||
size="s"
|
||||
>
|
||||
${stateObj.state === "locked"
|
||||
? this.hass!.localize("ui.card.lock.unlock")
|
||||
|
||||
@@ -214,7 +214,7 @@ export class HaMoreInfoViewVacuumCleanAreas extends LitElement {
|
||||
? html`
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
size="small"
|
||||
size="s"
|
||||
@click=${this._openSegmentMapping}
|
||||
>
|
||||
<ha-svg-icon
|
||||
|
||||
@@ -31,7 +31,7 @@ class MoreInfoAutomation extends LitElement {
|
||||
<div class="actions">
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
size="small"
|
||||
size="s"
|
||||
@click=${this._runActions}
|
||||
.disabled=${this.stateObj!.state === UNAVAILABLE}
|
||||
>
|
||||
|
||||
@@ -22,7 +22,7 @@ class MoreInfoCounter extends LitElement {
|
||||
<div class="actions">
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
size="small"
|
||||
size="s"
|
||||
.action=${"increment"}
|
||||
@click=${this._handleActionClick}
|
||||
.disabled=${disabled ||
|
||||
@@ -32,7 +32,7 @@ class MoreInfoCounter extends LitElement {
|
||||
</ha-button>
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
size="small"
|
||||
size="s"
|
||||
.action=${"decrement"}
|
||||
@click=${this._handleActionClick}
|
||||
.disabled=${disabled ||
|
||||
@@ -42,7 +42,7 @@ class MoreInfoCounter extends LitElement {
|
||||
</ha-button>
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
size="small"
|
||||
size="s"
|
||||
.action=${"reset"}
|
||||
@click=${this._handleActionClick}
|
||||
.disabled=${disabled}
|
||||
|
||||
@@ -431,7 +431,7 @@ class MoreInfoMediaPlayer extends LitElement {
|
||||
? html`<ha-button
|
||||
variant="brand"
|
||||
appearance="filled"
|
||||
size="medium"
|
||||
size="m"
|
||||
action=${action}
|
||||
@click=${this._handleClick}
|
||||
class="center-control"
|
||||
|
||||
@@ -39,7 +39,7 @@ class MoreInfoPerson extends LitElement {
|
||||
<div class="actions">
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
size="small"
|
||||
size="s"
|
||||
@click=${this._handleAction}
|
||||
>
|
||||
${this.hass.localize(
|
||||
|
||||
@@ -52,7 +52,7 @@ class MoreInfoSiren extends LitElement {
|
||||
${allowAdvanced
|
||||
? html`<ha-button
|
||||
appearance="plain"
|
||||
size="small"
|
||||
size="s"
|
||||
@click=${this._showAdvancedControlsDialog}
|
||||
>
|
||||
${this.hass.localize("ui.components.siren.advanced_controls")}
|
||||
|
||||
@@ -21,7 +21,7 @@ class MoreInfoTimer extends LitElement {
|
||||
? html`
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
size="small"
|
||||
size="s"
|
||||
.action=${"start"}
|
||||
@click=${this._handleActionClick}
|
||||
>
|
||||
@@ -33,7 +33,7 @@ class MoreInfoTimer extends LitElement {
|
||||
? html`
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
size="small"
|
||||
size="s"
|
||||
.action=${"pause"}
|
||||
@click=${this._handleActionClick}
|
||||
>
|
||||
@@ -45,7 +45,7 @@ class MoreInfoTimer extends LitElement {
|
||||
? html`
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
size="small"
|
||||
size="s"
|
||||
.action=${"cancel"}
|
||||
@click=${this._handleActionClick}
|
||||
>
|
||||
@@ -53,7 +53,7 @@ class MoreInfoTimer extends LitElement {
|
||||
</ha-button>
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
size="small"
|
||||
size="s"
|
||||
.action=${"finish"}
|
||||
@click=${this._handleActionClick}
|
||||
>
|
||||
|
||||
@@ -63,7 +63,6 @@ import { subscribeLabFeature } from "../../data/labs";
|
||||
import type { ItemType } from "../../data/search";
|
||||
import { SearchableDomains } from "../../data/search";
|
||||
import { getSensorNumericDeviceClasses } from "../../data/sensor";
|
||||
import { DirtyStateProviderMixin } from "../../mixins/dirty-state-provider-mixin";
|
||||
import { ScrollableFadeMixin } from "../../mixins/scrollable-fade-mixin";
|
||||
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
|
||||
import {
|
||||
@@ -122,8 +121,8 @@ declare global {
|
||||
const DEFAULT_VIEW: MoreInfoView = "info";
|
||||
|
||||
@customElement("ha-more-info-dialog")
|
||||
export class MoreInfoDialog extends DirtyStateProviderMixin()(
|
||||
SubscribeMixin(ScrollableFadeMixin(LitElement))
|
||||
export class MoreInfoDialog extends SubscribeMixin(
|
||||
ScrollableFadeMixin(LitElement)
|
||||
) {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@@ -641,8 +640,7 @@ export class MoreInfoDialog extends DirtyStateProviderMixin()(
|
||||
@closed=${this._dialogClosed}
|
||||
@opened=${this._handleOpened}
|
||||
@show-child-view=${this._showChildView}
|
||||
.preventScrimClose=${(this._currView === "settings" &&
|
||||
this.isDirtyState) ||
|
||||
.preventScrimClose=${this._currView === "settings" ||
|
||||
!this._isEscapeEnabled}
|
||||
flexcontent
|
||||
>
|
||||
@@ -959,12 +957,6 @@ export class MoreInfoDialog extends DirtyStateProviderMixin()(
|
||||
}
|
||||
}
|
||||
|
||||
if (changedProps.has("_currView") || changedProps.has("_entry")) {
|
||||
if (this._currView === "settings" && this._entry) {
|
||||
this._initDirtyTracking({ type: "deep" });
|
||||
}
|
||||
}
|
||||
|
||||
if (changedProps.has("_currView")) {
|
||||
this._infoEditMode = false;
|
||||
this._detailsYamlMode = false;
|
||||
|
||||
@@ -95,10 +95,7 @@ export class HaVoiceAssistantSetupStepLocal extends LitElement {
|
||||
"ui.panel.config.voice_assistants.satellite_wizard.local.failed_secondary"
|
||||
)}
|
||||
</p>
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
size="small"
|
||||
@click=${this._prevStep}
|
||||
<ha-button appearance="plain" size="s" @click=${this._prevStep}
|
||||
>${this.hass.localize("ui.common.back")}</ha-button
|
||||
>
|
||||
<ha-button
|
||||
@@ -108,7 +105,7 @@ export class HaVoiceAssistantSetupStepLocal extends LitElement {
|
||||
)}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
size="small"
|
||||
size="s"
|
||||
appearance="plain"
|
||||
>
|
||||
<ha-svg-icon .path=${mdiOpenInNew} slot="start"></ha-svg-icon>
|
||||
@@ -131,10 +128,7 @@ export class HaVoiceAssistantSetupStepLocal extends LitElement {
|
||||
"ui.panel.config.voice_assistants.satellite_wizard.local.not_supported_secondary"
|
||||
)}
|
||||
</p>
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
size="small"
|
||||
@click=${this._prevStep}
|
||||
<ha-button appearance="plain" size="s" @click=${this._prevStep}
|
||||
>${this.hass.localize("ui.common.back")}</ha-button
|
||||
>
|
||||
<ha-button
|
||||
@@ -145,7 +139,7 @@ export class HaVoiceAssistantSetupStepLocal extends LitElement {
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
appearance="plain"
|
||||
size="small"
|
||||
size="s"
|
||||
>
|
||||
<ha-svg-icon .path=${mdiOpenInNew} slot="start"></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
|
||||
@@ -131,7 +131,7 @@ export class HaVoiceAssistantSetupStepSuccess extends LitElement {
|
||||
></ha-select>
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
size="small"
|
||||
size="s"
|
||||
@click=${this._testWakeWord}
|
||||
>
|
||||
<ha-svg-icon
|
||||
@@ -166,7 +166,7 @@ export class HaVoiceAssistantSetupStepSuccess extends LitElement {
|
||||
</ha-select>
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
size="small"
|
||||
size="s"
|
||||
@click=${this._openPipeline}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiCog}></ha-svg-icon>
|
||||
@@ -186,11 +186,7 @@ export class HaVoiceAssistantSetupStepSuccess extends LitElement {
|
||||
@value-changed=${this._voicePicked}
|
||||
@closed=${stopPropagation}
|
||||
></ha-tts-voice-picker>
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
size="small"
|
||||
@click=${this._testTts}
|
||||
>
|
||||
<ha-button appearance="plain" size="s" @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"
|
||||
|
||||
@@ -150,7 +150,7 @@ export class HaVoiceAssistantSetupStepWakeWord extends LitElement {
|
||||
? html`<div class="footer centered">
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
size="small"
|
||||
size="s"
|
||||
@click=${this._changeWakeWord}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.voice_assistants.satellite_wizard.wake_word.change_wake_word"
|
||||
|
||||
@@ -117,7 +117,7 @@ export class HaVoiceCommandDialog extends LitElement {
|
||||
slot="trigger"
|
||||
appearance="plain"
|
||||
variant="neutral"
|
||||
size="small"
|
||||
size="s"
|
||||
.loading=${!this._pipelines}
|
||||
>
|
||||
${this._pipeline?.name}
|
||||
|
||||
@@ -23,7 +23,7 @@ class HaInitPage extends LitElement {
|
||||
<p class="retry-text">
|
||||
Retrying in ${this._retryInSeconds} seconds...
|
||||
</p>
|
||||
<ha-button size="small" appearance="plain" @click=${this._retry}
|
||||
<ha-button size="s" appearance="plain" @click=${this._retry}
|
||||
>Retry now</ha-button
|
||||
>
|
||||
${location.host.includes("ui.nabu.casa")
|
||||
|
||||
@@ -33,7 +33,6 @@ class HassErrorScreen extends LitElement {
|
||||
`
|
||||
: html`
|
||||
<ha-icon-button-arrow-prev
|
||||
.hass=${this.hass}
|
||||
@click=${this._handleBack}
|
||||
></ha-icon-button-arrow-prev>
|
||||
`}
|
||||
@@ -42,7 +41,7 @@ class HassErrorScreen extends LitElement {
|
||||
<div class="content">
|
||||
<ha-alert alert-type="error">${this.error}</ha-alert>
|
||||
<slot>
|
||||
<ha-button appearance="plain" size="small" @click=${this._handleBack}>
|
||||
<ha-button appearance="plain" size="s" @click=${this._handleBack}>
|
||||
${this.hass?.localize("ui.common.back")}
|
||||
</ha-button>
|
||||
</slot>
|
||||
|
||||
@@ -35,7 +35,6 @@ class HassLoadingScreen extends LitElement {
|
||||
`
|
||||
: html`
|
||||
<ha-icon-button-arrow-prev
|
||||
.hass=${this.hass}
|
||||
@click=${this._handleBack}
|
||||
></ha-icon-button-arrow-prev>
|
||||
`}
|
||||
|
||||
@@ -43,12 +43,10 @@ class HassSubpage extends LitElement {
|
||||
? html`
|
||||
<ha-icon-button-arrow-prev
|
||||
href=${this.backPath}
|
||||
.hass=${this.hass}
|
||||
></ha-icon-button-arrow-prev>
|
||||
`
|
||||
: html`
|
||||
<ha-icon-button-arrow-prev
|
||||
.hass=${this.hass}
|
||||
@click=${this._backTapped}
|
||||
></ha-icon-button-arrow-prev>
|
||||
`}
|
||||
|
||||
@@ -139,6 +139,8 @@ export class HassTabsSubpage extends LitElement {
|
||||
);
|
||||
|
||||
public willUpdate(changedProperties: PropertyValues<this>) {
|
||||
this.toggleAttribute("narrow", this._narrow);
|
||||
|
||||
if (changedProperties.has("route")) {
|
||||
const currentPath = `${this.route.prefix}${this.route.path}`;
|
||||
this._activeTab = this.tabs.find((tab) =>
|
||||
@@ -173,12 +175,10 @@ export class HassTabsSubpage extends LitElement {
|
||||
? html`
|
||||
<ha-icon-button-arrow-prev
|
||||
.href=${this.backPath}
|
||||
.hass=${this.hass}
|
||||
></ha-icon-button-arrow-prev>
|
||||
`
|
||||
: html`
|
||||
<ha-icon-button-arrow-prev
|
||||
.hass=${this.hass}
|
||||
@click=${this._backTapped}
|
||||
></ha-icon-button-arrow-prev>
|
||||
`}
|
||||
|
||||
@@ -97,7 +97,7 @@ class NotificationManager extends LitElement {
|
||||
? html`
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
size="small"
|
||||
size="s"
|
||||
slot="action"
|
||||
@click=${this._buttonClicked}
|
||||
>
|
||||
|
||||
@@ -1,193 +0,0 @@
|
||||
import { provide } from "@lit/context";
|
||||
import type { LitElement } from "lit";
|
||||
import { state } from "lit/decorators";
|
||||
import { deepEqual } from "../common/util/deep-equal";
|
||||
import { shallowEqual } from "../common/util/shallow-equal";
|
||||
import {
|
||||
dirtyStateContext,
|
||||
type DirtyStateContext,
|
||||
} from "../data/context/dirty-state";
|
||||
import type { Constructor } from "../types";
|
||||
|
||||
export type CompareStrategy<State> =
|
||||
| { type: "deep" }
|
||||
| { type: "shallow" }
|
||||
| { type: "custom"; compare: (a: State, b: State) => boolean };
|
||||
|
||||
function resolveCompare<State>(
|
||||
strategy: CompareStrategy<State>
|
||||
): (a: State, b: State) => boolean {
|
||||
switch (strategy.type) {
|
||||
case "deep":
|
||||
return (a, b) => deepEqual(a, b);
|
||||
case "shallow":
|
||||
return (a, b) => shallowEqual(a, b);
|
||||
default:
|
||||
return strategy.compare;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mixin that provides dirty-state tracking via Lit context.
|
||||
*
|
||||
* Uses the `@provide` decorator so any descendant component can consume
|
||||
* dirty-state with `@consume({ context: dirtyStateContext, subscribe: true })`.
|
||||
*
|
||||
* Curried generic pattern: `State` is explicitly provided while `Base` is
|
||||
* inferred from the superclass argument.
|
||||
*
|
||||
* @example Eager init (state known upfront, e.g. dialog open):
|
||||
* ```ts
|
||||
* interface MyDialogState { name: string; icon: string }
|
||||
*
|
||||
* class MyDialog extends DirtyStateProviderMixin<MyDialogState>()(LitElement) {
|
||||
* open() {
|
||||
* this._initDirtyTracking({ type: "shallow" }, { name: "", icon: "" });
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @example Deferred init (child consumer reports initial state):
|
||||
* ```ts
|
||||
* class MyPage extends DirtyStateProviderMixin<FormState>()(LitElement) {
|
||||
* connectedCallback() {
|
||||
* super.connectedCallback();
|
||||
* this._initDirtyTracking({ type: "deep" });
|
||||
* // First setState from a child consumer sets the baseline
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* Child consumers:
|
||||
* ```ts
|
||||
* @consume({ context: dirtyStateContext, subscribe: true })
|
||||
* @state()
|
||||
* private _dirtyState?: DirtyStateContext;
|
||||
*
|
||||
* // Read: this._dirtyState?.isDirty
|
||||
* // Write: this._dirtyState?.setState(newState)
|
||||
* ```
|
||||
*/
|
||||
export const DirtyStateProviderMixin =
|
||||
<State = unknown>() =>
|
||||
<Base extends Constructor<LitElement>>(superClass: Base) => {
|
||||
class DirtyStateProviderMixinClass extends superClass {
|
||||
private _dirtyInitialState: State | undefined;
|
||||
|
||||
private _dirtyCurrentState: State | undefined;
|
||||
|
||||
private _dirtyCompareFn: (a: State, b: State) => boolean = deepEqual;
|
||||
|
||||
@provide({ context: dirtyStateContext })
|
||||
@state()
|
||||
private _dirtyStateContext: DirtyStateContext = this._buildContextValue(
|
||||
undefined,
|
||||
false
|
||||
);
|
||||
|
||||
/**
|
||||
* Build the context value object for the provider.
|
||||
*
|
||||
* The returned type is `DirtyStateContext` (i.e. `DirtyStateContext<unknown>`)
|
||||
* because the singleton context key is typed at `unknown`. The single
|
||||
* `unknown → State` narrowing cast in `setState` is the only unsafe boundary
|
||||
* and is confined here.
|
||||
*/
|
||||
private _buildContextValue(
|
||||
currentState: State | undefined,
|
||||
isDirty: boolean
|
||||
): DirtyStateContext {
|
||||
return {
|
||||
isDirty,
|
||||
state: currentState,
|
||||
setState: (incoming: unknown) => {
|
||||
this._updateDirtyState(incoming as State);
|
||||
},
|
||||
markClean: () => {
|
||||
this._markDirtyStateClean();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize dirty state tracking.
|
||||
*
|
||||
* When `initialState` is provided, tracking starts immediately.
|
||||
* When omitted (deferred mode), the first `_updateDirtyState` /
|
||||
* `setState` call from a consumer becomes the baseline snapshot.
|
||||
*
|
||||
* Call again to reset (e.g. when the underlying entity changes).
|
||||
*/
|
||||
protected _initDirtyTracking(
|
||||
strategy: CompareStrategy<State>,
|
||||
initialState?: State
|
||||
): void {
|
||||
this._dirtyCompareFn = resolveCompare(strategy);
|
||||
if (initialState !== undefined) {
|
||||
this._dirtyInitialState = initialState;
|
||||
this._dirtyCurrentState = initialState;
|
||||
this._dirtyStateContext = this._buildContextValue(
|
||||
initialState,
|
||||
false
|
||||
);
|
||||
} else {
|
||||
this._dirtyInitialState = undefined;
|
||||
this._dirtyCurrentState = undefined;
|
||||
this._dirtyStateContext = this._buildContextValue(undefined, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the tracked state. Triggers dirty comparison against initial snapshot.
|
||||
*
|
||||
* If called before `_initDirtyTracking` provided an initial state (deferred
|
||||
* mode), the first call sets the baseline and reports clean.
|
||||
*
|
||||
* Guarded: no-ops if the computed dirty status and state reference are
|
||||
* unchanged, preventing render loops when called from `updated()`.
|
||||
*/
|
||||
protected _updateDirtyState(newState: State): void {
|
||||
// Deferred init: first state becomes the baseline
|
||||
if (this._dirtyInitialState === undefined) {
|
||||
this._dirtyInitialState = newState;
|
||||
this._dirtyCurrentState = newState;
|
||||
this._dirtyStateContext = this._buildContextValue(newState, false);
|
||||
return;
|
||||
}
|
||||
|
||||
const isDirty = !this._dirtyCompareFn(
|
||||
this._dirtyInitialState,
|
||||
newState
|
||||
);
|
||||
if (
|
||||
this._dirtyCurrentState !== undefined &&
|
||||
this._dirtyCompareFn(this._dirtyCurrentState, newState) &&
|
||||
this._dirtyStateContext.isDirty === isDirty
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this._dirtyCurrentState = newState;
|
||||
this._dirtyStateContext = this._buildContextValue(newState, isDirty);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the initial snapshot to the current state, marking the state as clean.
|
||||
* Call this after a successful save.
|
||||
*/
|
||||
protected _markDirtyStateClean(): void {
|
||||
this._dirtyInitialState = this._dirtyCurrentState;
|
||||
this._dirtyStateContext = this._buildContextValue(
|
||||
this._dirtyCurrentState,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the current state differs from the initial snapshot.
|
||||
*/
|
||||
public get isDirtyState(): boolean {
|
||||
return this._dirtyStateContext.isDirty;
|
||||
}
|
||||
}
|
||||
return DirtyStateProviderMixinClass;
|
||||
};
|
||||
@@ -1,39 +1,20 @@
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import type { LocalizeFunc } from "../../common/translations/localize";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { customElement } from "lit/decorators";
|
||||
import "../../components/ha-dialog";
|
||||
import { DialogMixin } from "../../dialogs/dialog-mixin";
|
||||
import type { AppDialogParams } from "./show-app-dialog";
|
||||
|
||||
@customElement("app-dialog")
|
||||
class DialogApp extends LitElement {
|
||||
@property({ attribute: false }) public localize?: LocalizeFunc;
|
||||
|
||||
@state() private _open = false;
|
||||
|
||||
public async showDialog(params: { localize: LocalizeFunc }): Promise<void> {
|
||||
this.localize = params.localize;
|
||||
this._open = true;
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this._open = false;
|
||||
}
|
||||
|
||||
private _dialogClosed(): void {
|
||||
this.localize = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
class DialogApp extends DialogMixin<AppDialogParams>(LitElement) {
|
||||
protected render() {
|
||||
if (!this.localize) {
|
||||
if (!this.params?.localize) {
|
||||
return nothing;
|
||||
}
|
||||
return html`<ha-dialog
|
||||
.open=${this._open}
|
||||
header-title=${this.localize(
|
||||
open
|
||||
header-title=${this.params.localize(
|
||||
"ui.panel.page-onboarding.welcome.download_app"
|
||||
) || "Click here to download the app"}
|
||||
@closed=${this._dialogClosed}
|
||||
>
|
||||
<div>
|
||||
<div class="app-qr">
|
||||
@@ -45,13 +26,17 @@ class DialogApp extends LitElement {
|
||||
<img
|
||||
loading="lazy"
|
||||
src="/static/images/appstore.svg"
|
||||
alt=${this.localize("ui.panel.page-onboarding.welcome.appstore")}
|
||||
alt=${this.params.localize(
|
||||
"ui.panel.page-onboarding.welcome.appstore"
|
||||
)}
|
||||
class="icon"
|
||||
/>
|
||||
<img
|
||||
loading="lazy"
|
||||
src="/static/images/qr-appstore.svg"
|
||||
alt=${this.localize("ui.panel.page-onboarding.welcome.appstore")}
|
||||
alt=${this.params.localize(
|
||||
"ui.panel.page-onboarding.welcome.appstore"
|
||||
)}
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
@@ -62,13 +47,17 @@ class DialogApp extends LitElement {
|
||||
<img
|
||||
loading="lazy"
|
||||
src="/static/images/playstore.svg"
|
||||
alt=${this.localize("ui.panel.page-onboarding.welcome.playstore")}
|
||||
alt=${this.params.localize(
|
||||
"ui.panel.page-onboarding.welcome.playstore"
|
||||
)}
|
||||
class="icon"
|
||||
/>
|
||||
<img
|
||||
loading="lazy"
|
||||
src="/static/images/qr-playstore.svg"
|
||||
alt=${this.localize("ui.panel.page-onboarding.welcome.playstore")}
|
||||
alt=${this.params.localize(
|
||||
"ui.panel.page-onboarding.welcome.playstore"
|
||||
)}
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -1,103 +1,87 @@
|
||||
import { mdiAccountGroup, mdiOpenInNew } from "@mdi/js";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import type { LocalizeFunc } from "../../common/translations/localize";
|
||||
import { customElement } from "lit/decorators";
|
||||
import "../../components/ha-dialog";
|
||||
import "../../components/ha-list";
|
||||
import "../../components/ha-list-item";
|
||||
import "../../components/ha-svg-icon";
|
||||
import "../../components/item/ha-list-item-button";
|
||||
import "../../components/list/ha-list-nav";
|
||||
import { DialogMixin } from "../../dialogs/dialog-mixin";
|
||||
import type { CommunityDialogParams } from "./show-community-dialog";
|
||||
|
||||
@customElement("community-dialog")
|
||||
class DialogCommunity extends LitElement {
|
||||
@property({ attribute: false }) public localize?: LocalizeFunc;
|
||||
|
||||
@state() private _open = false;
|
||||
|
||||
public async showDialog(params): Promise<void> {
|
||||
this.localize = params.localize;
|
||||
this._open = true;
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this._open = false;
|
||||
}
|
||||
|
||||
private _dialogClosed(): void {
|
||||
this.localize = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
class DialogCommunity extends DialogMixin<CommunityDialogParams>(LitElement) {
|
||||
protected render() {
|
||||
if (!this.localize) {
|
||||
if (!this.params?.localize) {
|
||||
return nothing;
|
||||
}
|
||||
return html`<ha-dialog
|
||||
.open=${this._open}
|
||||
header-title=${this.localize(
|
||||
open
|
||||
header-title=${this.params.localize(
|
||||
"ui.panel.page-onboarding.welcome.community"
|
||||
)}
|
||||
@closed=${this._dialogClosed}
|
||||
>
|
||||
<ha-list>
|
||||
<a
|
||||
<ha-list-nav>
|
||||
<ha-list-item-button
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
href="https://community.home-assistant.io/"
|
||||
>
|
||||
<ha-list-item hasMeta graphic="icon">
|
||||
<img
|
||||
src="/static/icons/favicon-192x192.png"
|
||||
slot="graphic"
|
||||
alt="Home Assistant Logo"
|
||||
/>
|
||||
${this.localize("ui.panel.page-onboarding.welcome.forums")}
|
||||
<ha-svg-icon slot="meta" .path=${mdiOpenInNew}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</a>
|
||||
<a
|
||||
<img
|
||||
src="/static/icons/favicon-192x192.png"
|
||||
slot="start"
|
||||
alt="Home Assistant Logo"
|
||||
/>
|
||||
<span slot="headline">
|
||||
${this.params.localize("ui.panel.page-onboarding.welcome.forums")}
|
||||
</span>
|
||||
<ha-svg-icon slot="end" .path=${mdiOpenInNew}></ha-svg-icon>
|
||||
</ha-list-item-button>
|
||||
<ha-list-item-button
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
href="https://newsletter.openhomefoundation.org/"
|
||||
>
|
||||
<ha-list-item hasMeta graphic="icon">
|
||||
<img
|
||||
src="/static/icons/logo_ohf.svg"
|
||||
slot="graphic"
|
||||
alt="Open Home Foundation Logo"
|
||||
/>
|
||||
${this.localize(
|
||||
<img
|
||||
src="/static/icons/logo_ohf.svg"
|
||||
slot="start"
|
||||
alt="Open Home Foundation Logo"
|
||||
/>
|
||||
<span slot="headline">
|
||||
${this.params.localize(
|
||||
"ui.panel.page-onboarding.welcome.open_home_newsletter"
|
||||
)}
|
||||
<ha-svg-icon slot="meta" .path=${mdiOpenInNew}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</a>
|
||||
<a
|
||||
</span>
|
||||
<ha-svg-icon slot="end" .path=${mdiOpenInNew}></ha-svg-icon>
|
||||
</ha-list-item-button>
|
||||
<ha-list-item-button
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
href="https://www.home-assistant.io/join-chat"
|
||||
>
|
||||
<ha-list-item hasMeta graphic="icon">
|
||||
<img
|
||||
src="/static/images/logo_discord.png"
|
||||
slot="graphic"
|
||||
alt="Discord Logo"
|
||||
/>
|
||||
${this.localize("ui.panel.page-onboarding.welcome.discord")}
|
||||
<ha-svg-icon slot="meta" .path=${mdiOpenInNew}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</a>
|
||||
<a
|
||||
<img
|
||||
src="/static/images/logo_discord.png"
|
||||
slot="start"
|
||||
alt="Discord Logo"
|
||||
/>
|
||||
<span slot="headline">
|
||||
${this.params.localize("ui.panel.page-onboarding.welcome.discord")}
|
||||
</span>
|
||||
<ha-svg-icon slot="end" .path=${mdiOpenInNew}></ha-svg-icon>
|
||||
</ha-list-item-button>
|
||||
<ha-list-item-button
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
href="https://fosstodon.org/@homeassistant"
|
||||
>
|
||||
<ha-list-item hasMeta graphic="icon">
|
||||
<ha-svg-icon .path=${mdiAccountGroup} slot="graphic"></ha-svg-icon>
|
||||
${this.localize("ui.panel.page-onboarding.welcome.social_media")}
|
||||
<ha-svg-icon slot="meta" .path=${mdiOpenInNew}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</a>
|
||||
</ha-list>
|
||||
<ha-svg-icon .path=${mdiAccountGroup} slot="start"></ha-svg-icon>
|
||||
<span slot="headline">
|
||||
${this.params.localize(
|
||||
"ui.panel.page-onboarding.welcome.social_media"
|
||||
)}
|
||||
</span>
|
||||
<ha-svg-icon slot="end" .path=${mdiOpenInNew}></ha-svg-icon>
|
||||
</ha-list-item-button>
|
||||
</ha-list-nav>
|
||||
</ha-dialog>`;
|
||||
}
|
||||
|
||||
@@ -105,12 +89,12 @@ class DialogCommunity extends LitElement {
|
||||
ha-dialog {
|
||||
--dialog-content-padding: 0;
|
||||
}
|
||||
ha-list-item {
|
||||
height: 56px;
|
||||
--mdc-list-item-meta-size: 20px;
|
||||
img {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
ha-svg-icon {
|
||||
color: var(--ha-color-text-secondary);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -3,13 +3,18 @@ import type { LocalizeFunc } from "../../common/translations/localize";
|
||||
|
||||
export const loadAppDialog = () => import("./app-dialog");
|
||||
|
||||
export interface AppDialogParams {
|
||||
localize: LocalizeFunc;
|
||||
}
|
||||
|
||||
export const showAppDialog = (
|
||||
element: HTMLElement,
|
||||
params: { localize: LocalizeFunc }
|
||||
params: AppDialogParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "app-dialog",
|
||||
dialogImport: loadAppDialog,
|
||||
dialogParams: params,
|
||||
addHistory: false,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -3,13 +3,18 @@ import type { LocalizeFunc } from "../../common/translations/localize";
|
||||
|
||||
export const loadCommunityDialog = () => import("./community-dialog");
|
||||
|
||||
export interface CommunityDialogParams {
|
||||
localize: LocalizeFunc;
|
||||
}
|
||||
|
||||
export const showCommunityDialog = (
|
||||
element: HTMLElement,
|
||||
params: { localize: LocalizeFunc }
|
||||
params: CommunityDialogParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "community-dialog",
|
||||
dialogImport: loadCommunityDialog,
|
||||
dialogParams: params,
|
||||
addHistory: false,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -132,7 +132,7 @@ class OnboardingRestoreBackupRestore extends LitElement {
|
||||
href="https://www.home-assistant.io/installation/#advanced-installation-methods"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
size="small"
|
||||
size="s"
|
||||
>
|
||||
${this.localize(
|
||||
"ui.panel.page-onboarding.restore.ha-cloud.learn_more"
|
||||
|
||||
@@ -147,7 +147,7 @@ export class HAFullCalendar extends LitElement {
|
||||
<div class="navigation">
|
||||
<ha-button
|
||||
appearance="filled"
|
||||
size="small"
|
||||
size="s"
|
||||
class="today"
|
||||
@click=${this._handleToday}
|
||||
>${this.hass.localize(
|
||||
@@ -171,7 +171,7 @@ export class HAFullCalendar extends LitElement {
|
||||
<ha-button-toggle-group
|
||||
.buttons=${viewToggleButtons}
|
||||
.active=${this._activeView}
|
||||
size="small"
|
||||
size="s"
|
||||
no-wrap
|
||||
@value-changed=${this._handleView}
|
||||
></ha-button-toggle-group>
|
||||
@@ -197,7 +197,7 @@ export class HAFullCalendar extends LitElement {
|
||||
<div class="controls buttons">
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
size="small"
|
||||
size="s"
|
||||
class="today"
|
||||
@click=${this._handleToday}
|
||||
>${this.hass.localize(
|
||||
@@ -207,7 +207,7 @@ export class HAFullCalendar extends LitElement {
|
||||
<ha-button-toggle-group
|
||||
.buttons=${viewToggleButtons}
|
||||
.active=${this._activeView}
|
||||
size="small"
|
||||
size="s"
|
||||
no-wrap
|
||||
@value-changed=${this._handleView}
|
||||
></ha-button-toggle-group>
|
||||
@@ -219,7 +219,7 @@ export class HAFullCalendar extends LitElement {
|
||||
|
||||
<div id="calendar"></div>
|
||||
${this.addFab && this._hasMutableCalendars
|
||||
? html`<ha-button size="large" slot="fab" @click=${this._createEvent}>
|
||||
? html`<ha-button size="l" slot="fab" @click=${this._createEvent}>
|
||||
<ha-svg-icon slot="start" .path=${mdiPlus}></ha-svg-icon>
|
||||
${this.hass.localize("ui.components.calendar.event.add")}
|
||||
</ha-button>`
|
||||
|
||||
@@ -175,7 +175,7 @@ export class HaConfigApplicationCredentials extends LitElement {
|
||||
? html`
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
size="small"
|
||||
size="s"
|
||||
@click=${this._deleteSelected}
|
||||
variant="danger"
|
||||
>${this.hass.localize(
|
||||
@@ -199,11 +199,7 @@ export class HaConfigApplicationCredentials extends LitElement {
|
||||
</ha-help-tooltip>
|
||||
`}
|
||||
</div>
|
||||
<ha-button
|
||||
slot="fab"
|
||||
size="large"
|
||||
@click=${this._addApplicationCredential}
|
||||
>
|
||||
<ha-button slot="fab" size="l" @click=${this._addApplicationCredential}>
|
||||
<ha-svg-icon slot="start" .path=${mdiPlus}></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.application_credentials.picker.add_application_credential"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import "@home-assistant/webawesome/dist/components/tag/tag";
|
||||
import { mdiHelpCircleOutline } from "@mdi/js";
|
||||
import { mdiCheckCircle, mdiHelpCircleOutline } from "@mdi/js";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
@@ -25,7 +25,9 @@ class SupervisorAppsCardContent extends LitElement {
|
||||
|
||||
@property() public stage: AddonStage = "stable";
|
||||
|
||||
@property() public state: AddonState = null;
|
||||
@property() public state?: AddonState;
|
||||
|
||||
@property({ type: Boolean }) public installed = false;
|
||||
|
||||
@property() public description?: string;
|
||||
|
||||
@@ -77,13 +79,23 @@ class SupervisorAppsCardContent extends LitElement {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
${this.tags?.length || this.state
|
||||
${this.tags?.length || this.state !== undefined || this.installed
|
||||
? html`
|
||||
<div class="footer">
|
||||
<supervisor-apps-state
|
||||
.state=${this.state || "unknown"}
|
||||
></supervisor-apps-state>
|
||||
|
||||
${this.state !== undefined
|
||||
? html`<supervisor-apps-state
|
||||
.state=${this.state || "unknown"}
|
||||
></supervisor-apps-state>`
|
||||
: this.installed
|
||||
? html`<div class="installed">
|
||||
<ha-svg-icon .path=${mdiCheckCircle}></ha-svg-icon>
|
||||
<span
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.apps.state.installed"
|
||||
)}</span
|
||||
>
|
||||
</div>`
|
||||
: html`<span></span>`}
|
||||
${this.tags?.length
|
||||
? html`<div class="tags">
|
||||
${this.tags.map(
|
||||
@@ -159,6 +171,17 @@ class SupervisorAppsCardContent extends LitElement {
|
||||
display: flex;
|
||||
gap: var(--ha-space-2);
|
||||
}
|
||||
.installed {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--ha-space-2);
|
||||
color: var(--ha-color-text-secondary);
|
||||
font-size: var(--ha-font-size-m);
|
||||
}
|
||||
.installed ha-svg-icon {
|
||||
--mdc-icon-size: 16px;
|
||||
color: var(--ha-color-on-success-normal);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -156,7 +156,7 @@ export class HaConfigAppsInstalled extends LitElement {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<ha-button size="large" href="/config/apps/available">
|
||||
<ha-button size="l" href="/config/apps/available">
|
||||
<ha-svg-icon slot="start" .path=${mdiStorePlus}></ha-svg-icon>
|
||||
${this.hass.localize("ui.panel.config.apps.installed.add_app")}
|
||||
</ha-button>
|
||||
@@ -295,7 +295,7 @@ export class HaConfigAppsInstalled extends LitElement {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
ha-button[size="large"] {
|
||||
ha-button[size="l"] {
|
||||
position: fixed;
|
||||
right: calc(var(--ha-space-4) + var(--safe-area-inset-right));
|
||||
bottom: calc(var(--ha-space-4) + var(--safe-area-inset-bottom));
|
||||
|
||||
@@ -116,7 +116,7 @@ export class HaConfigAppsRegistries extends LitElement {
|
||||
id="registry"
|
||||
has-fab
|
||||
></ha-data-table>
|
||||
<ha-button size="large" @click=${this._showAddRegistryDialog}>
|
||||
<ha-button size="l" @click=${this._showAddRegistryDialog}>
|
||||
<ha-svg-icon slot="start" .path=${mdiPlus}></ha-svg-icon>
|
||||
${this.hass.localize("ui.panel.config.apps.registries.add")}
|
||||
</ha-button>
|
||||
@@ -184,7 +184,7 @@ export class HaConfigAppsRegistries extends LitElement {
|
||||
ha-icon-button.delete {
|
||||
color: var(--error-color);
|
||||
}
|
||||
ha-button[size="large"] {
|
||||
ha-button[size="l"] {
|
||||
position: fixed;
|
||||
right: calc(var(--ha-space-4) + var(--safe-area-inset-right));
|
||||
bottom: calc(var(--ha-space-4) + var(--safe-area-inset-bottom));
|
||||
|
||||
@@ -195,7 +195,7 @@ export class HaConfigAppsRepositories extends LitElement {
|
||||
id="slug"
|
||||
has-fab
|
||||
></ha-data-table>
|
||||
<ha-button size="large" @click=${this._showAddRepositoryDialog}>
|
||||
<ha-button size="l" @click=${this._showAddRepositoryDialog}>
|
||||
<ha-svg-icon slot="start" .path=${mdiPlus}></ha-svg-icon>
|
||||
${this.hass.localize("ui.panel.config.apps.repositories.add")}
|
||||
</ha-button>
|
||||
@@ -292,7 +292,7 @@ export class HaConfigAppsRepositories extends LitElement {
|
||||
ha-icon-button.delete {
|
||||
color: var(--error-color);
|
||||
}
|
||||
ha-button[size="large"] {
|
||||
ha-button[size="l"] {
|
||||
position: fixed;
|
||||
right: calc(var(--ha-space-4) + var(--safe-area-inset-right));
|
||||
bottom: calc(var(--ha-space-4) + var(--safe-area-inset-bottom));
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import { mdiArrowUpBoldCircle, mdiPuzzle } from "@mdi/js";
|
||||
import {
|
||||
mdiAlertDecagramOutline,
|
||||
mdiArrowUpBoldCircle,
|
||||
mdiArrowUpBoldCircleOutline,
|
||||
mdiFlask,
|
||||
mdiPuzzle,
|
||||
} from "@mdi/js";
|
||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import { caseInsensitiveStringCompare } from "../../../common/string/compare";
|
||||
@@ -10,6 +17,7 @@ import type { HassioAddonRepository } from "../../../data/hassio/addon";
|
||||
import type { StoreAddon } from "../../../data/supervisor/store";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import "./components/supervisor-apps-card-content";
|
||||
import type { AppTag } from "./components/supervisor-apps-card-content";
|
||||
import { filterAndSort } from "./components/supervisor-apps-filter";
|
||||
import { supervisorAppsStyle } from "./resources/supervisor-apps-style";
|
||||
|
||||
@@ -54,21 +62,29 @@ export class SupervisorAppsRepositoryEl extends LitElement {
|
||||
<div class="content">
|
||||
<h1>${repo.name}</h1>
|
||||
<div class="card-group">
|
||||
${addons.map(
|
||||
(addon) => html`
|
||||
${addons.map((addon) => {
|
||||
const tags = this._getAppTags(addon);
|
||||
return html`
|
||||
<ha-card
|
||||
outlined
|
||||
.addon=${addon}
|
||||
class=${addon.available ? "" : "not_available"}
|
||||
@click=${this._addonTapped}
|
||||
>
|
||||
<div class="card-content">
|
||||
<div
|
||||
class=${classMap({
|
||||
"card-content": true,
|
||||
"has-footer": tags.length > 0 || addon.installed,
|
||||
})}
|
||||
>
|
||||
<supervisor-apps-card-content
|
||||
.hass=${this.hass}
|
||||
.title=${addon.name}
|
||||
.stage=${addon.stage}
|
||||
.description=${addon.description}
|
||||
.available=${addon.available}
|
||||
.installed=${addon.installed}
|
||||
.tags=${tags}
|
||||
.icon=${addon.installed && addon.update_available
|
||||
? mdiArrowUpBoldCircle
|
||||
: mdiPuzzle}
|
||||
@@ -108,8 +124,8 @@ export class SupervisorAppsRepositoryEl extends LitElement {
|
||||
></supervisor-apps-card-content>
|
||||
</div>
|
||||
</ha-card>
|
||||
`
|
||||
)}
|
||||
`;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -119,6 +135,32 @@ export class SupervisorAppsRepositoryEl extends LitElement {
|
||||
navigate(`/config/app/${ev.currentTarget.addon.slug}/info?store=true`);
|
||||
}
|
||||
|
||||
private _getAppTags(addon: StoreAddon): AppTag[] {
|
||||
const labels: AppTag[] = [];
|
||||
|
||||
if (addon.installed && addon.update_available) {
|
||||
labels.push({
|
||||
label: this.hass.localize(
|
||||
`ui.panel.config.apps.state.update_available`
|
||||
),
|
||||
variant: "brand",
|
||||
iconPath: mdiArrowUpBoldCircleOutline,
|
||||
});
|
||||
}
|
||||
if (addon.stage !== "stable") {
|
||||
labels.push({
|
||||
label: this.hass.localize(
|
||||
`ui.panel.config.apps.dashboard.capability.stages.${addon.stage}`
|
||||
),
|
||||
variant: addon.stage === "experimental" ? "warning" : "danger",
|
||||
iconPath:
|
||||
addon.stage === "experimental" ? mdiFlask : mdiAlertDecagramOutline,
|
||||
});
|
||||
}
|
||||
|
||||
return labels;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
supervisorAppsStyle,
|
||||
@@ -127,6 +169,9 @@ export class SupervisorAppsRepositoryEl extends LitElement {
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
}
|
||||
.card-content.has-footer {
|
||||
padding: var(--ha-space-4) var(--ha-space-4) var(--ha-space-2);
|
||||
}
|
||||
.not_available {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
@@ -318,7 +318,7 @@ export class HaConfigAreasDashboard extends LitElement {
|
||||
: nothing}
|
||||
</div>
|
||||
<ha-dropdown slot="fab" @wa-select=${this._handleCreateAction}>
|
||||
<ha-button slot="trigger" id="fab" size="large">
|
||||
<ha-button slot="trigger" id="fab" size="l">
|
||||
<ha-svg-icon slot="start" .path=${mdiPlus}></ha-svg-icon>
|
||||
${this.hass.localize("ui.common.add")}
|
||||
</ha-button>
|
||||
|
||||
@@ -120,7 +120,7 @@ export default class HaAutomationAction extends AutomationSortableListMixin<Acti
|
||||
.disabled=${this.disabled}
|
||||
@click=${this._addActionDialog}
|
||||
.appearance=${this.root ? "accent" : "filled"}
|
||||
.size=${this.root ? "medium" : "small"}
|
||||
.size=${this.root ? "m" : "s"}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiPlus} slot="start"></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
|
||||
@@ -728,7 +728,7 @@ class DialogAddAutomationElement
|
||||
active-variant="brand"
|
||||
.buttons=${tabButtons}
|
||||
.active=${this._tab}
|
||||
size="small"
|
||||
size="s"
|
||||
full-width
|
||||
@value-changed=${this._switchTab}
|
||||
></ha-button-toggle-group>`
|
||||
|
||||
@@ -35,7 +35,7 @@ export class HaBlueprintAutomationEditor extends HaBlueprintGenericEditor {
|
||||
)}
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
size="small"
|
||||
size="s"
|
||||
slot="action"
|
||||
@click=${this._enable}
|
||||
>
|
||||
@@ -57,7 +57,7 @@ export class HaBlueprintAutomationEditor extends HaBlueprintGenericEditor {
|
||||
|
||||
<ha-button
|
||||
slot="fab"
|
||||
size="large"
|
||||
size="l"
|
||||
class=${this.dirty ? "dirty" : ""}
|
||||
.disabled=${this.saving}
|
||||
@click=${this._saveAutomation}
|
||||
|
||||
@@ -52,6 +52,7 @@ import type { HaDropdownSelectEvent } from "../../../../components/ha-dropdown";
|
||||
import "../../../../components/ha-dropdown-item";
|
||||
import "../../../../components/ha-expansion-panel";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-tooltip";
|
||||
import type {
|
||||
AutomationClipboard,
|
||||
Condition,
|
||||
@@ -211,11 +212,27 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
);
|
||||
|
||||
return html`
|
||||
<ha-condition-icon
|
||||
slot="leading-icon"
|
||||
.hass=${this.hass}
|
||||
.condition=${this.condition.condition}
|
||||
></ha-condition-icon>
|
||||
<div id="condition-icon" class="icon-badge-wrapper" slot="leading-icon">
|
||||
<ha-condition-icon
|
||||
.hass=${this.hass}
|
||||
.condition=${this.condition.condition}
|
||||
></ha-condition-icon>
|
||||
${this.optionsInSidebar && this.condition.condition !== "trigger"
|
||||
? html`<ha-automation-row-live-test
|
||||
.state=${this._liveTestResult.state}
|
||||
.label=${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.conditions.live_test_state.${this._liveTestResult.state}`
|
||||
)}
|
||||
></ha-automation-row-live-test>`
|
||||
: nothing}
|
||||
</div>
|
||||
${this.optionsInSidebar &&
|
||||
this.condition.condition !== "trigger" &&
|
||||
this._liveTestResult.message
|
||||
? html`<ha-tooltip for="condition-icon" slot="leading-icon"
|
||||
>${this._liveTestResult.message}</ha-tooltip
|
||||
>`
|
||||
: nothing}
|
||||
<h3 slot="header">
|
||||
${capitalizeFirstLetter(
|
||||
describeCondition(this.condition, this.hass, this._entityReg)
|
||||
@@ -531,17 +548,7 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
@click=${this._toggleSidebar}
|
||||
@toggle-collapsed=${this._toggleCollapse}
|
||||
>${this._renderRow()}
|
||||
<ha-automation-row-live-test
|
||||
slot="icons"
|
||||
.state=${this.condition.condition !== "trigger"
|
||||
? this._liveTestResult.state
|
||||
: "unknown"}
|
||||
.label=${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.conditions.live_test_state.${this.condition.condition !== "trigger" ? this._liveTestResult.state : "unknown"}`
|
||||
)}
|
||||
.message=${this._liveTestResult.message}
|
||||
></ha-automation-row-live-test
|
||||
></ha-automation-row>`
|
||||
</ha-automation-row>`
|
||||
: html`
|
||||
<ha-expansion-panel
|
||||
left-chevron
|
||||
|
||||
@@ -294,7 +294,7 @@ export default class HaAutomationCondition extends AutomationSortableListMixin<C
|
||||
.disabled=${this.disabled}
|
||||
@click=${this._addConditionDialog}
|
||||
.appearance=${this.root ? "accent" : "filled"}
|
||||
.size=${this.root ? "medium" : "small"}
|
||||
.size=${this.root ? "m" : "s"}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiPlus} slot="start"></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
|
||||
@@ -237,7 +237,7 @@ export class HaAutomationEditor extends AutomationScriptEditorMixin<AutomationCo
|
||||
? html`
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
size="small"
|
||||
size="s"
|
||||
@click=${this._showTrace}
|
||||
slot="toolbar-icon"
|
||||
>
|
||||
@@ -490,7 +490,7 @@ export class HaAutomationEditor extends AutomationScriptEditorMixin<AutomationCo
|
||||
)}
|
||||
<ha-button
|
||||
appearance="filled"
|
||||
size="small"
|
||||
size="s"
|
||||
variant="warning"
|
||||
slot="action"
|
||||
@click=${this._duplicate}
|
||||
@@ -508,7 +508,7 @@ export class HaAutomationEditor extends AutomationScriptEditorMixin<AutomationCo
|
||||
"ui.panel.config.automation.editor.disabled"
|
||||
)}
|
||||
<ha-button
|
||||
size="small"
|
||||
size="s"
|
||||
slot="action"
|
||||
@click=${this._toggle}
|
||||
>
|
||||
@@ -533,7 +533,7 @@ export class HaAutomationEditor extends AutomationScriptEditorMixin<AutomationCo
|
||||
)}
|
||||
<ha-button
|
||||
appearance="filled"
|
||||
size="small"
|
||||
size="s"
|
||||
slot="action"
|
||||
@click=${this._toggle}
|
||||
>
|
||||
@@ -553,7 +553,7 @@ export class HaAutomationEditor extends AutomationScriptEditorMixin<AutomationCo
|
||||
></ha-yaml-editor>
|
||||
<ha-button
|
||||
slot="fab"
|
||||
size="large"
|
||||
size="l"
|
||||
class=${this.dirty ? "dirty" : ""}
|
||||
.disabled=${this.saving}
|
||||
@click=${this._handleSaveAutomation}
|
||||
|
||||
@@ -23,11 +23,7 @@ export class HaAutomationNote extends LitElement {
|
||||
"ui.panel.config.automation.editor.note.label"
|
||||
)}
|
||||
</span>
|
||||
<ha-button
|
||||
@click=${this._handleClick}
|
||||
size="small"
|
||||
appearance="plain"
|
||||
>
|
||||
<ha-button @click=${this._handleClick} size="s" appearance="plain">
|
||||
${this._i18n.localize("ui.common.edit")}
|
||||
</ha-button>
|
||||
</div>
|
||||
|
||||
@@ -695,14 +695,14 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
|
||||
target="_blank"
|
||||
appearance="plain"
|
||||
rel="noreferrer"
|
||||
size="small"
|
||||
size="s"
|
||||
>
|
||||
${this.hass.localize("ui.panel.config.common.learn_more")}
|
||||
<ha-svg-icon slot="end" .path=${mdiOpenInNew}> </ha-svg-icon>
|
||||
</ha-button>
|
||||
</div>`
|
||||
: nothing}
|
||||
<ha-button slot="fab" size="large" @click=${this._createNew}>
|
||||
<ha-button slot="fab" size="l" @click=${this._createNew}>
|
||||
<ha-svg-icon slot="start" .path=${mdiPlus}></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.picker.add_automation"
|
||||
|
||||
@@ -110,7 +110,7 @@ export class HaAutomationTrace extends LitElement {
|
||||
? html`
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
size="small"
|
||||
size="s"
|
||||
class="trace-link"
|
||||
@click=${this._navigateToAutomation}
|
||||
slot="toolbar-icon"
|
||||
|
||||
@@ -114,7 +114,7 @@ export const ManualEditorMixin = <TConfig>(
|
||||
<div class="fab-positioner">
|
||||
<ha-button
|
||||
slot="fab"
|
||||
size="large"
|
||||
size="l"
|
||||
class=${this.dirty ? "dirty" : ""}
|
||||
.disabled=${this.saving}
|
||||
@click=${this.saveConfig}
|
||||
|
||||
@@ -92,7 +92,7 @@ export default class HaAutomationOption extends AutomationSortableListMixin<Opti
|
||||
<div class="buttons">
|
||||
<ha-button
|
||||
appearance="filled"
|
||||
size="small"
|
||||
size="s"
|
||||
.disabled=${this.disabled}
|
||||
@click=${this._addOption}
|
||||
>
|
||||
@@ -104,7 +104,7 @@ export default class HaAutomationOption extends AutomationSortableListMixin<Opti
|
||||
${!this.showDefaultActions
|
||||
? html`<ha-button
|
||||
appearance="plain"
|
||||
size="small"
|
||||
size="s"
|
||||
.disabled=${this.disabled}
|
||||
@click=${this._showDefaultActions}
|
||||
>
|
||||
|
||||
@@ -53,6 +53,11 @@ export const rowStyles = css`
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.icon-badge-wrapper {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.note-indicator {
|
||||
color: var(--ha-color-on-neutral-normal);
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ export default class HaAutomationTrigger extends AutomationSortableListMixin<Tri
|
||||
.disabled=${this.disabled}
|
||||
@click=${this._addTriggerDialog}
|
||||
.appearance=${this.root ? "accent" : "filled"}
|
||||
.size=${this.root ? "medium" : "small"}
|
||||
.size=${this.root ? "m" : "s"}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.add"
|
||||
|
||||
@@ -40,7 +40,7 @@ class HaBackupConfigEncryptionKey extends LitElement {
|
||||
appearance="plain"
|
||||
slot="end"
|
||||
@click=${this._download}
|
||||
size="small"
|
||||
size="s"
|
||||
>
|
||||
<ha-svg-icon .path=${mdiDownload} slot="start"></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
@@ -63,7 +63,7 @@ class HaBackupConfigEncryptionKey extends LitElement {
|
||||
appearance="plain"
|
||||
slot="end"
|
||||
@click=${this._show}
|
||||
size="small"
|
||||
size="s"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.encryption_key.show_encryption_key_action"
|
||||
@@ -84,7 +84,7 @@ class HaBackupConfigEncryptionKey extends LitElement {
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
variant="danger"
|
||||
size="small"
|
||||
size="s"
|
||||
slot="end"
|
||||
@click=${this._change}
|
||||
>
|
||||
@@ -156,7 +156,7 @@ class HaBackupConfigEncryptionKey extends LitElement {
|
||||
ha-list-item-base::part(supporting-text) {
|
||||
white-space: wrap;
|
||||
}
|
||||
ha-button[size="small"] ha-svg-icon {
|
||||
ha-button[size="s"] ha-svg-icon {
|
||||
--mdc-icon-size: 16px;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -379,7 +379,7 @@ class DialogBackupOnboarding extends LitElement implements HassDialog {
|
||||
)}
|
||||
</span>
|
||||
<ha-button
|
||||
size="small"
|
||||
size="s"
|
||||
appearance="plain"
|
||||
slot="end"
|
||||
@click=${this._downloadKey}
|
||||
|
||||
@@ -146,7 +146,7 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog {
|
||||
)}
|
||||
</span>
|
||||
<ha-button
|
||||
size="small"
|
||||
size="s"
|
||||
appearance="plain"
|
||||
slot="end"
|
||||
@click=${this._download}
|
||||
|
||||
@@ -533,7 +533,7 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
? html`
|
||||
<ha-button
|
||||
slot="fab"
|
||||
size="large"
|
||||
size="l"
|
||||
?disabled=${backupInProgress}
|
||||
@click=${this._newBackup}
|
||||
>
|
||||
|
||||
@@ -161,7 +161,7 @@ class HaConfigBackupDetails extends LitElement {
|
||||
slot="end"
|
||||
rel="noreferrer noopener"
|
||||
appearance="plain"
|
||||
size="small"
|
||||
size="s"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.location.encryption.location_encrypted_cloud_learn_more"
|
||||
|
||||
@@ -323,7 +323,7 @@ class HaConfigBackupOverview extends LitElement {
|
||||
|
||||
<ha-button
|
||||
slot="fab"
|
||||
size="large"
|
||||
size="l"
|
||||
.loading=${backupInProgress}
|
||||
@click=${this._newBackup}
|
||||
>
|
||||
|
||||
@@ -289,7 +289,7 @@ class HaConfigBackupSettings extends LitElement {
|
||||
: nothing}
|
||||
<div class="card-actions">
|
||||
<ha-button
|
||||
size="small"
|
||||
size="s"
|
||||
href=${documentationUrl(this.hass, "/integrations/#backup")}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
@@ -302,7 +302,7 @@ class HaConfigBackupSettings extends LitElement {
|
||||
</ha-button>
|
||||
${supervisor
|
||||
? html`<ha-button
|
||||
size="small"
|
||||
size="s"
|
||||
appearance="plain"
|
||||
href="/config/storage"
|
||||
>
|
||||
@@ -478,7 +478,7 @@ class HaConfigBackupSettings extends LitElement {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
ha-button[size="small"] ha-svg-icon {
|
||||
ha-button[size="s"] ha-svg-icon {
|
||||
--mdc-icon-size: 16px;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -174,7 +174,7 @@ class DialogImportBlueprint extends LitElement {
|
||||
)}
|
||||
</p>
|
||||
<ha-button
|
||||
size="small"
|
||||
size="s"
|
||||
appearance="plain"
|
||||
href=${documentationUrl(this.hass, "/get-blueprints")}
|
||||
target="_blank"
|
||||
|
||||
@@ -349,7 +349,7 @@ class HaBlueprintOverview extends LitElement {
|
||||
href=${documentationUrl(this.hass, "/get-blueprints")}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
size="small"
|
||||
size="s"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.blueprint.overview.discover_more"
|
||||
@@ -375,7 +375,7 @@ class HaBlueprintOverview extends LitElement {
|
||||
.path=${mdiHelpCircleOutline}
|
||||
@click=${this._showHelp}
|
||||
></ha-icon-button>
|
||||
<ha-button slot="fab" size="large" @click=${this._addBlueprintClicked}>
|
||||
<ha-button slot="fab" size="l" @click=${this._addBlueprintClicked}>
|
||||
<ha-svg-icon slot="start" .path=${mdiDownload}></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.blueprint.overview.add_blueprint"
|
||||
|
||||
@@ -15,19 +15,13 @@ import type {
|
||||
} from "../../../data/category_registry";
|
||||
import { internationalizationContext } from "../../../data/context";
|
||||
import { DialogMixin } from "../../../dialogs/dialog-mixin";
|
||||
import { DirtyStateProviderMixin } from "../../../mixins/dirty-state-provider-mixin";
|
||||
import { haStyleDialog } from "../../../resources/styles";
|
||||
import type { ValueChangedEvent } from "../../../types";
|
||||
import type { CategoryRegistryDetailDialogParams } from "./show-dialog-category-registry-detail";
|
||||
|
||||
interface CategoryFormState {
|
||||
name: string;
|
||||
icon: string | null;
|
||||
}
|
||||
|
||||
@customElement("dialog-category-registry-detail")
|
||||
class DialogCategoryDetail extends DirtyStateProviderMixin<CategoryFormState>()(
|
||||
DialogMixin<CategoryRegistryDetailDialogParams>(LitElement)
|
||||
class DialogCategoryDetail extends DialogMixin<CategoryRegistryDetailDialogParams>(
|
||||
LitElement
|
||||
) {
|
||||
@state()
|
||||
@consume({ context: internationalizationContext, subscribe: true })
|
||||
@@ -50,10 +44,6 @@ class DialogCategoryDetail extends DirtyStateProviderMixin<CategoryFormState>()(
|
||||
this._name = this.params?.suggestedName || "";
|
||||
this._icon = null;
|
||||
}
|
||||
this._initDirtyTracking(
|
||||
{ type: "shallow" },
|
||||
{ name: this._name, icon: this._icon }
|
||||
);
|
||||
}
|
||||
|
||||
protected render() {
|
||||
@@ -62,14 +52,13 @@ class DialogCategoryDetail extends DirtyStateProviderMixin<CategoryFormState>()(
|
||||
}
|
||||
const entry = this.params.entry;
|
||||
const nameInvalid = !this._isNameValid();
|
||||
const isCreate = !entry;
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
header-title=${entry
|
||||
? this._i18n.localize("ui.panel.config.category.editor.edit")
|
||||
: this._i18n.localize("ui.panel.config.category.editor.create")}
|
||||
.preventScrimClose=${this.isDirtyState}
|
||||
prevent-scrim-close
|
||||
>
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
@@ -107,9 +96,7 @@ class DialogCategoryDetail extends DirtyStateProviderMixin<CategoryFormState>()(
|
||||
<ha-button
|
||||
slot="primaryAction"
|
||||
@click=${this._updateEntry}
|
||||
.disabled=${nameInvalid ||
|
||||
!!this._submitting ||
|
||||
(!isCreate && !this.isDirtyState)}
|
||||
.disabled=${nameInvalid || !!this._submitting}
|
||||
>
|
||||
${entry
|
||||
? this._i18n.localize("ui.common.save")
|
||||
@@ -127,17 +114,15 @@ class DialogCategoryDetail extends DirtyStateProviderMixin<CategoryFormState>()(
|
||||
private _nameChanged(ev: InputEvent) {
|
||||
this._error = undefined;
|
||||
this._name = (ev.target as HaInput).value ?? "";
|
||||
this._updateDirtyState({ name: this._name, icon: this._icon });
|
||||
}
|
||||
|
||||
private _iconChanged(ev: ValueChangedEvent<string>) {
|
||||
this._error = undefined;
|
||||
this._icon = ev.detail.value;
|
||||
this._updateDirtyState({ name: this._name, icon: this._icon });
|
||||
}
|
||||
|
||||
private async _updateEntry() {
|
||||
const create = !this.params?.entry;
|
||||
const create = !this.params!.entry;
|
||||
this._submitting = true;
|
||||
let newValue: CategoryRegistryEntry | undefined;
|
||||
try {
|
||||
@@ -146,11 +131,10 @@ class DialogCategoryDetail extends DirtyStateProviderMixin<CategoryFormState>()(
|
||||
icon: this._icon || (create ? undefined : null),
|
||||
};
|
||||
if (create) {
|
||||
newValue = await this.params?.createEntry?.(values);
|
||||
newValue = await this.params!.createEntry!(values);
|
||||
} else {
|
||||
newValue = await this.params?.updateEntry?.(values);
|
||||
newValue = await this.params!.updateEntry!(values);
|
||||
}
|
||||
this._markDirtyStateClean();
|
||||
this.closeDialog();
|
||||
} catch (err: any) {
|
||||
this._error =
|
||||
|
||||
@@ -187,7 +187,7 @@ export class CloudRemotePref extends LitElement {
|
||||
<ha-button
|
||||
slot="end"
|
||||
appearance="plain"
|
||||
size="small"
|
||||
size="s"
|
||||
@click=${this._openCertInfo}
|
||||
>
|
||||
${this.hass.localize(
|
||||
|
||||
@@ -95,7 +95,7 @@ export class CloudWebhooks extends LitElement {
|
||||
<ha-button
|
||||
slot="end"
|
||||
appearance="plain"
|
||||
size="small"
|
||||
size="s"
|
||||
@click=${this._handleManageButton}
|
||||
>
|
||||
${this.hass!.localize(
|
||||
|
||||
@@ -123,7 +123,7 @@ class ConfigAnalytics extends SubscribeMixin(LitElement) {
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-button
|
||||
size="small"
|
||||
size="s"
|
||||
appearance="plain"
|
||||
@click=${this._downloadDeviceInfo}
|
||||
>
|
||||
|
||||
@@ -217,7 +217,7 @@ class HaConfigSectionUpdates extends LitElement {
|
||||
? html`
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
size="small"
|
||||
size="s"
|
||||
.group=${group}
|
||||
.disabled=${group.entities.every((entity) =>
|
||||
updateIsInstalling(entity)
|
||||
|
||||
@@ -167,7 +167,7 @@ class HaPanelDevAction extends MatchMinHeightMixin(LitElement) {
|
||||
)}
|
||||
</div>
|
||||
<ha-button-toggle-group
|
||||
size="small"
|
||||
size="s"
|
||||
class="yaml-mode-toggle"
|
||||
.buttons=${modeButtons}
|
||||
.active=${this._yamlMode ? "yaml" : "ui"}
|
||||
|
||||
@@ -36,7 +36,6 @@ class PanelDeveloperTools extends LitElement {
|
||||
<div class="toolbar">
|
||||
<ha-icon-button-arrow-prev
|
||||
slot="navigationIcon"
|
||||
.hass=${this.hass}
|
||||
@click=${this._handleBack}
|
||||
></ha-icon-button-arrow-prev>
|
||||
<div class="main-title">
|
||||
|
||||
@@ -274,7 +274,7 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
|
||||
@click=${this._fixIssue}
|
||||
.data=${statistic.issues}
|
||||
appearance="plain"
|
||||
size="small"
|
||||
size="s"
|
||||
>
|
||||
${localize(
|
||||
statistic.issues.some((issue) =>
|
||||
|
||||
@@ -519,7 +519,7 @@ export class HaConfigDevicePage extends LitElement {
|
||||
<div class="card-actions" slot="actions">
|
||||
<ha-button
|
||||
variant="warning"
|
||||
size="small"
|
||||
size="s"
|
||||
@click=${this._enableDevice}
|
||||
>
|
||||
${this.hass.localize("ui.common.enable")}
|
||||
|
||||
@@ -811,7 +811,7 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
.hass=${this.hass}
|
||||
slot="toolbar-icon"
|
||||
></ha-integration-overflow-menu>
|
||||
<ha-button slot="fab" size="large" @click=${this._addDevice}>
|
||||
<ha-button slot="fab" size="l" @click=${this._addDevice}>
|
||||
<ha-svg-icon slot="start" .path=${mdiPlus}></ha-svg-icon>
|
||||
${this.hass.localize("ui.panel.config.devices.add_device")}
|
||||
</ha-button>
|
||||
|
||||
@@ -140,11 +140,7 @@ export class EnergyBatterySettings extends LitElement {
|
||||
`
|
||||
: ""}
|
||||
<div class="row">
|
||||
<ha-button
|
||||
@click=${this._addSource}
|
||||
appearance="filled"
|
||||
size="small"
|
||||
>
|
||||
<ha-button @click=${this._addSource} appearance="filled" size="s">
|
||||
<ha-svg-icon slot="start" .path=${mdiPlus}></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.energy.battery.add_battery_system"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user