mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-11 10:19:25 +00:00
Compare commits
69 Commits
20250806.0
...
dev
Author | SHA1 | Date | |
---|---|---|---|
![]() |
bc52ab410c | ||
![]() |
3b0220fa92 | ||
![]() |
a60c9f788d | ||
![]() |
d9c297c06a | ||
![]() |
3789bebb2b | ||
![]() |
bbecf5f368 | ||
![]() |
e580b30219 | ||
![]() |
ed8c8ad3e3 | ||
![]() |
4f61d5689b | ||
![]() |
60a18185d7 | ||
![]() |
e0246b8488 | ||
![]() |
1cd0fae84a | ||
![]() |
e8a1ebbff4 | ||
![]() |
c5010b8502 | ||
![]() |
a7db401b62 | ||
![]() |
49c7dad6eb | ||
![]() |
521c3d40b7 | ||
![]() |
709a1d2ef0 | ||
![]() |
3c5d7b97d1 | ||
![]() |
9165c8bc57 | ||
![]() |
0b3e4eab23 | ||
![]() |
39d14c943c | ||
![]() |
09469be93f | ||
![]() |
6e215870ef | ||
![]() |
d5985dcaaf | ||
![]() |
bbd9d8887d | ||
![]() |
9588987e30 | ||
![]() |
52c05a4426 | ||
![]() |
e8224df4e5 | ||
![]() |
83a6df1621 | ||
![]() |
c46ebc8d3e | ||
![]() |
fca530411f | ||
![]() |
c2c64b9923 | ||
![]() |
9968c27a8e | ||
![]() |
96796ac5da | ||
![]() |
37def6d3e4 | ||
![]() |
013d603ba0 | ||
![]() |
b76407d28d | ||
![]() |
4e969ccf09 | ||
![]() |
cdfd6431c3 | ||
![]() |
c363995718 | ||
![]() |
53497aa632 | ||
![]() |
8d89b0e57f | ||
![]() |
92cf8b5579 | ||
![]() |
6068c32176 | ||
![]() |
38893324af | ||
![]() |
a39ab3c174 | ||
![]() |
797d2be5bf | ||
![]() |
99a91e1019 | ||
![]() |
5de8d07ce0 | ||
![]() |
3a31a4a721 | ||
![]() |
05f4419a92 | ||
![]() |
5ea8feb86b | ||
![]() |
8fd70b3ae6 | ||
![]() |
343aa40bc8 | ||
![]() |
6022f9a77e | ||
![]() |
bd9de0680e | ||
![]() |
b8000d5bc1 | ||
![]() |
c6efa1127f | ||
![]() |
688a3d91d3 | ||
![]() |
68151a2a70 | ||
![]() |
c2ca556151 | ||
![]() |
df86b27af4 | ||
![]() |
eba1f401cc | ||
![]() |
19c2f9c9e8 | ||
![]() |
4250447d14 | ||
![]() |
4666197f28 | ||
![]() |
a5ca36c93f | ||
![]() |
a88950e16c |
2
.github/workflows/release.yaml
vendored
2
.github/workflows/release.yaml
vendored
@@ -74,7 +74,7 @@ jobs:
|
||||
echo "home-assistant-frontend==$version" > ./requirements.txt
|
||||
|
||||
- name: Build wheels
|
||||
uses: home-assistant/wheels@2025.03.0
|
||||
uses: home-assistant/wheels@2025.07.0
|
||||
with:
|
||||
abi: cp313
|
||||
tag: musllinux_1_2
|
||||
|
8
CODEOWNERS
Normal file
8
CODEOWNERS
Normal 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
|
@@ -64,4 +64,4 @@ Check the [webawesome documentation](https://webawesome.com/docs/components/butt
|
||||
**CSS Custom Properties**
|
||||
|
||||
- `--ha-button-height` - Height of the button.
|
||||
- `--ha-button-border-radius` - Border radius of the button. Defaults to `var(--border-radius-pill)`.
|
||||
- `--ha-button-border-radius` - Border radius of the button. Defaults to `var(--ha-border-radius-pill)`.
|
||||
|
@@ -2,13 +2,13 @@ import { mdiDelete, mdiPlus } from "@mdi/js";
|
||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../../../src/components/ha-button";
|
||||
import { createCloseHeading } from "../../../../src/components/ha-dialog";
|
||||
import "../../../../src/components/ha-form/ha-form";
|
||||
import type { SchemaUnion } from "../../../../src/components/ha-form/types";
|
||||
import "../../../../src/components/ha-icon-button";
|
||||
import "../../../../src/components/ha-button";
|
||||
import "../../../../src/components/ha-svg-icon";
|
||||
import "../../../../src/components/ha-settings-row";
|
||||
import "../../../../src/components/ha-svg-icon";
|
||||
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
||||
import {
|
||||
addHassioDockerRegistry,
|
||||
|
@@ -7,10 +7,14 @@ import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import { caseInsensitiveStringCompare } from "../../../../src/common/string/compare";
|
||||
import "../../../../src/components/ha-alert";
|
||||
import "../../../../src/components/ha-button";
|
||||
import "../../../../src/components/ha-tooltip";
|
||||
import "../../../../src/components/ha-svg-icon";
|
||||
import { createCloseHeading } from "../../../../src/components/ha-dialog";
|
||||
import "../../../../src/components/ha-icon-button";
|
||||
import "../../../../src/components/ha-md-list";
|
||||
import "../../../../src/components/ha-md-list-item";
|
||||
import "../../../../src/components/ha-svg-icon";
|
||||
import "../../../../src/components/ha-textfield";
|
||||
import type { HaTextField } from "../../../../src/components/ha-textfield";
|
||||
import "../../../../src/components/ha-tooltip";
|
||||
import type {
|
||||
HassioAddonInfo,
|
||||
HassioAddonRepository,
|
||||
@@ -24,10 +28,6 @@ import {
|
||||
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
|
||||
import type { HomeAssistant } from "../../../../src/types";
|
||||
import type { HassioRepositoryDialogParams } from "./show-dialog-repositories";
|
||||
import type { HaTextField } from "../../../../src/components/ha-textfield";
|
||||
import "../../../../src/components/ha-textfield";
|
||||
import "../../../../src/components/ha-md-list";
|
||||
import "../../../../src/components/ha-md-list-item";
|
||||
|
||||
@customElement("dialog-hassio-repositories")
|
||||
class HassioRepositoriesDialog extends LitElement {
|
||||
|
28
package.json
28
package.json
@@ -26,7 +26,7 @@
|
||||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@awesome.me/webawesome": "3.0.0-beta.3",
|
||||
"@awesome.me/webawesome": "3.0.0-beta.4",
|
||||
"@babel/runtime": "7.28.2",
|
||||
"@braintree/sanitize-url": "7.1.1",
|
||||
"@codemirror/autocomplete": "6.18.6",
|
||||
@@ -88,7 +88,7 @@
|
||||
"@shoelace-style/shoelace": "2.20.1",
|
||||
"@swc/helpers": "0.5.17",
|
||||
"@thomasloven/round-slider": "0.6.0",
|
||||
"@tsparticles/engine": "3.8.1",
|
||||
"@tsparticles/engine": "3.9.1",
|
||||
"@tsparticles/preset-links": "3.2.0",
|
||||
"@vaadin/combo-box": "24.7.9",
|
||||
"@vaadin/vaadin-themable-mixin": "24.7.9",
|
||||
@@ -100,7 +100,7 @@
|
||||
"barcode-detector": "3.0.5",
|
||||
"color-name": "2.0.0",
|
||||
"comlink": "4.4.2",
|
||||
"core-js": "3.44.0",
|
||||
"core-js": "3.45.0",
|
||||
"cropperjs": "1.6.2",
|
||||
"culori": "4.0.2",
|
||||
"date-fns": "4.1.0",
|
||||
@@ -108,12 +108,12 @@
|
||||
"deep-clone-simple": "1.1.1",
|
||||
"deep-freeze": "0.0.1",
|
||||
"dialog-polyfill": "0.5.6",
|
||||
"echarts": "5.6.0",
|
||||
"echarts": "6.0.0",
|
||||
"element-internals-polyfill": "3.0.2",
|
||||
"fuse.js": "7.1.0",
|
||||
"google-timezones-json": "1.2.0",
|
||||
"gulp-zopfli-green": "6.0.2",
|
||||
"hls.js": "1.6.7",
|
||||
"hls.js": "1.6.9",
|
||||
"home-assistant-js-websocket": "9.5.0",
|
||||
"idb-keyval": "6.2.2",
|
||||
"intl-messageformat": "10.7.16",
|
||||
@@ -124,7 +124,7 @@
|
||||
"lit": "3.3.1",
|
||||
"lit-html": "3.3.1",
|
||||
"luxon": "3.7.1",
|
||||
"marked": "16.1.1",
|
||||
"marked": "16.1.2",
|
||||
"memoize-one": "6.0.0",
|
||||
"node-vibrant": "4.0.3",
|
||||
"object-hash": "3.0.0",
|
||||
@@ -154,14 +154,14 @@
|
||||
"@babel/helper-define-polyfill-provider": "0.6.5",
|
||||
"@babel/plugin-transform-runtime": "7.28.0",
|
||||
"@babel/preset-env": "7.28.0",
|
||||
"@bundle-stats/plugin-webpack-filter": "4.21.1",
|
||||
"@bundle-stats/plugin-webpack-filter": "4.21.2",
|
||||
"@lokalise/node-api": "15.0.0",
|
||||
"@octokit/auth-oauth-device": "8.0.1",
|
||||
"@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",
|
||||
"@rsdoctor/rspack-plugin": "1.2.0",
|
||||
"@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",
|
||||
@@ -195,7 +195,7 @@
|
||||
"eslint-plugin-unused-imports": "4.1.4",
|
||||
"eslint-plugin-wc": "3.0.1",
|
||||
"fancy-log": "2.0.0",
|
||||
"fs-extra": "11.3.0",
|
||||
"fs-extra": "11.3.1",
|
||||
"glob": "11.0.3",
|
||||
"gulp": "5.0.1",
|
||||
"gulp-brotli": "3.0.0",
|
||||
@@ -205,7 +205,7 @@
|
||||
"husky": "9.1.7",
|
||||
"jsdom": "26.1.0",
|
||||
"jszip": "3.10.1",
|
||||
"lint-staged": "16.1.2",
|
||||
"lint-staged": "16.1.4",
|
||||
"lit-analyzer": "2.0.3",
|
||||
"lodash.merge": "4.6.2",
|
||||
"lodash.template": "4.5.0",
|
||||
@@ -219,7 +219,7 @@
|
||||
"terser-webpack-plugin": "5.3.14",
|
||||
"ts-lit-plugin": "2.0.2",
|
||||
"typescript": "5.8.3",
|
||||
"typescript-eslint": "8.38.0",
|
||||
"typescript-eslint": "8.39.0",
|
||||
"vite-tsconfig-paths": "5.1.4",
|
||||
"vitest": "3.2.4",
|
||||
"webpack-stats-plugin": "1.1.3",
|
||||
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "home-assistant-frontend"
|
||||
version = "20250806.0"
|
||||
version = "20250730.0"
|
||||
license = "Apache-2.0"
|
||||
license-files = ["LICENSE*"]
|
||||
description = "The Home Assistant frontend"
|
||||
|
@@ -132,15 +132,13 @@ export const shiftDateRange = (
|
||||
end = calcDate(endDate, addDays, locale, config, difference);
|
||||
} else {
|
||||
const difference =
|
||||
((calcDateDifferenceProperty(
|
||||
(calcDateDifferenceProperty(
|
||||
endDate,
|
||||
startDate,
|
||||
differenceInMilliseconds,
|
||||
locale,
|
||||
config
|
||||
) as number) +
|
||||
1) *
|
||||
(forward ? 1 : -1);
|
||||
) as number) * (forward ? 1 : -1);
|
||||
start = calcDate(startDate, addMilliseconds, locale, config, difference);
|
||||
end = calcDate(endDate, addMilliseconds, locale, config, difference);
|
||||
}
|
||||
|
@@ -387,24 +387,25 @@ export class HaChartBase extends LitElement {
|
||||
lastTipX = e.x;
|
||||
lastTipY = e.y;
|
||||
this.chart?.setOption({
|
||||
xAxis: ensureArray(this.chart?.getOption().xAxis as any).map(
|
||||
(axis: XAXisOption) =>
|
||||
axis.show
|
||||
? {
|
||||
...axis,
|
||||
axisPointer: {
|
||||
...axis.axisPointer,
|
||||
status: "show",
|
||||
handle: {
|
||||
color: style.getPropertyValue("primary-color"),
|
||||
margin: 0,
|
||||
size: 20,
|
||||
...axis.axisPointer?.handle,
|
||||
show: true,
|
||||
},
|
||||
xAxis: ensureArray(
|
||||
(this.chart?.getOption().xAxis as any) ?? []
|
||||
).map((axis: XAXisOption) =>
|
||||
axis.show
|
||||
? {
|
||||
...axis,
|
||||
axisPointer: {
|
||||
...axis.axisPointer,
|
||||
status: "show",
|
||||
handle: {
|
||||
color: style.getPropertyValue("primary-color"),
|
||||
margin: 0,
|
||||
size: 20,
|
||||
...axis.axisPointer?.handle,
|
||||
show: true,
|
||||
},
|
||||
}
|
||||
: axis
|
||||
},
|
||||
}
|
||||
: axis
|
||||
),
|
||||
});
|
||||
});
|
||||
@@ -417,21 +418,22 @@ export class HaChartBase extends LitElement {
|
||||
return;
|
||||
}
|
||||
this.chart?.setOption({
|
||||
xAxis: ensureArray(this.chart?.getOption().xAxis as any).map(
|
||||
(axis: XAXisOption) =>
|
||||
axis.show
|
||||
? {
|
||||
...axis,
|
||||
axisPointer: {
|
||||
...axis.axisPointer,
|
||||
handle: {
|
||||
...axis.axisPointer?.handle,
|
||||
show: false,
|
||||
},
|
||||
status: "hide",
|
||||
xAxis: ensureArray(
|
||||
(this.chart?.getOption().xAxis as any) ?? []
|
||||
).map((axis: XAXisOption) =>
|
||||
axis.show
|
||||
? {
|
||||
...axis,
|
||||
axisPointer: {
|
||||
...axis.axisPointer,
|
||||
handle: {
|
||||
...axis.axisPointer?.handle,
|
||||
show: false,
|
||||
},
|
||||
}
|
||||
: axis
|
||||
status: "hide",
|
||||
},
|
||||
}
|
||||
: axis
|
||||
),
|
||||
});
|
||||
this.chart?.dispatchAction({
|
||||
@@ -598,6 +600,13 @@ export class HaChartBase extends LitElement {
|
||||
textBorderWidth: 2,
|
||||
},
|
||||
},
|
||||
sankey: {
|
||||
label: {
|
||||
color: style.getPropertyValue("--primary-text-color"),
|
||||
textBorderColor: style.getPropertyValue("--primary-background-color"),
|
||||
textBorderWidth: 2,
|
||||
},
|
||||
},
|
||||
categoryAxis: {
|
||||
axisLine: { show: false },
|
||||
axisTick: { show: false },
|
||||
|
@@ -186,23 +186,22 @@ export class HaSankeyChart extends LitElement {
|
||||
""
|
||||
);
|
||||
const wordWidth = measureTextWidth(longestWord, FONT_SIZE);
|
||||
const availableWidth = params.rect.width + 6;
|
||||
const fontSize = Math.min(
|
||||
FONT_SIZE,
|
||||
(params.rect.width / wordWidth) * FONT_SIZE
|
||||
(availableWidth / wordWidth) * FONT_SIZE
|
||||
);
|
||||
return {
|
||||
fontSize: fontSize > 1 ? fontSize : 0,
|
||||
width: params.rect.width,
|
||||
width: availableWidth,
|
||||
align: "center",
|
||||
dy: -2, // shift up or the lowest row labels may be cut off
|
||||
};
|
||||
}
|
||||
|
||||
// estimate the number of lines after the label is wrapped
|
||||
// this is a very rough estimate, but it works for now
|
||||
const lineCount = Math.ceil(params.labelRect.width / labelSpace);
|
||||
// `overflow: "break"` allows the label to overflow outside its height, so we need to account for that
|
||||
const availableHeight = params.rect.height + 8; // account for the margin
|
||||
const fontSize = Math.min(
|
||||
(params.rect.height / lineCount) * FONT_SIZE,
|
||||
(availableHeight / params.labelRect.height) * FONT_SIZE,
|
||||
FONT_SIZE
|
||||
);
|
||||
return {
|
||||
|
149
src/components/entity/ha-entity-states-picker.ts
Normal file
149
src/components/entity/ha-entity-states-picker.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import type { PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { keyed } from "lit/directives/keyed";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { ensureArray } from "../../common/array/ensure-array";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "./ha-entity-state-picker";
|
||||
|
||||
@customElement("ha-entity-states-picker")
|
||||
export class HaEntityStatesPicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public entityId?: string;
|
||||
|
||||
@property() public attribute?: string;
|
||||
|
||||
@property({ attribute: false }) public extraOptions?: any[];
|
||||
|
||||
@property({ type: Boolean, attribute: "allow-custom-value" })
|
||||
public allowCustomValue;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property({ type: Array }) public value?: string[];
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = false;
|
||||
|
||||
@property({ attribute: false })
|
||||
public hideStates?: string[];
|
||||
|
||||
private _keys: string[] = [];
|
||||
|
||||
private _getKey(index: number) {
|
||||
if (!this._keys[index]) {
|
||||
this._keys[index] = Math.random().toString();
|
||||
}
|
||||
return this._keys[index];
|
||||
}
|
||||
|
||||
protected willUpdate(changedProps: PropertyValues): void {
|
||||
super.willUpdate(changedProps);
|
||||
if (changedProps.has("value")) {
|
||||
this.value = ensureArray(this.value);
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this.hass) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const value = this.value || [];
|
||||
const hide = [...(this.hideStates || []), ...value];
|
||||
|
||||
return html`
|
||||
${repeat(
|
||||
value,
|
||||
(_state, index) => this._getKey(index),
|
||||
(state, index) => html`
|
||||
<div>
|
||||
<ha-entity-state-picker
|
||||
.index=${index}
|
||||
.hass=${this.hass}
|
||||
.entityId=${this.entityId}
|
||||
.attribute=${this.attribute}
|
||||
.extraOptions=${this.extraOptions}
|
||||
.hideStates=${hide.filter((v) => v !== state)}
|
||||
.allowCustomValue=${this.allowCustomValue}
|
||||
.label=${this.label}
|
||||
.value=${state}
|
||||
.disabled=${this.disabled}
|
||||
.helper=${this.disabled && index === value.length - 1
|
||||
? this.helper
|
||||
: undefined}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-entity-state-picker>
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
<div>
|
||||
${this.disabled && value.length
|
||||
? nothing
|
||||
: keyed(
|
||||
value.length,
|
||||
html`<ha-entity-state-picker
|
||||
.hass=${this.hass}
|
||||
.entityId=${this.entityId}
|
||||
.attribute=${this.attribute}
|
||||
.extraOptions=${this.extraOptions}
|
||||
.hideStates=${hide}
|
||||
.allowCustomValue=${this.allowCustomValue}
|
||||
.label=${this.label}
|
||||
.helper=${this.helper}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required && !value.length}
|
||||
@value-changed=${this._addValue}
|
||||
></ha-entity-state-picker>`
|
||||
)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
const newState = ev.detail.value;
|
||||
const newValue = [...this.value!];
|
||||
const index = (ev.currentTarget as any)?.index;
|
||||
if (index == null) {
|
||||
return;
|
||||
}
|
||||
if (newState === undefined) {
|
||||
newValue.splice(index, 1);
|
||||
this._keys.splice(index, 1);
|
||||
fireEvent(this, "value-changed", {
|
||||
value: newValue,
|
||||
});
|
||||
return;
|
||||
}
|
||||
newValue[index] = newState;
|
||||
fireEvent(this, "value-changed", {
|
||||
value: newValue,
|
||||
});
|
||||
}
|
||||
|
||||
private _addValue(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
fireEvent(this, "value-changed", {
|
||||
value: [...(this.value || []), ev.detail.value],
|
||||
});
|
||||
}
|
||||
|
||||
static override styles = css`
|
||||
div {
|
||||
margin-top: 8px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-entity-states-picker": HaEntityStatesPicker;
|
||||
}
|
||||
}
|
@@ -27,7 +27,7 @@ export type Appearance = "accent" | "filled" | "outlined" | "plain";
|
||||
* @csspart spinner - The spinner that shows when the button is in the loading state.
|
||||
*
|
||||
* @cssprop --ha-button-height - The height of the button.
|
||||
* @cssprop --ha-button-border-radius - The border radius of the button. defaults to `var(--border-radius-pill)`.
|
||||
* @cssprop --ha-button-border-radius - The border radius of the button. defaults to `var(--ha-border-radius-pill)`.
|
||||
*
|
||||
* @attr {("small"|"medium")} size - Sets the button size.
|
||||
* @attr {("brand"|"neutral"|"danger"|"warning"|"success")} variant - Sets the button color variant. "primary" is default.
|
||||
@@ -57,7 +57,7 @@ export class HaButton extends Button {
|
||||
--wa-font-weight-action: var(--ha-font-weight-medium);
|
||||
--wa-form-control-border-radius: var(
|
||||
--ha-button-border-radius,
|
||||
var(--border-radius-pill)
|
||||
var(--ha-border-radius-pill)
|
||||
);
|
||||
|
||||
--wa-form-control-height: var(
|
||||
@@ -154,6 +154,7 @@ export class HaButton extends Button {
|
||||
|
||||
:host([appearance~="plain"]) .button {
|
||||
color: var(--wa-color-on-normal);
|
||||
background-color: transparent;
|
||||
}
|
||||
:host([appearance~="plain"]) .button.disabled {
|
||||
background-color: transparent;
|
||||
@@ -210,6 +211,13 @@ export class HaButton extends Button {
|
||||
.button.disabled {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
slot[name="start"]::slotted(*) {
|
||||
margin-inline-end: 4px;
|
||||
}
|
||||
slot[name="end"]::slotted(*) {
|
||||
margin-inline-start: 4px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -207,6 +207,7 @@ export class HaComboBox extends LitElement {
|
||||
aria-label=${ifDefined(this.hass?.localize("ui.common.clear"))}
|
||||
class=${`clear-button ${this.label ? "" : "no-label"}`}
|
||||
.path=${mdiClose}
|
||||
?disabled=${this.disabled}
|
||||
@click=${this._clearValue}
|
||||
></ha-svg-icon>`
|
||||
: ""}
|
||||
@@ -393,7 +394,8 @@ export class HaComboBox extends LitElement {
|
||||
:host([opened]) .toggle-button {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
.toggle-button[disabled] {
|
||||
.toggle-button[disabled],
|
||||
.clear-button[disabled] {
|
||||
color: var(--disabled-text-color);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
@@ -506,7 +506,7 @@ export class HaControlSlider extends LitElement {
|
||||
width: 100%;
|
||||
}
|
||||
.slider .slider-track-bar {
|
||||
--border-radius: var(--control-slider-border-radius);
|
||||
--ha-border-radius: var(--control-slider-border-radius);
|
||||
--slider-size: 100%;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
|
@@ -254,21 +254,37 @@ export class HaDateRangePicker extends LitElement {
|
||||
}
|
||||
|
||||
private _applyDateRange() {
|
||||
if (this.hass.locale.time_zone === TimeZone.server) {
|
||||
const dateRangePicker = this._dateRangePicker;
|
||||
let start = new Date(this._dateRangePicker.start);
|
||||
let end = new Date(this._dateRangePicker.end);
|
||||
|
||||
const startDate = fromZonedTime(
|
||||
dateRangePicker.start,
|
||||
this.hass.config.time_zone
|
||||
);
|
||||
const endDate = fromZonedTime(
|
||||
dateRangePicker.end,
|
||||
this.hass.config.time_zone
|
||||
);
|
||||
if (this.timePicker) {
|
||||
start.setSeconds(0);
|
||||
start.setMilliseconds(0);
|
||||
end.setSeconds(0);
|
||||
end.setMilliseconds(0);
|
||||
|
||||
dateRangePicker.clickRange([startDate, endDate]);
|
||||
if (
|
||||
end.getHours() === 0 &&
|
||||
end.getMinutes() === 0 &&
|
||||
start.getFullYear() === end.getFullYear() &&
|
||||
start.getMonth() === end.getMonth() &&
|
||||
start.getDate() === end.getDate()
|
||||
) {
|
||||
end.setDate(end.getDate() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.hass.locale.time_zone === TimeZone.server) {
|
||||
start = fromZonedTime(start, this.hass.config.time_zone);
|
||||
end = fromZonedTime(end, this.hass.config.time_zone);
|
||||
}
|
||||
|
||||
if (
|
||||
start.getTime() !== this._dateRangePicker.start.getTime() ||
|
||||
end.getTime() !== this._dateRangePicker.end.getTime()
|
||||
) {
|
||||
this._dateRangePicker.clickRange([start, end]);
|
||||
}
|
||||
this._dateRangePicker.clickedApply();
|
||||
}
|
||||
|
||||
|
@@ -23,13 +23,13 @@ export class HaFab extends FabBase {
|
||||
:host .mdc-fab--extended {
|
||||
border-radius: var(
|
||||
--ha-button-border-radius,
|
||||
var(--border-radius-pill)
|
||||
var(--ha-border-radius-pill)
|
||||
);
|
||||
}
|
||||
:host .mdc-fab.mdc-fab--extended .ripple {
|
||||
border-radius: var(
|
||||
--ha-button-border-radius,
|
||||
var(--border-radius-pill)
|
||||
var(--ha-border-radius-pill)
|
||||
);
|
||||
}
|
||||
:host .mdc-fab--extended .mdc-fab__icon {
|
||||
|
@@ -106,6 +106,8 @@ export const computeInitialHaFormData = (
|
||||
data[field.name] = [];
|
||||
} else if ("media" in selector || "target" in selector) {
|
||||
data[field.name] = {};
|
||||
} else if ("state" in selector) {
|
||||
data[field.name] = selector.state?.multiple ? [] : "";
|
||||
} else {
|
||||
throw new Error(
|
||||
`Selector ${Object.keys(selector)[0]} not supported in initial form data`
|
||||
|
@@ -7,8 +7,8 @@ import { haStyle } from "../resources/styles";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./ha-button";
|
||||
import "./ha-icon-button";
|
||||
import "./ha-textfield";
|
||||
import "./ha-input-helper-text";
|
||||
import "./ha-textfield";
|
||||
import type { HaTextField } from "./ha-textfield";
|
||||
|
||||
@customElement("ha-multi-textfield")
|
||||
@@ -79,6 +79,7 @@ class HaMultiTextField extends LitElement {
|
||||
@click=${this._addItem}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiPlus}></ha-svg-icon>
|
||||
${this.addLabel ??
|
||||
(this.label
|
||||
? this.hass?.localize("ui.components.multi-textfield.add_item", {
|
||||
@@ -86,7 +87,6 @@ class HaMultiTextField extends LitElement {
|
||||
})
|
||||
: this.hass?.localize("ui.common.add")) ??
|
||||
"Add"}
|
||||
<ha-svg-icon slot="end" .path=${mdiPlus}></ha-svg-icon>
|
||||
</ha-button>
|
||||
</div>
|
||||
${this.helper
|
||||
|
@@ -121,6 +121,10 @@ const SELECTOR_SCHEMAS = {
|
||||
name: "entity_id",
|
||||
selector: { entity: {} },
|
||||
},
|
||||
{
|
||||
name: "multiple",
|
||||
selector: { boolean: {} },
|
||||
},
|
||||
] as const,
|
||||
target: [] as const,
|
||||
template: [] as const,
|
||||
|
@@ -4,6 +4,7 @@ import type { StateSelector } from "../../data/selector";
|
||||
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../entity/ha-entity-state-picker";
|
||||
import "../entity/ha-entity-states-picker";
|
||||
|
||||
@customElement("ha-selector-state")
|
||||
export class HaSelectorState extends SubscribeMixin(LitElement) {
|
||||
@@ -27,6 +28,25 @@ export class HaSelectorState extends SubscribeMixin(LitElement) {
|
||||
};
|
||||
|
||||
protected render() {
|
||||
if (this.selector.state?.multiple) {
|
||||
return html`
|
||||
<ha-entity-states-picker
|
||||
.hass=${this.hass}
|
||||
.entityId=${this.selector.state?.entity_id ||
|
||||
this.context?.filter_entity}
|
||||
.attribute=${this.selector.state?.attribute ||
|
||||
this.context?.filter_attribute}
|
||||
.extraOptions=${this.selector.state?.extra_options}
|
||||
.value=${this.value}
|
||||
.label=${this.label}
|
||||
.helper=${this.helper}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
allow-custom-value
|
||||
.hideStates=${this.selector.state?.hide_states}
|
||||
></ha-entity-states-picker>
|
||||
`;
|
||||
}
|
||||
return html`
|
||||
<ha-entity-state-picker
|
||||
.hass=${this.hass}
|
||||
|
@@ -116,7 +116,7 @@ class DialogMediaManage extends LitElement {
|
||||
`
|
||||
: html`
|
||||
<ha-button
|
||||
class="danger"
|
||||
variant="danger"
|
||||
slot="navigationIcon"
|
||||
.disabled=${this._deleting}
|
||||
@click=${this._handleDelete}
|
||||
@@ -327,10 +327,6 @@ class DialogMediaManage extends LitElement {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.danger {
|
||||
--mdc-theme-primary: var(--error-color);
|
||||
}
|
||||
|
||||
ha-tip {
|
||||
margin: 16px;
|
||||
}
|
||||
|
@@ -18,9 +18,9 @@ import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { debounce } from "../../common/util/debounce";
|
||||
import { isUnavailableState } from "../../data/entity";
|
||||
import type {
|
||||
MediaPlayerItem,
|
||||
MediaPickedEvent,
|
||||
MediaPlayerBrowseAction,
|
||||
MediaPlayerItem,
|
||||
MediaPlayerLayoutType,
|
||||
} from "../../data/media-player";
|
||||
import {
|
||||
@@ -32,6 +32,7 @@ import { browseLocalMediaPlayer } from "../../data/media_source";
|
||||
import { isTTSMediaSource } from "../../data/tts";
|
||||
import { showAlertDialog } from "../../dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import { loadVirtualizer } from "../../resources/virtualizer";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import {
|
||||
brandsUrl,
|
||||
@@ -44,16 +45,15 @@ import "../ha-alert";
|
||||
import "../ha-button";
|
||||
import "../ha-button-menu";
|
||||
import "../ha-card";
|
||||
import "../ha-spinner";
|
||||
import "../ha-fab";
|
||||
import "../ha-icon-button";
|
||||
import "../ha-svg-icon";
|
||||
import "../ha-tooltip";
|
||||
import "../ha-list";
|
||||
import "../ha-list-item";
|
||||
import "../ha-spinner";
|
||||
import "../ha-svg-icon";
|
||||
import "../ha-tooltip";
|
||||
import "./ha-browse-media-tts";
|
||||
import type { TtsMediaPickedEvent } from "./ha-browse-media-tts";
|
||||
import { loadVirtualizer } from "../../resources/virtualizer";
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
|
@@ -30,6 +30,10 @@ export const ACTION_ICONS = {
|
||||
wait_template: mdiCodeBraces,
|
||||
wait_for_trigger: mdiTrafficLight,
|
||||
repeat: mdiRefresh,
|
||||
repeat_count: mdiRefresh,
|
||||
repeat_while: mdiRefresh,
|
||||
repeat_until: mdiRefresh,
|
||||
repeat_for_each: mdiRefresh,
|
||||
choose: mdiArrowDecision,
|
||||
if: mdiCallSplit,
|
||||
device_id: mdiDevices,
|
||||
@@ -57,7 +61,10 @@ export const ACTION_GROUPS: AutomationElementGroup = {
|
||||
delay: {},
|
||||
wait_template: {},
|
||||
wait_for_trigger: {},
|
||||
repeat: {},
|
||||
repeat_count: {},
|
||||
repeat_while: {},
|
||||
repeat_until: {},
|
||||
repeat_for_each: {},
|
||||
choose: {},
|
||||
if: {},
|
||||
stop: {},
|
||||
|
@@ -397,6 +397,7 @@ export interface StateSelector {
|
||||
entity_id?: string | string[];
|
||||
attribute?: string;
|
||||
hide_states?: string[];
|
||||
multiple?: boolean;
|
||||
} | null;
|
||||
}
|
||||
|
||||
|
@@ -4,7 +4,7 @@ import type {
|
||||
HassEntityBase,
|
||||
HassEvent,
|
||||
} from "home-assistant-js-websocket";
|
||||
import { BINARY_STATE_ON } from "../common/const";
|
||||
import { BINARY_STATE_ON, BINARY_STATE_OFF } from "../common/const";
|
||||
import { computeDomain } from "../common/entity/compute_domain";
|
||||
import { computeStateDomain } from "../common/entity/compute_state_domain";
|
||||
import { supportsFeature } from "../common/entity/supports-feature";
|
||||
@@ -52,6 +52,15 @@ export const updateCanInstall = (
|
||||
(showSkipped && Boolean(entity.attributes.skipped_version))) &&
|
||||
supportsFeature(entity, UpdateEntityFeature.INSTALL);
|
||||
|
||||
export const latestVersionIsSkipped = (entity: UpdateEntity): boolean =>
|
||||
!!(
|
||||
entity.attributes.latest_version &&
|
||||
entity.attributes.skipped_version === entity.attributes.latest_version
|
||||
);
|
||||
|
||||
export const updateButtonIsDisabled = (entity: UpdateEntity): boolean =>
|
||||
entity.state === BINARY_STATE_OFF && !latestVersionIsSkipped(entity);
|
||||
|
||||
export const updateIsInstalling = (entity: UpdateEntity): boolean =>
|
||||
!!entity.attributes.in_progress;
|
||||
|
||||
|
@@ -7,6 +7,7 @@ import { relativeTime } from "../../../common/datetime/relative_time";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/buttons/ha-progress-button";
|
||||
import "../../../components/ha-checkbox";
|
||||
import "../../../components/ha-faded";
|
||||
import "../../../components/ha-markdown";
|
||||
@@ -26,6 +27,8 @@ import {
|
||||
UpdateEntityFeature,
|
||||
updateIsInstalling,
|
||||
updateReleaseNotes,
|
||||
latestVersionIsSkipped,
|
||||
updateButtonIsDisabled,
|
||||
} from "../../../data/update";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { showAlertDialog } from "../../generic/show-dialog-box";
|
||||
@@ -180,11 +183,6 @@ class MoreInfoUpdate extends LitElement {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const skippedVersion =
|
||||
this.stateObj.attributes.latest_version &&
|
||||
this.stateObj.attributes.skipped_version ===
|
||||
this.stateObj.attributes.latest_version;
|
||||
|
||||
const createBackupTexts = this._computeCreateBackupTexts();
|
||||
|
||||
return html`
|
||||
@@ -312,7 +310,7 @@ class MoreInfoUpdate extends LitElement {
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
@click=${this._handleSkip}
|
||||
.disabled=${skippedVersion ||
|
||||
.disabled=${latestVersionIsSkipped(this.stateObj) ||
|
||||
this.stateObj.state === BINARY_STATE_OFF ||
|
||||
updateIsInstalling(this.stateObj)}
|
||||
>
|
||||
@@ -325,9 +323,8 @@ class MoreInfoUpdate extends LitElement {
|
||||
? html`
|
||||
<ha-button
|
||||
@click=${this._handleInstall}
|
||||
.disabled=${(this.stateObj.state === BINARY_STATE_OFF &&
|
||||
!skippedVersion) ||
|
||||
updateIsInstalling(this.stateObj)}
|
||||
.loading=${updateIsInstalling(this.stateObj)}
|
||||
.disabled=${updateButtonIsDisabled(this.stateObj)}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.more_info_control.update.update"
|
||||
|
34
src/mixins/mobile-aware-mixin.ts
Normal file
34
src/mixins/mobile-aware-mixin.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import type { LitElement } from "lit";
|
||||
import { state } from "lit/decorators";
|
||||
import type { Constructor } from "../types";
|
||||
import { isMobileClient } from "../util/is_mobile";
|
||||
import { listenMediaQuery } from "../common/dom/media_query";
|
||||
|
||||
export const MobileAwareMixin = <T extends Constructor<LitElement>>(
|
||||
superClass: T
|
||||
) => {
|
||||
class MobileAwareClass extends superClass {
|
||||
@state() protected _isMobileSize = false;
|
||||
|
||||
protected _isMobileClient = isMobileClient;
|
||||
|
||||
private _unsubMql?: () => void;
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._unsubMql = listenMediaQuery(
|
||||
"all and (max-width: 450px), all and (max-height: 500px)",
|
||||
(matches) => {
|
||||
this._isMobileSize = matches;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this._unsubMql?.();
|
||||
this._unsubMql = undefined;
|
||||
}
|
||||
}
|
||||
return MobileAwareClass;
|
||||
};
|
@@ -17,6 +17,7 @@ import type { Action } from "../../../../data/script";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import {
|
||||
PASTE_VALUE,
|
||||
VIRTUAL_ACTIONS,
|
||||
showAddAutomationElementDialog,
|
||||
} from "../show-add-automation-element-dialog";
|
||||
import type HaAutomationActionRow from "./ha-automation-action-row";
|
||||
@@ -184,6 +185,8 @@ export default class HaAutomationAction extends LitElement {
|
||||
let actions: Action[];
|
||||
if (action === PASTE_VALUE) {
|
||||
actions = this.actions.concat(deepClone(this._clipboard!.action));
|
||||
} else if (action in VIRTUAL_ACTIONS) {
|
||||
actions = this.actions.concat(VIRTUAL_ACTIONS[action]);
|
||||
} else if (isService(action)) {
|
||||
actions = this.actions.concat({
|
||||
action: getService(action),
|
||||
@@ -317,9 +320,6 @@ export default class HaAutomationAction extends LitElement {
|
||||
display: block;
|
||||
scroll-margin-top: 48px;
|
||||
}
|
||||
ha-svg-icon {
|
||||
height: 20px;
|
||||
}
|
||||
.handle {
|
||||
padding: 12px;
|
||||
cursor: move; /* fallback if grab cursor is unsupported */
|
||||
|
@@ -39,7 +39,7 @@ export class HaIfAction extends LitElement implements ActionElement {
|
||||
)}*:
|
||||
</h3>
|
||||
<ha-automation-condition
|
||||
.conditions=${action.if}
|
||||
.conditions=${action.if ?? []}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._ifChanged}
|
||||
.hass=${this.hass}
|
||||
@@ -52,7 +52,7 @@ export class HaIfAction extends LitElement implements ActionElement {
|
||||
)}*:
|
||||
</h3>
|
||||
<ha-automation-action
|
||||
.actions=${action.then}
|
||||
.actions=${action.then ?? []}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._thenChanged}
|
||||
.hass=${this.hass}
|
||||
|
@@ -15,6 +15,8 @@ export class HaParallelAction extends LitElement implements ActionElement {
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ attribute: false }) public action!: ParallelAction;
|
||||
|
||||
public static get defaultConfig(): ParallelAction {
|
||||
@@ -29,6 +31,7 @@ export class HaParallelAction extends LitElement implements ActionElement {
|
||||
return html`
|
||||
<ha-automation-action
|
||||
.actions=${action.parallel}
|
||||
.narrow=${this.narrow}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._actionsChanged}
|
||||
.hass=${this.hass}
|
||||
|
@@ -11,7 +11,6 @@ import "../ha-automation-action";
|
||||
import type { ActionElement } from "../ha-automation-action-row";
|
||||
|
||||
import { isTemplate } from "../../../../../common/string/has-template";
|
||||
import type { LocalizeFunc } from "../../../../../common/translations/localize";
|
||||
import "../../../../../components/ha-form/ha-form";
|
||||
import type {
|
||||
HaFormSchema,
|
||||
@@ -35,22 +34,8 @@ export class HaRepeatAction extends LitElement implements ActionElement {
|
||||
}
|
||||
|
||||
private _schema = memoizeOne(
|
||||
(localize: LocalizeFunc, type: string, template: boolean) =>
|
||||
(type: string, template: boolean) =>
|
||||
[
|
||||
{
|
||||
name: "type",
|
||||
selector: {
|
||||
select: {
|
||||
mode: "dropdown",
|
||||
options: OPTIONS.map((opt) => ({
|
||||
value: opt,
|
||||
label: localize(
|
||||
`ui.panel.config.automation.editor.actions.type.repeat.type.${opt}.label`
|
||||
),
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
...(type === "count"
|
||||
? ([
|
||||
{
|
||||
@@ -94,7 +79,6 @@ export class HaRepeatAction extends LitElement implements ActionElement {
|
||||
const action = this.action.repeat;
|
||||
const type = getType(action);
|
||||
const schema = this._schema(
|
||||
this.hass.localize,
|
||||
type ?? "count",
|
||||
"count" in action && typeof action.count === "string"
|
||||
? isTemplate(action.count)
|
||||
@@ -170,10 +154,6 @@ export class HaRepeatAction extends LitElement implements ActionElement {
|
||||
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
||||
): string => {
|
||||
switch (schema.name) {
|
||||
case "type":
|
||||
return this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.repeat.type_select"
|
||||
);
|
||||
case "count":
|
||||
return this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.repeat.type.count.label"
|
||||
|
@@ -15,6 +15,8 @@ export class HaSequenceAction extends LitElement implements ActionElement {
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ attribute: false }) public action!: SequenceAction;
|
||||
|
||||
public static get defaultConfig(): SequenceAction {
|
||||
@@ -29,6 +31,7 @@ export class HaSequenceAction extends LitElement implements ActionElement {
|
||||
return html`
|
||||
<ha-automation-action
|
||||
.actions=${action.sequence}
|
||||
.narrow=${this.narrow}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._actionsChanged}
|
||||
.hass=${this.hass}
|
||||
|
@@ -345,9 +345,6 @@ export default class HaAutomationCondition extends LitElement {
|
||||
.buttons {
|
||||
order: 1;
|
||||
}
|
||||
ha-svg-icon {
|
||||
height: 20px;
|
||||
}
|
||||
.handle {
|
||||
padding: 12px;
|
||||
cursor: move; /* fallback if grab cursor is unsupported */
|
||||
|
@@ -487,6 +487,7 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
|
||||
.disabled=${Boolean(this._readOnly)}
|
||||
.dirty=${this._dirty}
|
||||
@value-changed=${this._valueChanged}
|
||||
@editor-save=${this._handleSaveAutomation}
|
||||
></manual-automation-editor>
|
||||
`}
|
||||
</div>
|
||||
@@ -517,6 +518,7 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
|
||||
.defaultValue=${this._preprocessYaml()}
|
||||
.readOnly=${this._readOnly}
|
||||
@value-changed=${this._yamlChanged}
|
||||
@editor-save=${this._handleSaveAutomation}
|
||||
.showErrors=${false}
|
||||
disable-fullscreen
|
||||
></ha-yaml-editor>`
|
||||
|
@@ -255,9 +255,6 @@ export default class HaAutomationOption extends LitElement {
|
||||
display: block;
|
||||
scroll-margin-top: 48px;
|
||||
}
|
||||
ha-svg-icon {
|
||||
height: 20px;
|
||||
}
|
||||
.handle {
|
||||
padding: 12px;
|
||||
cursor: move; /* fallback if grab cursor is unsupported */
|
||||
|
@@ -1,7 +1,40 @@
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import type { ACTION_GROUPS } from "../../../data/action";
|
||||
import type { ActionType } from "../../../data/script";
|
||||
|
||||
export const PASTE_VALUE = "__paste__";
|
||||
|
||||
// These will be replaced with the correct action
|
||||
export const VIRTUAL_ACTIONS: Record<
|
||||
keyof (typeof ACTION_GROUPS)["building_blocks"]["members"],
|
||||
ActionType
|
||||
> = {
|
||||
repeat_count: {
|
||||
repeat: {
|
||||
count: 2,
|
||||
sequence: [],
|
||||
},
|
||||
},
|
||||
repeat_while: {
|
||||
repeat: {
|
||||
while: [],
|
||||
sequence: [],
|
||||
},
|
||||
},
|
||||
repeat_until: {
|
||||
repeat: {
|
||||
until: [],
|
||||
sequence: [],
|
||||
},
|
||||
},
|
||||
repeat_for_each: {
|
||||
repeat: {
|
||||
for_each: {},
|
||||
sequence: [],
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export interface AddAutomationElementDialogParams {
|
||||
type: "trigger" | "condition" | "action";
|
||||
add: (key: string) => void;
|
||||
|
@@ -296,9 +296,6 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
display: block;
|
||||
scroll-margin-top: 48px;
|
||||
}
|
||||
ha-svg-icon {
|
||||
height: 20px;
|
||||
}
|
||||
.handle {
|
||||
padding: 12px;
|
||||
cursor: move; /* fallback if grab cursor is unsupported */
|
||||
|
@@ -1442,10 +1442,11 @@ export class HaConfigDevicePage extends LitElement {
|
||||
}
|
||||
|
||||
private async _signUrl(ev) {
|
||||
const signedUrl = await getSignedPath(
|
||||
this.hass,
|
||||
ev.currentTarget.getAttribute("href")
|
||||
);
|
||||
const a = ev.currentTarget.getAttribute("href")
|
||||
? ev.currentTarget
|
||||
: ev.currentTarget.closest("a");
|
||||
|
||||
const signedUrl = await getSignedPath(this.hass, a.getAttribute("href"));
|
||||
fileDownload(signedUrl.path);
|
||||
}
|
||||
|
||||
|
@@ -17,6 +17,10 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import { until } from "lit/directives/until";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import {
|
||||
PROTOCOL_INTEGRATIONS,
|
||||
protocolIntegrationPicked,
|
||||
} from "../../../common/integrations/protocolIntegrationPicked";
|
||||
import { caseInsensitiveStringCompare } from "../../../common/string/compare";
|
||||
import { nextRender } from "../../../common/util/render-status";
|
||||
import "../../../components/ha-button";
|
||||
@@ -64,10 +68,6 @@ import "./ha-config-entry-row";
|
||||
import type { DataEntryFlowProgressExtended } from "./ha-config-integrations";
|
||||
import { showAddIntegrationDialog } from "./show-add-integration-dialog";
|
||||
import { showPickConfigEntryDialog } from "./show-pick-config-entry-dialog";
|
||||
import {
|
||||
PROTOCOL_INTEGRATIONS,
|
||||
protocolIntegrationPicked,
|
||||
} from "../../../common/integrations/protocolIntegrationPicked";
|
||||
|
||||
export const renderConfigEntryError = (
|
||||
hass: HomeAssistant,
|
||||
|
@@ -14,6 +14,9 @@ import "../../../../../layouts/hass-subpage";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import "./mqtt-subscribe-card";
|
||||
import type { Action } from "../../../../../data/script";
|
||||
import { callExecuteScript } from "../../../../../data/service";
|
||||
import { showToast } from "../../../../../util/toast";
|
||||
|
||||
const qosLevel = ["0", "1", "2"];
|
||||
|
||||
@@ -55,14 +58,6 @@ export class MQTTConfigPanel extends LitElement {
|
||||
})
|
||||
private _retain = false;
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
key: "panel-dev-mqtt-allow-template-ls",
|
||||
state: true,
|
||||
subscribe: false,
|
||||
})
|
||||
private _allowTemplate = false;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<hass-subpage .narrow=${this.narrow} .hass=${this.hass}>
|
||||
@@ -108,25 +103,7 @@ export class MQTTConfigPanel extends LitElement {
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
</div>
|
||||
<p>
|
||||
<ha-formfield
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.config.mqtt.allow_template"
|
||||
)}
|
||||
>
|
||||
<ha-switch
|
||||
@change=${this._handleAllowTemplate}
|
||||
.checked=${this._allowTemplate}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
</p>
|
||||
<p>
|
||||
${this._allowTemplate
|
||||
? this.hass.localize("ui.panel.config.mqtt.payload")
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.mqtt.payload_no_template"
|
||||
)}
|
||||
</p>
|
||||
<p>${this.hass.localize("ui.panel.config.mqtt.payload")}</p>
|
||||
<ha-code-editor
|
||||
mode="jinja2"
|
||||
autocomplete-entities
|
||||
@@ -171,21 +148,28 @@ export class MQTTConfigPanel extends LitElement {
|
||||
this._retain = (ev.target! as any).checked;
|
||||
}
|
||||
|
||||
private _handleAllowTemplate(ev: CustomEvent) {
|
||||
this._allowTemplate = (ev.target! as any).checked;
|
||||
}
|
||||
|
||||
private _publish(): void {
|
||||
if (!this.hass) {
|
||||
return;
|
||||
}
|
||||
this.hass.callService("mqtt", "publish", {
|
||||
topic: this._topic,
|
||||
payload: !this._allowTemplate ? this._payload : undefined,
|
||||
payload_template: this._allowTemplate ? this._payload : undefined,
|
||||
qos: parseInt(this._qos),
|
||||
retain: this._retain,
|
||||
});
|
||||
|
||||
const script: Action[] = [
|
||||
{
|
||||
action: "mqtt.publish",
|
||||
data: {
|
||||
topic: this._topic,
|
||||
payload: this._payload,
|
||||
qos: parseInt(this._qos),
|
||||
retain: this._retain,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
callExecuteScript(this.hass, script).catch((err) =>
|
||||
showToast(this, {
|
||||
message: err.message,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private async _openOptionFlow() {
|
||||
|
@@ -105,7 +105,6 @@ class MqttSubscribeCard extends LitElement {
|
||||
size="small"
|
||||
.disabled=${this._topic === ""}
|
||||
@click=${this._handleSubmit}
|
||||
type="submit"
|
||||
>
|
||||
${this._subscribed
|
||||
? this.hass.localize("ui.panel.config.mqtt.stop_listening")
|
||||
|
@@ -320,6 +320,7 @@ export class HaSceneEditor extends PreventUnsavedMixin(
|
||||
.hass=${this.hass}
|
||||
.defaultValue=${this._config}
|
||||
@value-changed=${this._yamlChanged}
|
||||
@editor-save=${this._saveScene}
|
||||
.showErrors=${false}
|
||||
disable-fullscreen
|
||||
></ha-yaml-editor>`;
|
||||
|
@@ -438,6 +438,7 @@ export class HaScriptEditor extends SubscribeMixin(
|
||||
.disabled=${this._readOnly}
|
||||
.dirty=${this._dirty}
|
||||
@value-changed=${this._valueChanged}
|
||||
@editor-save=${this._handleSave}
|
||||
></manual-script-editor>
|
||||
`}
|
||||
</div>
|
||||
@@ -450,6 +451,7 @@ export class HaScriptEditor extends SubscribeMixin(
|
||||
.readOnly=${this._readOnly}
|
||||
disable-fullscreen
|
||||
@value-changed=${this._yamlChanged}
|
||||
@editor-save=${this._handleSave}
|
||||
.showErrors=${false}
|
||||
></ha-yaml-editor>`
|
||||
: nothing}
|
||||
|
@@ -426,13 +426,7 @@ class HaPanelHistory extends LitElement {
|
||||
|
||||
private _dateRangeChanged(ev) {
|
||||
this._startDate = ev.detail.value.startDate;
|
||||
const endDate = ev.detail.value.endDate;
|
||||
if (endDate.getHours() === 0 && endDate.getMinutes() === 0) {
|
||||
endDate.setDate(endDate.getDate() + 1);
|
||||
endDate.setMilliseconds(endDate.getMilliseconds() - 1);
|
||||
}
|
||||
this._endDate = endDate;
|
||||
|
||||
this._endDate = ev.detail.value.endDate;
|
||||
this._updatePath();
|
||||
}
|
||||
|
||||
|
@@ -236,10 +236,6 @@ export class HaPanelLogbook extends LitElement {
|
||||
private _dateRangeChanged(ev) {
|
||||
const startDate = ev.detail.value.startDate;
|
||||
const endDate = ev.detail.value.endDate;
|
||||
if (endDate.getHours() === 0 && endDate.getMinutes() === 0) {
|
||||
endDate.setDate(endDate.getDate() + 1);
|
||||
endDate.setMilliseconds(endDate.getMilliseconds() - 1);
|
||||
}
|
||||
this._time = {
|
||||
range: [startDate, endDate],
|
||||
};
|
||||
|
@@ -21,7 +21,7 @@ export const supportsButtonCardFeature = (
|
||||
: undefined;
|
||||
if (!stateObj) return false;
|
||||
const domain = computeDomain(stateObj.entity_id);
|
||||
return ["button", "script"].includes(domain);
|
||||
return ["button", "input_button", "script"].includes(domain);
|
||||
};
|
||||
|
||||
@customElement("hui-button-card-feature")
|
||||
@@ -43,7 +43,8 @@ class HuiButtonCardFeature extends LitElement implements LovelaceCardFeature {
|
||||
if (!this.hass || !this._stateObj) return;
|
||||
|
||||
const domain = computeDomain(this._stateObj.entity_id);
|
||||
const service = domain === "button" ? "press" : "turn_on";
|
||||
const service =
|
||||
domain === "button" || domain === "input_button" ? "press" : "turn_on";
|
||||
|
||||
this.hass.callService(domain, service, {
|
||||
entity_id: this._stateObj.entity_id,
|
||||
|
@@ -23,6 +23,7 @@ import type { Link, Node } from "../../../../components/chart/ha-sankey-chart";
|
||||
import { getGraphColorByIndex } from "../../../../common/color/colors";
|
||||
import { formatNumber } from "../../../../common/number/format_number";
|
||||
import { getEntityContext } from "../../../../common/entity/context/get_entity_context";
|
||||
import { MobileAwareMixin } from "../../../../mixins/mobile-aware-mixin";
|
||||
|
||||
const DEFAULT_CONFIG: Partial<EnergySankeyCardConfig> = {
|
||||
group_by_floor: true,
|
||||
@@ -31,7 +32,7 @@ const DEFAULT_CONFIG: Partial<EnergySankeyCardConfig> = {
|
||||
|
||||
@customElement("hui-energy-sankey-card")
|
||||
class HuiEnergySankeyCard
|
||||
extends SubscribeMixin(LitElement)
|
||||
extends SubscribeMixin(MobileAwareMixin(LitElement))
|
||||
implements LovelaceCard
|
||||
{
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -70,7 +71,11 @@ class HuiEnergySankeyCard
|
||||
}
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||
return changedProps.has("_config") || changedProps.has("_data");
|
||||
return (
|
||||
changedProps.has("_config") ||
|
||||
changedProps.has("_data") ||
|
||||
changedProps.has("_isMobileSize")
|
||||
);
|
||||
}
|
||||
|
||||
protected render() {
|
||||
@@ -373,13 +378,17 @@ class HuiEnergySankeyCard
|
||||
|
||||
const hasData = nodes.some((node) => node.value > 0);
|
||||
|
||||
const vertical =
|
||||
this._config.layout === "vertical" ||
|
||||
(this._config.layout !== "horizontal" && this._isMobileSize);
|
||||
|
||||
return html`
|
||||
<ha-card .header=${this._config.title}>
|
||||
<div class="card-content">
|
||||
${hasData
|
||||
? html`<ha-sankey-chart
|
||||
.data=${{ nodes, links }}
|
||||
.vertical=${this._config.layout === "vertical"}
|
||||
.vertical=${vertical}
|
||||
.valueFormatter=${this._valueFormatter}
|
||||
></ha-sankey-chart>`
|
||||
: html`${this.hass.localize(
|
||||
|
@@ -213,7 +213,7 @@ export interface EnergyCarbonGaugeCardConfig extends EnergyCardBaseConfig {
|
||||
export interface EnergySankeyCardConfig extends EnergyCardBaseConfig {
|
||||
type: "energy-sankey";
|
||||
title?: string;
|
||||
layout?: "vertical" | "horizontal";
|
||||
layout?: "vertical" | "horizontal" | "auto";
|
||||
group_by_floor?: boolean;
|
||||
group_by_area?: boolean;
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@ import {
|
||||
mdiAccount,
|
||||
mdiAmpersand,
|
||||
mdiGateOr,
|
||||
mdiNotEqualVariant,
|
||||
mdiNumeric,
|
||||
mdiResponsive,
|
||||
mdiStateMachine,
|
||||
@@ -14,5 +15,6 @@ export const ICON_CONDITION: Record<Condition["condition"], string> = {
|
||||
screen: mdiResponsive,
|
||||
user: mdiAccount,
|
||||
and: mdiAmpersand,
|
||||
not: mdiNotEqualVariant,
|
||||
or: mdiGateOr,
|
||||
};
|
||||
|
@@ -11,7 +11,8 @@ export type Condition =
|
||||
| ScreenCondition
|
||||
| UserCondition
|
||||
| OrCondition
|
||||
| AndCondition;
|
||||
| AndCondition
|
||||
| NotCondition;
|
||||
|
||||
// Legacy conditional card condition
|
||||
export interface LegacyCondition {
|
||||
@@ -58,6 +59,11 @@ export interface AndCondition extends BaseCondition {
|
||||
conditions?: Condition[];
|
||||
}
|
||||
|
||||
export interface NotCondition extends BaseCondition {
|
||||
condition: "not";
|
||||
conditions?: Condition[];
|
||||
}
|
||||
|
||||
function getValueFromEntityId(
|
||||
hass: HomeAssistant,
|
||||
value: string
|
||||
@@ -149,6 +155,11 @@ function checkAndCondition(condition: AndCondition, hass: HomeAssistant) {
|
||||
return checkConditionsMet(condition.conditions, hass);
|
||||
}
|
||||
|
||||
function checkNotCondition(condition: NotCondition, hass: HomeAssistant) {
|
||||
if (!condition.conditions) return true;
|
||||
return !checkConditionsMet(condition.conditions, hass);
|
||||
}
|
||||
|
||||
function checkOrCondition(condition: OrCondition, hass: HomeAssistant) {
|
||||
if (!condition.conditions) return true;
|
||||
return condition.conditions.some((c) => checkConditionsMet([c], hass));
|
||||
@@ -175,6 +186,8 @@ export function checkConditionsMet(
|
||||
return checkStateNumericCondition(c, hass);
|
||||
case "and":
|
||||
return checkAndCondition(c, hass);
|
||||
case "not":
|
||||
return checkNotCondition(c, hass);
|
||||
case "or":
|
||||
return checkOrCondition(c, hass);
|
||||
default:
|
||||
@@ -247,6 +260,10 @@ function validateAndCondition(condition: AndCondition) {
|
||||
return condition.conditions != null;
|
||||
}
|
||||
|
||||
function validateNotCondition(condition: NotCondition) {
|
||||
return condition.conditions != null;
|
||||
}
|
||||
|
||||
function validateOrCondition(condition: OrCondition) {
|
||||
return condition.conditions != null;
|
||||
}
|
||||
@@ -276,6 +293,8 @@ export function validateConditionalConfig(
|
||||
return validateNumericStateCondition(c);
|
||||
case "and":
|
||||
return validateAndCondition(c);
|
||||
case "not":
|
||||
return validateNotCondition(c);
|
||||
case "or":
|
||||
return validateOrCondition(c);
|
||||
default:
|
||||
|
@@ -18,6 +18,7 @@ import "./ha-card-condition-editor";
|
||||
import type { HaCardConditionEditor } from "./ha-card-condition-editor";
|
||||
import type { LovelaceConditionEditorConstructor } from "./types";
|
||||
import "./types/ha-card-condition-and";
|
||||
import "./types/ha-card-condition-not";
|
||||
import "./types/ha-card-condition-numeric_state";
|
||||
import "./types/ha-card-condition-or";
|
||||
import "./types/ha-card-condition-screen";
|
||||
@@ -30,6 +31,7 @@ const UI_CONDITION = [
|
||||
"screen",
|
||||
"user",
|
||||
"and",
|
||||
"not",
|
||||
"or",
|
||||
] as const satisfies readonly Condition["condition"][];
|
||||
|
||||
|
@@ -0,0 +1,62 @@
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { any, array, assert, literal, object, optional } from "superstruct";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/ha-form/ha-form";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import type {
|
||||
NotCondition,
|
||||
Condition,
|
||||
StateCondition,
|
||||
} from "../../../common/validate-condition";
|
||||
import "../ha-card-conditions-editor";
|
||||
|
||||
const notConditionStruct = object({
|
||||
condition: literal("not"),
|
||||
conditions: optional(array(any())),
|
||||
});
|
||||
|
||||
@customElement("ha-card-condition-not")
|
||||
export class HaCardConditionNot extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public condition!: NotCondition;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
public static get defaultConfig(): NotCondition {
|
||||
return { condition: "not", conditions: [] };
|
||||
}
|
||||
|
||||
protected static validateUIConfig(condition: StateCondition) {
|
||||
return assert(condition, notConditionStruct);
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-card-conditions-editor
|
||||
nested
|
||||
.hass=${this.hass}
|
||||
.conditions=${this.condition.conditions}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</ha-card-conditions-editor>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
const conditions = ev.detail.value as Condition[];
|
||||
const condition = {
|
||||
...this.condition,
|
||||
conditions,
|
||||
};
|
||||
fireEvent(this, "value-changed", { value: condition });
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-card-condition-not": HaCardConditionNot;
|
||||
}
|
||||
}
|
@@ -32,7 +32,7 @@ export const coreColorStyles = css`
|
||||
--ha-color-neutral-50: #7a7a7a;
|
||||
--ha-color-neutral-60: #989898;
|
||||
--ha-color-neutral-70: #b1b1b1;
|
||||
--ha-color-neutral-80: #b1b1b1;
|
||||
--ha-color-neutral-80: #cccccc;
|
||||
--ha-color-neutral-90: #e6e6e6;
|
||||
--ha-color-neutral-95: #f3f3f3;
|
||||
|
||||
|
@@ -15,9 +15,9 @@ export const semanticColorStyles = css`
|
||||
--ha-color-text-disabled: var(--ha-color-neutral-60);
|
||||
--ha-color-text-link: var(--ha-color-primary-40);
|
||||
/* border primary */
|
||||
--ha-color-border-quiet: var(--ha-color-primary-80);
|
||||
--ha-color-border-normal: var(--ha-color-primary-70);
|
||||
--ha-color-border-loud: var(--ha-color-primary-40);
|
||||
--ha-color-border-primary-quiet: var(--ha-color-primary-80);
|
||||
--ha-color-border-primary-normal: var(--ha-color-primary-70);
|
||||
--ha-color-border-primary-loud: var(--ha-color-primary-40);
|
||||
|
||||
/* border neutral */
|
||||
--ha-color-border-neutral-quiet: var(--ha-color-neutral-80);
|
||||
|
@@ -3,21 +3,21 @@ import { extractDerivedVars } from "../../common/style/derived-css-vars";
|
||||
|
||||
export const coreStyles = css`
|
||||
html {
|
||||
--border-width-sm: 1px;
|
||||
--border-width-md: 2px;
|
||||
--border-width-lg: 3px;
|
||||
--ha-border-width-sm: 1px;
|
||||
--ha-border-width-md: 2px;
|
||||
--ha-border-width-lg: 3px;
|
||||
|
||||
--border-radius-sm: 4px;
|
||||
--border-radius-md: 8px;
|
||||
--border-radius-lg: 12px;
|
||||
--border-radius-xl: 16px;
|
||||
--border-radius-2xl: 24px;
|
||||
--border-radius-3xl: 28px;
|
||||
--border-radius-4xl: 32px;
|
||||
--border-radius-5xl: 36px;
|
||||
--border-radius-pill: 9999px;
|
||||
--border-radius-circle: 50%;
|
||||
--border-radius-square: 0;
|
||||
--ha-border-radius-sm: 4px;
|
||||
--ha-border-radius-md: 8px;
|
||||
--ha-border-radius-lg: 12px;
|
||||
--ha-border-radius-xl: 16px;
|
||||
--ha-border-radius-2xl: 24px;
|
||||
--ha-border-radius-3xl: 28px;
|
||||
--ha-border-radius-4xl: 32px;
|
||||
--ha-border-radius-5xl: 36px;
|
||||
--ha-border-radius-pill: 9999px;
|
||||
--ha-border-radius-circle: 50%;
|
||||
--ha-border-radius-square: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
|
@@ -256,10 +256,10 @@
|
||||
},
|
||||
"timer": {
|
||||
"actions": {
|
||||
"start": "start",
|
||||
"pause": "pause",
|
||||
"cancel": "cancel",
|
||||
"finish": "finish"
|
||||
"start": "Start",
|
||||
"pause": "Pause",
|
||||
"cancel": "Cancel",
|
||||
"finish": "Finish"
|
||||
}
|
||||
},
|
||||
"vacuum": {
|
||||
@@ -438,7 +438,6 @@
|
||||
"supports_markdown": "Supports {markdown_help_link}",
|
||||
"markdown": "Markdown"
|
||||
},
|
||||
|
||||
"components": {
|
||||
"selectors": {
|
||||
"media": {
|
||||
@@ -1972,11 +1971,11 @@
|
||||
},
|
||||
"google_home": {
|
||||
"header": "Share from Google Home",
|
||||
"step_1": "Find your device in the Google Home app. Tap the gear icon to open the device settings.",
|
||||
"step_1": "Find your device in the Google Home app. Tap the gear icon to open the device settings, then tap on Device information.",
|
||||
"step_2": "Tap {linked_matter_apps_services}.",
|
||||
"step_3": "Tap {link_apps_services} and choose {home_assistant} from the list.",
|
||||
"linked_matter_apps_services": "Linked Matter apps and services",
|
||||
"link_apps_services": "Link apps & services",
|
||||
"link_apps_services": "Link apps and services",
|
||||
"no_home_assistant": "I can't find Home Assistant on the list",
|
||||
"redirect": "You are redirected to the Home Assistant app. Please follow the instructions."
|
||||
},
|
||||
@@ -4375,7 +4374,6 @@
|
||||
},
|
||||
"repeat": {
|
||||
"label": "Repeat",
|
||||
"type_select": "Repeat type",
|
||||
"type": {
|
||||
"count": {
|
||||
"label": "Count"
|
||||
@@ -4403,6 +4401,30 @@
|
||||
"for_each": "for every item: {items}"
|
||||
}
|
||||
},
|
||||
"repeat_count": {
|
||||
"label": "Repeat multiple times",
|
||||
"description": {
|
||||
"picker": "Repeat a sequence of actions a fixed number of times."
|
||||
}
|
||||
},
|
||||
"repeat_while": {
|
||||
"label": "Repeat while",
|
||||
"description": {
|
||||
"picker": "Repeat a sequence of actions as long as a condition is satisfied. The condition is checked before each run of the sequence."
|
||||
}
|
||||
},
|
||||
"repeat_until": {
|
||||
"label": "Repeat until",
|
||||
"description": {
|
||||
"picker": "Repeat a sequence of actions until a condition is satisfied. The condition is checked after each run of the sequence."
|
||||
}
|
||||
},
|
||||
"repeat_for_each": {
|
||||
"label": "Repeat for each",
|
||||
"description": {
|
||||
"picker": "Repeat a sequence for each element of a list."
|
||||
}
|
||||
},
|
||||
"choose": {
|
||||
"label": "Choose",
|
||||
"default": "Default actions",
|
||||
@@ -4418,9 +4440,9 @@
|
||||
"no_conditions": "[%key:ui::panel::config::devices::automation::conditions::no_conditions%]",
|
||||
"sequence": "Actions",
|
||||
"description": {
|
||||
"picker": "Choose what to do based on conditions (Similar to If-Then, but more powerful).",
|
||||
"full": "Choose {number, plural,\n one {an action}\n other{between {number} actions}\n}",
|
||||
"no_action": "Choose an action"
|
||||
"picker": "Choose what to do based on conditions (Similar to If-then, but more powerful).",
|
||||
"full": "Choose {number, plural,\n one {an option}\n other{between {number} options}\n}",
|
||||
"no_action": "Choose an option"
|
||||
}
|
||||
},
|
||||
"if": {
|
||||
@@ -5633,8 +5655,6 @@
|
||||
"description_publish": "Publish a packet",
|
||||
"topic": "Topic",
|
||||
"payload": "Payload (template allowed)",
|
||||
"payload_no_template": "Payload",
|
||||
"allow_template": "Allow template",
|
||||
"publish": "Publish",
|
||||
"description_listen": "Listen to a topic",
|
||||
"json_formatting": "Format JSON content",
|
||||
@@ -7285,6 +7305,9 @@
|
||||
"or": {
|
||||
"label": "Or"
|
||||
},
|
||||
"not": {
|
||||
"label": "Not"
|
||||
},
|
||||
"and": {
|
||||
"label": "And"
|
||||
}
|
||||
@@ -7795,6 +7818,9 @@
|
||||
"edit": "Edit feature",
|
||||
"remove": "Remove feature",
|
||||
"types": {
|
||||
"button": {
|
||||
"label": "Button"
|
||||
},
|
||||
"cover-open-close": {
|
||||
"label": "Cover open/close"
|
||||
},
|
||||
|
@@ -46,7 +46,7 @@ export const registerServiceWorker = async (
|
||||
// the new service worker. Above we listen for `controllerchange`
|
||||
// so we reload the page once a new service worker activates.
|
||||
action: () => installingWorker.postMessage({ type: "skipWaiting" }),
|
||||
text: "reload",
|
||||
text: "Reload",
|
||||
},
|
||||
duration: -1,
|
||||
dismissable: false,
|
||||
|
Reference in New Issue
Block a user