mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-24 01:36:49 +00:00
Merge pull request #10190 from home-assistant/dev
This commit is contained in:
commit
80bbc9990a
@ -29,6 +29,7 @@
|
|||||||
"__BUILD__": false,
|
"__BUILD__": false,
|
||||||
"__VERSION__": false,
|
"__VERSION__": false,
|
||||||
"__STATIC_PATH__": false,
|
"__STATIC_PATH__": false,
|
||||||
|
"__SUPERVISOR__": false,
|
||||||
"Polymer": true
|
"Polymer": true
|
||||||
},
|
},
|
||||||
"env": {
|
"env": {
|
||||||
|
@ -35,6 +35,7 @@ module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({
|
|||||||
__BUILD__: JSON.stringify(latestBuild ? "latest" : "es5"),
|
__BUILD__: JSON.stringify(latestBuild ? "latest" : "es5"),
|
||||||
__VERSION__: JSON.stringify(env.version()),
|
__VERSION__: JSON.stringify(env.version()),
|
||||||
__DEMO__: false,
|
__DEMO__: false,
|
||||||
|
__SUPERVISOR__: false,
|
||||||
__BACKWARDS_COMPAT__: false,
|
__BACKWARDS_COMPAT__: false,
|
||||||
__STATIC_PATH__: "/static/",
|
__STATIC_PATH__: "/static/",
|
||||||
"process.env.NODE_ENV": JSON.stringify(
|
"process.env.NODE_ENV": JSON.stringify(
|
||||||
@ -194,6 +195,9 @@ module.exports.config = {
|
|||||||
publicPath: publicPath(latestBuild, paths.hassio_publicPath),
|
publicPath: publicPath(latestBuild, paths.hassio_publicPath),
|
||||||
isProdBuild,
|
isProdBuild,
|
||||||
latestBuild,
|
latestBuild,
|
||||||
|
defineOverlay: {
|
||||||
|
__SUPERVISOR__: true,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import { property } from "lit/decorators";
|
|||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { atLeastVersion } from "../../../src/common/config/version";
|
import { atLeastVersion } from "../../../src/common/config/version";
|
||||||
import { navigate } from "../../../src/common/navigate";
|
import { navigate } from "../../../src/common/navigate";
|
||||||
|
import { caseInsensitiveStringCompare } from "../../../src/common/string/compare";
|
||||||
import "../../../src/components/ha-card";
|
import "../../../src/components/ha-card";
|
||||||
import {
|
import {
|
||||||
HassioAddonInfo,
|
HassioAddonInfo,
|
||||||
@ -32,7 +33,7 @@ class HassioAddonRepositoryEl extends LitElement {
|
|||||||
return filterAndSort(addons, filter);
|
return filterAndSort(addons, filter);
|
||||||
}
|
}
|
||||||
return addons.sort((a, b) =>
|
return addons.sort((a, b) =>
|
||||||
a.name.toUpperCase() < b.name.toUpperCase() ? -1 : 1
|
caseInsensitiveStringCompare(a.name, b.name)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -297,10 +297,11 @@ class HassioAddonInfo extends LitElement {
|
|||||||
})}
|
})}
|
||||||
@click=${this._showMoreInfo}
|
@click=${this._showMoreInfo}
|
||||||
id="rating"
|
id="rating"
|
||||||
.value=${this.addon.rating}
|
|
||||||
label="rating"
|
label="rating"
|
||||||
description=""
|
description=""
|
||||||
></ha-label-badge>
|
>
|
||||||
|
${this.addon.rating}
|
||||||
|
</ha-label-badge>
|
||||||
${this.addon.host_network
|
${this.addon.host_network
|
||||||
? html`
|
? html`
|
||||||
<ha-label-badge
|
<ha-label-badge
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { mdiHelpCircle } from "@mdi/js";
|
import { mdiHelpCircle } from "@mdi/js";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import "../../../src/components/ha-relative-time";
|
|
||||||
import "../../../src/components/ha-svg-icon";
|
import "../../../src/components/ha-svg-icon";
|
||||||
import { HomeAssistant } from "../../../src/types";
|
import { HomeAssistant } from "../../../src/types";
|
||||||
|
|
||||||
@ -19,8 +18,6 @@ class HassioCardContent extends LitElement {
|
|||||||
|
|
||||||
@property() public topbarClass?: string;
|
@property() public topbarClass?: string;
|
||||||
|
|
||||||
@property() public datetime?: string;
|
|
||||||
|
|
||||||
@property() public iconTitle?: string;
|
@property() public iconTitle?: string;
|
||||||
|
|
||||||
@property() public iconClass?: string;
|
@property() public iconClass?: string;
|
||||||
@ -56,15 +53,6 @@ class HassioCardContent extends LitElement {
|
|||||||
/* treat as available when undefined */
|
/* treat as available when undefined */
|
||||||
this.available === false ? " (Not available)" : ""
|
this.available === false ? " (Not available)" : ""
|
||||||
}
|
}
|
||||||
${this.datetime
|
|
||||||
? html`
|
|
||||||
<ha-relative-time
|
|
||||||
.hass=${this.hass}
|
|
||||||
class="addition"
|
|
||||||
.datetime=${this.datetime}
|
|
||||||
></ha-relative-time>
|
|
||||||
`
|
|
||||||
: undefined}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@ -106,9 +94,6 @@ class HassioCardContent extends LitElement {
|
|||||||
height: 2.4em;
|
height: 2.4em;
|
||||||
line-height: 1.2em;
|
line-height: 1.2em;
|
||||||
}
|
}
|
||||||
ha-relative-time {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.icon_image img {
|
.icon_image img {
|
||||||
max-height: 40px;
|
max-height: 40px;
|
||||||
max-width: 40px;
|
max-width: 40px;
|
||||||
|
@ -3,7 +3,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
|||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { atLeastVersion } from "../../../src/common/config/version";
|
import { atLeastVersion } from "../../../src/common/config/version";
|
||||||
import { navigate } from "../../../src/common/navigate";
|
import { navigate } from "../../../src/common/navigate";
|
||||||
import { stringCompare } from "../../../src/common/string/compare";
|
import { caseInsensitiveStringCompare } from "../../../src/common/string/compare";
|
||||||
import "../../../src/components/ha-card";
|
import "../../../src/components/ha-card";
|
||||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||||
import { haStyle } from "../../../src/resources/styles";
|
import { haStyle } from "../../../src/resources/styles";
|
||||||
@ -33,7 +33,7 @@ class HassioAddons extends LitElement {
|
|||||||
</ha-card>
|
</ha-card>
|
||||||
`
|
`
|
||||||
: this.supervisor.supervisor.addons
|
: this.supervisor.supervisor.addons
|
||||||
.sort((a, b) => stringCompare(a.name, b.name))
|
.sort((a, b) => caseInsensitiveStringCompare(a.name, b.name))
|
||||||
.map(
|
.map(
|
||||||
(addon) => html`
|
(addon) => html`
|
||||||
<ha-card .addon=${addon} @click=${this._addonTapped}>
|
<ha-card .addon=${addon} @click=${this._addonTapped}>
|
||||||
|
@ -9,6 +9,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
|||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||||
|
import { caseInsensitiveStringCompare } from "../../../../src/common/string/compare";
|
||||||
import "../../../../src/components/ha-alert";
|
import "../../../../src/components/ha-alert";
|
||||||
import "../../../../src/components/ha-circular-progress";
|
import "../../../../src/components/ha-circular-progress";
|
||||||
import { createCloseHeading } from "../../../../src/components/ha-dialog";
|
import { createCloseHeading } from "../../../../src/components/ha-dialog";
|
||||||
@ -57,7 +58,7 @@ class HassioRepositoriesDialog extends LitElement {
|
|||||||
private _filteredRepositories = memoizeOne((repos: HassioAddonRepository[]) =>
|
private _filteredRepositories = memoizeOne((repos: HassioAddonRepository[]) =>
|
||||||
repos
|
repos
|
||||||
.filter((repo) => repo.slug !== "core" && repo.slug !== "local")
|
.filter((repo) => repo.slug !== "core" && repo.slug !== "local")
|
||||||
.sort((a, b) => (a.name < b.name ? -1 : 1))
|
.sort((a, b) => caseInsensitiveStringCompare(a.name, b.name))
|
||||||
);
|
);
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
|
2
setup.py
2
setup.py
@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="home-assistant-frontend",
|
name="home-assistant-frontend",
|
||||||
version="20211006.0",
|
version="20211007.0",
|
||||||
description="The Home Assistant frontend",
|
description="The Home Assistant frontend",
|
||||||
url="https://github.com/home-assistant/frontend",
|
url="https://github.com/home-assistant/frontend",
|
||||||
author="The Home Assistant Authors",
|
author="The Home Assistant Authors",
|
||||||
|
2
src/common/string/capitalize-first-letter.ts
Normal file
2
src/common/string/capitalize-first-letter.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export const capitalizeFirstLetter = (str: string) =>
|
||||||
|
str.charAt(0).toUpperCase() + str.slice(1);
|
@ -4,7 +4,7 @@ import { shouldPolyfill as shouldPolyfillRelativeTime } from "@formatjs/intl-rel
|
|||||||
import { shouldPolyfill as shouldPolyfillDateTime } from "@formatjs/intl-datetimeformat/lib/should-polyfill";
|
import { shouldPolyfill as shouldPolyfillDateTime } from "@formatjs/intl-datetimeformat/lib/should-polyfill";
|
||||||
import IntlMessageFormat from "intl-messageformat";
|
import IntlMessageFormat from "intl-messageformat";
|
||||||
import { Resources } from "../../types";
|
import { Resources } from "../../types";
|
||||||
import { getLocalLanguage } from "../../util/hass-translation";
|
import { getLocalLanguage } from "../../util/common-translation";
|
||||||
|
|
||||||
export type LocalizeFunc = (key: string, ...args: any[]) => string;
|
export type LocalizeFunc = (key: string, ...args: any[]) => string;
|
||||||
interface FormatType {
|
interface FormatType {
|
||||||
|
@ -86,6 +86,7 @@ export default class HaChartBase extends LitElement {
|
|||||||
class=${classMap({
|
class=${classMap({
|
||||||
hidden: this._hiddenDatasets.has(index),
|
hidden: this._hiddenDatasets.has(index),
|
||||||
})}
|
})}
|
||||||
|
.title=${dataset.label}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="bullet"
|
class="bullet"
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Layout1d, scroll } from "@lit-labs/virtualizer";
|
import { Layout1d, scroll } from "@lit-labs/virtualizer";
|
||||||
|
import { mdiArrowDown, mdiArrowUp } from "@mdi/js";
|
||||||
import deepClone from "deep-clone-simple";
|
import deepClone from "deep-clone-simple";
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
@ -27,7 +28,7 @@ import { nextRender } from "../../common/util/render-status";
|
|||||||
import { haStyleScrollbar } from "../../resources/styles";
|
import { haStyleScrollbar } from "../../resources/styles";
|
||||||
import "../ha-checkbox";
|
import "../ha-checkbox";
|
||||||
import type { HaCheckbox } from "../ha-checkbox";
|
import type { HaCheckbox } from "../ha-checkbox";
|
||||||
import "../ha-icon";
|
import "../ha-svg-icon";
|
||||||
import { filterData, sortData } from "./sort-filter";
|
import { filterData, sortData } from "./sort-filter";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
@ -311,11 +312,11 @@ export class HaDataTable extends LitElement {
|
|||||||
>
|
>
|
||||||
${column.sortable
|
${column.sortable
|
||||||
? html`
|
? html`
|
||||||
<ha-icon
|
<ha-svg-icon
|
||||||
.icon=${sorted && this._sortDirection === "desc"
|
.path=${sorted && this._sortDirection === "desc"
|
||||||
? "hass:arrow-down"
|
? mdiArrowDown
|
||||||
: "hass:arrow-up"}
|
: mdiArrowUp}
|
||||||
></ha-icon>
|
></ha-svg-icon>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
<span>${column.title}</span>
|
<span>${column.title}</span>
|
||||||
@ -863,14 +864,14 @@ export class HaDataTable extends LitElement {
|
|||||||
:host([dir="rtl"]) .mdc-data-table__header-cell > * {
|
:host([dir="rtl"]) .mdc-data-table__header-cell > * {
|
||||||
transition: right 0.2s ease;
|
transition: right 0.2s ease;
|
||||||
}
|
}
|
||||||
.mdc-data-table__header-cell ha-icon {
|
.mdc-data-table__header-cell ha-svg-icon {
|
||||||
top: -3px;
|
top: -3px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
.mdc-data-table__header-cell.not-sorted ha-icon {
|
.mdc-data-table__header-cell.not-sorted ha-svg-icon {
|
||||||
left: -20px;
|
left: -20px;
|
||||||
}
|
}
|
||||||
:host([dir="rtl"]) .mdc-data-table__header-cell.not-sorted ha-icon {
|
:host([dir="rtl"]) .mdc-data-table__header-cell.not-sorted ha-svg-icon {
|
||||||
right: -20px;
|
right: -20px;
|
||||||
}
|
}
|
||||||
.mdc-data-table__header-cell.sortable:not(.not-sorted) span,
|
.mdc-data-table__header-cell.sortable:not(.not-sorted) span,
|
||||||
@ -886,16 +887,16 @@ export class HaDataTable extends LitElement {
|
|||||||
left: auto;
|
left: auto;
|
||||||
right: 24px;
|
right: 24px;
|
||||||
}
|
}
|
||||||
.mdc-data-table__header-cell.sortable:not(.not-sorted) ha-icon,
|
.mdc-data-table__header-cell.sortable:not(.not-sorted) ha-svg-icon,
|
||||||
.mdc-data-table__header-cell.sortable:hover.not-sorted ha-icon {
|
.mdc-data-table__header-cell.sortable:hover.not-sorted ha-svg-icon {
|
||||||
left: 12px;
|
left: 12px;
|
||||||
}
|
}
|
||||||
:host([dir="rtl"])
|
:host([dir="rtl"])
|
||||||
.mdc-data-table__header-cell.sortable:not(.not-sorted)
|
.mdc-data-table__header-cell.sortable:not(.not-sorted)
|
||||||
ha-icon,
|
ha-svg-icon,
|
||||||
:host([dir="rtl"])
|
:host([dir="rtl"])
|
||||||
.mdc-data-table__header-cell.sortable:hover.not-sorted
|
.mdc-data-table__header-cell.sortable:hover.not-sorted
|
||||||
ha-icon {
|
ha-svg-icon {
|
||||||
left: auto;
|
left: auto;
|
||||||
right: 12px;
|
right: 12px;
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { mdiAlert } from "@mdi/js";
|
||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
@ -14,11 +15,12 @@ import { computeStateDisplay } from "../../common/entity/compute_state_display";
|
|||||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||||
import { stateIcon } from "../../common/entity/state_icon";
|
import { stateIcon } from "../../common/entity/state_icon";
|
||||||
import { timerTimeRemaining } from "../../data/timer";
|
|
||||||
import { formatNumber } from "../../common/number/format_number";
|
import { formatNumber } from "../../common/number/format_number";
|
||||||
import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
|
import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
|
||||||
|
import { timerTimeRemaining } from "../../data/timer";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import "../ha-label-badge";
|
import "../ha-label-badge";
|
||||||
|
import "../ha-icon";
|
||||||
|
|
||||||
@customElement("ha-state-label-badge")
|
@customElement("ha-state-label-badge")
|
||||||
export class HaStateLabelBadge extends LitElement {
|
export class HaStateLabelBadge extends LitElement {
|
||||||
@ -58,16 +60,20 @@ export class HaStateLabelBadge extends LitElement {
|
|||||||
<ha-label-badge
|
<ha-label-badge
|
||||||
class="warning"
|
class="warning"
|
||||||
label=${this.hass!.localize("state_badge.default.error")}
|
label=${this.hass!.localize("state_badge.default.error")}
|
||||||
icon="hass:alert"
|
|
||||||
description=${this.hass!.localize(
|
description=${this.hass!.localize(
|
||||||
"state_badge.default.entity_not_found"
|
"state_badge.default.entity_not_found"
|
||||||
)}
|
)}
|
||||||
></ha-label-badge>
|
>
|
||||||
|
<ha-svg-icon .path=${mdiAlert}></ha-svg-icon>
|
||||||
|
</ha-label-badge>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const domain = computeStateDomain(entityState);
|
const domain = computeStateDomain(entityState);
|
||||||
|
|
||||||
|
const value = this._computeValue(domain, entityState);
|
||||||
|
const icon = this.icon ? this.icon : this._computeIcon(domain, entityState);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-label-badge
|
<ha-label-badge
|
||||||
class=${classMap({
|
class=${classMap({
|
||||||
@ -75,8 +81,6 @@ export class HaStateLabelBadge extends LitElement {
|
|||||||
"has-unit_of_measurement":
|
"has-unit_of_measurement":
|
||||||
"unit_of_measurement" in entityState.attributes,
|
"unit_of_measurement" in entityState.attributes,
|
||||||
})}
|
})}
|
||||||
.value=${this._computeValue(domain, entityState)}
|
|
||||||
.icon=${this.icon ? this.icon : this._computeIcon(domain, entityState)}
|
|
||||||
.image=${this.icon
|
.image=${this.icon
|
||||||
? ""
|
? ""
|
||||||
: this.image
|
: this.image
|
||||||
@ -88,8 +92,15 @@ export class HaStateLabelBadge extends LitElement {
|
|||||||
entityState,
|
entityState,
|
||||||
this._timerTimeRemaining
|
this._timerTimeRemaining
|
||||||
)}
|
)}
|
||||||
.description=${this.name ? this.name : computeStateName(entityState)}
|
.description=${this.name ?? computeStateName(entityState)}
|
||||||
></ha-label-badge>
|
>
|
||||||
|
${icon ? html`<ha-icon .icon=${icon}></ha-icon>` : ""}
|
||||||
|
${value && (this.icon || !this.image)
|
||||||
|
? html`<span class=${value && value.length > 4 ? "big" : ""}
|
||||||
|
>${value}</span
|
||||||
|
>`
|
||||||
|
: ""}
|
||||||
|
</ha-label-badge>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -208,7 +219,9 @@ export class HaStateLabelBadge extends LitElement {
|
|||||||
:host {
|
:host {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
.big {
|
||||||
|
font-size: 70%;
|
||||||
|
}
|
||||||
ha-label-badge {
|
ha-label-badge {
|
||||||
--ha-label-badge-color: var(--label-badge-red, #df4c1e);
|
--ha-label-badge-color: var(--label-badge-red, #df4c1e);
|
||||||
}
|
}
|
||||||
|
@ -24,13 +24,15 @@ class StateInfo extends LitElement {
|
|||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const name = computeStateName(this.stateObj);
|
||||||
|
|
||||||
return html`<state-badge
|
return html`<state-badge
|
||||||
.stateObj=${this.stateObj}
|
.stateObj=${this.stateObj}
|
||||||
.stateColor=${true}
|
.stateColor=${true}
|
||||||
></state-badge>
|
></state-badge>
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<div class="name" .inDialog=${this.inDialog}>
|
<div class="name" .title=${name} .inDialog=${this.inDialog}>
|
||||||
${computeStateName(this.stateObj)}
|
${name}
|
||||||
</div>
|
</div>
|
||||||
${this.inDialog
|
${this.inDialog
|
||||||
? html`<div class="time-ago">
|
? html`<div class="time-ago">
|
||||||
@ -38,6 +40,7 @@ class StateInfo extends LitElement {
|
|||||||
id="last_changed"
|
id="last_changed"
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.datetime=${this.stateObj.last_changed}
|
.datetime=${this.stateObj.last_changed}
|
||||||
|
capitalize
|
||||||
></ha-relative-time>
|
></ha-relative-time>
|
||||||
<paper-tooltip animation-delay="0" for="last_changed">
|
<paper-tooltip animation-delay="0" for="last_changed">
|
||||||
<div>
|
<div>
|
||||||
@ -92,7 +95,6 @@ class StateInfo extends LitElement {
|
|||||||
state-badge {
|
state-badge {
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host([rtl]) state-badge {
|
:host([rtl]) state-badge {
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@ import {
|
|||||||
unsafeCSS,
|
unsafeCSS,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import "./ha-icon";
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
// for fire event
|
// for fire event
|
||||||
|
@ -4,7 +4,8 @@ import { css, CSSResultGroup, html, TemplateResult } from "lit";
|
|||||||
import { customElement } from "lit/decorators";
|
import { customElement } from "lit/decorators";
|
||||||
import { computeRTLDirection } from "../common/util/compute_rtl";
|
import { computeRTLDirection } from "../common/util/compute_rtl";
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
import "./ha-icon-button";
|
import "@material/mwc-icon-button/mwc-icon-button";
|
||||||
|
import "./ha-svg-icon";
|
||||||
|
|
||||||
export const createCloseHeading = (
|
export const createCloseHeading = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { mdiMenuDown } from "@mdi/js";
|
||||||
import "@polymer/paper-checkbox/paper-checkbox";
|
import "@polymer/paper-checkbox/paper-checkbox";
|
||||||
import "@polymer/paper-input/paper-input";
|
import "@polymer/paper-input/paper-input";
|
||||||
import "@polymer/paper-item/paper-icon-item";
|
import "@polymer/paper-item/paper-icon-item";
|
||||||
@ -7,7 +8,7 @@ import "@polymer/paper-ripple/paper-ripple";
|
|||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state, query } from "lit/decorators";
|
import { customElement, property, state, query } from "lit/decorators";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import "../ha-icon";
|
import "../ha-svg-icon";
|
||||||
import {
|
import {
|
||||||
HaFormElement,
|
HaFormElement,
|
||||||
HaFormMultiSelectData,
|
HaFormMultiSelectData,
|
||||||
@ -56,11 +57,11 @@ export class HaFormMultiSelect extends LitElement implements HaFormElement {
|
|||||||
input-aria-haspopup="listbox"
|
input-aria-haspopup="listbox"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
>
|
>
|
||||||
<ha-icon
|
<ha-svg-icon
|
||||||
icon="paper-dropdown-menu:arrow-drop-down"
|
.path=${mdiMenuDown}
|
||||||
suffix
|
suffix
|
||||||
slot="suffix"
|
slot="suffix"
|
||||||
></ha-icon>
|
></ha-svg-icon>
|
||||||
</paper-input>
|
</paper-input>
|
||||||
</div>
|
</div>
|
||||||
<paper-listbox
|
<paper-listbox
|
||||||
|
@ -361,7 +361,10 @@ const mdiDeprecatedIcons: DeprecatedIcon = {
|
|||||||
|
|
||||||
const chunks: Chunks = {};
|
const chunks: Chunks = {};
|
||||||
|
|
||||||
|
// Supervisor doesn't use icons, and should not update/downgrade the icon DB.
|
||||||
|
if (!__SUPERVISOR__) {
|
||||||
checkCacheVersion();
|
checkCacheVersion();
|
||||||
|
}
|
||||||
|
|
||||||
const debouncedWriteCache = debounce(() => writeCache(chunks), 2000);
|
const debouncedWriteCache = debounce(() => writeCache(chunks), 2000);
|
||||||
|
|
||||||
|
@ -8,13 +8,9 @@ import {
|
|||||||
} from "lit";
|
} from "lit";
|
||||||
import { property } from "lit/decorators";
|
import { property } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import "./ha-icon";
|
import "./ha-svg-icon";
|
||||||
|
|
||||||
class HaLabelBadge extends LitElement {
|
class HaLabelBadge extends LitElement {
|
||||||
@property() public value?: string;
|
|
||||||
|
|
||||||
@property() public icon?: string;
|
|
||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
@property() public description?: string;
|
@property() public description?: string;
|
||||||
@ -25,20 +21,8 @@ class HaLabelBadge extends LitElement {
|
|||||||
return html`
|
return html`
|
||||||
<div class="badge-container">
|
<div class="badge-container">
|
||||||
<div class="label-badge" id="badge">
|
<div class="label-badge" id="badge">
|
||||||
<div
|
<div class="value">
|
||||||
class=${classMap({
|
<slot></slot>
|
||||||
value: true,
|
|
||||||
big: Boolean(this.value && this.value.length > 4),
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<slot>
|
|
||||||
${this.icon && !this.value && !this.image
|
|
||||||
? html`<ha-icon .icon=${this.icon}></ha-icon>`
|
|
||||||
: ""}
|
|
||||||
${this.value && !this.image
|
|
||||||
? html`<span>${this.value}</span>`
|
|
||||||
: ""}
|
|
||||||
</slot>
|
|
||||||
</div>
|
</div>
|
||||||
${this.label
|
${this.label
|
||||||
? html`
|
? html`
|
||||||
@ -87,14 +71,15 @@ class HaLabelBadge extends LitElement {
|
|||||||
background-size: cover;
|
background-size: cover;
|
||||||
transition: border 0.3s ease-in-out;
|
transition: border 0.3s ease-in-out;
|
||||||
}
|
}
|
||||||
|
.label-badge .label.big span {
|
||||||
|
font-size: 90%;
|
||||||
|
padding: 10% 12% 7% 12%; /* push smaller text a bit down to center vertically */
|
||||||
|
}
|
||||||
.label-badge .value {
|
.label-badge .value {
|
||||||
font-size: 90%;
|
font-size: 90%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
.label-badge .value.big {
|
|
||||||
font-size: 70%;
|
|
||||||
}
|
|
||||||
.label-badge .label {
|
.label-badge .label {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: -1em;
|
bottom: -1em;
|
||||||
@ -119,10 +104,6 @@ class HaLabelBadge extends LitElement {
|
|||||||
transition: background-color 0.3s ease-in-out;
|
transition: background-color 0.3s ease-in-out;
|
||||||
text-transform: var(--ha-label-badge-label-text-transform, uppercase);
|
text-transform: var(--ha-label-badge-label-text-transform, uppercase);
|
||||||
}
|
}
|
||||||
.label-badge .label.big span {
|
|
||||||
font-size: 90%;
|
|
||||||
padding: 10% 12% 7% 12%; /* push smaller text a bit down to center vertically */
|
|
||||||
}
|
|
||||||
.badge-container .title {
|
.badge-container .title {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
font-size: var(--ha-label-badge-title-font-size, 0.9em);
|
font-size: var(--ha-label-badge-title-font-size, 0.9em);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { PropertyValues, ReactiveElement } from "lit";
|
import { PropertyValues, ReactiveElement } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { relativeTime } from "../common/datetime/relative_time";
|
import { relativeTime } from "../common/datetime/relative_time";
|
||||||
|
import { capitalizeFirstLetter } from "../common/string/capitalize-first-letter";
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
|
|
||||||
@customElement("ha-relative-time")
|
@customElement("ha-relative-time")
|
||||||
@ -9,6 +10,8 @@ class HaRelativeTime extends ReactiveElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public datetime?: string | Date;
|
@property({ attribute: false }) public datetime?: string | Date;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public capitalize = false;
|
||||||
|
|
||||||
private _interval?: number;
|
private _interval?: number;
|
||||||
|
|
||||||
public disconnectedCallback(): void {
|
public disconnectedCallback(): void {
|
||||||
@ -55,7 +58,10 @@ class HaRelativeTime extends ReactiveElement {
|
|||||||
if (!this.datetime) {
|
if (!this.datetime) {
|
||||||
this.innerHTML = this.hass.localize("ui.components.relative_time.never");
|
this.innerHTML = this.hass.localize("ui.components.relative_time.never");
|
||||||
} else {
|
} else {
|
||||||
this.innerHTML = relativeTime(new Date(this.datetime), this.hass.locale);
|
const relTime = relativeTime(new Date(this.datetime), this.hass.locale);
|
||||||
|
this.innerHTML = this.capitalize
|
||||||
|
? capitalizeFirstLetter(relTime)
|
||||||
|
: relTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,6 @@ import {
|
|||||||
state,
|
state,
|
||||||
} from "lit/decorators";
|
} from "lit/decorators";
|
||||||
import { ifDefined } from "lit/directives/if-defined";
|
import { ifDefined } from "lit/directives/if-defined";
|
||||||
import "./ha-icon";
|
|
||||||
import "./ha-svg-icon";
|
|
||||||
|
|
||||||
@customElement("ha-tab")
|
@customElement("ha-tab")
|
||||||
export class HaTab extends LitElement {
|
export class HaTab extends LitElement {
|
||||||
|
@ -10,7 +10,6 @@ import { fireEvent } from "../../common/dom/fire_event";
|
|||||||
import { stringCompare } from "../../common/string/compare";
|
import { stringCompare } from "../../common/string/compare";
|
||||||
import { fetchUsers, User } from "../../data/user";
|
import { fetchUsers, User } from "../../data/user";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import "../ha-icon-button";
|
|
||||||
import "./ha-user-badge";
|
import "./ha-user-badge";
|
||||||
|
|
||||||
class HaUserPicker extends LitElement {
|
class HaUserPicker extends LitElement {
|
||||||
|
@ -490,7 +490,7 @@ export const reduceSumStatisticsByDay = (
|
|||||||
// add init value if the first value isn't end of previous period
|
// add init value if the first value isn't end of previous period
|
||||||
result.push({
|
result.push({
|
||||||
...values[0]!,
|
...values[0]!,
|
||||||
start: startOfMonth(addDays(new Date(values[0].start), -1)).toISOString(),
|
start: startOfDay(addDays(new Date(values[0].start), -1)).toISOString(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let lastValue: StatisticValue;
|
let lastValue: StatisticValue;
|
||||||
@ -546,7 +546,7 @@ export const reduceSumStatisticsByMonth = (
|
|||||||
prevMonth = month;
|
prevMonth = month;
|
||||||
}
|
}
|
||||||
if (prevMonth !== month) {
|
if (prevMonth !== month) {
|
||||||
// Last value of the day
|
// Last value of the month
|
||||||
result.push({
|
result.push({
|
||||||
...lastValue!,
|
...lastValue!,
|
||||||
start: startOfMonth(new Date(lastValue!.start)).toISOString(),
|
start: startOfMonth(new Date(lastValue!.start)).toISOString(),
|
||||||
|
@ -7,6 +7,7 @@ import { styleMap } from "lit/directives/style-map";
|
|||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import "../../common/search/search-input";
|
import "../../common/search/search-input";
|
||||||
|
import { caseInsensitiveStringCompare } from "../../common/string/compare";
|
||||||
import { LocalizeFunc } from "../../common/translations/localize";
|
import { LocalizeFunc } from "../../common/translations/localize";
|
||||||
import "../../components/ha-icon-next";
|
import "../../components/ha-icon-next";
|
||||||
import { domainToName } from "../../data/integration";
|
import { domainToName } from "../../data/integration";
|
||||||
@ -59,7 +60,7 @@ class StepFlowPickHandler extends LitElement {
|
|||||||
return fuse.search(filter).map((result) => result.item);
|
return fuse.search(filter).map((result) => result.item);
|
||||||
}
|
}
|
||||||
return handlers.sort((a, b) =>
|
return handlers.sort((a, b) =>
|
||||||
a.name.toUpperCase() < b.name.toUpperCase() ? -1 : 1
|
caseInsensitiveStringCompare(a.name, b.name)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -25,6 +25,7 @@ class MoreInfoAutomation extends LitElement {
|
|||||||
<ha-relative-time
|
<ha-relative-time
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.datetime=${this.stateObj.attributes.last_triggered}
|
.datetime=${this.stateObj.attributes.last_triggered}
|
||||||
|
capitalize
|
||||||
></ha-relative-time>
|
></ha-relative-time>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -28,6 +28,7 @@ class MoreInfoScript extends LitElement {
|
|||||||
<ha-relative-time
|
<ha-relative-time
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.datetime=${this.stateObj.attributes.last_triggered}
|
.datetime=${this.stateObj.attributes.last_triggered}
|
||||||
|
capitalize
|
||||||
></ha-relative-time>
|
></ha-relative-time>
|
||||||
`
|
`
|
||||||
: this.hass.localize("ui.components.relative_time.never")}
|
: this.hass.localize("ui.components.relative_time.never")}
|
||||||
|
@ -73,9 +73,6 @@ class MoreInfoSun extends LitElement {
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
ha-relative-time::first-letter {
|
|
||||||
text-transform: lowercase;
|
|
||||||
}
|
|
||||||
hr {
|
hr {
|
||||||
border-color: var(--divider-color);
|
border-color: var(--divider-color);
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
|
@ -94,12 +94,14 @@ export class MoreInfoDialog extends LitElement {
|
|||||||
}
|
}
|
||||||
const entityId = this._entityId;
|
const entityId = this._entityId;
|
||||||
const stateObj = this.hass.states[entityId];
|
const stateObj = this.hass.states[entityId];
|
||||||
const domain = computeDomain(entityId);
|
|
||||||
|
|
||||||
if (!stateObj) {
|
if (!stateObj) {
|
||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const domain = computeDomain(entityId);
|
||||||
|
const name = computeStateName(stateObj);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-dialog
|
<ha-dialog
|
||||||
open
|
open
|
||||||
@ -119,8 +121,13 @@ export class MoreInfoDialog extends LitElement {
|
|||||||
>
|
>
|
||||||
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
||||||
</mwc-icon-button>
|
</mwc-icon-button>
|
||||||
<div slot="title" class="main-title" @click=${this._enlarge}>
|
<div
|
||||||
${computeStateName(stateObj)}
|
slot="title"
|
||||||
|
class="main-title"
|
||||||
|
.title=${name}
|
||||||
|
@click=${this._enlarge}
|
||||||
|
>
|
||||||
|
${name}
|
||||||
</div>
|
</div>
|
||||||
${this.hass.user!.is_admin
|
${this.hass.user!.is_admin
|
||||||
? html`
|
? html`
|
||||||
|
@ -31,6 +31,7 @@ export class HuiPersistentNotificationItem extends LitElement {
|
|||||||
<ha-relative-time
|
<ha-relative-time
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.datetime=${this.notification.created_at}
|
.datetime=${this.notification.created_at}
|
||||||
|
capitalize
|
||||||
></ha-relative-time>
|
></ha-relative-time>
|
||||||
<paper-tooltip animation-delay="0">
|
<paper-tooltip animation-delay="0">
|
||||||
${this._computeTooltip(this.hass, this.notification)}
|
${this._computeTooltip(this.hass, this.notification)}
|
||||||
|
@ -25,7 +25,7 @@ import { computeStateName } from "../../common/entity/compute_state_name";
|
|||||||
import { domainIcon } from "../../common/entity/domain_icon";
|
import { domainIcon } from "../../common/entity/domain_icon";
|
||||||
import { navigate } from "../../common/navigate";
|
import { navigate } from "../../common/navigate";
|
||||||
import "../../common/search/search-input";
|
import "../../common/search/search-input";
|
||||||
import { stringCompare } from "../../common/string/compare";
|
import { caseInsensitiveStringCompare } from "../../common/string/compare";
|
||||||
import {
|
import {
|
||||||
fuzzyFilterSort,
|
fuzzyFilterSort,
|
||||||
ScorableTextItem,
|
ScorableTextItem,
|
||||||
@ -399,7 +399,7 @@ export class QuickBar extends LitElement {
|
|||||||
};
|
};
|
||||||
})
|
})
|
||||||
.sort((a, b) =>
|
.sort((a, b) =>
|
||||||
stringCompare(a.primaryText.toLowerCase(), b.primaryText.toLowerCase())
|
caseInsensitiveStringCompare(a.primaryText, b.primaryText)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -409,10 +409,7 @@ export class QuickBar extends LitElement {
|
|||||||
...this._generateServerControlCommands(),
|
...this._generateServerControlCommands(),
|
||||||
...this._generateNavigationCommands(),
|
...this._generateNavigationCommands(),
|
||||||
].sort((a, b) =>
|
].sort((a, b) =>
|
||||||
stringCompare(
|
caseInsensitiveStringCompare(a.strings.join(" "), b.strings.join(" "))
|
||||||
a.strings.join(" ").toLowerCase(),
|
|
||||||
b.strings.join(" ").toLowerCase()
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ import { DEFAULT_PANEL } from "../data/panel";
|
|||||||
import { NumberFormat, TimeFormat } from "../data/translation";
|
import { NumberFormat, TimeFormat } from "../data/translation";
|
||||||
import { translationMetadata } from "../resources/translations-metadata";
|
import { translationMetadata } from "../resources/translations-metadata";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import { getLocalLanguage, getTranslation } from "../util/hass-translation";
|
import { getLocalLanguage, getTranslation } from "../util/common-translation";
|
||||||
import { demoConfig } from "./demo_config";
|
import { demoConfig } from "./demo_config";
|
||||||
import { demoPanels } from "./demo_panels";
|
import { demoPanels } from "./demo_panels";
|
||||||
import { demoServices } from "./demo_services";
|
import { demoServices } from "./demo_services";
|
||||||
|
@ -279,7 +279,7 @@ export class HaTabsSubpageDataTable extends LitElement {
|
|||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
.active-filters ha-icon {
|
.active-filters ha-svg-icon {
|
||||||
color: var(--primary-color);
|
color: var(--primary-color);
|
||||||
}
|
}
|
||||||
.active-filters mwc-button {
|
.active-filters mwc-button {
|
||||||
|
@ -14,7 +14,6 @@ import { isComponentLoaded } from "../common/config/is_component_loaded";
|
|||||||
import { restoreScroll } from "../common/decorators/restore-scroll";
|
import { restoreScroll } from "../common/decorators/restore-scroll";
|
||||||
import { LocalizeFunc } from "../common/translations/localize";
|
import { LocalizeFunc } from "../common/translations/localize";
|
||||||
import { computeRTL } from "../common/util/compute_rtl";
|
import { computeRTL } from "../common/util/compute_rtl";
|
||||||
import "../components/ha-icon";
|
|
||||||
import "../components/ha-icon-button-arrow-prev";
|
import "../components/ha-icon-button-arrow-prev";
|
||||||
import "../components/ha-menu-button";
|
import "../components/ha-menu-button";
|
||||||
import "../components/ha-svg-icon";
|
import "../components/ha-svg-icon";
|
||||||
@ -28,7 +27,6 @@ export interface PageNavigation {
|
|||||||
name?: string;
|
name?: string;
|
||||||
core?: boolean;
|
core?: boolean;
|
||||||
advancedOnly?: boolean;
|
advancedOnly?: boolean;
|
||||||
icon?: string;
|
|
||||||
iconPath?: string;
|
iconPath?: string;
|
||||||
info?: any;
|
info?: any;
|
||||||
}
|
}
|
||||||
@ -98,7 +96,7 @@ class HassTabsSubpage extends LitElement {
|
|||||||
slot="icon"
|
slot="icon"
|
||||||
.path=${page.iconPath}
|
.path=${page.iconPath}
|
||||||
></ha-svg-icon>`
|
></ha-svg-icon>`
|
||||||
: html`<ha-icon slot="icon" .icon=${page.icon}></ha-icon>`}
|
: ""}
|
||||||
</ha-tab>
|
</ha-tab>
|
||||||
</a>
|
</a>
|
||||||
`
|
`
|
||||||
|
@ -2,7 +2,7 @@ import { LitElement, PropertyValues } from "lit";
|
|||||||
import { property } from "lit/decorators";
|
import { property } from "lit/decorators";
|
||||||
import { computeLocalize, LocalizeFunc } from "../common/translations/localize";
|
import { computeLocalize, LocalizeFunc } from "../common/translations/localize";
|
||||||
import { Constructor, Resources } from "../types";
|
import { Constructor, Resources } from "../types";
|
||||||
import { getLocalLanguage, getTranslation } from "../util/hass-translation";
|
import { getLocalLanguage, getTranslation } from "../util/common-translation";
|
||||||
|
|
||||||
const empty = () => "";
|
const empty = () => "";
|
||||||
|
|
||||||
|
@ -34,6 +34,8 @@ export class DialogEnergyGasSettings
|
|||||||
|
|
||||||
@state() private _costs?: "no-costs" | "number" | "entity" | "statistic";
|
@state() private _costs?: "no-costs" | "number" | "entity" | "statistic";
|
||||||
|
|
||||||
|
@state() private _unit?: string;
|
||||||
|
|
||||||
@state() private _error?: string;
|
@state() private _error?: string;
|
||||||
|
|
||||||
public async showDialog(
|
public async showDialog(
|
||||||
@ -55,6 +57,7 @@ export class DialogEnergyGasSettings
|
|||||||
public closeDialog(): void {
|
public closeDialog(): void {
|
||||||
this._params = undefined;
|
this._params = undefined;
|
||||||
this._source = undefined;
|
this._source = undefined;
|
||||||
|
this._unit = undefined;
|
||||||
this._error = undefined;
|
this._error = undefined;
|
||||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||||
}
|
}
|
||||||
@ -64,6 +67,14 @@ export class DialogEnergyGasSettings
|
|||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const unit =
|
||||||
|
this._unit ||
|
||||||
|
(this._params.unit === undefined
|
||||||
|
? "m³ or kWh"
|
||||||
|
: this._params.unit === "energy"
|
||||||
|
? "kWh"
|
||||||
|
: "m³");
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-dialog
|
<ha-dialog
|
||||||
open
|
open
|
||||||
@ -154,7 +165,8 @@ export class DialogEnergyGasSettings
|
|||||||
include-domains='["sensor", "input_number"]'
|
include-domains='["sensor", "input_number"]'
|
||||||
.value=${this._source.entity_energy_price}
|
.value=${this._source.entity_energy_price}
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
`ui.panel.config.energy.gas.dialog.cost_entity_input`
|
`ui.panel.config.energy.gas.dialog.cost_entity_input`,
|
||||||
|
{ unit }
|
||||||
)}
|
)}
|
||||||
@value-changed=${this._priceEntityChanged}
|
@value-changed=${this._priceEntityChanged}
|
||||||
></ha-entity-picker>`
|
></ha-entity-picker>`
|
||||||
@ -174,7 +186,8 @@ export class DialogEnergyGasSettings
|
|||||||
${this._costs === "number"
|
${this._costs === "number"
|
||||||
? html`<paper-input
|
? html`<paper-input
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
`ui.panel.config.energy.gas.dialog.cost_number_input`
|
`ui.panel.config.energy.gas.dialog.cost_number_input`,
|
||||||
|
{ unit }
|
||||||
)}
|
)}
|
||||||
no-label-float
|
no-label-float
|
||||||
class="price-options"
|
class="price-options"
|
||||||
@ -183,12 +196,7 @@ export class DialogEnergyGasSettings
|
|||||||
.value=${this._source.number_energy_price}
|
.value=${this._source.number_energy_price}
|
||||||
@value-changed=${this._numberPriceChanged}
|
@value-changed=${this._numberPriceChanged}
|
||||||
>
|
>
|
||||||
<span slot="suffix"
|
<span slot="suffix">${this.hass.config.currency}/${unit}</span>
|
||||||
>${this.hass.localize(
|
|
||||||
`ui.panel.config.energy.gas.dialog.cost_number_suffix`,
|
|
||||||
{ currency: this.hass.config.currency }
|
|
||||||
)}</span
|
|
||||||
>
|
|
||||||
</paper-input>`
|
</paper-input>`
|
||||||
: ""}
|
: ""}
|
||||||
|
|
||||||
@ -239,6 +247,18 @@ export class DialogEnergyGasSettings
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _statisticChanged(ev: CustomEvent<{ value: string }>) {
|
private _statisticChanged(ev: CustomEvent<{ value: string }>) {
|
||||||
|
if (ev.detail.value) {
|
||||||
|
const entity = this.hass.states[ev.detail.value];
|
||||||
|
if (entity?.attributes.unit_of_measurement) {
|
||||||
|
// Wh is normalized to kWh by stats generation
|
||||||
|
this._unit =
|
||||||
|
entity.attributes.unit_of_measurement === "Wh"
|
||||||
|
? "kWh"
|
||||||
|
: entity.attributes.unit_of_measurement;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this._unit = undefined;
|
||||||
|
}
|
||||||
this._source = {
|
this._source = {
|
||||||
...this._source!,
|
...this._source!,
|
||||||
stat_energy_from: ev.detail.value,
|
stat_energy_from: ev.detail.value,
|
||||||
|
@ -16,6 +16,7 @@ import { haStyle } from "../../../../../resources/styles";
|
|||||||
import { HomeAssistant, Route } from "../../../../../types";
|
import { HomeAssistant, Route } from "../../../../../types";
|
||||||
import { fileDownload } from "../../../../../util/file_download";
|
import { fileDownload } from "../../../../../util/file_download";
|
||||||
import { configTabs } from "./zwave_js-config-router";
|
import { configTabs } from "./zwave_js-config-router";
|
||||||
|
import { capitalizeFirstLetter } from "../../../../../common/string/capitalize-first-letter";
|
||||||
|
|
||||||
@customElement("zwave_js-logs")
|
@customElement("zwave_js-logs")
|
||||||
class ZWaveJSLogs extends SubscribeMixin(LitElement) {
|
class ZWaveJSLogs extends SubscribeMixin(LitElement) {
|
||||||
@ -149,7 +150,7 @@ class ZWaveJSLogs extends SubscribeMixin(LitElement) {
|
|||||||
setZWaveJSLogLevel(this.hass!, this.configEntryId, selected);
|
setZWaveJSLogLevel(this.hass!, this.configEntryId, selected);
|
||||||
this._textarea!.value += `${this.hass.localize(
|
this._textarea!.value += `${this.hass.localize(
|
||||||
"ui.panel.config.zwave_js.logs.log_level_changed",
|
"ui.panel.config.zwave_js.logs.log_level_changed",
|
||||||
{ level: selected.charAt(0).toUpperCase() + selected.slice(1) }
|
{ level: capitalizeFirstLetter(selected) }
|
||||||
)}\n`;
|
)}\n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,6 +79,7 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
|
|||||||
? html`<ha-relative-time
|
? html`<ha-relative-time
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.datetime=${tag.last_scanned_datetime}
|
.datetime=${tag.last_scanned_datetime}
|
||||||
|
capitalize
|
||||||
></ha-relative-time>`
|
></ha-relative-time>`
|
||||||
: this.hass.localize("ui.panel.config.tag.never_scanned")}
|
: this.hass.localize("ui.panel.config.tag.never_scanned")}
|
||||||
</div>`
|
</div>`
|
||||||
@ -96,6 +97,7 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
|
|||||||
? html`<ha-relative-time
|
? html`<ha-relative-time
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.datetime=${last_scanned_datetime}
|
.datetime=${last_scanned_datetime}
|
||||||
|
capitalize
|
||||||
></ha-relative-time>`
|
></ha-relative-time>`
|
||||||
: this.hass.localize("ui.panel.config.tag.never_scanned")}
|
: this.hass.localize("ui.panel.config.tag.never_scanned")}
|
||||||
`,
|
`,
|
||||||
|
@ -81,14 +81,16 @@ class HaPanelDevStatistics extends LitElement {
|
|||||||
issue.data
|
issue.data
|
||||||
) || issue.type
|
) || issue.type
|
||||||
)
|
)
|
||||||
: "No issues"}`,
|
: localize("ui.panel.developer-tools.tabs.statistics.no_issue")}`,
|
||||||
},
|
},
|
||||||
fix: {
|
fix: {
|
||||||
title: "",
|
title: "",
|
||||||
template: (_, data: any) =>
|
template: (_, data: any) =>
|
||||||
html`${data.issues
|
html`${data.issues
|
||||||
? html`<mwc-button @click=${this._fixIssue} .data=${data.issues}>
|
? html`<mwc-button @click=${this._fixIssue} .data=${data.issues}>
|
||||||
Fix issue
|
${localize(
|
||||||
|
"ui.panel.developer-tools.tabs.statistics.fix_issue.fix"
|
||||||
|
)}
|
||||||
</mwc-button>`
|
</mwc-button>`
|
||||||
: ""}`,
|
: ""}`,
|
||||||
width: "113px",
|
width: "113px",
|
||||||
|
@ -82,7 +82,7 @@ export class DialogStatisticsFixUnitsChanged extends LitElement {
|
|||||||
|
|
||||||
<mwc-button slot="primaryAction" @click=${this._fixIssue}>
|
<mwc-button slot="primaryAction" @click=${this._fixIssue}>
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.developer-tools.tabs.statistics.fix_issue.units_changed.fix"
|
"ui.panel.developer-tools.tabs.statistics.fix_issue.fix"
|
||||||
)}
|
)}
|
||||||
</mwc-button>
|
</mwc-button>
|
||||||
<mwc-button slot="secondaryAction" @click=${this.closeDialog}>
|
<mwc-button slot="secondaryAction" @click=${this.closeDialog}>
|
||||||
|
@ -37,10 +37,10 @@ export class DialogStatisticsFixUnsupportedUnitMetadata extends LitElement {
|
|||||||
heading="Unsupported unit in recorded statistics"
|
heading="Unsupported unit in recorded statistics"
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
The unit of the statistics in your database for this entity is not a
|
The unit ${this._params.issue.data.metadata_unit} of the statistics in
|
||||||
supported unit for the device class of the entity,
|
your database for this entity is not a supported unit for the device
|
||||||
${this._params.issue.data.device_class}. It should be
|
class of the entity, ${this._params.issue.data.device_class}. It
|
||||||
${this._params.issue.data.supported_unit}.
|
should be ${this._params.issue.data.supported_unit}.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Do you want to update the unit of the history statistics to
|
Do you want to update the unit of the history statistics to
|
||||||
|
@ -210,6 +210,7 @@ class HaLogbook extends LitElement {
|
|||||||
<ha-relative-time
|
<ha-relative-time
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.datetime=${item.when}
|
.datetime=${item.when}
|
||||||
|
capitalize
|
||||||
></ha-relative-time>
|
></ha-relative-time>
|
||||||
${item.domain === "automation" &&
|
${item.domain === "automation" &&
|
||||||
item.context_id! in this.traceContexts
|
item.context_id! in this.traceContexts
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
import { mdiAlert } from "@mdi/js";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, state } from "lit/decorators";
|
import { customElement, state } from "lit/decorators";
|
||||||
import "../../../components/ha-label-badge";
|
import "../../../components/ha-label-badge";
|
||||||
|
import "../../../components/ha-svg-icon";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { LovelaceBadge } from "../types";
|
import { LovelaceBadge } from "../types";
|
||||||
import { ErrorBadgeConfig } from "./types";
|
import { ErrorBadgeConfig } from "./types";
|
||||||
@ -32,11 +34,9 @@ export class HuiErrorBadge extends LitElement implements LovelaceBadge {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-label-badge
|
<ha-label-badge label="Error" description=${this._config.error}>
|
||||||
label="Error"
|
<ha-svg-icon .path=${mdiAlert}></ha-svg-icon>
|
||||||
icon="hass:alert"
|
</ha-label-badge>
|
||||||
description=${this._config.error}
|
|
||||||
></ha-label-badge>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -763,6 +763,8 @@ class HuiEnergyDistrubutionCard
|
|||||||
stroke-width: 4px;
|
stroke-width: 4px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
}
|
}
|
||||||
.gas path,
|
.gas path,
|
||||||
.gas circle {
|
.gas circle {
|
||||||
|
@ -10,7 +10,13 @@ import {
|
|||||||
ChartOptions,
|
ChartOptions,
|
||||||
ScatterDataPoint,
|
ScatterDataPoint,
|
||||||
} from "chart.js";
|
} from "chart.js";
|
||||||
import { differenceInDays, endOfToday, isToday, startOfToday } from "date-fns";
|
import {
|
||||||
|
addHours,
|
||||||
|
differenceInDays,
|
||||||
|
endOfToday,
|
||||||
|
isToday,
|
||||||
|
startOfToday,
|
||||||
|
} from "date-fns";
|
||||||
import { HomeAssistant } from "../../../../types";
|
import { HomeAssistant } from "../../../../types";
|
||||||
import { LovelaceCard } from "../../types";
|
import { LovelaceCard } from "../../types";
|
||||||
import { EnergyGasGraphCardConfig } from "../types";
|
import { EnergyGasGraphCardConfig } from "../types";
|
||||||
@ -39,6 +45,7 @@ import {
|
|||||||
reduceSumStatisticsByMonth,
|
reduceSumStatisticsByMonth,
|
||||||
reduceSumStatisticsByDay,
|
reduceSumStatisticsByDay,
|
||||||
} from "../../../../data/history";
|
} from "../../../../data/history";
|
||||||
|
import { formatTime } from "../../../../common/datetime/format_time";
|
||||||
|
|
||||||
@customElement("hui-energy-gas-graph-card")
|
@customElement("hui-energy-gas-graph-card")
|
||||||
export class HuiEnergyGasGraphCard
|
export class HuiEnergyGasGraphCard
|
||||||
@ -180,6 +187,16 @@ export class HuiEnergyGasGraphCard
|
|||||||
tooltip: {
|
tooltip: {
|
||||||
mode: "nearest",
|
mode: "nearest",
|
||||||
callbacks: {
|
callbacks: {
|
||||||
|
title: (datasets) => {
|
||||||
|
if (dayDifference > 0) {
|
||||||
|
return datasets[0].label;
|
||||||
|
}
|
||||||
|
const date = new Date(datasets[0].parsed.x);
|
||||||
|
return `${formatTime(date, locale)} - ${formatTime(
|
||||||
|
addHours(date, 1),
|
||||||
|
locale
|
||||||
|
)}`;
|
||||||
|
},
|
||||||
label: (context) =>
|
label: (context) =>
|
||||||
`${context.dataset.label}: ${formatNumber(
|
`${context.dataset.label}: ${formatNumber(
|
||||||
context.parsed.y,
|
context.parsed.y,
|
||||||
|
@ -10,7 +10,13 @@ import {
|
|||||||
ChartOptions,
|
ChartOptions,
|
||||||
ScatterDataPoint,
|
ScatterDataPoint,
|
||||||
} from "chart.js";
|
} from "chart.js";
|
||||||
import { differenceInDays, endOfToday, isToday, startOfToday } from "date-fns";
|
import {
|
||||||
|
addHours,
|
||||||
|
differenceInDays,
|
||||||
|
endOfToday,
|
||||||
|
isToday,
|
||||||
|
startOfToday,
|
||||||
|
} from "date-fns";
|
||||||
import { HomeAssistant } from "../../../../types";
|
import { HomeAssistant } from "../../../../types";
|
||||||
import { LovelaceCard } from "../../types";
|
import { LovelaceCard } from "../../types";
|
||||||
import { EnergySolarGraphCardConfig } from "../types";
|
import { EnergySolarGraphCardConfig } from "../types";
|
||||||
@ -40,6 +46,7 @@ import {
|
|||||||
reduceSumStatisticsByMonth,
|
reduceSumStatisticsByMonth,
|
||||||
reduceSumStatisticsByDay,
|
reduceSumStatisticsByDay,
|
||||||
} from "../../../../data/history";
|
} from "../../../../data/history";
|
||||||
|
import { formatTime } from "../../../../common/datetime/format_time";
|
||||||
|
|
||||||
@customElement("hui-energy-solar-graph-card")
|
@customElement("hui-energy-solar-graph-card")
|
||||||
export class HuiEnergySolarGraphCard
|
export class HuiEnergySolarGraphCard
|
||||||
@ -173,6 +180,16 @@ export class HuiEnergySolarGraphCard
|
|||||||
tooltip: {
|
tooltip: {
|
||||||
mode: "nearest",
|
mode: "nearest",
|
||||||
callbacks: {
|
callbacks: {
|
||||||
|
title: (datasets) => {
|
||||||
|
if (dayDifference > 0) {
|
||||||
|
return datasets[0].label;
|
||||||
|
}
|
||||||
|
const date = new Date(datasets[0].parsed.x);
|
||||||
|
return `${formatTime(date, locale)} - ${formatTime(
|
||||||
|
addHours(date, 1),
|
||||||
|
locale
|
||||||
|
)}`;
|
||||||
|
},
|
||||||
label: (context) =>
|
label: (context) =>
|
||||||
`${context.dataset.label}: ${formatNumber(
|
`${context.dataset.label}: ${formatNumber(
|
||||||
context.parsed.y,
|
context.parsed.y,
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
import { ChartData, ChartDataset, ChartOptions } from "chart.js";
|
import { ChartData, ChartDataset, ChartOptions } from "chart.js";
|
||||||
import { startOfToday, endOfToday, isToday, differenceInDays } from "date-fns";
|
import {
|
||||||
|
startOfToday,
|
||||||
|
endOfToday,
|
||||||
|
isToday,
|
||||||
|
differenceInDays,
|
||||||
|
addHours,
|
||||||
|
} from "date-fns";
|
||||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
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";
|
||||||
@ -13,6 +19,7 @@ import {
|
|||||||
} from "../../../../common/color/convert-color";
|
} from "../../../../common/color/convert-color";
|
||||||
import { hexBlend } from "../../../../common/color/hex";
|
import { hexBlend } from "../../../../common/color/hex";
|
||||||
import { labDarken } from "../../../../common/color/lab";
|
import { labDarken } from "../../../../common/color/lab";
|
||||||
|
import { formatTime } from "../../../../common/datetime/format_time";
|
||||||
import { computeStateName } from "../../../../common/entity/compute_state_name";
|
import { computeStateName } from "../../../../common/entity/compute_state_name";
|
||||||
import {
|
import {
|
||||||
formatNumber,
|
formatNumber,
|
||||||
@ -168,6 +175,16 @@ export class HuiEnergyUsageGraphCard
|
|||||||
position: "nearest",
|
position: "nearest",
|
||||||
filter: (val) => val.formattedValue !== "0",
|
filter: (val) => val.formattedValue !== "0",
|
||||||
callbacks: {
|
callbacks: {
|
||||||
|
title: (datasets) => {
|
||||||
|
if (dayDifference > 0) {
|
||||||
|
return datasets[0].label;
|
||||||
|
}
|
||||||
|
const date = new Date(datasets[0].parsed.x);
|
||||||
|
return `${formatTime(date, locale)} - ${formatTime(
|
||||||
|
addHours(date, 1),
|
||||||
|
locale
|
||||||
|
)}`;
|
||||||
|
},
|
||||||
label: (context) =>
|
label: (context) =>
|
||||||
`${context.dataset.label}: ${formatNumber(
|
`${context.dataset.label}: ${formatNumber(
|
||||||
Math.abs(context.parsed.y),
|
Math.abs(context.parsed.y),
|
||||||
|
@ -143,6 +143,10 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const name = this._config.show_name
|
||||||
|
? this._config.name || (stateObj ? computeStateName(stateObj) : "")
|
||||||
|
: "";
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-card
|
<ha-card
|
||||||
@action=${this._handleAction}
|
@action=${this._handleAction}
|
||||||
@ -186,12 +190,7 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
|||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
${this._config.show_name
|
${this._config.show_name
|
||||||
? html`
|
? html`<span tabindex="-1" .title=${name}>${name}</span>`
|
||||||
<span tabindex="-1">
|
|
||||||
${this._config.name ||
|
|
||||||
(stateObj ? computeStateName(stateObj) : "")}
|
|
||||||
</span>
|
|
||||||
`
|
|
||||||
: ""}
|
: ""}
|
||||||
${this._config.show_state && stateObj
|
${this._config.show_state && stateObj
|
||||||
? html`<span class="state">
|
? html`<span class="state">
|
||||||
|
@ -115,12 +115,12 @@ export class HuiEntityCard extends LitElement implements LovelaceCard {
|
|||||||
? this._config.attribute in stateObj.attributes
|
? this._config.attribute in stateObj.attributes
|
||||||
: !UNAVAILABLE_STATES.includes(stateObj.state);
|
: !UNAVAILABLE_STATES.includes(stateObj.state);
|
||||||
|
|
||||||
|
const name = this._config.name || computeStateName(stateObj);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-card @click=${this._handleClick} tabindex="0">
|
<ha-card @click=${this._handleClick} tabindex="0">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<div class="name">
|
<div class="name" .title=${name}>${name}</div>
|
||||||
${this._config.name || computeStateName(stateObj)}
|
|
||||||
</div>
|
|
||||||
<div class="icon">
|
<div class="icon">
|
||||||
<ha-icon
|
<ha-icon
|
||||||
.icon=${this._config.icon || stateIcon(stateObj)}
|
.icon=${this._config.icon || stateIcon(stateObj)}
|
||||||
|
@ -44,9 +44,8 @@ export class HuiErrorCard extends LitElement implements LovelaceCard {
|
|||||||
return css`
|
return css`
|
||||||
pre {
|
pre {
|
||||||
font-family: var(--code-font-family, monospace);
|
font-family: var(--code-font-family, monospace);
|
||||||
text-overflow: ellipsis;
|
white-space: break-spaces;
|
||||||
user-select: text;
|
user-select: text;
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -119,6 +119,8 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const name = this._config.name ?? computeStateName(stateObj);
|
||||||
|
|
||||||
// Use `stateObj.state` as value to keep formatting (e.g trailing zeros)
|
// Use `stateObj.state` as value to keep formatting (e.g trailing zeros)
|
||||||
// for consistent value display across gauge, entity, entity-row, etc.
|
// for consistent value display across gauge, entity, entity-row, etc.
|
||||||
return html`
|
return html`
|
||||||
@ -138,9 +140,7 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
|
|||||||
.needle=${this._config!.needle}
|
.needle=${this._config!.needle}
|
||||||
.levels=${this._config!.needle ? this._severityLevels() : undefined}
|
.levels=${this._config!.needle ? this._severityLevels() : undefined}
|
||||||
></ha-gauge>
|
></ha-gauge>
|
||||||
<div class="name">
|
<div class="name" .title=${name}>${name}</div>
|
||||||
${this._config.name || computeStateName(stateObj)}
|
|
||||||
</div>
|
|
||||||
</ha-card>
|
</ha-card>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -278,6 +278,8 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard {
|
|||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const name = entityConf.name ?? computeStateName(stateObj);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div
|
<div
|
||||||
class="entity"
|
class="entity"
|
||||||
@ -292,13 +294,7 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
${this._config!.show_name
|
${this._config!.show_name
|
||||||
? html`
|
? html` <div class="name" .title=${name}>${name}</div> `
|
||||||
<div class="name">
|
|
||||||
${"name" in entityConf
|
|
||||||
? entityConf.name
|
|
||||||
: computeStateName(stateObj)}
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
: ""}
|
: ""}
|
||||||
${this._config!.show_icon
|
${this._config!.show_icon
|
||||||
? html`
|
? html`
|
||||||
|
@ -173,7 +173,7 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="info">${name}</div>
|
<div id="info" .title=${name}>${name}</div>
|
||||||
</div>
|
</div>
|
||||||
</ha-card>
|
</ha-card>
|
||||||
`;
|
`;
|
||||||
|
@ -96,6 +96,8 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
|
|||||||
const brightness =
|
const brightness =
|
||||||
Math.round((stateObj.attributes.brightness / 255) * 100) || 0;
|
Math.round((stateObj.attributes.brightness / 255) * 100) || 0;
|
||||||
|
|
||||||
|
const name = this._config.name ?? computeStateName(stateObj);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-card>
|
<ha-card>
|
||||||
<mwc-icon-button
|
<mwc-icon-button
|
||||||
@ -145,7 +147,7 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="info">
|
<div id="info" .title=${name}>
|
||||||
${UNAVAILABLE_STATES.includes(stateObj.state)
|
${UNAVAILABLE_STATES.includes(stateObj.state)
|
||||||
? html`
|
? html`
|
||||||
<div>
|
<div>
|
||||||
@ -157,7 +159,7 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
|
|||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: html` <div class="brightness">%</div> `}
|
: html` <div class="brightness">%</div> `}
|
||||||
${this._config.name || computeStateName(stateObj)}
|
${name}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ha-card>
|
</ha-card>
|
||||||
|
@ -251,7 +251,7 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="info">
|
<div id="info" .title=${name}>
|
||||||
<div id="modes">
|
<div id="modes">
|
||||||
${(stateObj.attributes.hvac_modes || [])
|
${(stateObj.attributes.hvac_modes || [])
|
||||||
.concat()
|
.concat()
|
||||||
|
@ -190,6 +190,7 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const weatherStateIcon = getWeatherStateIcon(stateObj.state, this);
|
const weatherStateIcon = getWeatherStateIcon(stateObj.state, this);
|
||||||
|
const name = this._config.name ?? computeStateName(stateObj);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-card
|
<ha-card
|
||||||
@ -221,9 +222,7 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
|||||||
this.hass.locale
|
this.hass.locale
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div class="name">
|
<div class="name" .title=${name}>${name}</div>
|
||||||
${this._config.name || computeStateName(stateObj)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="temp-attribute">
|
<div class="temp-attribute">
|
||||||
<div class="temp">
|
<div class="temp">
|
||||||
|
@ -54,6 +54,7 @@ class HuiGenericEntityRow extends LitElement {
|
|||||||
!DOMAINS_HIDE_MORE_INFO.includes(computeDomain(this.config.entity)));
|
!DOMAINS_HIDE_MORE_INFO.includes(computeDomain(this.config.entity)));
|
||||||
|
|
||||||
const hasSecondary = this.secondaryText || this.config.secondary_info;
|
const hasSecondary = this.secondaryText || this.config.secondary_info;
|
||||||
|
const name = this.config.name ?? computeStateName(stateObj);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<state-badge
|
<state-badge
|
||||||
@ -82,8 +83,9 @@ class HuiGenericEntityRow extends LitElement {
|
|||||||
hasHold: hasAction(this.config!.hold_action),
|
hasHold: hasAction(this.config!.hold_action),
|
||||||
hasDoubleClick: hasAction(this.config!.double_tap_action),
|
hasDoubleClick: hasAction(this.config!.double_tap_action),
|
||||||
})}
|
})}
|
||||||
|
.title=${name}
|
||||||
>
|
>
|
||||||
${this.config.name || computeStateName(stateObj)}
|
${name}
|
||||||
${hasSecondary
|
${hasSecondary
|
||||||
? html`
|
? html`
|
||||||
<div class="secondary">
|
<div class="secondary">
|
||||||
@ -95,6 +97,7 @@ class HuiGenericEntityRow extends LitElement {
|
|||||||
<ha-relative-time
|
<ha-relative-time
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.datetime=${stateObj.last_changed}
|
.datetime=${stateObj.last_changed}
|
||||||
|
capitalize
|
||||||
></ha-relative-time>
|
></ha-relative-time>
|
||||||
`
|
`
|
||||||
: this.config.secondary_info === "last-updated"
|
: this.config.secondary_info === "last-updated"
|
||||||
@ -102,6 +105,7 @@ class HuiGenericEntityRow extends LitElement {
|
|||||||
<ha-relative-time
|
<ha-relative-time
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.datetime=${stateObj.last_updated}
|
.datetime=${stateObj.last_updated}
|
||||||
|
capitalize
|
||||||
></ha-relative-time>
|
></ha-relative-time>
|
||||||
`
|
`
|
||||||
: this.config.secondary_info === "last-triggered"
|
: this.config.secondary_info === "last-triggered"
|
||||||
@ -110,6 +114,7 @@ class HuiGenericEntityRow extends LitElement {
|
|||||||
<ha-relative-time
|
<ha-relative-time
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.datetime=${stateObj.attributes.last_triggered}
|
.datetime=${stateObj.attributes.last_triggered}
|
||||||
|
capitalize
|
||||||
></ha-relative-time>
|
></ha-relative-time>
|
||||||
`
|
`
|
||||||
: this.hass.localize(
|
: this.hass.localize(
|
||||||
|
@ -103,6 +103,7 @@ export class HuiEntityPickerTable extends LitElement {
|
|||||||
<ha-relative-time
|
<ha-relative-time
|
||||||
.hass=${this.hass!}
|
.hass=${this.hass!}
|
||||||
.datetime=${lastChanged}
|
.datetime=${lastChanged}
|
||||||
|
capitalize
|
||||||
></ha-relative-time>
|
></ha-relative-time>
|
||||||
`,
|
`,
|
||||||
};
|
};
|
||||||
|
@ -63,7 +63,7 @@ export class HuiGenericEntityRowEditor
|
|||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
|
|
||||||
const domain = computeDomain(this._config.entity);
|
const domain = computeDomain(this._entity);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="card-config">
|
<div class="card-config">
|
||||||
|
@ -50,6 +50,9 @@ export class HuiButtonRow extends LitElement implements LovelaceRow {
|
|||||||
? this.hass.states[this._config.entity]
|
? this.hass.states[this._config.entity]
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
const name =
|
||||||
|
this._config.name ?? (stateObj ? computeStateName(stateObj) : "");
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-icon
|
<ha-icon
|
||||||
.icon=${this._config.icon ||
|
.icon=${this._config.icon ||
|
||||||
@ -57,9 +60,7 @@ export class HuiButtonRow extends LitElement implements LovelaceRow {
|
|||||||
>
|
>
|
||||||
</ha-icon>
|
</ha-icon>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div>
|
<div .title=${name}>${name}</div>
|
||||||
${this._config.name || (stateObj ? computeStateName(stateObj) : "")}
|
|
||||||
</div>
|
|
||||||
<mwc-button
|
<mwc-button
|
||||||
@action=${this._handleAction}
|
@action=${this._handleAction}
|
||||||
.actionHandler=${actionHandler({
|
.actionHandler=${actionHandler({
|
||||||
|
@ -26,7 +26,11 @@ class HuiSectionRow extends LitElement implements LovelaceRow {
|
|||||||
return html`
|
return html`
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
${this._config.label
|
${this._config.label
|
||||||
? html` <div class="label">${this._config.label}</div> `
|
? html`
|
||||||
|
<div class="label" .title=${this._config.label}>
|
||||||
|
${this._config.label}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
: html``}
|
: html``}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -22,8 +22,8 @@ class HuiTextRow extends LitElement implements LovelaceRow {
|
|||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-icon .icon=${this._config.icon}></ha-icon>
|
<ha-icon .icon=${this._config.icon}></ha-icon>
|
||||||
<div class="name">${this._config.name}</div>
|
<div class="name" .title=${this._config.name}>${this._config.name}</div>
|
||||||
<div class="text">${this._config.text}</div>
|
<div class="text" .title=${this._config.text}>${this._config.text}</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ class HuiWeblinkRow extends LitElement implements LovelaceRow {
|
|||||||
?download=${this._config.download}
|
?download=${this._config.download}
|
||||||
>
|
>
|
||||||
<ha-icon .icon=${this._config.icon}></ha-icon>
|
<ha-icon .icon=${this._config.icon}></ha-icon>
|
||||||
<div>${this._config.name}</div>
|
<div .title=${this._config.name}>${this._config.name}</div>
|
||||||
</a>
|
</a>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ import { Constructor, ServiceCallResponse } from "../types";
|
|||||||
import { fetchWithAuth } from "../util/fetch-with-auth";
|
import { fetchWithAuth } from "../util/fetch-with-auth";
|
||||||
import { getState } from "../util/ha-pref-storage";
|
import { getState } from "../util/ha-pref-storage";
|
||||||
import hassCallApi from "../util/hass-call-api";
|
import hassCallApi from "../util/hass-call-api";
|
||||||
import { getLocalLanguage } from "../util/hass-translation";
|
import { getLocalLanguage } from "../util/common-translation";
|
||||||
import { HassBaseEl } from "./hass-base-mixin";
|
import { HassBaseEl } from "./hass-base-mixin";
|
||||||
|
|
||||||
export const connectionMixin = <T extends Constructor<HassBaseEl>>(
|
export const connectionMixin = <T extends Constructor<HassBaseEl>>(
|
||||||
|
@ -17,7 +17,7 @@ import {
|
|||||||
getLocalLanguage,
|
getLocalLanguage,
|
||||||
getTranslation,
|
getTranslation,
|
||||||
getUserLocale,
|
getUserLocale,
|
||||||
} from "../util/hass-translation";
|
} from "../util/common-translation";
|
||||||
import { HassBaseEl } from "./hass-base-mixin";
|
import { HassBaseEl } from "./hass-base-mixin";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
@ -376,3 +376,7 @@ export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Load selected translation into memory immediately so it is ready when Polymer
|
||||||
|
// initializes.
|
||||||
|
getTranslation(null, getLocalLanguage());
|
||||||
|
@ -1045,10 +1045,9 @@
|
|||||||
"cost_stat": "Use an entity tracking the total costs",
|
"cost_stat": "Use an entity tracking the total costs",
|
||||||
"cost_stat_input": "Total Costs Entity",
|
"cost_stat_input": "Total Costs Entity",
|
||||||
"cost_entity": "Use an entity with current price",
|
"cost_entity": "Use an entity with current price",
|
||||||
"cost_entity_input": "Entity with the current price",
|
"cost_entity_input": "Entity with the current price per {unit}",
|
||||||
"cost_number": "Use a static price",
|
"cost_number": "Use a static price",
|
||||||
"cost_number_input": "Price per m³",
|
"cost_number_input": "Price per {unit}"
|
||||||
"cost_number_suffix": "{currency}/m³"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"device_consumption": {
|
"device_consumption": {
|
||||||
@ -3730,19 +3729,20 @@
|
|||||||
"title": "Statistics",
|
"title": "Statistics",
|
||||||
"entity": "Entity",
|
"entity": "Entity",
|
||||||
"issue": "Issue",
|
"issue": "Issue",
|
||||||
|
"no_issue": "No issue",
|
||||||
"issues": {
|
"issues": {
|
||||||
"units_changed": "The unit of this entity changed from ''{metadata_unit}'' to ''{state_unit}''.",
|
"units_changed": "The unit of this entity changed from ''{metadata_unit}'' to ''{state_unit}''.",
|
||||||
"unsupported_unit_state": "The unit (''{state_unit}'') of this entity doesn't match a unit of device class ''{device_class}''.",
|
"unsupported_unit_state": "The unit (''{state_unit}'') of this entity doesn't match a unit of device class ''{device_class}''.",
|
||||||
"unsupported_unit_metadata": "The unit (''{state_unit}'') of the recorded statistics doesn't match a unit of device class ''{device_class}''.",
|
"unsupported_unit_metadata": "The unit (''{metadata_unit}'') of the recorded statistics doesn't match the supported unit ''{supported_unit}'' of device class ''{device_class}''.",
|
||||||
"unsupported_state_class": "The state class ''{state_class}'' of this entity is not supported.",
|
"unsupported_state_class": "The state class ''{state_class}'' of this entity is not supported.",
|
||||||
"entity_not_recorded": "This entity is excluded from being recorded."
|
"entity_not_recorded": "This entity is excluded from being recorded."
|
||||||
},
|
},
|
||||||
"fix_issue": {
|
"fix_issue": {
|
||||||
|
"fix": "Fix issue",
|
||||||
"units_changed": {
|
"units_changed": {
|
||||||
"title": "The unit of this entity changed",
|
"title": "The unit of this entity changed",
|
||||||
"update": "Update the historic statistic values from ''{metadata_unit}'' to ''{state_unit}''",
|
"update": "Update the historic statistic values from ''{metadata_unit}'' to ''{state_unit}''",
|
||||||
"clear": "Delete all old statistic data for this entity",
|
"clear": "Delete all old statistic data for this entity"
|
||||||
"fix": "Fix issue"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ declare global {
|
|||||||
var __VERSION__: string;
|
var __VERSION__: string;
|
||||||
var __STATIC_PATH__: string;
|
var __STATIC_PATH__: string;
|
||||||
var __BACKWARDS_COMPAT__: boolean;
|
var __BACKWARDS_COMPAT__: boolean;
|
||||||
|
var __SUPERVISOR__: boolean;
|
||||||
/* eslint-enable no-var, no-redeclare */
|
/* eslint-enable no-var, no-redeclare */
|
||||||
|
|
||||||
interface Window {
|
interface Window {
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
|
import {
|
||||||
|
fetchTranslationPreferences,
|
||||||
|
FrontendLocaleData,
|
||||||
|
} from "../data/translation";
|
||||||
import { translationMetadata } from "../resources/translations-metadata";
|
import { translationMetadata } from "../resources/translations-metadata";
|
||||||
|
import { HomeAssistant } from "../types";
|
||||||
|
|
||||||
const DEFAULT_BASE_URL = "/static/translations";
|
const DEFAULT_BASE_URL = "/static/translations";
|
||||||
|
const STORAGE = window.localStorage || {};
|
||||||
|
|
||||||
// Store loaded translations in memory so translations are available immediately
|
// Store loaded translations in memory so translations are available immediately
|
||||||
// when DOM is created in Polymer. Even a cache lookup creates noticeable latency.
|
// when DOM is created in Polymer. Even a cache lookup creates noticeable latency.
|
||||||
@ -18,6 +24,108 @@ async function fetchTranslation(fingerprint: string, base_url: string) {
|
|||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Chinese locales need map to Simplified or Traditional Chinese
|
||||||
|
const LOCALE_LOOKUP = {
|
||||||
|
"zh-cn": "zh-Hans",
|
||||||
|
"zh-sg": "zh-Hans",
|
||||||
|
"zh-my": "zh-Hans",
|
||||||
|
"zh-tw": "zh-Hant",
|
||||||
|
"zh-hk": "zh-Hant",
|
||||||
|
"zh-mo": "zh-Hant",
|
||||||
|
zh: "zh-Hant", // all other Chinese locales map to Traditional Chinese
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for a matching translation from most specific to general
|
||||||
|
*/
|
||||||
|
export function findAvailableLanguage(language: string) {
|
||||||
|
// In most case, the language has the same format with our translation meta data
|
||||||
|
if (language in translationMetadata.translations) {
|
||||||
|
return language;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform case-insenstive comparison since browser isn't required to
|
||||||
|
// report languages with specific cases.
|
||||||
|
const langLower = language.toLowerCase();
|
||||||
|
|
||||||
|
if (langLower in LOCALE_LOOKUP) {
|
||||||
|
return LOCALE_LOOKUP[langLower];
|
||||||
|
}
|
||||||
|
|
||||||
|
const translation = Object.keys(translationMetadata.translations).find(
|
||||||
|
(lang) => lang.toLowerCase() === langLower
|
||||||
|
);
|
||||||
|
if (translation) {
|
||||||
|
return translation;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (language.includes("-")) {
|
||||||
|
return findAvailableLanguage(language.split("-")[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get user selected locale data from backend
|
||||||
|
*/
|
||||||
|
export async function getUserLocale(
|
||||||
|
hass: HomeAssistant
|
||||||
|
): Promise<Partial<FrontendLocaleData>> {
|
||||||
|
const result = await fetchTranslationPreferences(hass);
|
||||||
|
const language = result?.language;
|
||||||
|
const number_format = result?.number_format;
|
||||||
|
const time_format = result?.time_format;
|
||||||
|
if (language) {
|
||||||
|
const availableLanguage = findAvailableLanguage(language);
|
||||||
|
if (availableLanguage) {
|
||||||
|
return {
|
||||||
|
language: availableLanguage,
|
||||||
|
number_format,
|
||||||
|
time_format,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
number_format,
|
||||||
|
time_format,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get browser specific language
|
||||||
|
*/
|
||||||
|
export function getLocalLanguage() {
|
||||||
|
let language = null;
|
||||||
|
if (STORAGE.selectedLanguage) {
|
||||||
|
try {
|
||||||
|
const stored = JSON.parse(STORAGE.selectedLanguage);
|
||||||
|
if (stored) {
|
||||||
|
language = findAvailableLanguage(stored);
|
||||||
|
if (language) {
|
||||||
|
return language;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
// Ignore parsing error.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (navigator.languages) {
|
||||||
|
for (const locale of navigator.languages) {
|
||||||
|
language = findAvailableLanguage(locale);
|
||||||
|
if (language) {
|
||||||
|
return language;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
language = findAvailableLanguage(navigator.language);
|
||||||
|
if (language) {
|
||||||
|
return language;
|
||||||
|
}
|
||||||
|
// Final fallback
|
||||||
|
return "en";
|
||||||
|
}
|
||||||
|
|
||||||
export async function getTranslation(
|
export async function getTranslation(
|
||||||
fragment: string | null,
|
fragment: string | null,
|
||||||
language: string,
|
language: string,
|
||||||
|
@ -3,6 +3,7 @@ import { until } from "lit/directives/until";
|
|||||||
import checkValidDate from "../common/datetime/check_valid_date";
|
import checkValidDate from "../common/datetime/check_valid_date";
|
||||||
import { formatDate } from "../common/datetime/format_date";
|
import { formatDate } from "../common/datetime/format_date";
|
||||||
import { formatDateTimeWithSeconds } from "../common/datetime/format_date_time";
|
import { formatDateTimeWithSeconds } from "../common/datetime/format_date_time";
|
||||||
|
import { capitalizeFirstLetter } from "../common/string/capitalize-first-letter";
|
||||||
import { isDate } from "../common/string/is_date";
|
import { isDate } from "../common/string/is_date";
|
||||||
import { isTimestamp } from "../common/string/is_timestamp";
|
import { isTimestamp } from "../common/string/is_timestamp";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
@ -159,7 +160,7 @@ export function formatAttributeName(value: string): string {
|
|||||||
.replace(/\bip\b/g, "IP")
|
.replace(/\bip\b/g, "IP")
|
||||||
.replace(/\bmac\b/g, "MAC")
|
.replace(/\bmac\b/g, "MAC")
|
||||||
.replace(/\bgps\b/g, "GPS");
|
.replace(/\bgps\b/g, "GPS");
|
||||||
return value.charAt(0).toUpperCase() + value.slice(1);
|
return capitalizeFirstLetter(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatAttributeValue(
|
export function formatAttributeValue(
|
||||||
|
@ -1,118 +0,0 @@
|
|||||||
import {
|
|
||||||
fetchTranslationPreferences,
|
|
||||||
FrontendLocaleData,
|
|
||||||
} from "../data/translation";
|
|
||||||
import { translationMetadata } from "../resources/translations-metadata";
|
|
||||||
import { HomeAssistant } from "../types";
|
|
||||||
import { getTranslation as commonGetTranslation } from "./common-translation";
|
|
||||||
|
|
||||||
const STORAGE = window.localStorage || {};
|
|
||||||
|
|
||||||
// Chinese locales need map to Simplified or Traditional Chinese
|
|
||||||
const LOCALE_LOOKUP = {
|
|
||||||
"zh-cn": "zh-Hans",
|
|
||||||
"zh-sg": "zh-Hans",
|
|
||||||
"zh-my": "zh-Hans",
|
|
||||||
"zh-tw": "zh-Hant",
|
|
||||||
"zh-hk": "zh-Hant",
|
|
||||||
"zh-mo": "zh-Hant",
|
|
||||||
zh: "zh-Hant", // all other Chinese locales map to Traditional Chinese
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Search for a matching translation from most specific to general
|
|
||||||
*/
|
|
||||||
export function findAvailableLanguage(language: string) {
|
|
||||||
// In most case, the language has the same format with our translation meta data
|
|
||||||
if (language in translationMetadata.translations) {
|
|
||||||
return language;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Perform case-insenstive comparison since browser isn't required to
|
|
||||||
// report languages with specific cases.
|
|
||||||
const langLower = language.toLowerCase();
|
|
||||||
|
|
||||||
if (langLower in LOCALE_LOOKUP) {
|
|
||||||
return LOCALE_LOOKUP[langLower];
|
|
||||||
}
|
|
||||||
|
|
||||||
const translation = Object.keys(translationMetadata.translations).find(
|
|
||||||
(lang) => lang.toLowerCase() === langLower
|
|
||||||
);
|
|
||||||
if (translation) {
|
|
||||||
return translation;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (language.includes("-")) {
|
|
||||||
return findAvailableLanguage(language.split("-")[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get user selected locale data from backend
|
|
||||||
*/
|
|
||||||
export async function getUserLocale(
|
|
||||||
hass: HomeAssistant
|
|
||||||
): Promise<Partial<FrontendLocaleData>> {
|
|
||||||
const result = await fetchTranslationPreferences(hass);
|
|
||||||
const language = result?.language;
|
|
||||||
const number_format = result?.number_format;
|
|
||||||
const time_format = result?.time_format;
|
|
||||||
if (language) {
|
|
||||||
const availableLanguage = findAvailableLanguage(language);
|
|
||||||
if (availableLanguage) {
|
|
||||||
return {
|
|
||||||
language: availableLanguage,
|
|
||||||
number_format,
|
|
||||||
time_format,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
number_format,
|
|
||||||
time_format,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get browser specific language
|
|
||||||
*/
|
|
||||||
export function getLocalLanguage() {
|
|
||||||
let language = null;
|
|
||||||
if (STORAGE.selectedLanguage) {
|
|
||||||
try {
|
|
||||||
const stored = JSON.parse(STORAGE.selectedLanguage);
|
|
||||||
if (stored) {
|
|
||||||
language = findAvailableLanguage(stored);
|
|
||||||
if (language) {
|
|
||||||
return language;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err: any) {
|
|
||||||
// Ignore parsing error.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (navigator.languages) {
|
|
||||||
for (const locale of navigator.languages) {
|
|
||||||
language = findAvailableLanguage(locale);
|
|
||||||
if (language) {
|
|
||||||
return language;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
language = findAvailableLanguage(navigator.language);
|
|
||||||
if (language) {
|
|
||||||
return language;
|
|
||||||
}
|
|
||||||
// Final fallback
|
|
||||||
return "en";
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getTranslation = (fragment: string | null, language: string) =>
|
|
||||||
commonGetTranslation(fragment, language);
|
|
||||||
|
|
||||||
// Load selected translation into memory immediately so it is ready when Polymer
|
|
||||||
// initializes.
|
|
||||||
commonGetTranslation(null, getLocalLanguage());
|
|
Loading…
x
Reference in New Issue
Block a user