mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-25 18:26:35 +00:00
20221201.0 (#14505)
This commit is contained in:
commit
77a53ffc6c
@ -1,5 +1,5 @@
|
|||||||
const gulp = require("gulp");
|
const gulp = require("gulp");
|
||||||
const fs = require("fs");
|
const fs = require("fs/promises");
|
||||||
const mapStream = require("map-stream");
|
const mapStream = require("map-stream");
|
||||||
|
|
||||||
const inDirFrontend = "translations/frontend";
|
const inDirFrontend = "translations/frontend";
|
||||||
@ -46,18 +46,21 @@ gulp.task("check-translations-html", function () {
|
|||||||
return gulp.src([`${inDirFrontend}/*.json`]).pipe(checkHtml());
|
return gulp.src([`${inDirFrontend}/*.json`]).pipe(checkHtml());
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task("check-all-files-exist", function () {
|
gulp.task("check-all-files-exist", async function () {
|
||||||
const file = fs.readFileSync(srcMeta, { encoding });
|
const file = await fs.readFile(srcMeta, { encoding });
|
||||||
const meta = JSON.parse(file);
|
const meta = JSON.parse(file);
|
||||||
|
const writings = [];
|
||||||
Object.keys(meta).forEach((lang) => {
|
Object.keys(meta).forEach((lang) => {
|
||||||
if (!fs.existsSync(`${inDirFrontend}/${lang}.json`)) {
|
writings.push(
|
||||||
fs.writeFileSync(`${inDirFrontend}/${lang}.json`, JSON.stringify({}));
|
fs.writeFile(`${inDirFrontend}/${lang}.json`, JSON.stringify({}), {
|
||||||
}
|
flag: "wx",
|
||||||
if (!fs.existsSync(`${inDirBackend}/${lang}.json`)) {
|
}),
|
||||||
fs.writeFileSync(`${inDirBackend}/${lang}.json`, JSON.stringify({}));
|
fs.writeFile(`${inDirBackend}/${lang}.json`, JSON.stringify({}), {
|
||||||
}
|
flag: "wx",
|
||||||
|
})
|
||||||
|
);
|
||||||
});
|
});
|
||||||
return Promise.resolve();
|
await Promise.allSettled(writings);
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task(
|
gulp.task(
|
||||||
|
@ -307,7 +307,8 @@ export class DemoEntityState extends LitElement {
|
|||||||
html`${computeStateDisplay(
|
html`${computeStateDisplay(
|
||||||
hass.localize,
|
hass.localize,
|
||||||
entry.stateObj,
|
entry.stateObj,
|
||||||
hass.locale
|
hass.locale,
|
||||||
|
hass.entities
|
||||||
)}`,
|
)}`,
|
||||||
},
|
},
|
||||||
device_class: {
|
device_class: {
|
||||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "home-assistant-frontend"
|
name = "home-assistant-frontend"
|
||||||
version = "20221130.0"
|
version = "20221201.0"
|
||||||
license = {text = "Apache-2.0"}
|
license = {text = "Apache-2.0"}
|
||||||
description = "The Home Assistant frontend"
|
description = "The Home Assistant frontend"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@ -7,4 +7,4 @@ set -e
|
|||||||
cd "$(dirname "$0")/.."
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
# Install node modules
|
# Install node modules
|
||||||
yarn install
|
yarn install
|
9
script/setup_translations
Executable file
9
script/setup_translations
Executable file
@ -0,0 +1,9 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Setup translation fetching during development
|
||||||
|
|
||||||
|
# Stop on errors
|
||||||
|
set -e
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
./node_modules/.bin/gulp setup-and-fetch-nightly-translations
|
@ -5,6 +5,7 @@ const ALERTING_DEVICE_CLASSES = new Set([
|
|||||||
"carbon_monoxide",
|
"carbon_monoxide",
|
||||||
"gas",
|
"gas",
|
||||||
"heat",
|
"heat",
|
||||||
|
"moisture",
|
||||||
"problem",
|
"problem",
|
||||||
"safety",
|
"safety",
|
||||||
"smoke",
|
"smoke",
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
|
import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
|
||||||
|
import { EntityRegistryEntry } from "../../data/entity_registry";
|
||||||
import { FrontendLocaleData } from "../../data/translation";
|
import { FrontendLocaleData } from "../../data/translation";
|
||||||
import {
|
import {
|
||||||
updateIsInstallingFromAttributes,
|
updateIsInstallingFromAttributes,
|
||||||
UPDATE_SUPPORT_PROGRESS,
|
UPDATE_SUPPORT_PROGRESS,
|
||||||
} from "../../data/update";
|
} from "../../data/update";
|
||||||
|
import { HomeAssistant } from "../../types";
|
||||||
import { formatDuration, UNIT_TO_SECOND_CONVERT } from "../datetime/duration";
|
import { formatDuration, UNIT_TO_SECOND_CONVERT } from "../datetime/duration";
|
||||||
import { formatDate } from "../datetime/format_date";
|
import { formatDate } from "../datetime/format_date";
|
||||||
import { formatDateTime } from "../datetime/format_date_time";
|
import { formatDateTime } from "../datetime/format_date_time";
|
||||||
@ -23,11 +25,13 @@ export const computeStateDisplay = (
|
|||||||
localize: LocalizeFunc,
|
localize: LocalizeFunc,
|
||||||
stateObj: HassEntity,
|
stateObj: HassEntity,
|
||||||
locale: FrontendLocaleData,
|
locale: FrontendLocaleData,
|
||||||
|
entities: HomeAssistant["entities"],
|
||||||
state?: string
|
state?: string
|
||||||
): string =>
|
): string =>
|
||||||
computeStateDisplayFromEntityAttributes(
|
computeStateDisplayFromEntityAttributes(
|
||||||
localize,
|
localize,
|
||||||
locale,
|
locale,
|
||||||
|
entities,
|
||||||
stateObj.entity_id,
|
stateObj.entity_id,
|
||||||
stateObj.attributes,
|
stateObj.attributes,
|
||||||
state !== undefined ? state : stateObj.state
|
state !== undefined ? state : stateObj.state
|
||||||
@ -36,6 +40,7 @@ export const computeStateDisplay = (
|
|||||||
export const computeStateDisplayFromEntityAttributes = (
|
export const computeStateDisplayFromEntityAttributes = (
|
||||||
localize: LocalizeFunc,
|
localize: LocalizeFunc,
|
||||||
locale: FrontendLocaleData,
|
locale: FrontendLocaleData,
|
||||||
|
entities: HomeAssistant["entities"],
|
||||||
entityId: string,
|
entityId: string,
|
||||||
attributes: any,
|
attributes: any,
|
||||||
state: string
|
state: string
|
||||||
@ -194,7 +199,13 @@ export const computeStateDisplayFromEntityAttributes = (
|
|||||||
: localize("ui.card.update.up_to_date");
|
: localize("ui.card.update.up_to_date");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const entity = entities[entityId] as EntityRegistryEntry | undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
(entity?.translation_key &&
|
||||||
|
localize(
|
||||||
|
`component.${entity.platform}.entity.${domain}.${entity.translation_key}.state.${state}`
|
||||||
|
)) ||
|
||||||
// Return device class translation
|
// Return device class translation
|
||||||
(attributes.device_class &&
|
(attributes.device_class &&
|
||||||
localize(
|
localize(
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
export const countries = [
|
import memoizeOne from "memoize-one";
|
||||||
|
import { caseInsensitiveStringCompare } from "../common/string/compare";
|
||||||
|
|
||||||
|
export const COUNTRIES = [
|
||||||
"AD",
|
"AD",
|
||||||
"AE",
|
"AE",
|
||||||
"AF",
|
"AF",
|
||||||
@ -250,23 +253,31 @@ export const countries = [
|
|||||||
"ZW",
|
"ZW",
|
||||||
];
|
];
|
||||||
|
|
||||||
export const countryDisplayNames =
|
export const getCountryOptions = memoizeOne((language?: string) => {
|
||||||
Intl && "DisplayNames" in Intl
|
const countryDisplayNames =
|
||||||
? new Intl.DisplayNames(undefined, {
|
Intl && "DisplayNames" in Intl
|
||||||
type: "region",
|
? new Intl.DisplayNames(language, {
|
||||||
fallback: "code",
|
type: "region",
|
||||||
})
|
fallback: "code",
|
||||||
: undefined;
|
})
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const options = COUNTRIES.map((country) => ({
|
||||||
|
value: country,
|
||||||
|
label: countryDisplayNames ? countryDisplayNames.of(country)! : country,
|
||||||
|
}));
|
||||||
|
options.sort((a, b) => caseInsensitiveStringCompare(a.label, b.label));
|
||||||
|
return options;
|
||||||
|
});
|
||||||
|
|
||||||
export const createCountryListEl = () => {
|
export const createCountryListEl = () => {
|
||||||
const list = document.createElement("datalist");
|
const list = document.createElement("datalist");
|
||||||
list.id = "countries";
|
list.id = "countries";
|
||||||
for (const country of countries) {
|
const options = getCountryOptions();
|
||||||
|
for (const country of options) {
|
||||||
const option = document.createElement("option");
|
const option = document.createElement("option");
|
||||||
option.value = country;
|
option.value = country.value;
|
||||||
option.innerText = countryDisplayNames
|
option.innerText = country.label;
|
||||||
? countryDisplayNames.of(country)!
|
|
||||||
: country;
|
|
||||||
list.appendChild(option);
|
list.appendChild(option);
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
export const currencies = [
|
import memoizeOne from "memoize-one";
|
||||||
|
import { caseInsensitiveStringCompare } from "../common/string/compare";
|
||||||
|
|
||||||
|
export const CURRENCIES = [
|
||||||
"AED",
|
"AED",
|
||||||
"AFN",
|
"AFN",
|
||||||
"ALL",
|
"ALL",
|
||||||
@ -158,23 +161,29 @@ export const currencies = [
|
|||||||
"ZWL",
|
"ZWL",
|
||||||
];
|
];
|
||||||
|
|
||||||
export const currencyDisplayNames =
|
export const getCurrencyOptions = memoizeOne((language?: string) => {
|
||||||
Intl && "DisplayNames" in Intl
|
const currencyDisplayNames =
|
||||||
? new Intl.DisplayNames(undefined, {
|
Intl && "DisplayNames" in Intl
|
||||||
type: "currency",
|
? new Intl.DisplayNames(language, {
|
||||||
fallback: "code",
|
type: "currency",
|
||||||
})
|
fallback: "code",
|
||||||
: undefined;
|
})
|
||||||
|
: undefined;
|
||||||
|
const options = CURRENCIES.map((currency) => ({
|
||||||
|
value: currency,
|
||||||
|
label: currencyDisplayNames ? currencyDisplayNames.of(currency)! : currency,
|
||||||
|
}));
|
||||||
|
options.sort((a, b) => caseInsensitiveStringCompare(a.label, b.label));
|
||||||
|
return options;
|
||||||
|
});
|
||||||
|
|
||||||
export const createCurrencyListEl = () => {
|
export const createCurrencyListEl = () => {
|
||||||
const list = document.createElement("datalist");
|
const list = document.createElement("datalist");
|
||||||
list.id = "currencies";
|
list.id = "currencies";
|
||||||
for (const currency of currencies) {
|
for (const currency of getCurrencyOptions()) {
|
||||||
const option = document.createElement("option");
|
const option = document.createElement("option");
|
||||||
option.value = currency;
|
option.value = currency.value;
|
||||||
option.innerText = currencyDisplayNames
|
option.innerText = currency.label;
|
||||||
? currencyDisplayNames.of(currency)!
|
|
||||||
: currency;
|
|
||||||
list.appendChild(option);
|
list.appendChild(option);
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
|
@ -55,6 +55,7 @@ class HaEntityStatePicker extends LitElement {
|
|||||||
this.hass.localize,
|
this.hass.localize,
|
||||||
state,
|
state,
|
||||||
this.hass.locale,
|
this.hass.locale,
|
||||||
|
this.hass.entities,
|
||||||
key
|
key
|
||||||
)
|
)
|
||||||
: formatAttributeValue(this.hass, key),
|
: formatAttributeValue(this.hass, key),
|
||||||
|
@ -158,7 +158,8 @@ export class HaStateLabelBadge extends LitElement {
|
|||||||
: computeStateDisplay(
|
: computeStateDisplay(
|
||||||
this.hass!.localize,
|
this.hass!.localize,
|
||||||
entityState,
|
entityState,
|
||||||
this.hass!.locale
|
this.hass!.locale,
|
||||||
|
this.hass!.entities
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ import { mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
|||||||
import { ComboBoxLitRenderer, comboBoxRenderer } from "@vaadin/combo-box/lit";
|
import { ComboBoxLitRenderer, comboBoxRenderer } from "@vaadin/combo-box/lit";
|
||||||
import "@vaadin/combo-box/theme/material/vaadin-combo-box-light";
|
import "@vaadin/combo-box/theme/material/vaadin-combo-box-light";
|
||||||
import type {
|
import type {
|
||||||
|
ComboBoxDataProvider,
|
||||||
ComboBoxLight,
|
ComboBoxLight,
|
||||||
ComboBoxLightFilterChangedEvent,
|
ComboBoxLightFilterChangedEvent,
|
||||||
ComboBoxLightOpenedChangedEvent,
|
ComboBoxLightOpenedChangedEvent,
|
||||||
@ -82,6 +83,9 @@ export class HaComboBox extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public filteredItems?: any[];
|
@property({ attribute: false }) public filteredItems?: any[];
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
public dataProvider?: ComboBoxDataProvider<any>;
|
||||||
|
|
||||||
@property({ attribute: "allow-custom-value", type: Boolean })
|
@property({ attribute: "allow-custom-value", type: Boolean })
|
||||||
public allowCustomValue = false;
|
public allowCustomValue = false;
|
||||||
|
|
||||||
@ -148,6 +152,7 @@ export class HaComboBox extends LitElement {
|
|||||||
.items=${this.items}
|
.items=${this.items}
|
||||||
.value=${this.value || ""}
|
.value=${this.value || ""}
|
||||||
.filteredItems=${this.filteredItems}
|
.filteredItems=${this.filteredItems}
|
||||||
|
.dataProvider=${this.dataProvider}
|
||||||
.allowCustomValue=${this.allowCustomValue}
|
.allowCustomValue=${this.allowCustomValue}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
@ -225,13 +230,13 @@ export class HaComboBox extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _openedChanged(ev: ComboBoxLightOpenedChangedEvent) {
|
private _openedChanged(ev: ComboBoxLightOpenedChangedEvent) {
|
||||||
|
ev.stopPropagation();
|
||||||
const opened = ev.detail.value;
|
const opened = ev.detail.value;
|
||||||
// delay this so we can handle click event for toggle button before setting _opened
|
// delay this so we can handle click event for toggle button before setting _opened
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.opened = opened;
|
this.opened = opened;
|
||||||
}, 0);
|
}, 0);
|
||||||
// @ts-ignore
|
fireEvent(this, "opened-changed", { value: ev.detail.value });
|
||||||
fireEvent(this, ev.type, ev.detail);
|
|
||||||
|
|
||||||
if (opened) {
|
if (opened) {
|
||||||
const overlay = document.querySelector<HTMLElement>(
|
const overlay = document.querySelector<HTMLElement>(
|
||||||
@ -300,8 +305,8 @@ export class HaComboBox extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _filterChanged(ev: ComboBoxLightFilterChangedEvent) {
|
private _filterChanged(ev: ComboBoxLightFilterChangedEvent) {
|
||||||
// @ts-ignore
|
ev.stopPropagation();
|
||||||
fireEvent(this, ev.type, ev.detail, { composed: false });
|
fireEvent(this, "filter-changed", { value: ev.detail.value });
|
||||||
}
|
}
|
||||||
|
|
||||||
private _valueChanged(ev: ComboBoxLightValueChangedEvent) {
|
private _valueChanged(ev: ComboBoxLightValueChangedEvent) {
|
||||||
@ -363,3 +368,10 @@ declare global {
|
|||||||
"ha-combo-box": HaComboBox;
|
"ha-combo-box": HaComboBox;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HASSDomEvents {
|
||||||
|
"filter-changed": { value: string };
|
||||||
|
"opened-changed": { value: boolean };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -54,6 +54,7 @@ export class HaDateInput extends LitElement {
|
|||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
iconTrailing
|
iconTrailing
|
||||||
helperPersistent
|
helperPersistent
|
||||||
|
readonly
|
||||||
@click=${this._openDialog}
|
@click=${this._openDialog}
|
||||||
.value=${this.value
|
.value=${this.value
|
||||||
? formatDateNumeric(new Date(this.value), this.locale)
|
? formatDateNumeric(new Date(this.value), this.locale)
|
||||||
|
@ -1,28 +1,76 @@
|
|||||||
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
|
|
||||||
import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import {
|
||||||
|
ComboBoxDataProviderCallback,
|
||||||
|
ComboBoxDataProviderParams,
|
||||||
|
} from "@vaadin/combo-box/vaadin-combo-box-light";
|
||||||
|
import { css, html, LitElement, TemplateResult } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import { customIcons } from "../data/custom_icons";
|
import { customIcons } from "../data/custom_icons";
|
||||||
import { PolymerChangedEvent } from "../polymer-types";
|
import { PolymerChangedEvent } from "../polymer-types";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import "./ha-combo-box";
|
import "./ha-combo-box";
|
||||||
import type { HaComboBox } from "./ha-combo-box";
|
|
||||||
import "./ha-icon";
|
import "./ha-icon";
|
||||||
|
|
||||||
type IconItem = {
|
type IconItem = {
|
||||||
icon: string;
|
icon: string;
|
||||||
|
parts: Set<string>;
|
||||||
keywords: string[];
|
keywords: string[];
|
||||||
};
|
};
|
||||||
let iconItems: IconItem[] = [];
|
|
||||||
let iconLoaded = false;
|
|
||||||
|
|
||||||
// eslint-disable-next-line lit/prefer-static-styles
|
type RankedIcon = {
|
||||||
const rowRenderer: ComboBoxLitRenderer<IconItem> = (item) => html`<mwc-list-item
|
icon: string;
|
||||||
graphic="avatar"
|
rank: number;
|
||||||
>
|
};
|
||||||
<ha-icon .icon=${item.icon} slot="graphic"></ha-icon>
|
|
||||||
${item.icon}
|
let ICONS: IconItem[] = [];
|
||||||
</mwc-list-item>`;
|
let ICONS_LOADED = false;
|
||||||
|
|
||||||
|
const loadIcons = async () => {
|
||||||
|
ICONS_LOADED = true;
|
||||||
|
|
||||||
|
const iconList = await import("../../build/mdi/iconList.json");
|
||||||
|
ICONS = iconList.default.map((icon) => ({
|
||||||
|
icon: `mdi:${icon.name}`,
|
||||||
|
parts: new Set(icon.name.split("-")),
|
||||||
|
keywords: icon.keywords,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const customIconLoads: Promise<IconItem[]>[] = [];
|
||||||
|
Object.keys(customIcons).forEach((iconSet) => {
|
||||||
|
customIconLoads.push(loadCustomIconItems(iconSet));
|
||||||
|
});
|
||||||
|
(await Promise.all(customIconLoads)).forEach((customIconItems) => {
|
||||||
|
ICONS.push(...customIconItems);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadCustomIconItems = async (iconsetPrefix: string) => {
|
||||||
|
try {
|
||||||
|
const getIconList = customIcons[iconsetPrefix].getIconList;
|
||||||
|
if (typeof getIconList !== "function") {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const iconList = await getIconList();
|
||||||
|
const customIconItems = iconList.map((icon) => ({
|
||||||
|
icon: `${iconsetPrefix}:${icon.name}`,
|
||||||
|
parts: new Set(icon.name.split("-")),
|
||||||
|
keywords: icon.keywords ?? [],
|
||||||
|
}));
|
||||||
|
return customIconItems;
|
||||||
|
} catch (e) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.warn(`Unable to load icon list for ${iconsetPrefix} iconset`);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const rowRenderer: ComboBoxLitRenderer<IconItem | RankedIcon> = (item) =>
|
||||||
|
html`<mwc-list-item graphic="avatar">
|
||||||
|
<ha-icon .icon=${item.icon} slot="graphic"></ha-icon>
|
||||||
|
${item.icon}
|
||||||
|
</mwc-list-item>`;
|
||||||
|
|
||||||
@customElement("ha-icon-picker")
|
@customElement("ha-icon-picker")
|
||||||
export class HaIconPicker extends LitElement {
|
export class HaIconPicker extends LitElement {
|
||||||
@ -46,10 +94,6 @@ export class HaIconPicker extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public invalid = false;
|
@property({ type: Boolean }) public invalid = false;
|
||||||
|
|
||||||
@state() private _opened = false;
|
|
||||||
|
|
||||||
@query("ha-combo-box", true) private comboBox!: HaComboBox;
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<ha-combo-box
|
<ha-combo-box
|
||||||
@ -58,7 +102,7 @@ export class HaIconPicker extends LitElement {
|
|||||||
item-label-path="icon"
|
item-label-path="icon"
|
||||||
.value=${this._value}
|
.value=${this._value}
|
||||||
allow-custom-value
|
allow-custom-value
|
||||||
.filteredItems=${iconItems}
|
.dataProvider=${ICONS_LOADED ? this._iconProvider : undefined}
|
||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
.helper=${this.helper}
|
.helper=${this.helper}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
@ -70,7 +114,6 @@ export class HaIconPicker extends LitElement {
|
|||||||
icon
|
icon
|
||||||
@opened-changed=${this._openedChanged}
|
@opened-changed=${this._openedChanged}
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
@filter-changed=${this._filterChanged}
|
|
||||||
>
|
>
|
||||||
${this._value || this.placeholder
|
${this._value || this.placeholder
|
||||||
? html`
|
? html`
|
||||||
@ -87,46 +130,55 @@ export class HaIconPicker extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _openedChanged(ev: PolymerChangedEvent<boolean>) {
|
// Filter can take a significant chunk of frame (up to 3-5 ms)
|
||||||
this._opened = ev.detail.value;
|
private _filterIcons = memoizeOne(
|
||||||
if (this._opened && !iconLoaded) {
|
(filter: string, iconItems: IconItem[] = ICONS) => {
|
||||||
const iconList = await import("../../build/mdi/iconList.json");
|
if (!filter) {
|
||||||
|
return iconItems;
|
||||||
iconItems = iconList.default.map((icon) => ({
|
|
||||||
icon: `mdi:${icon.name}`,
|
|
||||||
keywords: icon.keywords,
|
|
||||||
}));
|
|
||||||
iconLoaded = true;
|
|
||||||
|
|
||||||
this.comboBox.filteredItems = iconItems;
|
|
||||||
|
|
||||||
Object.keys(customIcons).forEach((iconSet) => {
|
|
||||||
this._loadCustomIconItems(iconSet);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _loadCustomIconItems(iconsetPrefix: string) {
|
|
||||||
try {
|
|
||||||
const getIconList = customIcons[iconsetPrefix].getIconList;
|
|
||||||
if (typeof getIconList !== "function") {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
const iconList = await getIconList();
|
|
||||||
const customIconItems = iconList.map((icon) => ({
|
|
||||||
icon: `${iconsetPrefix}:${icon.name}`,
|
|
||||||
keywords: icon.keywords ?? [],
|
|
||||||
}));
|
|
||||||
iconItems.push(...customIconItems);
|
|
||||||
this.comboBox.filteredItems = iconItems;
|
|
||||||
} catch (e) {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
console.warn(`Unable to load icon list for ${iconsetPrefix} iconset`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected shouldUpdate(changedProps: PropertyValues) {
|
const filteredItems: RankedIcon[] = [];
|
||||||
return !this._opened || changedProps.has("_opened");
|
const addIcon = (icon: string, rank: number) =>
|
||||||
|
filteredItems.push({ icon, rank });
|
||||||
|
|
||||||
|
// Filter and rank such that exact matches rank higher, and prefer icon name matches over keywords
|
||||||
|
for (const item of iconItems) {
|
||||||
|
if (item.parts.has(filter)) {
|
||||||
|
addIcon(item.icon, 1);
|
||||||
|
} else if (item.keywords.includes(filter)) {
|
||||||
|
addIcon(item.icon, 2);
|
||||||
|
} else if (item.icon.includes(filter)) {
|
||||||
|
addIcon(item.icon, 3);
|
||||||
|
} else if (item.keywords.some((word) => word.includes(filter))) {
|
||||||
|
addIcon(item.icon, 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow preview for custom icon not in list
|
||||||
|
if (filteredItems.length === 0) {
|
||||||
|
addIcon(filter, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return filteredItems.sort((itemA, itemB) => itemA.rank - itemB.rank);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
private _iconProvider = (
|
||||||
|
params: ComboBoxDataProviderParams,
|
||||||
|
callback: ComboBoxDataProviderCallback<IconItem | RankedIcon>
|
||||||
|
) => {
|
||||||
|
const filteredItems = this._filterIcons(params.filter.toLowerCase(), ICONS);
|
||||||
|
const iStart = params.page * params.pageSize;
|
||||||
|
const iEnd = iStart + params.pageSize;
|
||||||
|
callback(filteredItems.slice(iStart, iEnd), filteredItems.length);
|
||||||
|
};
|
||||||
|
|
||||||
|
private async _openedChanged(ev: PolymerChangedEvent<boolean>) {
|
||||||
|
const opened = ev.detail.value;
|
||||||
|
if (opened && !ICONS_LOADED) {
|
||||||
|
await loadIcons();
|
||||||
|
this.requestUpdate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _valueChanged(ev: PolymerChangedEvent<string>) {
|
private _valueChanged(ev: PolymerChangedEvent<string>) {
|
||||||
@ -147,35 +199,6 @@ export class HaIconPicker extends LitElement {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _filterChanged(ev: CustomEvent): void {
|
|
||||||
const filterString = ev.detail.value.toLowerCase();
|
|
||||||
const characterCount = filterString.length;
|
|
||||||
if (characterCount >= 2) {
|
|
||||||
const filteredItems: IconItem[] = [];
|
|
||||||
const filteredItemsByKeywords: IconItem[] = [];
|
|
||||||
|
|
||||||
iconItems.forEach((item) => {
|
|
||||||
if (item.icon.includes(filterString)) {
|
|
||||||
filteredItems.push(item);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (item.keywords.some((t) => t.includes(filterString))) {
|
|
||||||
filteredItemsByKeywords.push(item);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
filteredItems.push(...filteredItemsByKeywords);
|
|
||||||
|
|
||||||
if (filteredItems.length > 0) {
|
|
||||||
this.comboBox.filteredItems = filteredItems;
|
|
||||||
} else {
|
|
||||||
this.comboBox.filteredItems = [{ icon: filterString, keywords: [] }];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.comboBox.filteredItems = iconItems;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private get _value() {
|
private get _value() {
|
||||||
return this.value || "";
|
return this.value || "";
|
||||||
}
|
}
|
||||||
|
@ -85,7 +85,12 @@ class HaWaterHeaterState extends LocalizeMixin(PolymerElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_localizeState(stateObj) {
|
_localizeState(stateObj) {
|
||||||
return computeStateDisplay(this.hass.localize, stateObj, this.hass.locale);
|
return computeStateDisplay(
|
||||||
|
this.hass.localize,
|
||||||
|
stateObj,
|
||||||
|
this.hass.locale,
|
||||||
|
this.hass.entities
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
customElements.define("ha-water_heater-state", HaWaterHeaterState);
|
customElements.define("ha-water_heater-state", HaWaterHeaterState);
|
||||||
|
@ -44,7 +44,7 @@ export class HaTileInfo extends LitElement {
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 16px;
|
line-height: 16px;
|
||||||
letter-spacing: 0.4px;
|
letter-spacing: 0.4px;
|
||||||
color: var(--secondary-text-color);
|
color: var(--primary-text-color);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ export interface EntityRegistryEntry {
|
|||||||
has_entity_name: boolean;
|
has_entity_name: boolean;
|
||||||
original_name?: string;
|
original_name?: string;
|
||||||
unique_id: string;
|
unique_id: string;
|
||||||
|
translation_key?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExtEntityRegistryEntry extends EntityRegistryEntry {
|
export interface ExtEntityRegistryEntry extends EntityRegistryEntry {
|
||||||
|
@ -184,6 +184,7 @@ const equalState = (obj1: LineChartState, obj2: LineChartState) =>
|
|||||||
const processTimelineEntity = (
|
const processTimelineEntity = (
|
||||||
localize: LocalizeFunc,
|
localize: LocalizeFunc,
|
||||||
language: FrontendLocaleData,
|
language: FrontendLocaleData,
|
||||||
|
entities: HomeAssistant["entities"],
|
||||||
entityId: string,
|
entityId: string,
|
||||||
states: EntityHistoryState[],
|
states: EntityHistoryState[],
|
||||||
current_state: HassEntity | undefined
|
current_state: HassEntity | undefined
|
||||||
@ -198,6 +199,7 @@ const processTimelineEntity = (
|
|||||||
state_localize: computeStateDisplayFromEntityAttributes(
|
state_localize: computeStateDisplayFromEntityAttributes(
|
||||||
localize,
|
localize,
|
||||||
language,
|
language,
|
||||||
|
entities,
|
||||||
entityId,
|
entityId,
|
||||||
state.a || first.a,
|
state.a || first.a,
|
||||||
state.s
|
state.s
|
||||||
@ -344,6 +346,7 @@ export const computeHistory = (
|
|||||||
processTimelineEntity(
|
processTimelineEntity(
|
||||||
localize,
|
localize,
|
||||||
hass.locale,
|
hass.locale,
|
||||||
|
hass.entities,
|
||||||
entityId,
|
entityId,
|
||||||
stateInfo,
|
stateInfo,
|
||||||
currentState
|
currentState
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
import { Connection, createCollection } from "home-assistant-js-websocket";
|
||||||
import { LocalizeFunc } from "../common/translations/localize";
|
import { LocalizeFunc } from "../common/translations/localize";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
|
import { debounce } from "../common/util/debounce";
|
||||||
|
|
||||||
export type IntegrationType =
|
export type IntegrationType =
|
||||||
| "device"
|
| "device"
|
||||||
@ -23,6 +25,7 @@ export interface IntegrationManifest {
|
|||||||
zeroconf?: string[];
|
zeroconf?: string[];
|
||||||
homekit?: { models: string[] };
|
homekit?: { models: string[] };
|
||||||
integration_type?: IntegrationType;
|
integration_type?: IntegrationType;
|
||||||
|
loggers?: string[];
|
||||||
quality_scale?: "gold" | "internal" | "platinum" | "silver";
|
quality_scale?: "gold" | "internal" | "platinum" | "silver";
|
||||||
iot_class:
|
iot_class:
|
||||||
| "assumed_state"
|
| "assumed_state"
|
||||||
@ -36,6 +39,24 @@ export interface IntegrationSetup {
|
|||||||
seconds?: number;
|
seconds?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IntegrationLogInfo {
|
||||||
|
domain: string;
|
||||||
|
level?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum LogSeverity {
|
||||||
|
CRITICAL = 50,
|
||||||
|
FATAL = 50,
|
||||||
|
ERROR = 40,
|
||||||
|
WARNING = 30,
|
||||||
|
WARN = 30,
|
||||||
|
INFO = 20,
|
||||||
|
DEBUG = 10,
|
||||||
|
NOTSET = 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IntegrationLogPersistance = "none" | "once" | "permanent";
|
||||||
|
|
||||||
export const integrationIssuesUrl = (
|
export const integrationIssuesUrl = (
|
||||||
domain: string,
|
domain: string,
|
||||||
manifest: IntegrationManifest
|
manifest: IntegrationManifest
|
||||||
@ -69,3 +90,46 @@ export const fetchIntegrationManifest = (
|
|||||||
|
|
||||||
export const fetchIntegrationSetups = (hass: HomeAssistant) =>
|
export const fetchIntegrationSetups = (hass: HomeAssistant) =>
|
||||||
hass.callWS<IntegrationSetup[]>({ type: "integration/setup_info" });
|
hass.callWS<IntegrationSetup[]>({ type: "integration/setup_info" });
|
||||||
|
|
||||||
|
export const fetchIntegrationLogInfo = (conn) =>
|
||||||
|
conn.sendMessagePromise({
|
||||||
|
type: "logger/log_info",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const setIntegrationLogLevel = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
integration: string,
|
||||||
|
level: string,
|
||||||
|
persistence: IntegrationLogPersistance
|
||||||
|
) =>
|
||||||
|
hass.callWS({
|
||||||
|
type: "logger/integration_log_level",
|
||||||
|
integration,
|
||||||
|
level,
|
||||||
|
persistence,
|
||||||
|
});
|
||||||
|
|
||||||
|
const subscribeLogInfoUpdates = (conn, store) =>
|
||||||
|
conn.subscribeEvents(
|
||||||
|
debounce(
|
||||||
|
() =>
|
||||||
|
fetchIntegrationLogInfo(conn).then((log_infos) =>
|
||||||
|
store.setState(log_infos, true)
|
||||||
|
),
|
||||||
|
200,
|
||||||
|
true
|
||||||
|
),
|
||||||
|
"logging_changed"
|
||||||
|
);
|
||||||
|
|
||||||
|
export const subscribeLogInfo = (
|
||||||
|
conn: Connection,
|
||||||
|
onChange: (devices: IntegrationLogInfo[]) => void
|
||||||
|
) =>
|
||||||
|
createCollection<IntegrationLogInfo[]>(
|
||||||
|
"_integration_log_info",
|
||||||
|
fetchIntegrationLogInfo,
|
||||||
|
subscribeLogInfoUpdates,
|
||||||
|
conn,
|
||||||
|
onChange
|
||||||
|
);
|
||||||
|
@ -435,7 +435,13 @@ export const localizeStateMessage = (
|
|||||||
`${LOGBOOK_LOCALIZE_PATH}.changed_to_state`,
|
`${LOGBOOK_LOCALIZE_PATH}.changed_to_state`,
|
||||||
"state",
|
"state",
|
||||||
stateObj
|
stateObj
|
||||||
? computeStateDisplay(localize, stateObj, hass.locale, state)
|
? computeStateDisplay(
|
||||||
|
localize,
|
||||||
|
stateObj,
|
||||||
|
hass.locale,
|
||||||
|
hass.entities,
|
||||||
|
state
|
||||||
|
)
|
||||||
: state
|
: state
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -90,7 +90,12 @@ export const computeDisplayTimer = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (stateObj.state === "idle" || timeRemaining === 0) {
|
if (stateObj.state === "idle" || timeRemaining === 0) {
|
||||||
return computeStateDisplay(hass.localize, stateObj, hass.locale);
|
return computeStateDisplay(
|
||||||
|
hass.localize,
|
||||||
|
stateObj,
|
||||||
|
hass.locale,
|
||||||
|
hass.entities
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let display = secondsToDuration(timeRemaining || 0);
|
let display = secondsToDuration(timeRemaining || 0);
|
||||||
@ -99,7 +104,8 @@ export const computeDisplayTimer = (
|
|||||||
display = `${display} (${computeStateDisplay(
|
display = `${display} (${computeStateDisplay(
|
||||||
hass.localize,
|
hass.localize,
|
||||||
stateObj,
|
stateObj,
|
||||||
hass.locale
|
hass.locale,
|
||||||
|
hass.entities
|
||||||
)})`;
|
)})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,6 +44,7 @@ declare global {
|
|||||||
export type TranslationCategory =
|
export type TranslationCategory =
|
||||||
| "title"
|
| "title"
|
||||||
| "state"
|
| "state"
|
||||||
|
| "entity"
|
||||||
| "config"
|
| "config"
|
||||||
| "config_panel"
|
| "config_panel"
|
||||||
| "options"
|
| "options"
|
||||||
|
@ -37,7 +37,8 @@ export class HuiConfiguratorNotificationItem extends LitElement {
|
|||||||
>${computeStateDisplay(
|
>${computeStateDisplay(
|
||||||
this.hass.localize,
|
this.hass.localize,
|
||||||
this.notification,
|
this.notification,
|
||||||
this.hass.locale
|
this.hass.locale,
|
||||||
|
this.hass.entities
|
||||||
)}</mwc-button
|
)}</mwc-button
|
||||||
>
|
>
|
||||||
</notification-item-template>
|
</notification-item-template>
|
||||||
|
@ -307,7 +307,9 @@ export const provideHass = (
|
|||||||
true
|
true
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
areas: {},
|
||||||
|
devices: {},
|
||||||
|
entities: {},
|
||||||
...overrideData,
|
...overrideData,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -137,6 +137,8 @@ export class HomeAssistantAppEl extends QuickBarMixin(HassElement) {
|
|||||||
super.hassConnected();
|
super.hassConnected();
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this._loadHassTranslations(this.hass!.language, "state");
|
this._loadHassTranslations(this.hass!.language, "state");
|
||||||
|
// @ts-ignore
|
||||||
|
this._loadHassTranslations(this.hass!.language, "entity");
|
||||||
|
|
||||||
document.addEventListener(
|
document.addEventListener(
|
||||||
"visibilitychange",
|
"visibilitychange",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import { mdiCalendarClock, mdiClose } from "@mdi/js";
|
import { mdiCalendarClock, mdiClose } from "@mdi/js";
|
||||||
import { isSameDay } from "date-fns/esm";
|
import { addDays, isSameDay } from "date-fns/esm";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { property, state } from "lit/decorators";
|
import { property, state } from "lit/decorators";
|
||||||
import { RRule } from "rrule";
|
import { RRule } from "rrule";
|
||||||
@ -134,7 +134,10 @@ class DialogCalendarEventDetail extends LitElement {
|
|||||||
|
|
||||||
private _formatDateRange() {
|
private _formatDateRange() {
|
||||||
const start = new Date(this._data!.dtstart);
|
const start = new Date(this._data!.dtstart);
|
||||||
const end = new Date(this._data!.dtend);
|
// All day events should be displayed as a day earlier
|
||||||
|
const end = isDate(this._data.dtend)
|
||||||
|
? addDays(new Date(this._data!.dtend), -1)
|
||||||
|
: new Date(this._data!.dtend);
|
||||||
// The range can be shortened when the start and end are on the same day.
|
// The range can be shortened when the start and end are on the same day.
|
||||||
if (isSameDay(start, end)) {
|
if (isSameDay(start, end)) {
|
||||||
if (isDate(this._data.dtstart)) {
|
if (isDate(this._data.dtstart)) {
|
||||||
@ -148,10 +151,15 @@ class DialogCalendarEventDetail extends LitElement {
|
|||||||
)} - ${formatTime(end, this.hass.locale)}`;
|
)} - ${formatTime(end, this.hass.locale)}`;
|
||||||
}
|
}
|
||||||
// An event across multiple dates, optionally with a time range
|
// An event across multiple dates, optionally with a time range
|
||||||
return `${formatDateTime(start, this.hass.locale)} - ${formatDateTime(
|
return `${
|
||||||
end,
|
isDate(this._data.dtstart)
|
||||||
this.hass.locale
|
? formatDate(start, this.hass.locale)
|
||||||
)}`;
|
: formatDateTime(start, this.hass.locale)
|
||||||
|
} - ${
|
||||||
|
isDate(this._data.dtend)
|
||||||
|
? formatDate(end, this.hass.locale)
|
||||||
|
: formatDateTime(end, this.hass.locale)
|
||||||
|
}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _editEvent() {
|
private async _editEvent() {
|
||||||
|
@ -4,6 +4,7 @@ import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
|||||||
import { addDays, addHours, startOfHour } from "date-fns/esm";
|
import { addDays, addHours, startOfHour } from "date-fns/esm";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
import { isDate } from "../../common/string/is_date";
|
import { isDate } from "../../common/string/is_date";
|
||||||
import "../../components/ha-date-input";
|
import "../../components/ha-date-input";
|
||||||
import "../../components/ha-time-input";
|
import "../../components/ha-time-input";
|
||||||
@ -39,7 +40,9 @@ class DialogCalendarEventEditor extends LitElement {
|
|||||||
|
|
||||||
@state() private _calendarId?: string;
|
@state() private _calendarId?: string;
|
||||||
|
|
||||||
@state() private _data?: CalendarEventMutableParams;
|
@state() private _summary = "";
|
||||||
|
|
||||||
|
@state() private _rrule?: string;
|
||||||
|
|
||||||
@state() private _allDay = false;
|
@state() private _allDay = false;
|
||||||
|
|
||||||
@ -49,40 +52,30 @@ class DialogCalendarEventEditor extends LitElement {
|
|||||||
|
|
||||||
@state() private _submitting = false;
|
@state() private _submitting = false;
|
||||||
|
|
||||||
public async showDialog(
|
public showDialog(params: CalendarEventEditDialogParams): void {
|
||||||
params: CalendarEventEditDialogParams
|
|
||||||
): Promise<void> {
|
|
||||||
this._error = undefined;
|
this._error = undefined;
|
||||||
this._params = params;
|
this._params = params;
|
||||||
this._calendars = params.calendars;
|
this._calendars = params.calendars;
|
||||||
this._calendarId = params.calendarId || this._calendars[0].entity_id;
|
this._calendarId = params.calendarId || this._calendars[0].entity_id;
|
||||||
if (params.entry) {
|
if (params.entry) {
|
||||||
const entry = params.entry!;
|
const entry = params.entry!;
|
||||||
this._data = entry;
|
|
||||||
this._allDay = isDate(entry.dtstart);
|
this._allDay = isDate(entry.dtstart);
|
||||||
|
this._summary = entry.summary;
|
||||||
|
this._rrule = entry.rrule;
|
||||||
if (this._allDay) {
|
if (this._allDay) {
|
||||||
this._dtstart = new Date(entry.dtstart);
|
this._dtstart = new Date(entry.dtstart);
|
||||||
// Calendar event end dates are exclusive, but not shown that way in the UI. The
|
// Calendar event end dates are exclusive, but not shown that way in the UI. The
|
||||||
// reverse happens when persisting the event.
|
// reverse happens when persisting the event.
|
||||||
this._dtend = new Date(entry.dtend);
|
this._dtend = addDays(new Date(entry.dtend), -1);
|
||||||
this._dtend.setDate(this._dtend.getDate() - 1);
|
|
||||||
} else {
|
} else {
|
||||||
this._dtstart = new Date(entry.dtstart);
|
this._dtstart = new Date(entry.dtstart);
|
||||||
this._dtend = new Date(entry.dtend);
|
this._dtend = new Date(entry.dtend);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this._data = {
|
|
||||||
summary: "",
|
|
||||||
// Dates are set in _dateChanged()
|
|
||||||
dtstart: "",
|
|
||||||
dtend: "",
|
|
||||||
};
|
|
||||||
this._allDay = false;
|
this._allDay = false;
|
||||||
this._dtstart = startOfHour(new Date());
|
this._dtstart = startOfHour(new Date());
|
||||||
this._dtend = addHours(this._dtstart, 1);
|
this._dtend = addHours(this._dtstart, 1);
|
||||||
this._dateChanged();
|
|
||||||
}
|
}
|
||||||
await this.updateComplete;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
@ -90,6 +83,12 @@ class DialogCalendarEventEditor extends LitElement {
|
|||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
const isCreate = this._params.entry === undefined;
|
const isCreate = this._params.entry === undefined;
|
||||||
|
|
||||||
|
const { startDate, startTime, endDate, endTime } = this._getLocaleStrings(
|
||||||
|
this._dtstart,
|
||||||
|
this._dtend
|
||||||
|
);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-dialog
|
<ha-dialog
|
||||||
open
|
open
|
||||||
@ -100,7 +99,7 @@ class DialogCalendarEventEditor extends LitElement {
|
|||||||
<div class="header_title">
|
<div class="header_title">
|
||||||
${isCreate
|
${isCreate
|
||||||
? this.hass.localize("ui.components.calendar.event.add")
|
? this.hass.localize("ui.components.calendar.event.add")
|
||||||
: this._data!.summary}
|
: this._summary}
|
||||||
</div>
|
</div>
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
.label=${this.hass.localize("ui.dialogs.generic.close")}
|
.label=${this.hass.localize("ui.dialogs.generic.close")}
|
||||||
@ -155,13 +154,13 @@ class DialogCalendarEventEditor extends LitElement {
|
|||||||
>
|
>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<ha-date-input
|
<ha-date-input
|
||||||
.value=${this._data!.dtstart}
|
.value=${startDate}
|
||||||
.locale=${this.hass.locale}
|
.locale=${this.hass.locale}
|
||||||
@value-changed=${this._startDateChanged}
|
@value-changed=${this._startDateChanged}
|
||||||
></ha-date-input>
|
></ha-date-input>
|
||||||
${!this._allDay
|
${!this._allDay
|
||||||
? html`<ha-time-input
|
? html`<ha-time-input
|
||||||
.value=${this._data!.dtstart.split("T")[1]}
|
.value=${startTime}
|
||||||
.locale=${this.hass.locale}
|
.locale=${this.hass.locale}
|
||||||
@value-changed=${this._startTimeChanged}
|
@value-changed=${this._startTimeChanged}
|
||||||
></ha-time-input>`
|
></ha-time-input>`
|
||||||
@ -174,14 +173,14 @@ class DialogCalendarEventEditor extends LitElement {
|
|||||||
>
|
>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<ha-date-input
|
<ha-date-input
|
||||||
.value=${this._data!.dtend}
|
.value=${endDate}
|
||||||
.min=${this._data!.dtstart}
|
.min=${startDate}
|
||||||
.locale=${this.hass.locale}
|
.locale=${this.hass.locale}
|
||||||
@value-changed=${this._endDateChanged}
|
@value-changed=${this._endDateChanged}
|
||||||
></ha-date-input>
|
></ha-date-input>
|
||||||
${!this._allDay
|
${!this._allDay
|
||||||
? html`<ha-time-input
|
? html`<ha-time-input
|
||||||
.value=${this._data!.dtend.split("T")[1]}
|
.value=${endTime}
|
||||||
.locale=${this.hass.locale}
|
.locale=${this.hass.locale}
|
||||||
@value-changed=${this._endTimeChanged}
|
@value-changed=${this._endTimeChanged}
|
||||||
></ha-time-input>`
|
></ha-time-input>`
|
||||||
@ -190,7 +189,7 @@ class DialogCalendarEventEditor extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
<ha-recurrence-rule-editor
|
<ha-recurrence-rule-editor
|
||||||
.locale=${this.hass.locale}
|
.locale=${this.hass.locale}
|
||||||
.value=${this._data!.rrule || ""}
|
.value=${this._rrule || ""}
|
||||||
@value-changed=${this._handleRRuleChanged}
|
@value-changed=${this._handleRRuleChanged}
|
||||||
>
|
>
|
||||||
</ha-recurrence-rule-editor>
|
</ha-recurrence-rule-editor>
|
||||||
@ -230,57 +229,78 @@ class DialogCalendarEventEditor extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _getLocaleStrings = memoizeOne((startDate?: Date, endDate?: Date) =>
|
||||||
|
// en-CA locale used for date format YYYY-MM-DD
|
||||||
|
// en-GB locale used for 24h time format HH:MM:SS
|
||||||
|
{
|
||||||
|
const timeZone = this.hass.config.time_zone;
|
||||||
|
return {
|
||||||
|
startDate: startDate?.toLocaleDateString("en-CA", { timeZone }),
|
||||||
|
startTime: startDate?.toLocaleTimeString("en-GB", { timeZone }),
|
||||||
|
endDate: endDate?.toLocaleDateString("en-CA", { timeZone }),
|
||||||
|
endTime: endDate?.toLocaleTimeString("en-GB", { timeZone }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
private _handleSummaryChanged(ev) {
|
private _handleSummaryChanged(ev) {
|
||||||
this._data!.summary = ev.target.value;
|
this._summary = ev.target.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleRRuleChanged(ev) {
|
private _handleRRuleChanged(ev) {
|
||||||
this._data!.rrule = ev.detail.value;
|
this._rrule = ev.detail.value;
|
||||||
this.requestUpdate();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _allDayToggleChanged(ev) {
|
private _allDayToggleChanged(ev) {
|
||||||
this._allDay = ev.target.checked;
|
this._allDay = ev.target.checked;
|
||||||
this._dateChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _startDateChanged(ev: CustomEvent) {
|
private _startDateChanged(ev: CustomEvent) {
|
||||||
this._dtstart = new Date(
|
this._dtstart = new Date(
|
||||||
ev.detail.value + "T" + this._dtstart!.toISOString().split("T")[1]
|
ev.detail.value + "T" + this._dtstart!.toISOString().split("T")[1]
|
||||||
);
|
);
|
||||||
this._dateChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _endDateChanged(ev: CustomEvent) {
|
private _endDateChanged(ev: CustomEvent) {
|
||||||
this._dtend = new Date(
|
this._dtend = new Date(
|
||||||
ev.detail.value + "T" + this._dtend!.toISOString().split("T")[1]
|
ev.detail.value + "T" + this._dtend!.toISOString().split("T")[1]
|
||||||
);
|
);
|
||||||
this._dateChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _startTimeChanged(ev: CustomEvent) {
|
private _startTimeChanged(ev: CustomEvent) {
|
||||||
this._dtstart = new Date(
|
this._dtstart = new Date(
|
||||||
this._dtstart!.toISOString().split("T")[0] + "T" + ev.detail.value
|
this._dtstart!.toISOString().split("T")[0] + "T" + ev.detail.value
|
||||||
);
|
);
|
||||||
this._dateChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _endTimeChanged(ev: CustomEvent) {
|
private _endTimeChanged(ev: CustomEvent) {
|
||||||
this._dtend = new Date(
|
this._dtend = new Date(
|
||||||
this._dtend!.toISOString().split("T")[0] + "T" + ev.detail.value
|
this._dtend!.toISOString().split("T")[0] + "T" + ev.detail.value
|
||||||
);
|
);
|
||||||
this._dateChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _dateChanged() {
|
private _calculateData() {
|
||||||
|
const { startDate, startTime, endDate, endTime } = this._getLocaleStrings(
|
||||||
|
this._dtstart,
|
||||||
|
this._dtend
|
||||||
|
);
|
||||||
|
const data: CalendarEventMutableParams = {
|
||||||
|
summary: this._summary,
|
||||||
|
rrule: this._rrule,
|
||||||
|
dtstart: "",
|
||||||
|
dtend: "",
|
||||||
|
};
|
||||||
if (this._allDay) {
|
if (this._allDay) {
|
||||||
this._data!.dtstart = this._dtstart!.toISOString();
|
data.dtstart = startDate!;
|
||||||
// End date/time is exclusive when persisted
|
// End date/time is exclusive when persisted
|
||||||
this._data!.dtend = addDays(new Date(this._dtend!), 1).toISOString();
|
data.dtend = addDays(new Date(this._dtend!), 1).toLocaleDateString(
|
||||||
|
"en-CA"
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
this._data!.dtstart = this._dtstart!.toISOString();
|
data.dtstart = `${startDate}T${startTime}`;
|
||||||
this._data!.dtend = this._dtend!.toISOString();
|
data.dtend = `${endDate}T${endTime}`;
|
||||||
}
|
}
|
||||||
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleCalendarChanged(ev: CustomEvent) {
|
private _handleCalendarChanged(ev: CustomEvent) {
|
||||||
@ -290,7 +310,11 @@ class DialogCalendarEventEditor extends LitElement {
|
|||||||
private async _createEvent() {
|
private async _createEvent() {
|
||||||
this._submitting = true;
|
this._submitting = true;
|
||||||
try {
|
try {
|
||||||
await createCalendarEvent(this.hass!, this._calendarId!, this._data!);
|
await createCalendarEvent(
|
||||||
|
this.hass!,
|
||||||
|
this._calendarId!,
|
||||||
|
this._calculateData()
|
||||||
|
);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
this._error = err ? err.message : "Unknown error";
|
this._error = err ? err.message : "Unknown error";
|
||||||
} finally {
|
} finally {
|
||||||
@ -358,9 +382,10 @@ class DialogCalendarEventEditor extends LitElement {
|
|||||||
this._calendars = [];
|
this._calendars = [];
|
||||||
this._calendarId = undefined;
|
this._calendarId = undefined;
|
||||||
this._params = undefined;
|
this._params = undefined;
|
||||||
this._data = undefined;
|
|
||||||
this._dtstart = undefined;
|
this._dtstart = undefined;
|
||||||
this._dtend = undefined;
|
this._dtend = undefined;
|
||||||
|
this._summary = "";
|
||||||
|
this._rrule = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
|
@ -53,19 +53,22 @@ export class RecurrenceRuleEditor extends LitElement {
|
|||||||
protected willUpdate(changedProps: PropertyValues) {
|
protected willUpdate(changedProps: PropertyValues) {
|
||||||
super.willUpdate(changedProps);
|
super.willUpdate(changedProps);
|
||||||
|
|
||||||
if (!changedProps.has("value") && !changedProps.has("locale")) {
|
if (changedProps.has("locale")) {
|
||||||
|
this._allWeekdays = getWeekdays(firstWeekdayIndex(this.locale)).map(
|
||||||
|
(day: Weekday) => day.toString() as WeekdayStr
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!changedProps.has("value") || this._computedRRule === this.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._interval = 1;
|
this._interval = 1;
|
||||||
this._weekday.clear();
|
this._weekday.clear();
|
||||||
this._end = "never";
|
this._end = "never";
|
||||||
this._count = undefined;
|
this._count = undefined;
|
||||||
this._until = undefined;
|
this._until = undefined;
|
||||||
|
|
||||||
this._allWeekdays = getWeekdays(firstWeekdayIndex(this.locale)).map(
|
|
||||||
(day: Weekday) => day.toString() as WeekdayStr
|
|
||||||
);
|
|
||||||
|
|
||||||
this._computedRRule = this.value;
|
this._computedRRule = this.value;
|
||||||
if (this.value === "") {
|
if (this.value === "") {
|
||||||
this._freq = "none";
|
this._freq = "none";
|
||||||
@ -274,6 +277,7 @@ export class RecurrenceRuleEditor extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _onUntilChange(e: CustomEvent) {
|
private _onUntilChange(e: CustomEvent) {
|
||||||
|
e.stopPropagation();
|
||||||
this._until = new Date(e.detail.value);
|
this._until = new Date(e.detail.value);
|
||||||
this._updateRule();
|
this._updateRule();
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// and the values defined by rrule.js.
|
// and the values defined by rrule.js.
|
||||||
import { RRule, Frequency, Weekday } from "rrule";
|
import { RRule, Frequency, Weekday } from "rrule";
|
||||||
import type { WeekdayStr } from "rrule";
|
import type { WeekdayStr } from "rrule";
|
||||||
|
import { addDays, addMonths, addWeeks, addYears } from "date-fns";
|
||||||
|
|
||||||
export type RepeatFrequency =
|
export type RepeatFrequency =
|
||||||
| "none"
|
| "none"
|
||||||
@ -35,14 +36,14 @@ export function untilValue(freq: RepeatFrequency): Date {
|
|||||||
const increment = DEFAULT_COUNT[freq];
|
const increment = DEFAULT_COUNT[freq];
|
||||||
switch (freq) {
|
switch (freq) {
|
||||||
case "yearly":
|
case "yearly":
|
||||||
return new Date(new Date().setFullYear(today.getFullYear() + increment));
|
return addYears(today, increment);
|
||||||
case "monthly":
|
case "monthly":
|
||||||
return new Date(new Date().setMonth(today.getMonth() + increment));
|
return addMonths(today, increment);
|
||||||
case "weekly":
|
case "weekly":
|
||||||
return new Date(new Date().setDate(today.getDate() + 7 * increment));
|
return addWeeks(today, increment);
|
||||||
case "daily":
|
case "daily":
|
||||||
default:
|
default:
|
||||||
return new Date(new Date().setDate(today.getDate() + increment));
|
return addDays(today, increment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,16 +6,11 @@ import memoizeOne from "memoize-one";
|
|||||||
import { UNIT_C } from "../../../common/const";
|
import { UNIT_C } from "../../../common/const";
|
||||||
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
||||||
import { navigate } from "../../../common/navigate";
|
import { navigate } from "../../../common/navigate";
|
||||||
|
import { caseInsensitiveStringCompare } from "../../../common/string/compare";
|
||||||
import "../../../components/buttons/ha-progress-button";
|
import "../../../components/buttons/ha-progress-button";
|
||||||
import type { HaProgressButton } from "../../../components/buttons/ha-progress-button";
|
import type { HaProgressButton } from "../../../components/buttons/ha-progress-button";
|
||||||
import {
|
import { getCountryOptions } from "../../../components/country-datalist";
|
||||||
countries,
|
import { getCurrencyOptions } from "../../../components/currency-datalist";
|
||||||
countryDisplayNames,
|
|
||||||
} from "../../../components/country-datalist";
|
|
||||||
import {
|
|
||||||
currencies,
|
|
||||||
currencyDisplayNames,
|
|
||||||
} from "../../../components/currency-datalist";
|
|
||||||
import "../../../components/ha-card";
|
import "../../../components/ha-card";
|
||||||
import "../../../components/ha-formfield";
|
import "../../../components/ha-formfield";
|
||||||
import "../../../components/ha-radio";
|
import "../../../components/ha-radio";
|
||||||
@ -55,6 +50,8 @@ class HaConfigSectionGeneral extends LitElement {
|
|||||||
|
|
||||||
@state() private _location?: [number, number];
|
@state() private _location?: [number, number];
|
||||||
|
|
||||||
|
@state() private _languages?: { value: string; label: string }[];
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
const canEdit = ["storage", "default"].includes(
|
const canEdit = ["storage", "default"].includes(
|
||||||
this.hass.config.config_source
|
this.hass.config.config_source
|
||||||
@ -187,13 +184,11 @@ class HaConfigSectionGeneral extends LitElement {
|
|||||||
@closed=${stopPropagation}
|
@closed=${stopPropagation}
|
||||||
@change=${this._handleChange}
|
@change=${this._handleChange}
|
||||||
>
|
>
|
||||||
${currencies.map(
|
${getCurrencyOptions(this.hass.locale.language).map(
|
||||||
(currency) =>
|
({ value, label }) =>
|
||||||
html`<mwc-list-item .value=${currency}
|
html`<mwc-list-item .value=${value}>
|
||||||
>${currencyDisplayNames
|
${label}
|
||||||
? currencyDisplayNames.of(currency)
|
</mwc-list-item>`
|
||||||
: currency}</mwc-list-item
|
|
||||||
>`
|
|
||||||
)}</ha-select
|
)}</ha-select
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
@ -218,13 +213,11 @@ class HaConfigSectionGeneral extends LitElement {
|
|||||||
@closed=${stopPropagation}
|
@closed=${stopPropagation}
|
||||||
@change=${this._handleChange}
|
@change=${this._handleChange}
|
||||||
>
|
>
|
||||||
${countries.map(
|
${getCountryOptions(this.hass.locale.language).map(
|
||||||
(country) =>
|
({ value, label }) =>
|
||||||
html`<mwc-list-item .value=${country}
|
html`<mwc-list-item .value=${value}>
|
||||||
>${countryDisplayNames
|
${label}
|
||||||
? countryDisplayNames.of(country)
|
</mwc-list-item>`
|
||||||
: country}</mwc-list-item
|
|
||||||
>`
|
|
||||||
)}</ha-select
|
)}</ha-select
|
||||||
>
|
>
|
||||||
<ha-select
|
<ha-select
|
||||||
@ -239,12 +232,10 @@ class HaConfigSectionGeneral extends LitElement {
|
|||||||
@closed=${stopPropagation}
|
@closed=${stopPropagation}
|
||||||
@change=${this._handleChange}
|
@change=${this._handleChange}
|
||||||
>
|
>
|
||||||
${Object.entries(
|
${this._languages?.map(
|
||||||
this.hass.translationMetadata.translations
|
({ value, label }) =>
|
||||||
).map(
|
html`<mwc-list-item .value=${value}
|
||||||
([code, metadata]) =>
|
>${label}</mwc-list-item
|
||||||
html`<mwc-list-item .value=${code}
|
|
||||||
>${metadata.nativeName}</mwc-list-item
|
|
||||||
>`
|
>`
|
||||||
)}</ha-select
|
)}</ha-select
|
||||||
>
|
>
|
||||||
@ -300,6 +291,21 @@ class HaConfigSectionGeneral extends LitElement {
|
|||||||
this._elevation = this.hass.config.elevation;
|
this._elevation = this.hass.config.elevation;
|
||||||
this._timeZone = this.hass.config.time_zone;
|
this._timeZone = this.hass.config.time_zone;
|
||||||
this._name = this.hass.config.location_name;
|
this._name = this.hass.config.location_name;
|
||||||
|
this._computeLanguages();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _computeLanguages() {
|
||||||
|
if (!this.hass.translationMetadata?.translations) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._languages = Object.entries(this.hass.translationMetadata.translations)
|
||||||
|
.sort((a, b) =>
|
||||||
|
caseInsensitiveStringCompare(a[1].nativeName, b[1].nativeName)
|
||||||
|
)
|
||||||
|
.map(([value, metaData]) => ({
|
||||||
|
value,
|
||||||
|
label: metaData.nativeName,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleChange(ev) {
|
private _handleChange(ev) {
|
||||||
|
@ -51,6 +51,8 @@ import {
|
|||||||
fetchIntegrationManifest,
|
fetchIntegrationManifest,
|
||||||
fetchIntegrationManifests,
|
fetchIntegrationManifests,
|
||||||
IntegrationManifest,
|
IntegrationManifest,
|
||||||
|
IntegrationLogInfo,
|
||||||
|
subscribeLogInfo,
|
||||||
} from "../../../data/integration";
|
} from "../../../data/integration";
|
||||||
import {
|
import {
|
||||||
getIntegrationDescriptions,
|
getIntegrationDescriptions,
|
||||||
@ -154,6 +156,10 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
@state() private _diagnosticHandlers?: Record<string, boolean>;
|
@state() private _diagnosticHandlers?: Record<string, boolean>;
|
||||||
|
|
||||||
|
@state() private _logInfos?: {
|
||||||
|
[integration: string]: IntegrationLogInfo;
|
||||||
|
};
|
||||||
|
|
||||||
public hassSubscribe(): Array<UnsubscribeFunc | Promise<UnsubscribeFunc>> {
|
public hassSubscribe(): Array<UnsubscribeFunc | Promise<UnsubscribeFunc>> {
|
||||||
return [
|
return [
|
||||||
subscribeEntityRegistry(this.hass.connection, (entries) => {
|
subscribeEntityRegistry(this.hass.connection, (entries) => {
|
||||||
@ -230,6 +236,13 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
},
|
},
|
||||||
{ type: ["device", "hub", "service"] }
|
{ type: ["device", "hub", "service"] }
|
||||||
),
|
),
|
||||||
|
subscribeLogInfo(this.hass.connection, (log_infos) => {
|
||||||
|
const logInfoLookup: { [integration: string]: IntegrationLogInfo } = {};
|
||||||
|
for (const log_info of log_infos) {
|
||||||
|
logInfoLookup[log_info.domain] = log_info;
|
||||||
|
}
|
||||||
|
this._logInfos = logInfoLookup;
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -514,6 +527,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
.supportsDiagnostics=${this._diagnosticHandlers
|
.supportsDiagnostics=${this._diagnosticHandlers
|
||||||
? this._diagnosticHandlers[domain]
|
? this._diagnosticHandlers[domain]
|
||||||
: false}
|
: false}
|
||||||
|
.logInfo=${this._logInfos ? this._logInfos[domain] : null}
|
||||||
></ha-integration-card>`
|
></ha-integration-card>`
|
||||||
)
|
)
|
||||||
: this._filter &&
|
: this._filter &&
|
||||||
|
@ -5,6 +5,8 @@ import {
|
|||||||
mdiAlertCircle,
|
mdiAlertCircle,
|
||||||
mdiBookshelf,
|
mdiBookshelf,
|
||||||
mdiBug,
|
mdiBug,
|
||||||
|
mdiBugPlay,
|
||||||
|
mdiBugStop,
|
||||||
mdiChevronLeft,
|
mdiChevronLeft,
|
||||||
mdiCog,
|
mdiCog,
|
||||||
mdiDelete,
|
mdiDelete,
|
||||||
@ -47,11 +49,17 @@ import {
|
|||||||
ERROR_STATES,
|
ERROR_STATES,
|
||||||
RECOVERABLE_STATES,
|
RECOVERABLE_STATES,
|
||||||
} from "../../../data/config_entries";
|
} from "../../../data/config_entries";
|
||||||
|
import { getErrorLogDownloadUrl } from "../../../data/error_log";
|
||||||
import type { DeviceRegistryEntry } from "../../../data/device_registry";
|
import type { DeviceRegistryEntry } from "../../../data/device_registry";
|
||||||
import { getConfigEntryDiagnosticsDownloadUrl } from "../../../data/diagnostics";
|
import { getConfigEntryDiagnosticsDownloadUrl } from "../../../data/diagnostics";
|
||||||
import type { EntityRegistryEntry } from "../../../data/entity_registry";
|
import type { EntityRegistryEntry } from "../../../data/entity_registry";
|
||||||
import type { IntegrationManifest } from "../../../data/integration";
|
import type { IntegrationManifest } from "../../../data/integration";
|
||||||
import { integrationIssuesUrl } from "../../../data/integration";
|
import {
|
||||||
|
integrationIssuesUrl,
|
||||||
|
IntegrationLogInfo,
|
||||||
|
LogSeverity,
|
||||||
|
setIntegrationLogLevel,
|
||||||
|
} from "../../../data/integration";
|
||||||
import { showConfigEntrySystemOptionsDialog } from "../../../dialogs/config-entry-system-options/show-dialog-config-entry-system-options";
|
import { showConfigEntrySystemOptionsDialog } from "../../../dialogs/config-entry-system-options/show-dialog-config-entry-system-options";
|
||||||
import { showOptionsFlowDialog } from "../../../dialogs/config-flow/show-dialog-options-flow";
|
import { showOptionsFlowDialog } from "../../../dialogs/config-flow/show-dialog-options-flow";
|
||||||
import {
|
import {
|
||||||
@ -95,6 +103,8 @@ export class HaIntegrationCard extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public supportsDiagnostics = false;
|
@property({ type: Boolean }) public supportsDiagnostics = false;
|
||||||
|
|
||||||
|
@property() public logInfo?: IntegrationLogInfo;
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
let item = this._selectededConfigEntry;
|
let item = this._selectededConfigEntry;
|
||||||
|
|
||||||
@ -137,6 +147,8 @@ export class HaIntegrationCard extends LitElement {
|
|||||||
.localizedDomainName=${item ? item.localized_domain_name : undefined}
|
.localizedDomainName=${item ? item.localized_domain_name : undefined}
|
||||||
.manifest=${this.manifest}
|
.manifest=${this.manifest}
|
||||||
.configEntry=${item}
|
.configEntry=${item}
|
||||||
|
.debugLoggingEnabled=${this.logInfo &&
|
||||||
|
this.logInfo.level === LogSeverity.DEBUG}
|
||||||
>
|
>
|
||||||
${this.items.length > 1
|
${this.items.length > 1
|
||||||
? html`
|
? html`
|
||||||
@ -398,6 +410,28 @@ export class HaIntegrationCard extends LitElement {
|
|||||||
</mwc-list-item>
|
</mwc-list-item>
|
||||||
</a>`
|
</a>`
|
||||||
: ""}
|
: ""}
|
||||||
|
${this.logInfo
|
||||||
|
? html`<mwc-list-item
|
||||||
|
@request-selected=${this.logInfo.level === LogSeverity.DEBUG
|
||||||
|
? this._handleDisableDebugLogging
|
||||||
|
: this._handleEnableDebugLogging}
|
||||||
|
graphic="icon"
|
||||||
|
>
|
||||||
|
${this.logInfo.level === LogSeverity.DEBUG
|
||||||
|
? this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.config_entry.disable_debug_logging"
|
||||||
|
)
|
||||||
|
: this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.config_entry.enable_debug_logging"
|
||||||
|
)}
|
||||||
|
<ha-svg-icon
|
||||||
|
slot="graphic"
|
||||||
|
.path=${this.logInfo.level === LogSeverity.DEBUG
|
||||||
|
? mdiBugStop
|
||||||
|
: mdiBugPlay}
|
||||||
|
></ha-svg-icon>
|
||||||
|
</mwc-list-item>`
|
||||||
|
: ""}
|
||||||
${this.manifest &&
|
${this.manifest &&
|
||||||
(this.manifest.is_built_in ||
|
(this.manifest.is_built_in ||
|
||||||
this.manifest.issue_tracker ||
|
this.manifest.issue_tracker ||
|
||||||
@ -501,6 +535,34 @@ export class HaIntegrationCard extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _handleEnableDebugLogging(ev: MouseEvent) {
|
||||||
|
const configEntry = ((ev.target as HTMLElement).closest("ha-card") as any)
|
||||||
|
.configEntry;
|
||||||
|
const integration = configEntry.domain;
|
||||||
|
await setIntegrationLogLevel(
|
||||||
|
this.hass,
|
||||||
|
integration,
|
||||||
|
LogSeverity[LogSeverity.DEBUG],
|
||||||
|
"once"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _handleDisableDebugLogging(ev: MouseEvent) {
|
||||||
|
const configEntry = ((ev.target as HTMLElement).closest("ha-card") as any)
|
||||||
|
.configEntry;
|
||||||
|
const integration = configEntry.domain;
|
||||||
|
await setIntegrationLogLevel(
|
||||||
|
this.hass,
|
||||||
|
integration,
|
||||||
|
LogSeverity[LogSeverity.NOTSET],
|
||||||
|
"once"
|
||||||
|
);
|
||||||
|
const timeString = new Date().toISOString().replace(/:/g, "-");
|
||||||
|
const logFileName = `home-assistant_${integration}_${timeString}.log`;
|
||||||
|
const signedUrl = await getSignedPath(this.hass, getErrorLogDownloadUrl);
|
||||||
|
fileDownload(signedUrl.path, logFileName);
|
||||||
|
}
|
||||||
|
|
||||||
private get _selectededConfigEntry(): ConfigEntryExtended | undefined {
|
private get _selectededConfigEntry(): ConfigEntryExtended | undefined {
|
||||||
return this.items.length === 1
|
return this.items.length === 1
|
||||||
? this.items[0]
|
? this.items[0]
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { mdiCloud, mdiPackageVariant, mdiSyncOff } from "@mdi/js";
|
import { mdiBugPlay, mdiCloud, mdiPackageVariant, mdiSyncOff } from "@mdi/js";
|
||||||
import "@polymer/paper-tooltip/paper-tooltip";
|
import "@polymer/paper-tooltip/paper-tooltip";
|
||||||
import { css, html, LitElement, TemplateResult } from "lit";
|
import { css, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
@ -24,6 +24,8 @@ export class HaIntegrationHeader extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public configEntry?: ConfigEntry;
|
@property({ attribute: false }) public configEntry?: ConfigEntry;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public debugLoggingEnabled?: boolean;
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
let primary: string;
|
let primary: string;
|
||||||
let secondary: string | undefined;
|
let secondary: string | undefined;
|
||||||
@ -76,6 +78,15 @@ export class HaIntegrationHeader extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.debugLoggingEnabled) {
|
||||||
|
icons.push([
|
||||||
|
mdiBugPlay,
|
||||||
|
this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.config_entry.debug_logging_enabled"
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
${!this.banner ? "" : html`<div class="banner">${this.banner}</div>`}
|
${!this.banner ? "" : html`<div class="banner">${this.banner}</div>`}
|
||||||
<slot name="above-header"></slot>
|
<slot name="above-header"></slot>
|
||||||
|
@ -205,7 +205,8 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
|||||||
${computeStateDisplay(
|
${computeStateDisplay(
|
||||||
this.hass.localize,
|
this.hass.localize,
|
||||||
stateObj,
|
stateObj,
|
||||||
this.hass.locale
|
this.hass.locale,
|
||||||
|
this.hass.entities
|
||||||
)}
|
)}
|
||||||
</span>`
|
</span>`
|
||||||
: ""}
|
: ""}
|
||||||
|
@ -167,7 +167,8 @@ export class HuiEntityCard extends LitElement implements LovelaceCard {
|
|||||||
: computeStateDisplay(
|
: computeStateDisplay(
|
||||||
this.hass.localize,
|
this.hass.localize,
|
||||||
stateObj,
|
stateObj,
|
||||||
this.hass.locale
|
this.hass.locale,
|
||||||
|
this.hass.entities
|
||||||
)}</span
|
)}</span
|
||||||
>${showUnit
|
>${showUnit
|
||||||
? html`
|
? html`
|
||||||
|
@ -335,7 +335,8 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard {
|
|||||||
: computeStateDisplay(
|
: computeStateDisplay(
|
||||||
this.hass!.localize,
|
this.hass!.localize,
|
||||||
stateObj,
|
stateObj,
|
||||||
this.hass!.locale
|
this.hass!.locale,
|
||||||
|
this.hass!.entities
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
|
@ -160,7 +160,8 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
|
|||||||
${computeStateDisplay(
|
${computeStateDisplay(
|
||||||
this.hass.localize,
|
this.hass.localize,
|
||||||
stateObj,
|
stateObj,
|
||||||
this.hass.locale
|
this.hass.locale,
|
||||||
|
this.hass.entities
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
|
@ -121,7 +121,8 @@ class HuiPictureEntityCard extends LitElement implements LovelaceCard {
|
|||||||
const entityState = computeStateDisplay(
|
const entityState = computeStateDisplay(
|
||||||
this.hass!.localize,
|
this.hass!.localize,
|
||||||
stateObj,
|
stateObj,
|
||||||
this.hass.locale
|
this.hass.locale,
|
||||||
|
this.hass.entities
|
||||||
);
|
);
|
||||||
|
|
||||||
let footer: TemplateResult | string = "";
|
let footer: TemplateResult | string = "";
|
||||||
|
@ -255,7 +255,8 @@ class HuiPictureGlanceCard extends LitElement implements LovelaceCard {
|
|||||||
title=${`${computeStateName(stateObj)} : ${computeStateDisplay(
|
title=${`${computeStateName(stateObj)} : ${computeStateDisplay(
|
||||||
this.hass!.localize,
|
this.hass!.localize,
|
||||||
stateObj,
|
stateObj,
|
||||||
this.hass!.locale
|
this.hass!.locale,
|
||||||
|
this.hass!.entities
|
||||||
)}`}
|
)}`}
|
||||||
>
|
>
|
||||||
<ha-state-icon
|
<ha-state-icon
|
||||||
@ -277,7 +278,8 @@ class HuiPictureGlanceCard extends LitElement implements LovelaceCard {
|
|||||||
: computeStateDisplay(
|
: computeStateDisplay(
|
||||||
this.hass!.localize,
|
this.hass!.localize,
|
||||||
stateObj,
|
stateObj,
|
||||||
this.hass!.locale
|
this.hass!.locale,
|
||||||
|
this.hass!.entities
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
`}
|
`}
|
||||||
|
@ -201,7 +201,8 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
|||||||
return computeStateDisplay(
|
return computeStateDisplay(
|
||||||
this.hass!.localize,
|
this.hass!.localize,
|
||||||
stateObj,
|
stateObj,
|
||||||
this.hass!.locale
|
this.hass!.locale,
|
||||||
|
this.hass!.entities
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,7 +221,8 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
|||||||
${computeStateDisplay(
|
${computeStateDisplay(
|
||||||
this.hass.localize,
|
this.hass.localize,
|
||||||
stateObj,
|
stateObj,
|
||||||
this.hass.locale
|
this.hass.locale,
|
||||||
|
this.hass.entities
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div class="name" .title=${name}>${name}</div>
|
<div class="name" .title=${name}>${name}</div>
|
||||||
|
@ -83,7 +83,12 @@ class HuiStateLabelElement extends LitElement implements LovelaceElement {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
${this._config.prefix}${!this._config.attribute
|
${this._config.prefix}${!this._config.attribute
|
||||||
? computeStateDisplay(this.hass.localize, stateObj, this.hass.locale)
|
? computeStateDisplay(
|
||||||
|
this.hass.localize,
|
||||||
|
stateObj,
|
||||||
|
this.hass.locale,
|
||||||
|
this.hass.entities
|
||||||
|
)
|
||||||
: stateObj.attributes[this._config.attribute]}${this._config.suffix}
|
: stateObj.attributes[this._config.attribute]}${this._config.suffix}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
@ -69,7 +69,8 @@ class HuiGroupEntityRow extends LitElement implements LovelaceRow {
|
|||||||
${computeStateDisplay(
|
${computeStateDisplay(
|
||||||
this.hass!.localize,
|
this.hass!.localize,
|
||||||
stateObj,
|
stateObj,
|
||||||
this.hass.locale
|
this.hass.locale,
|
||||||
|
this.hass.entities
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
`}
|
`}
|
||||||
|
@ -100,6 +100,7 @@ class HuiInputNumberEntityRow extends LitElement implements LovelaceRow {
|
|||||||
this.hass.localize,
|
this.hass.localize,
|
||||||
stateObj,
|
stateObj,
|
||||||
this.hass.locale,
|
this.hass.locale,
|
||||||
|
this.hass.entities,
|
||||||
stateObj.state
|
stateObj.state
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
|
@ -193,7 +193,12 @@ class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow {
|
|||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.config=${this._config}
|
.config=${this._config}
|
||||||
.secondaryText=${mediaDescription ||
|
.secondaryText=${mediaDescription ||
|
||||||
computeStateDisplay(this.hass.localize, stateObj, this.hass.locale)}
|
computeStateDisplay(
|
||||||
|
this.hass.localize,
|
||||||
|
stateObj,
|
||||||
|
this.hass.locale,
|
||||||
|
this.hass.entities
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
${supportsFeature(stateObj, SUPPORT_TURN_ON) &&
|
${supportsFeature(stateObj, SUPPORT_TURN_ON) &&
|
||||||
|
@ -104,6 +104,7 @@ class HuiNumberEntityRow extends LitElement implements LovelaceRow {
|
|||||||
this.hass.localize,
|
this.hass.localize,
|
||||||
stateObj,
|
stateObj,
|
||||||
this.hass.locale,
|
this.hass.locale,
|
||||||
|
this.hass.entities,
|
||||||
stateObj.state
|
stateObj.state
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
|
@ -83,7 +83,8 @@ class HuiSensorEntityRow extends LitElement implements LovelaceRow {
|
|||||||
: computeStateDisplay(
|
: computeStateDisplay(
|
||||||
this.hass!.localize,
|
this.hass!.localize,
|
||||||
stateObj,
|
stateObj,
|
||||||
this.hass.locale
|
this.hass.locale,
|
||||||
|
this.hass.entities
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</hui-generic-entity-row>
|
</hui-generic-entity-row>
|
||||||
|
@ -49,7 +49,12 @@ class HuiSimpleEntityRow extends LitElement implements LovelaceRow {
|
|||||||
|
|
||||||
return html`
|
return html`
|
||||||
<hui-generic-entity-row .hass=${this.hass} .config=${this._config}>
|
<hui-generic-entity-row .hass=${this.hass} .config=${this._config}>
|
||||||
${computeStateDisplay(this.hass!.localize, stateObj, this.hass.locale)}
|
${computeStateDisplay(
|
||||||
|
this.hass!.localize,
|
||||||
|
stateObj,
|
||||||
|
this.hass.locale,
|
||||||
|
this.hass.entities
|
||||||
|
)}
|
||||||
</hui-generic-entity-row>
|
</hui-generic-entity-row>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,8 @@ class HuiToggleEntityRow extends LitElement implements LovelaceRow {
|
|||||||
${computeStateDisplay(
|
${computeStateDisplay(
|
||||||
this.hass!.localize,
|
this.hass!.localize,
|
||||||
stateObj,
|
stateObj,
|
||||||
this.hass!.locale
|
this.hass!.locale,
|
||||||
|
this.hass!.entities
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
`}
|
`}
|
||||||
|
@ -120,7 +120,8 @@ class HuiWeatherEntityRow extends LitElement implements LovelaceRow {
|
|||||||
? computeStateDisplay(
|
? computeStateDisplay(
|
||||||
this.hass.localize,
|
this.hass.localize,
|
||||||
stateObj,
|
stateObj,
|
||||||
this.hass.locale
|
this.hass.locale,
|
||||||
|
this.hass.entities
|
||||||
)
|
)
|
||||||
: html`
|
: html`
|
||||||
${formatNumber(
|
${formatNumber(
|
||||||
|
@ -58,7 +58,12 @@ class StateCardConfigurator extends LocalizeMixin(PolymerElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_localizeState(stateObj) {
|
_localizeState(stateObj) {
|
||||||
return computeStateDisplay(this.hass.localize, stateObj, this.hass.locale);
|
return computeStateDisplay(
|
||||||
|
this.hass.localize,
|
||||||
|
stateObj,
|
||||||
|
this.hass.locale,
|
||||||
|
this.hass.entities
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
customElements.define("state-card-configurator", StateCardConfigurator);
|
customElements.define("state-card-configurator", StateCardConfigurator);
|
||||||
|
@ -52,7 +52,8 @@ export class StateCardDisplay extends LitElement {
|
|||||||
: computeStateDisplay(
|
: computeStateDisplay(
|
||||||
this.hass!.localize,
|
this.hass!.localize,
|
||||||
this.stateObj,
|
this.stateObj,
|
||||||
this.hass.locale
|
this.hass.locale,
|
||||||
|
this.hass.entities
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -165,6 +165,7 @@ class StateCardInputNumber extends mixinBehaviors(
|
|||||||
this.hass.localize,
|
this.hass.localize,
|
||||||
newVal,
|
newVal,
|
||||||
this.hass.locale,
|
this.hass.locale,
|
||||||
|
this.hass.entities,
|
||||||
newVal.state
|
newVal.state
|
||||||
),
|
),
|
||||||
mode: String(newVal.attributes.mode),
|
mode: String(newVal.attributes.mode),
|
||||||
|
@ -85,7 +85,12 @@ class StateCardMediaPlayer extends LocalizeMixin(PolymerElement) {
|
|||||||
computePrimaryText(localize, playerObj) {
|
computePrimaryText(localize, playerObj) {
|
||||||
return (
|
return (
|
||||||
playerObj.primaryTitle ||
|
playerObj.primaryTitle ||
|
||||||
computeStateDisplay(localize, playerObj.stateObj, this.hass.locale)
|
computeStateDisplay(
|
||||||
|
localize,
|
||||||
|
playerObj.stateObj,
|
||||||
|
this.hass.locale,
|
||||||
|
this.hass.entities
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -234,7 +234,7 @@ export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
|
|||||||
category: Parameters<typeof getHassTranslations>[2],
|
category: Parameters<typeof getHassTranslations>[2],
|
||||||
integration?: Parameters<typeof getHassTranslations>[3],
|
integration?: Parameters<typeof getHassTranslations>[3],
|
||||||
configFlow?: Parameters<typeof getHassTranslations>[4],
|
configFlow?: Parameters<typeof getHassTranslations>[4],
|
||||||
force = false
|
force = true
|
||||||
): Promise<LocalizeFunc> {
|
): Promise<LocalizeFunc> {
|
||||||
if (
|
if (
|
||||||
__BACKWARDS_COMPAT__ &&
|
__BACKWARDS_COMPAT__ &&
|
||||||
|
@ -3011,10 +3011,12 @@
|
|||||||
"system_options": "System options",
|
"system_options": "System options",
|
||||||
"documentation": "Documentation",
|
"documentation": "Documentation",
|
||||||
"download_diagnostics": "Download diagnostics",
|
"download_diagnostics": "Download diagnostics",
|
||||||
|
"disable_debug_logging": "Disable debug logging",
|
||||||
"known_issues": "Known issues",
|
"known_issues": "Known issues",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"delete_confirm_title": "Delete {title}?",
|
"delete_confirm_title": "Delete {title}?",
|
||||||
"delete_confirm_text": "Its devices and entities will be permanently deleted.",
|
"delete_confirm_text": "Its devices and entities will be permanently deleted.",
|
||||||
|
"enable_debug_logging": "Enable debug logging",
|
||||||
"reload": "Reload",
|
"reload": "Reload",
|
||||||
"restart_confirm": "Restart Home Assistant to finish removing this integration",
|
"restart_confirm": "Restart Home Assistant to finish removing this integration",
|
||||||
"reload_confirm": "The integration was reloaded",
|
"reload_confirm": "The integration was reloaded",
|
||||||
@ -3049,6 +3051,7 @@
|
|||||||
"depends_on_cloud": "Depends on the cloud",
|
"depends_on_cloud": "Depends on the cloud",
|
||||||
"yaml_only": "Needs manual configuration",
|
"yaml_only": "Needs manual configuration",
|
||||||
"disabled_polling": "Automatic polling for updated data disabled",
|
"disabled_polling": "Automatic polling for updated data disabled",
|
||||||
|
"debug_logging_enabled": "Debug logging enabled",
|
||||||
"state": {
|
"state": {
|
||||||
"loaded": "Loaded",
|
"loaded": "Loaded",
|
||||||
"setup_error": "Failed to set up",
|
"setup_error": "Failed to set up",
|
||||||
|
@ -31,7 +31,7 @@ describe("computeStateDisplay", () => {
|
|||||||
attributes: {},
|
attributes: {},
|
||||||
};
|
};
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
computeStateDisplay(localize, stateObj, localeData),
|
computeStateDisplay(localize, stateObj, localeData, {}),
|
||||||
"component.binary_sensor.state._.off"
|
"component.binary_sensor.state._.off"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -45,7 +45,7 @@ describe("computeStateDisplay", () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
computeStateDisplay(localize, stateObj, localeData),
|
computeStateDisplay(localize, stateObj, localeData, {}),
|
||||||
"component.binary_sensor.state.moisture.off"
|
"component.binary_sensor.state.moisture.off"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -65,7 +65,7 @@ describe("computeStateDisplay", () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
computeStateDisplay(altLocalize, stateObj, localeData),
|
computeStateDisplay(altLocalize, stateObj, localeData, {}),
|
||||||
"component.binary_sensor.state.invalid_device_class.off"
|
"component.binary_sensor.state.invalid_device_class.off"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -79,7 +79,7 @@ describe("computeStateDisplay", () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
computeStateDisplay(localize, stateObj, localeData),
|
computeStateDisplay(localize, stateObj, localeData, {}),
|
||||||
"123 m"
|
"123 m"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -93,7 +93,7 @@ describe("computeStateDisplay", () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
computeStateDisplay(localize, stateObj, localeData),
|
computeStateDisplay(localize, stateObj, localeData, {}),
|
||||||
"1,234.5 m"
|
"1,234.5 m"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -107,7 +107,7 @@ describe("computeStateDisplay", () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
computeStateDisplay(localize, stateObj, localeData),
|
computeStateDisplay(localize, stateObj, localeData, {}),
|
||||||
"1,234.5"
|
"1,234.5"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -127,7 +127,7 @@ describe("computeStateDisplay", () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
computeStateDisplay(altLocalize, stateObj, localeData),
|
computeStateDisplay(altLocalize, stateObj, localeData, {}),
|
||||||
"state.default.unknown"
|
"state.default.unknown"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -147,7 +147,7 @@ describe("computeStateDisplay", () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
computeStateDisplay(altLocalize, stateObj, localeData),
|
computeStateDisplay(altLocalize, stateObj, localeData, {}),
|
||||||
"state.default.unavailable"
|
"state.default.unavailable"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -165,7 +165,7 @@ describe("computeStateDisplay", () => {
|
|||||||
attributes: {},
|
attributes: {},
|
||||||
};
|
};
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
computeStateDisplay(altLocalize, stateObj, localeData),
|
computeStateDisplay(altLocalize, stateObj, localeData, {}),
|
||||||
"component.sensor.state._.custom_state"
|
"component.sensor.state._.custom_state"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -187,14 +187,14 @@ describe("computeStateDisplay", () => {
|
|||||||
};
|
};
|
||||||
it("Uses am/pm time format", () => {
|
it("Uses am/pm time format", () => {
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
computeStateDisplay(localize, stateObj, localeData),
|
computeStateDisplay(localize, stateObj, localeData, {}),
|
||||||
"November 18, 2017 at 11:12 PM"
|
"November 18, 2017 at 11:12 PM"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
it("Uses 24h time format", () => {
|
it("Uses 24h time format", () => {
|
||||||
localeData.time_format = TimeFormat.twenty_four;
|
localeData.time_format = TimeFormat.twenty_four;
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
computeStateDisplay(localize, stateObj, localeData),
|
computeStateDisplay(localize, stateObj, localeData, {}),
|
||||||
"November 18, 2017 at 23:12"
|
"November 18, 2017 at 23:12"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -216,7 +216,7 @@ describe("computeStateDisplay", () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
computeStateDisplay(localize, stateObj, localeData),
|
computeStateDisplay(localize, stateObj, localeData, {}),
|
||||||
"November 18, 2017"
|
"November 18, 2017"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -239,14 +239,14 @@ describe("computeStateDisplay", () => {
|
|||||||
it("Uses am/pm time format", () => {
|
it("Uses am/pm time format", () => {
|
||||||
localeData.time_format = TimeFormat.am_pm;
|
localeData.time_format = TimeFormat.am_pm;
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
computeStateDisplay(localize, stateObj, localeData),
|
computeStateDisplay(localize, stateObj, localeData, {}),
|
||||||
"11:12 PM"
|
"11:12 PM"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
it("Uses 24h time format", () => {
|
it("Uses 24h time format", () => {
|
||||||
localeData.time_format = TimeFormat.twenty_four;
|
localeData.time_format = TimeFormat.twenty_four;
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
computeStateDisplay(localize, stateObj, localeData),
|
computeStateDisplay(localize, stateObj, localeData, {}),
|
||||||
"23:12"
|
"23:12"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -273,6 +273,7 @@ describe("computeStateDisplay", () => {
|
|||||||
localize,
|
localize,
|
||||||
stateObj,
|
stateObj,
|
||||||
localeData,
|
localeData,
|
||||||
|
{},
|
||||||
"2021-07-04 15:40:03"
|
"2021-07-04 15:40:03"
|
||||||
),
|
),
|
||||||
"July 4, 2021 at 3:40 PM"
|
"July 4, 2021 at 3:40 PM"
|
||||||
@ -285,6 +286,7 @@ describe("computeStateDisplay", () => {
|
|||||||
localize,
|
localize,
|
||||||
stateObj,
|
stateObj,
|
||||||
localeData,
|
localeData,
|
||||||
|
{},
|
||||||
"2021-07-04 15:40:03"
|
"2021-07-04 15:40:03"
|
||||||
),
|
),
|
||||||
"July 4, 2021 at 15:40"
|
"July 4, 2021 at 15:40"
|
||||||
@ -308,7 +310,7 @@ describe("computeStateDisplay", () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
computeStateDisplay(localize, stateObj, localeData, "2021-07-04"),
|
computeStateDisplay(localize, stateObj, localeData, {}, "2021-07-04"),
|
||||||
"July 4, 2021"
|
"July 4, 2021"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -331,14 +333,14 @@ describe("computeStateDisplay", () => {
|
|||||||
it("Uses am/pm time format", () => {
|
it("Uses am/pm time format", () => {
|
||||||
localeData.time_format = TimeFormat.am_pm;
|
localeData.time_format = TimeFormat.am_pm;
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
computeStateDisplay(localize, stateObj, localeData, "17:05:07"),
|
computeStateDisplay(localize, stateObj, localeData, {}, "17:05:07"),
|
||||||
"5:05 PM"
|
"5:05 PM"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
it("Uses 24h time format", () => {
|
it("Uses 24h time format", () => {
|
||||||
localeData.time_format = TimeFormat.twenty_four;
|
localeData.time_format = TimeFormat.twenty_four;
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
computeStateDisplay(localize, stateObj, localeData, "17:05:07"),
|
computeStateDisplay(localize, stateObj, localeData, {}, "17:05:07"),
|
||||||
"17:05"
|
"17:05"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -357,7 +359,7 @@ describe("computeStateDisplay", () => {
|
|||||||
attributes: {},
|
attributes: {},
|
||||||
};
|
};
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
computeStateDisplay(altLocalize, stateObj, localeData),
|
computeStateDisplay(altLocalize, stateObj, localeData, {}),
|
||||||
"state.default.unavailable"
|
"state.default.unavailable"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -372,8 +374,26 @@ describe("computeStateDisplay", () => {
|
|||||||
attributes: {},
|
attributes: {},
|
||||||
};
|
};
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
computeStateDisplay(altLocalize, stateObj, localeData),
|
computeStateDisplay(altLocalize, stateObj, localeData, {}),
|
||||||
"My Custom State"
|
"My Custom State"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Localizes using translation key", () => {
|
||||||
|
const stateObj: any = {
|
||||||
|
entity_id: "sensor.test",
|
||||||
|
state: "custom_state",
|
||||||
|
attributes: {},
|
||||||
|
};
|
||||||
|
const entities: any = {
|
||||||
|
"sensor.test": {
|
||||||
|
translation_key: "custom_translation",
|
||||||
|
platform: "custom_integration",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
assert.strictEqual(
|
||||||
|
computeStateDisplay(localize, stateObj, localeData, entities),
|
||||||
|
"component.custom_integration.entity.sensor.custom_translation.state.custom_state"
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user