Compare commits

..
Author SHA1 Message Date
Paul Bottein e2891f67a3 Use repeat for ha-form schemas 2024-10-08 17:30:33 +02:00
44 changed files with 791 additions and 846 deletions
+2 -2
View File
@@ -21,7 +21,7 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps:
- name: Check out files from GitHub
uses: actions/[email protected].1
uses: actions/[email protected].0
with:
ref: dev
@@ -57,7 +57,7 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps:
- name: Check out files from GitHub
uses: actions/[email protected].1
uses: actions/[email protected].0
with:
ref: master
+7 -7
View File
@@ -24,7 +24,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/[email protected].1
uses: actions/[email protected].0
- name: Setup Node
uses: actions/[email protected]
with:
@@ -37,7 +37,7 @@ jobs:
- name: Build resources
run: ./node_modules/.bin/gulp gen-icons-json build-translations build-locale-data gather-gallery-pages
- name: Setup lint cache
uses: actions/[email protected].1
uses: actions/[email protected].0
with:
path: |
node_modules/.cache/prettier
@@ -58,7 +58,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/[email protected].1
uses: actions/[email protected].0
- name: Setup Node
uses: actions/[email protected]
with:
@@ -76,7 +76,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/[email protected].1
uses: actions/[email protected].0
- name: Setup Node
uses: actions/[email protected]
with:
@@ -89,7 +89,7 @@ jobs:
env:
IS_TEST: "true"
- name: Upload bundle stats
uses: actions/[email protected].3
uses: actions/[email protected].0
with:
name: frontend-bundle-stats
path: build/stats/*.json
@@ -100,7 +100,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/[email protected].1
uses: actions/[email protected].0
- name: Setup Node
uses: actions/[email protected]
with:
@@ -113,7 +113,7 @@ jobs:
env:
IS_TEST: "true"
- name: Upload bundle stats
uses: actions/[email protected].3
uses: actions/[email protected].0
with:
name: supervisor-bundle-stats
path: build/stats/*.json
+1 -1
View File
@@ -23,7 +23,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/[email protected].1
uses: actions/[email protected].0
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
+2 -2
View File
@@ -22,7 +22,7 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps:
- name: Check out files from GitHub
uses: actions/[email protected].1
uses: actions/[email protected].0
with:
ref: dev
@@ -58,7 +58,7 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps:
- name: Check out files from GitHub
uses: actions/[email protected].1
uses: actions/[email protected].0
with:
ref: master
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps:
- name: Check out files from GitHub
uses: actions/[email protected].1
uses: actions/[email protected].0
- name: Setup Node
uses: actions/[email protected]
+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
if: github.repository == 'home-assistant/frontend' && contains(github.event.pull_request.labels.*.name, 'needs design preview')
steps:
- name: Check out files from GitHub
uses: actions/[email protected].1
uses: actions/[email protected].0
- name: Setup Node
uses: actions/[email protected]
+3 -3
View File
@@ -20,7 +20,7 @@ jobs:
contents: write
steps:
- name: Checkout the repository
uses: actions/[email protected].1
uses: actions/[email protected].0
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v5
@@ -57,14 +57,14 @@ jobs:
run: tar -czvf translations.tar.gz translations
- name: Upload build artifacts
uses: actions/[email protected].3
uses: actions/[email protected].0
with:
name: wheels
path: dist/home_assistant_frontend*.whl
if-no-files-found: error
- name: Upload translations
uses: actions/[email protected].3
uses: actions/[email protected].0
with:
name: translations
path: translations.tar.gz
+1 -1
View File
@@ -23,7 +23,7 @@ jobs:
contents: write # Required to upload release assets
steps:
- name: Checkout the repository
uses: actions/[email protected].1
uses: actions/[email protected].0
- name: Verify version
uses: home-assistant/actions/helpers/verify-version@master
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/[email protected].1
uses: actions/[email protected].0
- name: Upload Translations
run: |
+17 -17
View File
@@ -28,21 +28,21 @@
"@babel/runtime": "7.25.7",
"@braintree/sanitize-url": "7.1.0",
"@codemirror/autocomplete": "6.18.1",
"@codemirror/commands": "6.7.0",
"@codemirror/commands": "6.6.2",
"@codemirror/language": "6.10.3",
"@codemirror/legacy-modes": "6.4.1",
"@codemirror/search": "6.5.6",
"@codemirror/state": "6.4.1",
"@codemirror/view": "6.34.1",
"@egjs/hammerjs": "2.0.17",
"@formatjs/intl-datetimeformat": "6.13.0",
"@formatjs/intl-displaynames": "6.6.9",
"@formatjs/intl-datetimeformat": "6.12.5",
"@formatjs/intl-displaynames": "6.6.8",
"@formatjs/intl-getcanonicallocales": "2.3.0",
"@formatjs/intl-listformat": "7.5.8",
"@formatjs/intl-locale": "4.0.1",
"@formatjs/intl-numberformat": "8.11.0",
"@formatjs/intl-pluralrules": "5.2.15",
"@formatjs/intl-relativetimeformat": "11.2.15",
"@formatjs/intl-listformat": "7.5.7",
"@formatjs/intl-locale": "4.0.0",
"@formatjs/intl-numberformat": "8.10.3",
"@formatjs/intl-pluralrules": "5.2.14",
"@formatjs/intl-relativetimeformat": "11.2.14",
"@fullcalendar/core": "6.1.15",
"@fullcalendar/daygrid": "6.1.15",
"@fullcalendar/interaction": "6.1.15",
@@ -89,8 +89,8 @@
"@polymer/polymer": "3.5.1",
"@replit/codemirror-indentation-markers": "6.5.3",
"@thomasloven/round-slider": "0.6.0",
"@vaadin/combo-box": "24.4.11",
"@vaadin/vaadin-themable-mixin": "24.4.11",
"@vaadin/combo-box": "24.4.10",
"@vaadin/vaadin-themable-mixin": "24.4.10",
"@vibrant/color": "3.2.1-alpha.1",
"@vibrant/core": "3.2.1-alpha.1",
"@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
@@ -114,7 +114,7 @@
"hls.js": "patch:hls.js@npm%3A1.5.7#~/.yarn/patches/hls.js-npm-1.5.7-f5bbd3d060.patch",
"home-assistant-js-websocket": "9.4.0",
"idb-keyval": "6.2.1",
"intl-messageformat": "10.6.0",
"intl-messageformat": "10.5.14",
"js-yaml": "4.1.0",
"leaflet": "1.9.4",
"leaflet-draw": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch",
@@ -151,11 +151,11 @@
"xss": "1.0.15"
},
"devDependencies": {
"@babel/core": "7.25.8",
"@babel/core": "7.25.7",
"@babel/helper-define-polyfill-provider": "0.6.2",
"@babel/plugin-proposal-decorators": "7.25.7",
"@babel/plugin-transform-runtime": "7.25.7",
"@babel/preset-env": "7.25.8",
"@babel/preset-env": "7.25.7",
"@babel/preset-typescript": "7.25.7",
"@bundle-stats/plugin-webpack-filter": "4.15.1",
"@koa/cors": "5.0.0",
@@ -195,7 +195,7 @@
"babel-plugin-template-html-minifier": "4.1.0",
"browserslist-useragent-regexp": "4.1.3",
"chai": "5.1.1",
"del": "8.0.0",
"del": "7.1.0",
"eslint": "8.57.1",
"eslint-config-airbnb-base": "15.0.0",
"eslint-config-airbnb-typescript": "18.0.0",
@@ -205,7 +205,7 @@
"eslint-plugin-lit": "1.15.0",
"eslint-plugin-lit-a11y": "4.1.4",
"eslint-plugin-unused-imports": "4.1.4",
"eslint-plugin-wc": "2.2.0",
"eslint-plugin-wc": "2.1.1",
"fancy-log": "2.0.0",
"fs-extra": "11.2.0",
"glob": "11.0.0",
@@ -222,7 +222,7 @@
"lit-analyzer": "2.0.3",
"lodash.merge": "4.6.2",
"lodash.template": "4.5.0",
"magic-string": "0.30.12",
"magic-string": "0.30.11",
"map-stream": "0.0.7",
"mocha": "10.5.0",
"object-hash": "3.0.0",
@@ -240,7 +240,7 @@
"terser-webpack-plugin": "5.3.10",
"transform-async-modules-webpack-plugin": "1.1.1",
"ts-lit-plugin": "2.0.2",
"typescript": "5.6.3",
"typescript": "5.6.2",
"webpack": "5.95.0",
"webpack-cli": "5.1.4",
"webpack-dev-server": "5.1.0",
+2 -2
View File
@@ -1,10 +1,10 @@
[build-system]
requires = ["setuptools~=75.1"]
requires = ["setuptools~=68.0", "wheel~=0.40.0"]
build-backend = "setuptools.build_meta"
[project]
name = "home-assistant-frontend"
version = "20241010.0"
version = "20241002.2"
license = {text = "Apache-2.0"}
description = "The Home Assistant frontend"
readme = "README.md"
+1 -5
View File
@@ -18,9 +18,5 @@ if [[ -n "$DEVCONTAINER" ]]; then
fi
fi
if ! command -v yarn &> /dev/null; then
echo "Error: yarn not found. Please install it following the official instructions: https://yarnpkg.com/getting-started/install" >&2
exit 1
fi
# Install node modules
yarn install
yarn install
+5 -18
View File
@@ -20,15 +20,6 @@ function findNestedItem(
}, obj);
}
function updateNestedItem(obj: any, path: ItemPath): any {
const lastKey = path.pop()!;
const parent = findNestedItem(obj, path);
parent[lastKey] = Array.isArray(parent[lastKey])
? [...parent[lastKey]]
: [parent[lastKey]];
return obj;
}
export function nestedArrayMove<A>(
obj: A,
oldIndex: number,
@@ -36,18 +27,14 @@ export function nestedArrayMove<A>(
oldPath?: ItemPath,
newPath?: ItemPath
): A {
let newObj = (Array.isArray(obj) ? [...obj] : { ...obj }) as A;
if (oldPath) {
newObj = updateNestedItem(newObj, [...oldPath]);
}
if (newPath) {
newObj = updateNestedItem(newObj, [...newPath]);
}
const newObj = (Array.isArray(obj) ? [...obj] : { ...obj }) as A;
const from = oldPath ? findNestedItem(newObj, oldPath) : newObj;
const to = newPath ? findNestedItem(newObj, newPath, true) : newObj;
if (!Array.isArray(from) || !Array.isArray(to)) {
return obj;
}
const item = from.splice(oldIndex, 1)[0];
to.splice(newIndex, 0, item);
+60 -46
View File
@@ -9,6 +9,7 @@ import {
TemplateResult,
} from "lit";
import { customElement, property } from "lit/decorators";
import { repeat } from "lit/directives/repeat";
import { dynamicElement } from "../../common/dom/dynamic-element-directive";
import { fireEvent } from "../../common/dom/fire_event";
import { HomeAssistant } from "../../types";
@@ -110,6 +111,16 @@ export class HaForm extends LitElement implements HaFormElement {
}
}
private _schemaKeys = new WeakMap<HaFormSchema, string>();
private _getSchemaKey(schema: HaFormSchema) {
if (!this._schemaKeys.has(schema)) {
this._schemaKeys.set(schema, Math.random().toString());
}
return this._schemaKeys.get(schema)!;
}
protected render(): TemplateResult {
return html`
<div class="root" part="root">
@@ -120,55 +131,58 @@ export class HaForm extends LitElement implements HaFormElement {
</ha-alert>
`
: ""}
${this.schema.map((item) => {
const error = getError(this.error, item);
const warning = getWarning(this.warning, item);
return html`
${error
? html`
<ha-alert own-margin alert-type="error">
${this._computeError(error, item)}
</ha-alert>
`
: warning
${repeat(
this.schema,
(item) => this._getSchemaKey(item),
(item) => {
const error = getError(this.error, item);
const warning = getWarning(this.warning, item);
return html`
${error
? html`
<ha-alert own-margin alert-type="warning">
${this._computeWarning(warning, item)}
<ha-alert own-margin alert-type="error">
${this._computeError(error, item)}
</ha-alert>
`
: ""}
${"selector" in item
? html`<ha-selector
.schema=${item}
.hass=${this.hass}
.name=${item.name}
.selector=${item.selector}
.value=${getValue(this.data, item)}
.label=${this._computeLabel(item, this.data)}
.disabled=${item.disabled || this.disabled || false}
.placeholder=${item.required ? "" : item.default}
.helper=${this._computeHelper(item)}
.localizeValue=${this.localizeValue}
.required=${item.required || false}
.context=${this._generateContext(item)}
></ha-selector>`
: dynamicElement(this.fieldElementName(item.type), {
schema: item,
data: getValue(this.data, item),
label: this._computeLabel(item, this.data),
helper: this._computeHelper(item),
disabled: this.disabled || item.disabled || false,
hass: this.hass,
localize: this.hass?.localize,
computeLabel: this.computeLabel,
computeHelper: this.computeHelper,
localizeValue: this.localizeValue,
context: this._generateContext(item),
...this.getFormProperties(),
})}
`;
})}
: warning
? html`
<ha-alert own-margin alert-type="warning">
${this._computeWarning(warning, item)}
</ha-alert>
`
: ""}
${"selector" in item
? html`<ha-selector
.schema=${item}
.hass=${this.hass}
.name=${item.name}
.selector=${item.selector}
.value=${getValue(this.data, item)}
.label=${this._computeLabel(item, this.data)}
.disabled=${item.disabled || this.disabled || false}
.placeholder=${item.required ? "" : item.default}
.helper=${this._computeHelper(item)}
.localizeValue=${this.localizeValue}
.required=${item.required || false}
.context=${this._generateContext(item)}
></ha-selector>`
: dynamicElement(this.fieldElementName(item.type), {
schema: item,
data: getValue(this.data, item),
label: this._computeLabel(item, this.data),
helper: this._computeHelper(item),
disabled: this.disabled || item.disabled || false,
hass: this.hass,
localize: this.hass?.localize,
computeLabel: this.computeLabel,
computeHelper: this.computeHelper,
localizeValue: this.localizeValue,
context: this._generateContext(item),
...this.getFormProperties(),
})}
`;
}
)}
</div>
`;
}
+10 -29
View File
@@ -499,23 +499,8 @@ export class HaServiceControl extends LitElement {
.defaultValue=${this._value?.data}
@value-changed=${this._dataChanged}
></ha-yaml-editor>`
: serviceData?.fields.map((dataField) => {
if (!dataField.fields) {
return this._renderField(
dataField,
hasOptional,
domain,
serviceName,
targetEntities
);
}
const fields = Object.entries(dataField.fields).map(
([key, field]) => ({ key, ...field })
);
return fields.length &&
this._hasFilteredFields(fields, targetEntities)
: serviceData?.fields.map((dataField) =>
dataField.fields
? html`<ha-expansion-panel
leftChevron
.expanded=${!dataField.collapsed}
@@ -546,8 +531,14 @@ export class HaServiceControl extends LitElement {
)
)}
</ha-expansion-panel>`
: nothing;
})} `;
: this._renderField(
dataField,
hasOptional,
domain,
serviceName,
targetEntities
)
)} `;
}
private _getSectionDescription(
@@ -560,16 +551,6 @@ export class HaServiceControl extends LitElement {
);
}
private _hasFilteredFields(
dataFields: ExtHassService["fields"],
targetEntities: string[]
) {
return dataFields.some(
(dataField) =>
!dataField.filter || this._filterField(dataField.filter, targetEntities)
);
}
private _renderField = (
dataField: ExtHassService["fields"][number],
hasOptional: boolean,
+1 -1
View File
@@ -167,7 +167,7 @@ export interface TagTrigger extends BaseTrigger {
export interface TimeTrigger extends BaseTrigger {
trigger: "time";
at: string | { entity_id: string; offset?: string };
at: string;
}
export interface TemplateTrigger extends BaseTrigger {
+7 -17
View File
@@ -8,7 +8,6 @@ import {
import secondsToDuration from "../common/datetime/seconds_to_duration";
import { computeAttributeNameDisplay } from "../common/entity/compute_attribute_display";
import { computeStateName } from "../common/entity/compute_state_name";
import { isValidEntityId } from "../common/entity/valid_entity_id";
import type { HomeAssistant } from "../types";
import { Condition, ForDict, Trigger } from "./automation";
import {
@@ -372,22 +371,13 @@ const tryDescribeTrigger = (
// Time Trigger
if (trigger.trigger === "time" && trigger.at) {
const result = ensureArray(trigger.at).map((at) => {
if (typeof at === "string") {
if (isValidEntityId(at)) {
return `entity ${hass.states[at] ? computeStateName(hass.states[at]) : at}`;
}
return localizeTimeString(at, hass.locale, hass.config);
}
const entityStr = `entity ${hass.states[at.entity_id] ? computeStateName(hass.states[at.entity_id]) : at.entity_id}`;
const offsetStr = at.offset
? " " +
hass.localize(`${triggerTranslationBaseKey}.time.offset_by`, {
offset: describeDuration(hass.locale, at.offset),
})
: "";
return `${entityStr}${offsetStr}`;
});
const result = ensureArray(trigger.at).map((at) =>
typeof at !== "string"
? at
: at.includes(".")
? `entity ${hass.states[at] ? computeStateName(hass.states[at]) : at}`
: localizeTimeString(at, hass.locale, hass.config)
);
return hass.localize(`${triggerTranslationBaseKey}.time.description.full`, {
time: formatListWithOrs(hass.locale, result),
-1
View File
@@ -28,7 +28,6 @@ export interface UrlActionConfig extends BaseActionConfig {
export interface MoreInfoActionConfig extends BaseActionConfig {
action: "more-info";
entity_id?: string;
}
export interface AssistActionConfig extends BaseActionConfig {
-4
View File
@@ -17,10 +17,6 @@ export interface LovelaceSectionConfig extends LovelaceBaseSectionConfig {
cards?: LovelaceCardConfig[];
}
export interface LovelaceGridSectionConfig extends LovelaceSectionConfig {
grid_base?: number;
}
export interface LovelaceStrategySectionConfig
extends LovelaceBaseSectionConfig {
strategy: LovelaceStrategyConfig;
@@ -51,7 +51,6 @@ export class HuiPersistentNotificationItem extends LitElement {
static get styles(): CSSResultGroup {
return css`
.time {
position: relative;
display: flex;
justify-content: flex-end;
margin-top: 6px;
@@ -52,7 +52,7 @@ export class HaPlayMediaAction extends LitElement implements ActionElement {
fireEvent(this, "value-changed", {
value: {
...this.action,
action: "media_player.play_media",
service: "media_player.play_media",
target: { entity_id: ev.detail.value.entity_id },
data: {
media_content_id: ev.detail.value.media_content_id,
@@ -117,7 +117,6 @@ export class HaServiceAction extends LitElement implements ActionElement {
.value=${this._action}
.disabled=${this.disabled}
.showAdvanced=${this.hass.userData?.showAdvanced}
.hidePicker=${!!this._action.metadata}
@value-changed=${this._actionChanged}
></ha-service-control>
${domain && service && this.hass.services[domain]?.[service]?.response
@@ -208,7 +208,6 @@ class DialogAddAutomationElement extends LitElement implements HassDialog {
const options: IFuseOptions<ListItem> = {
keys: ["key", "name", "description"],
isCaseSensitive: false,
ignoreLocation: true,
minMatchCharLength: Math.min(filter.length, 2),
threshold: 0.2,
getFn: getStripDiacriticsFn,
@@ -9,9 +9,6 @@ import type { TimeTrigger } from "../../../../../data/automation";
import type { HomeAssistant } from "../../../../../types";
import type { TriggerElement } from "../ha-automation-trigger-row";
const MODE_TIME = "time";
const MODE_ENTITY = "entity";
@customElement("ha-automation-trigger-time")
export class HaTimeTrigger extends LitElement implements TriggerElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -20,60 +17,48 @@ export class HaTimeTrigger extends LitElement implements TriggerElement {
@property({ type: Boolean }) public disabled = false;
@state() private _inputMode:
| undefined
| typeof MODE_TIME
| typeof MODE_ENTITY;
@state() private _inputMode?: boolean;
public static get defaultConfig(): TimeTrigger {
return { trigger: "time", at: "" };
}
private _schema = memoizeOne(
(
localize: LocalizeFunc,
inputMode: typeof MODE_TIME | typeof MODE_ENTITY,
showOffset: boolean
) =>
[
(localize: LocalizeFunc, inputMode?: boolean) => {
const atSelector = inputMode
? {
entity: {
filter: [
{ domain: "input_datetime" },
{ domain: "sensor", device_class: "timestamp" },
],
},
}
: { time: {} };
return [
{
name: "mode",
type: "select",
required: true,
options: [
[
MODE_TIME,
"value",
localize(
"ui.panel.config.automation.editor.triggers.type.time.type_value"
),
],
[
MODE_ENTITY,
"input",
localize(
"ui.panel.config.automation.editor.triggers.type.time.type_input"
),
],
],
},
...(inputMode === MODE_TIME
? ([{ name: "time", selector: { time: {} } }] as const)
: ([
{
name: "entity",
selector: {
entity: {
filter: [
{ domain: "input_datetime" },
{ domain: "sensor", device_class: "timestamp" },
],
},
},
},
] as const)),
...(showOffset
? ([{ name: "offset", selector: { text: {} } }] as const)
: ([] as const)),
] as const
{ name: "at", selector: atSelector },
] as const;
}
);
public willUpdate(changedProperties: PropertyValues) {
@@ -90,46 +75,23 @@ export class HaTimeTrigger extends LitElement implements TriggerElement {
}
}
private _data = memoizeOne(
(
inputMode: undefined | typeof MODE_ENTITY | typeof MODE_TIME,
at:
| string
| { entity_id: string | undefined; offset?: string | undefined }
): {
mode: typeof MODE_TIME | typeof MODE_ENTITY;
entity: string | undefined;
time: string | undefined;
offset: string | undefined;
} => {
const entity =
typeof at === "object"
? at.entity_id
: at?.startsWith("input_datetime.") || at?.startsWith("sensor.")
? at
: undefined;
const time = entity ? undefined : (at as string | undefined);
const offset = typeof at === "object" ? at.offset : undefined;
const mode = inputMode ?? (entity ? MODE_ENTITY : MODE_TIME);
return {
mode,
entity,
time,
offset,
};
}
);
protected render() {
const at = this.trigger.at;
if (Array.isArray(at)) {
return nothing;
}
const data = this._data(this._inputMode, at);
const showOffset =
data.mode === MODE_ENTITY && data.entity?.startsWith("sensor.");
const schema = this._schema(this.hass.localize, data.mode, !!showOffset);
const inputMode =
this._inputMode ??
(at?.startsWith("input_datetime.") || at?.startsWith("sensor."));
const schema = this._schema(this.hass.localize, inputMode);
const data = {
mode: inputMode ? "input" : "value",
...this.trigger,
};
return html`
<ha-form
@@ -145,43 +107,26 @@ export class HaTimeTrigger extends LitElement implements TriggerElement {
private _valueChanged(ev: CustomEvent): void {
ev.stopPropagation();
const newValue = { ...ev.detail.value };
this._inputMode = newValue.mode;
if (newValue.mode === MODE_TIME) {
delete newValue.entity;
delete newValue.offset;
} else {
delete newValue.time;
if (!newValue.entity?.startsWith("sensor.")) {
delete newValue.offset;
}
}
fireEvent(this, "value-changed", {
value: {
...this.trigger,
at: newValue.offset
? {
entity_id: newValue.entity,
offset: newValue.offset,
}
: newValue.entity || newValue.time,
},
});
const newValue = ev.detail.value;
this._inputMode = newValue.mode === "input";
delete newValue.mode;
Object.keys(newValue).forEach((key) =>
newValue[key] === undefined || newValue[key] === ""
? delete newValue[key]
: {}
);
fireEvent(this, "value-changed", { value: newValue });
}
private _computeLabelCallback = (
schema: SchemaUnion<ReturnType<typeof this._schema>>
): string => {
switch (schema.name) {
case "time":
return this.hass.localize(
`ui.panel.config.automation.editor.triggers.type.time.at`
);
}
return this.hass.localize(
): string =>
this.hass.localize(
`ui.panel.config.automation.editor.triggers.type.time.${schema.name}`
);
};
}
declare global {
@@ -83,15 +83,9 @@ export const getZHADeviceActions = async (
classes: "warning",
action: async () => {
const confirmed = await showConfirmationDialog(el, {
title: hass.localize(
"ui.dialogs.zha_device_info.confirmations.remove_title"
),
text: hass.localize(
"ui.dialogs.zha_device_info.confirmations.remove_text"
"ui.dialogs.zha_device_info.confirmations.remove"
),
confirmText: hass.localize("ui.common.remove"),
dismissText: hass.localize("ui.common.cancel"),
destructive: true,
});
if (!confirmed) {
@@ -482,9 +482,7 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
const network = (ev.currentTarget as any).network as ThreadNetwork;
const router = (ev.currentTarget as any).router as ThreadRouter;
const otbr = (ev.currentTarget as any).otbr as OTBRInfo;
const index = network.dataset
? Number(ev.detail.index)
: Number(ev.detail.index) + 1;
const index = Number(ev.detail.index);
switch (index) {
case 0:
this._setPreferredBorderAgent(network.dataset!, router);
@@ -24,7 +24,6 @@ import { haStyle } from "../../../../../resources/styles";
import { HomeAssistant, Route } from "../../../../../types";
import { formatAsPaddedHex, sortZHAGroups } from "./functions";
import { zhaTabs } from "./zha-config-dashboard";
import { LocalizeFunc } from "../../../../../common/translations/localize";
export interface GroupRowData extends ZHAGroup {
group?: GroupRowData;
@@ -72,35 +71,38 @@ export class ZHAGroupsDashboard extends LitElement {
});
private _columns = memoizeOne(
(localize: LocalizeFunc): DataTableColumnContainer => {
const columns: DataTableColumnContainer<GroupRowData> = {
name: {
title: localize("ui.panel.config.zha.groups.groups"),
sortable: true,
filterable: true,
showNarrow: true,
main: true,
hideable: false,
moveable: false,
direction: "asc",
flex: 2,
},
group_id: {
title: localize("ui.panel.config.zha.groups.group_id"),
type: "numeric",
template: (group) => html` ${formatAsPaddedHex(group.group_id)} `,
sortable: true,
},
members: {
title: localize("ui.panel.config.zha.groups.members"),
type: "numeric",
template: (group) => html` ${group.members.length} `,
sortable: true,
},
};
return columns;
}
(narrow: boolean): DataTableColumnContainer<GroupRowData> =>
narrow
? {
name: {
title: "Group",
sortable: true,
filterable: true,
direction: "asc",
flex: 2,
},
}
: {
name: {
title: this.hass.localize("ui.panel.config.zha.groups.groups"),
sortable: true,
filterable: true,
direction: "asc",
flex: 2,
},
group_id: {
title: this.hass.localize("ui.panel.config.zha.groups.group_id"),
type: "numeric",
template: (group) => html` ${formatAsPaddedHex(group.group_id)} `,
sortable: true,
},
members: {
title: this.hass.localize("ui.panel.config.zha.groups.members"),
type: "numeric",
template: (group) => html` ${group.members.length} `,
sortable: true,
},
}
);
protected render(): TemplateResult {
@@ -110,7 +112,7 @@ export class ZHAGroupsDashboard extends LitElement {
.hass=${this.hass}
.narrow=${this.narrow}
.route=${this.route}
.columns=${this._columns(this.hass.localize)}
.columns=${this._columns(this.narrow)}
.data=${this._formattedGroups(this._groups)}
@row-click=${this._handleRowClicked}
clickable
+28 -13
View File
@@ -83,6 +83,8 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
@state() private _config?: ScriptConfig;
@state() private _idError = false;
@state() private _dirty = false;
@state() private _errors?: string;
@@ -412,18 +414,6 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
this._loadConfig();
}
if (
(changedProps.has("scriptId") || changedProps.has("entityRegistry")) &&
this.scriptId &&
this.entityRegistry
) {
// find entity for when script entity id changed
const entity = this.entityRegistry.find(
(ent) => ent.platform === "script" && ent.unique_id === this.scriptId
);
this._entityId = entity?.entity_id;
}
if (changedProps.has("scriptId") && !this.scriptId && this.hass) {
const initData = getScriptEditorInitData();
this._dirty = !!initData;
@@ -458,6 +448,15 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
}
}
private _setEntityId(id?: string) {
this._entityId = id;
if (this.hass.states[`script.${this._entityId}`]) {
this._idError = true;
} else {
this._idError = false;
}
}
private async _checkValidation() {
this._validationErrors = undefined;
if (!this._entityId || !this._config) {
@@ -767,12 +766,28 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
}
private async _saveScript(): Promise<void> {
if (this._idError) {
showToast(this, {
message: this.hass.localize(
"ui.panel.config.script.editor.id_already_exists_save_error"
),
dismissable: false,
duration: -1,
action: {
action: () => {},
text: this.hass.localize("ui.dialogs.generic.ok"),
},
});
return;
}
if (!this.scriptId) {
const saved = await this._promptScriptAlias();
if (!saved) {
return;
}
this._entityId = this._computeEntityIdFromAlias(this._config!.alias);
const entityId = this._computeEntityIdFromAlias(this._config!.alias);
this._setEntityId(entityId);
}
const id = this.scriptId || this._entityId || Date.now();
@@ -68,16 +68,6 @@ class HaPanelDevAction extends LitElement {
@query("#yaml-editor") private _yamlEditor?: HaYamlEditor;
protected willUpdate() {
if (
!this.hasUpdated &&
this._serviceData?.action &&
typeof this._serviceData.action !== "string"
) {
this._serviceData.action = "";
}
}
protected firstUpdated(params) {
super.firstUpdated(params);
this.hass.loadBackendTranslation("services");
@@ -39,7 +39,6 @@ import {
StatisticsValidationResult,
clearStatistics,
getStatisticIds,
updateStatisticsIssues,
validateStatistics,
} from "../../../data/recorder";
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
@@ -637,8 +636,6 @@ class HaPanelDevStatistics extends SubscribeMixin(LitElement) {
validateStatistics(this.hass),
]);
updateStatisticsIssues(this.hass);
const statsIds = new Set();
this._data = statisticIds
@@ -8,7 +8,6 @@ import {
PropertyValues,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { repeat } from "lit/directives/repeat";
import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/ha-sortable";
@@ -125,7 +124,7 @@ export class HuiViewBadges extends LitElement {
.options=${BADGE_SORTABLE_OPTIONS}
invert-swap
>
<div class="badges ${classMap({ "edit-mode": editMode })}">
<div class="badges">
${repeat(
badges,
(badge) => this._getBadgeKey(badge),
@@ -186,8 +185,6 @@ export class HuiViewBadges extends LitElement {
hui-badge-edit-mode {
display: block;
position: relative;
min-width: 36px;
min-height: 36px;
}
.add {
@@ -187,6 +187,7 @@ export class HuiHeadingCard extends LitElement implements LovelaceCard {
}
.content p {
margin: 0;
font-family: Roboto;
font-style: normal;
white-space: nowrap;
overflow: hidden;
@@ -275,7 +275,7 @@ export class HuiTodoListCard extends LitElement implements LovelaceCard {
"ui.panel.lovelace.cards.todo-list.no_unchecked_items"
)}
</p>`}
${!this._config.hide_completed && checkedItems.length
${checkedItems.length
? html`
<div role="separator">
<div class="divider"></div>
-1
View File
@@ -453,7 +453,6 @@ export interface TodoListCardConfig extends LovelaceCardConfig {
title?: string;
theme?: string;
entity?: string;
hide_completed?: boolean;
}
export interface StackCardConfig extends LovelaceCardConfig {
+6 -7
View File
@@ -94,13 +94,12 @@ export const handleAction = async (
switch (actionConfig.action) {
case "more-info": {
const entityId =
actionConfig.entity_id ||
config.entity ||
config.camera_image ||
config.image_entity;
if (entityId) {
fireEvent(node, "hass-more-info", { entityId });
if (config.entity || config.camera_image || config.image_entity) {
fireEvent(node, "hass-more-info", {
entityId: (config.entity ||
config.camera_image ||
config.image_entity)!,
});
} else {
showToast(node, {
message: hass.localize(
@@ -3,15 +3,12 @@ import "@material/mwc-tab/mwc-tab";
import { CSSResultGroup, TemplateResult, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { LovelaceCardConfig } from "../../../../data/lovelace/config/card";
import {
LovelaceGridSectionConfig,
LovelaceSectionConfig,
} from "../../../../data/lovelace/config/section";
import { getCardElementClass } from "../../create-element/create-card-element";
import type { LovelaceCardEditor, LovelaceConfigForm } from "../../types";
import { HuiTypedElementEditor } from "../hui-typed-element-editor";
import "./hui-card-layout-editor";
import "./hui-card-visibility-editor";
import { LovelaceSectionConfig } from "../../../../data/lovelace/config/section";
const tabs = ["config", "visibility", "layout"] as const;
@@ -62,7 +59,7 @@ export class HuiCardElementEditor extends HuiTypedElementEditor<LovelaceCardConf
protected renderConfigElement(): TemplateResult {
const displayedTabs: string[] = ["config"];
if (this.showVisibilityTab) displayedTabs.push("visibility");
if (this.sectionConfig?.type === "grid") displayedTabs.push("layout");
if (this._showLayoutTab) displayedTabs.push("layout");
if (displayedTabs.length === 1) return super.renderConfigElement();
@@ -86,8 +83,8 @@ export class HuiCardElementEditor extends HuiTypedElementEditor<LovelaceCardConf
<hui-card-layout-editor
.hass=${this.hass}
.config=${this.value}
.sectionConfig=${this.sectionConfig!}
@value-changed=${this._configChanged}
.sectionConfig=${this.sectionConfig as LovelaceGridSectionConfig}
>
</hui-card-layout-editor>
`;
@@ -19,10 +19,7 @@ import "../../../../components/ha-switch";
import "../../../../components/ha-yaml-editor";
import type { HaYamlEditor } from "../../../../components/ha-yaml-editor";
import { LovelaceCardConfig } from "../../../../data/lovelace/config/card";
import {
LovelaceGridSectionConfig,
LovelaceSectionConfig,
} from "../../../../data/lovelace/config/section";
import { LovelaceSectionConfig } from "../../../../data/lovelace/config/section";
import { haStyle } from "../../../../resources/styles";
import { HomeAssistant } from "../../../../types";
import { HuiCard } from "../../cards/hui-card";
@@ -30,7 +27,6 @@ import {
CardGridSize,
computeCardGridSize,
} from "../../common/compute-card-grid-size";
import { DEFAULT_GRID_BASE } from "../../sections/hui-grid-section";
import { LovelaceLayoutOptions } from "../../types";
@customElement("hui-card-layout-editor")
@@ -76,10 +72,7 @@ export class HuiCardLayoutEditor extends LitElement {
const value = this._computeCardGridSize(options);
const totalColumns =
(this.sectionConfig.column_span ?? 1) *
((this.sectionConfig as LovelaceGridSectionConfig).grid_base ||
DEFAULT_GRID_BASE);
const totalColumns = (this.sectionConfig.column_span ?? 1) * 4;
return html`
<div class="header">
@@ -1,6 +1,6 @@
import { CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { assert, assign, boolean, object, optional, string } from "superstruct";
import { assert, assign, object, optional, string } from "superstruct";
import { isComponentLoaded } from "../../../../common/config/is_component_loaded";
import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-alert";
@@ -18,7 +18,6 @@ const cardConfigStruct = assign(
title: optional(string()),
theme: optional(string()),
entity: optional(string()),
hide_completed: optional(boolean()),
})
);
@@ -31,7 +30,6 @@ const SCHEMA = [
},
},
{ name: "theme", selector: { theme: {} } },
{ name: "hide_completed", selector: { boolean: {} } },
] as const;
@customElement("hui-todo-list-card-editor")
@@ -89,10 +87,6 @@ export class HuiTodoListEditor
)} (${this.hass!.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})`;
case "hide_completed":
return this.hass!.localize(
"ui.panel.lovelace.editor.card.todo-list.hide_completed"
);
default:
return this.hass!.localize(
`ui.panel.lovelace.editor.card.generic.${schema.name}`
@@ -1,4 +1,4 @@
import { html, LitElement } from "lit";
import { LitElement, html } from "lit";
import { customElement, property } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../common/dom/fire_event";
@@ -6,21 +6,12 @@ import {
HaFormSchema,
SchemaUnion,
} from "../../../../components/ha-form/types";
import {
isStrategySection,
LovelaceGridSectionConfig,
LovelaceSectionRawConfig,
} from "../../../../data/lovelace/config/section";
import { LovelaceSectionRawConfig } from "../../../../data/lovelace/config/section";
import { LovelaceViewConfig } from "../../../../data/lovelace/config/view";
import { HomeAssistant } from "../../../../types";
import { LocalizeFunc } from "../../../../common/translations/localize";
import { DEFAULT_GRID_BASE } from "../../sections/hui-grid-section";
type GridDensity = "default" | "dense" | "custom";
type SettingsData = {
column_span?: number;
grid_density?: GridDensity;
};
@customElement("hui-section-settings-editor")
@@ -32,89 +23,27 @@ export class HuiDialogEditSection extends LitElement {
@property({ attribute: false }) public viewConfig!: LovelaceViewConfig;
private _schema = memoizeOne(
(
maxColumns: number,
localize: LocalizeFunc,
type?: string | undefined,
columnDensity?: GridDensity,
columnBase?: number
) =>
(maxColumns: number) =>
[
{
name: "title",
selector: { text: {} },
name: "column_span",
selector: {
number: {
min: 1,
max: maxColumns,
slider_ticks: true,
},
},
},
...(type === "grid"
? ([
{
name: "grid_density",
default: "default",
selector: {
select: {
mode: "list",
options: [
{
label: localize(
`ui.panel.lovelace.editor.edit_section.settings.grid_density_options.default`,
{ count: 4 }
),
value: "default",
},
{
label: localize(
`ui.panel.lovelace.editor.edit_section.settings.grid_density_options.dense`,
{ count: 6 }
),
value: "dense",
},
...(columnDensity === "custom" && columnBase
? [
{
label: localize(
`ui.panel.lovelace.editor.edit_section.settings.grid_density_options.custom`,
{ count: columnBase }
),
value: "custom",
},
]
: []),
],
},
},
},
] as const satisfies readonly HaFormSchema[])
: []),
] as const satisfies HaFormSchema[]
);
private _isGridSectionConfig(
config: LovelaceSectionRawConfig
): config is LovelaceGridSectionConfig {
return !isStrategySection(config) && config.type === "grid";
}
render() {
const gridBase = this._isGridSectionConfig(this.config)
? this.config.grid_base || DEFAULT_GRID_BASE
: undefined;
const columnDensity =
gridBase === 6 ? "dense" : gridBase === 4 ? "default" : "custom";
const data: SettingsData = {
column_span: this.config.column_span || 1,
grid_density: columnDensity,
};
const type = "type" in this.config ? this.config.type : undefined;
const schema = this._schema(
this.viewConfig.max_columns || 4,
this.hass.localize,
type,
columnDensity,
gridBase
);
const schema = this._schema(this.viewConfig.max_columns || 4);
return html`
<ha-form
@@ -146,26 +75,11 @@ export class HuiDialogEditSection extends LitElement {
ev.stopPropagation();
const newData = ev.detail.value as SettingsData;
const { column_span, grid_density } = newData;
const newConfig: LovelaceSectionRawConfig = {
...this.config,
column_span: column_span,
column_span: newData.column_span,
};
if (this._isGridSectionConfig(newConfig)) {
const gridBase =
grid_density === "default"
? 4
: grid_density === "dense"
? 6
: undefined;
if (gridBase) {
(newConfig as LovelaceGridSectionConfig).grid_base = gridBase;
}
}
fireEvent(this, "value-changed", { value: newConfig });
}
}
@@ -61,11 +61,6 @@ const actionConfigStructAssist = type({
start_listening: optional(boolean()),
});
const actionConfigStructMoreInfo = type({
action: literal("more-info"),
entity_id: optional(string()),
});
export const actionConfigStructType = object({
action: enums([
"none",
@@ -98,9 +93,6 @@ export const actionConfigStruct = dynamic<any>((value) => {
case "assist": {
return actionConfigStructAssist;
}
case "more-info": {
return actionConfigStructMoreInfo;
}
}
}
@@ -8,7 +8,7 @@ import { fireEvent } from "../../../common/dom/fire_event";
import type { HaSortableOptions } from "../../../components/ha-sortable";
import { LovelaceSectionElement } from "../../../data/lovelace";
import { LovelaceCardConfig } from "../../../data/lovelace/config/card";
import type { LovelaceGridSectionConfig } from "../../../data/lovelace/config/section";
import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section";
import { haStyle } from "../../../resources/styles";
import type { HomeAssistant } from "../../../types";
import { HuiCard } from "../cards/hui-card";
@@ -24,8 +24,6 @@ const CARD_SORTABLE_OPTIONS: HaSortableOptions = {
invertedSwapThreshold: 0.7,
} as HaSortableOptions;
export const DEFAULT_GRID_BASE = 4;
export class GridSection extends LitElement implements LovelaceSectionElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -39,11 +37,11 @@ export class GridSection extends LitElement implements LovelaceSectionElement {
@property({ attribute: false }) public cards: HuiCard[] = [];
@state() _config?: LovelaceGridSectionConfig;
@state() _config?: LovelaceSectionConfig;
@state() _dragging = false;
public setConfig(config: LovelaceGridSectionConfig): void {
public setConfig(config: LovelaceSectionConfig): void {
this._config = config;
}
@@ -66,8 +64,6 @@ export class GridSection extends LitElement implements LovelaceSectionElement {
const editMode = Boolean(this.lovelace?.editMode && !this.isStrategy);
const columnCount = this._config.grid_base ?? DEFAULT_GRID_BASE;
return html`
<ha-sortable
.disabled=${!editMode}
@@ -81,10 +77,7 @@ export class GridSection extends LitElement implements LovelaceSectionElement {
.options=${CARD_SORTABLE_OPTIONS}
invert-swap
>
<div
class="container ${classMap({ "edit-mode": editMode })}"
style=${styleMap({ "--column-count": columnCount })}
>
<div class="container ${classMap({ "edit-mode": editMode })}">
${repeat(
cardsConfig,
(cardConfig) => this._getKey(cardConfig),
@@ -172,6 +165,7 @@ export class GridSection extends LitElement implements LovelaceSectionElement {
haStyle,
css`
:host {
--base-column-count: 4;
--row-gap: var(--ha-section-grid-row-gap, 8px);
--column-gap: var(--ha-section-grid-column-gap, 8px);
--row-height: var(--ha-section-grid-row-height, 56px);
@@ -181,7 +175,7 @@ export class GridSection extends LitElement implements LovelaceSectionElement {
}
.container {
--grid-column-count: calc(
var(--column-count, 4) * var(--column-span, 1)
var(--base-column-count) * var(--column-span, 1)
);
display: grid;
grid-template-columns: repeat(
@@ -210,10 +204,6 @@ export class GridSection extends LitElement implements LovelaceSectionElement {
grid-column: span min(var(--column-size, 1), var(--grid-column-count));
}
.container.edit-mode .card {
min-height: calc((var(--row-height) - var(--row-gap)) / 2);
}
.card.fit-rows {
height: calc(
(var(--row-size, 1) * (var(--row-height) + var(--row-gap))) - var(
+20 -27
View File
@@ -99,38 +99,31 @@ class StateDisplay extends LitElement {
if (content === "name") {
return html`${this.name || stateObj.attributes.friendly_name}`;
}
let relativeDateTime: string | undefined;
// Check last-changed for backwards compatibility
if (content === "last_changed" || content === "last-changed") {
relativeDateTime = stateObj.last_changed;
}
// Check last_updated for backwards compatibility
if (content === "last_updated" || content === "last-updated") {
relativeDateTime = stateObj.last_updated;
}
if (
content === "last_triggered" ||
(domain === "calendar" &&
(content === "start_time" || content === "end_time")) ||
(domain === "sun" &&
(content === "next_dawn" ||
content === "next_dusk" ||
content === "next_midnight" ||
content === "next_noon" ||
content === "next_rising" ||
content === "next_setting"))
) {
relativeDateTime = stateObj.attributes[content];
}
if (relativeDateTime) {
return html`
<ha-relative-time
.hass=${this.hass}
.datetime=${relativeDateTime}
.datetime=${stateObj.last_changed}
capitalize
></ha-relative-time>
`;
}
// Check last_updated for backwards compatibility
if (content === "last_updated" || content === "last-updated") {
return html`
<ha-relative-time
.hass=${this.hass}
.datetime=${stateObj.last_updated}
capitalize
></ha-relative-time>
`;
}
if (content === "last_triggered") {
return html`
<ha-relative-time
.hass=${this.hass}
.datetime=${stateObj.attributes.last_triggered}
capitalize
></ha-relative-time>
`;
+8 -16
View File
@@ -1635,8 +1635,7 @@
"zigbee_information": "View the Zigbee information for the device."
},
"confirmations": {
"remove_title": "Remove device",
"remove_text": "This device will be permanently removed from the Zigbee network."
"remove": "Are you sure that you want to remove the device?"
},
"quirk": "Quirk",
"last_seen": "Last seen",
@@ -2811,7 +2810,7 @@
"migrate": "Migrate",
"duplicate": "[%key:ui::common::duplicate%]",
"take_control": "Take control",
"confirm_take_control": "You are viewing a preview of the automation config, do you want to take control?",
"confirm_take_control": "Your are viewing a preview of the automation config, do you want to take control?",
"run": "[%key:ui::panel::config::automation::editor::actions::run%]",
"rename": "[%key:ui::panel::config::automation::editor::triggers::rename%]",
"show_trace": "Traces",
@@ -3054,9 +3053,6 @@
"type_input": "Value of a date/time helper or timestamp-class sensor",
"label": "Time",
"at": "At time",
"offset": "[%key:ui::panel::config::automation::editor::triggers::type::sun::offset%]",
"entity": "Entity with timestamp",
"offset_by": "offset by {offset}",
"mode": "Mode",
"description": {
"picker": "At a specific time, or on a specific date.",
@@ -3688,13 +3684,16 @@
"editor": {
"alias": "Name",
"icon": "Icon",
"id": "Entity ID",
"id_already_exists_save_error": "You can't save this script because the ID is not unique, pick another ID or leave it blank to automatically generate one.",
"id_already_exists": "This ID already exists",
"introduction": "Use scripts to run a sequence of actions.",
"show_trace": "[%key:ui::panel::config::automation::editor::show_trace%]",
"show_info": "[%key:ui::panel::config::automation::editor::show_info%]",
"rename": "[%key:ui::panel::config::automation::editor::triggers::rename%]",
"change_mode": "[%key:ui::panel::config::automation::editor::change_mode%]",
"take_control": "[%key:ui::panel::config::automation::editor::take_control%]",
"confirm_take_control": "You are viewing a preview of the script config, do you want to take control?",
"confirm_take_control": "Your are viewing a preview of the script config, do you want to take control?",
"read_only": "This script cannot be edited from the UI, because it is not stored in the ''scripts.yaml'' file.",
"unavailable": "Script is unavailable",
"migrate": "Migrate",
@@ -5714,13 +5713,7 @@
"title": "Title",
"title_helper": "The title will appear at the top of section. Leave empty to hide the title.",
"column_span": "Width",
"column_span_helper": "Larger sections will be made smaller to fit the display. (e.g. on mobile devices)",
"grid_density": "Grid density",
"grid_density_options": {
"default": "Default ({count} columns)",
"dense": "Dense ({count} columns)",
"custom": "Custom ({count} {count, plural,\n one {column}\n other {columns}\n})"
}
"column_span_helper": "Larger sections will be made smaller to fit the display. (e.g. on mobile devices)"
},
"visibility": {
"explanation": "The section will be shown when ALL conditions below are fulfilled. If no conditions are set, the section will always be shown."
@@ -6138,8 +6131,7 @@
"todo-list": {
"name": "To-do list",
"description": "The to-do list card allows you to add, edit, check-off, and clear items from your to-do list.",
"integration_not_loaded": "This card requires the `todo` integration to be set up.",
"hide_completed": "Hide completed items"
"integration_not_loaded": "This card requires the `todo` integration to be set up."
},
"thermostat": {
"name": "Thermostat",
+499 -316
View File
File diff suppressed because it is too large Load Diff