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