Merge pull request #5840 from home-assistant/dev

This commit is contained in:
Bram Kragten 2020-05-12 22:24:40 +02:00 committed by GitHub
commit de1ffe67b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 1489 additions and 895 deletions

View File

@ -9,25 +9,30 @@ const paths = require("../paths");
gulp.task("compress-app", function compressApp() { gulp.task("compress-app", function compressApp() {
const jsLatest = gulp const jsLatest = gulp
.src(path.resolve(paths.output, "**/*.js")) .src(path.resolve(paths.output, "**/*.js"))
.pipe(zopfli()) .pipe(zopfli({ threshold: 150 }))
.pipe(gulp.dest(paths.output)); .pipe(gulp.dest(paths.output));
const jsEs5 = gulp const jsEs5 = gulp
.src(path.resolve(paths.output_es5, "**/*.js")) .src(path.resolve(paths.output_es5, "**/*.js"))
.pipe(zopfli()) .pipe(zopfli({ threshold: 150 }))
.pipe(gulp.dest(paths.output_es5)); .pipe(gulp.dest(paths.output_es5));
const polyfills = gulp const polyfills = gulp
.src(path.resolve(paths.static, "polyfills/*.js")) .src(path.resolve(paths.static, "polyfills/*.js"))
.pipe(zopfli()) .pipe(zopfli({ threshold: 150 }))
.pipe(gulp.dest(path.resolve(paths.static, "polyfills"))); .pipe(gulp.dest(path.resolve(paths.static, "polyfills")));
const translations = gulp const translations = gulp
.src(path.resolve(paths.static, "translations/*.json")) .src(path.resolve(paths.static, "translations/**/*.json"))
.pipe(zopfli()) .pipe(zopfli({ threshold: 150 }))
.pipe(gulp.dest(path.resolve(paths.static, "translations"))); .pipe(gulp.dest(path.resolve(paths.static, "translations")));
return merge(jsLatest, jsEs5, polyfills, translations); const icons = gulp
.src(path.resolve(paths.static, "mdi/*.json"))
.pipe(zopfli({ threshold: 150 }))
.pipe(gulp.dest(path.resolve(paths.static, "mdi")));
return merge(jsLatest, jsEs5, polyfills, translations, icons);
}); });
gulp.task("compress-hassio", function compressApp() { gulp.task("compress-hassio", function compressApp() {

View File

@ -77,7 +77,7 @@
"@polymer/paper-toast": "^3.0.1", "@polymer/paper-toast": "^3.0.1",
"@polymer/paper-tooltip": "^3.0.1", "@polymer/paper-tooltip": "^3.0.1",
"@polymer/polymer": "3.1.0", "@polymer/polymer": "3.1.0",
"@thomasloven/round-slider": "0.3.7", "@thomasloven/round-slider": "0.4.1",
"@vaadin/vaadin-combo-box": "^5.0.10", "@vaadin/vaadin-combo-box": "^5.0.10",
"@vaadin/vaadin-date-picker": "^4.0.7", "@vaadin/vaadin-date-picker": "^4.0.7",
"@webcomponents/shadycss": "^1.9.0", "@webcomponents/shadycss": "^1.9.0",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 655 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 888 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 807 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 639 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 840 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 798 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 487 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 774 B

View File

@ -2,7 +2,7 @@ from setuptools import setup, find_packages
setup( setup(
name="home-assistant-frontend", name="home-assistant-frontend",
version="20200509.0", version="20200512.0",
description="The Home Assistant frontend", description="The Home Assistant frontend",
url="https://github.com/home-assistant/home-assistant-polymer", url="https://github.com/home-assistant/home-assistant-polymer",
author="The Home Assistant Authors", author="The Home Assistant Authors",

View File

@ -16,14 +16,6 @@ import { RippleHandlers } from "@material/mwc-ripple/ripple-handlers";
import "./ha-icon"; import "./ha-icon";
import "./ha-svg-icon"; import "./ha-svg-icon";
import { ifDefined } from "lit-html/directives/if-defined"; import { ifDefined } from "lit-html/directives/if-defined";
import { fireEvent } from "../common/dom/fire_event";
declare global {
// for fire event
interface HASSDomEvents {
activated: undefined;
}
}
@customElement("ha-tab") @customElement("ha-tab")
export class HaTab extends LitElement { export class HaTab extends LitElement {
@ -54,7 +46,6 @@ export class HaTab extends LitElement {
@touchend=${this.handleRippleDeactivate} @touchend=${this.handleRippleDeactivate}
@touchcancel=${this.handleRippleDeactivate} @touchcancel=${this.handleRippleDeactivate}
@keydown=${this._handleKeyDown} @keydown=${this._handleKeyDown}
@click=${this._handleClick}
> >
${this.narrow ? html`<slot name="icon"></slot>` : ""} ${this.narrow ? html`<slot name="icon"></slot>` : ""}
${!this.narrow || this.active ${!this.narrow || this.active
@ -72,14 +63,10 @@ export class HaTab extends LitElement {
private _handleKeyDown(ev: KeyboardEvent): void { private _handleKeyDown(ev: KeyboardEvent): void {
if (ev.keyCode === 13) { if (ev.keyCode === 13) {
fireEvent(this, "activated"); (ev.target as HTMLElement).click();
} }
} }
private _handleClick(): void {
fireEvent(this, "activated");
}
@eventOptions({ passive: true }) @eventOptions({ passive: true })
private handleRippleActivate(evt?: Event) { private handleRippleActivate(evt?: Event) {
this._rippleHandlers.startPress(evt); this._rippleHandlers.startPress(evt);

View File

@ -18,7 +18,11 @@ export interface IntegrationManifest {
quality_scale?: string; quality_scale?: string;
} }
export const integrationIssuesUrl = (domain: string) => export const integrationIssuesUrl = (
domain: string,
manifest: IntegrationManifest
) =>
manifest.issue_tracker ||
`https://github.com/home-assistant/home-assistant/issues?q=is%3Aissue+is%3Aopen+label%3A%22integration%3A+${domain}%22`; `https://github.com/home-assistant/home-assistant/issues?q=is%3Aissue+is%3Aopen+label%3A%22integration%3A+${domain}%22`;
export const domainToName = (localize: LocalizeFunc, domain: string) => export const domainToName = (localize: LocalizeFunc, domain: string) =>

View File

@ -1,26 +1,52 @@
import { HomeAssistant, WeatherEntity } from "../types"; import { SVGTemplateResult, svg, html, TemplateResult, css } from "lit-element";
import { styleMap } from "lit-html/directives/style-map";
export const weatherImages = { import type { HomeAssistant, WeatherEntity } from "../types";
"clear-night": "/static/images/weather/night.png",
cloudy: "/static/images/weather/cloudy.png", export const weatherSVGs = new Set<string>([
fog: "/static/images/weather/cloudy.png", "clear-night",
lightning: "/static/images/weather/lightning.png", "cloudy",
"lightning-rainy": "/static/images/weather/lightning-rainy.png", "fog",
partlycloudy: "/static/images/weather/partly-cloudy.png", "lightning",
pouring: "/static/images/weather/pouring.png", "lightning-rainy",
rainy: "/static/images/weather/rainy.png", "partlycloudy",
hail: "/static/images/weather/rainy.png", "pouring",
snowy: "/static/images/weather/snowy.png", "rainy",
"snowy-rainy": "/static/images/weather/snowy.png", "hail",
sunny: "/static/images/weather/sunny.png", "snowy",
windy: "/static/images/weather/windy.png", "snowy-rainy",
"windy-variant": "/static/images/weather/windy.png", "sunny",
}; "windy",
"windy-variant",
]);
export const weatherIcons = { export const weatherIcons = {
exceptional: "hass:alert-circle-outline", exceptional: "hass:alert-circle-outline",
}; };
const cloudyStates = new Set<string>([
"partlycloudy",
"cloudy",
"fog",
"windy",
"windy-variant",
"hail",
"rainy",
"snowy",
"snowy-rainy",
"pouring",
"lightning",
"lightning-rainy",
]);
const rainStates = new Set<string>(["hail", "rainy", "pouring"]);
const windyStates = new Set<string>(["windy", "windy-variant"]);
const snowyStates = new Set<string>(["snowy", "snowy-rainy"]);
const lightningStates = new Set<string>(["lightning", "lightning-rainy"]);
export const cardinalDirections = [ export const cardinalDirections = [
"N", "N",
"NNE", "NNE",
@ -164,3 +190,183 @@ const getWeatherExtrema = (
} }
`; `;
}; };
export const weatherSVGStyles = css`
.rain {
fill: var(--weather-icon-rain-color, #30b3ff);
}
.sun {
fill: var(--weather-icon-sun-color, #fdd93c);
}
.moon {
fill: var(--weather-icon-moon-color, #fdf9cc);
}
.cloud-back {
fill: var(--weather-icon-cloud-back-color, #d4d4d4);
}
.cloud-front {
fill: var(--weather-icon-cloud-front-color, #f9f9f9);
}
`;
export const getWeatherStateSVG = (state: string): SVGTemplateResult => {
return svg`
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 17 17"
>
${
state === "sunny"
? svg`
<path
class="sun"
d="m 14.39303,8.4033507 c 0,3.3114723 -2.684145,5.9956173 -5.9956169,5.9956173 -3.3114716,0 -5.9956168,-2.684145 -5.9956168,-5.9956173 0,-3.311471 2.6841452,-5.995617 5.9956168,-5.995617 3.3114719,0 5.9956169,2.684146 5.9956169,5.995617"
/>
`
: ""
}
${
state === "clear-night"
? svg`
<path
class="moon"
d="m 13.502891,11.382935 c -1.011285,1.859223 -2.976664,3.121381 -5.2405751,3.121381 -3.289929,0 -5.953329,-2.663833 -5.953329,-5.9537625 0,-2.263911 1.261724,-4.228856 3.120948,-5.240575 -0.452782,0.842738 -0.712753,1.806363 -0.712753,2.832381 0,3.289928 2.663833,5.9533275 5.9533291,5.9533275 1.026017,0 1.989641,-0.259969 2.83238,-0.712752"
/>
`
: ""
}
${
state === "partlycloudy"
? svg`
<path
class="sun"
d="m14.981 4.2112c0 1.9244-1.56 3.4844-3.484 3.4844-1.9244 0-3.4844-1.56-3.4844-3.4844s1.56-3.484 3.4844-3.484c1.924 0 3.484 1.5596 3.484 3.484"
/>
`
: ""
}
${
cloudyStates.has(state)
? svg`
<path
class="cloud-back"
d="m3.8863 5.035c-0.54892 0.16898-1.04 0.46637-1.4372 0.8636-0.63077 0.63041-1.0206 1.4933-1.0206 2.455 0 1.9251 1.5589 3.4682 3.4837 3.4682h6.9688c1.9251 0 3.484-1.5981 3.484-3.5232 0-1.9251-1.5589-3.5232-3.484-3.5232h-1.0834c-0.25294-1.6916-1.6986-2.9083-3.4463-2.9083-1.7995 0-3.2805 1.4153-3.465 3.1679"
/>
<path
class="cloud-front"
d="m4.1996 7.6995c-0.33902 0.10407-0.64276 0.28787-0.88794 0.5334-0.39017 0.38982-0.63147 0.92322-0.63147 1.5176 0 1.1896 0.96414 2.1431 2.1537 2.1431h4.3071c1.1896 0 2.153-0.98742 2.153-2.1777 0-1.1896-0.96344-2.1777-2.153-2.1777h-0.66992c-0.15593-1.0449-1.0499-1.7974-2.1297-1.7974-1.112 0-2.0274 0.87524-2.1417 1.9586"
/>
`
: ""
}
${
rainStates.has(state)
? svg`
<path
class="rain"
d="m5.2852 14.734c-0.22401 0.24765-0.57115 0.2988-0.77505 0.11395-0.20391-0.1845-0.18732-0.53481 0.036689-0.78281 0.14817-0.16298 0.59126-0.32914 0.87559-0.42369 0.12453-0.04092 0.22684 0.05186 0.19791 0.17956-0.065617 0.2921-0.18732 0.74965-0.33514 0.91299"
/>
<path
class="rain"
d="m11.257 14.163c-0.22437 0.24765-0.57115 0.2988-0.77505 0.11395-0.2039-0.1845-0.18768-0.53481 0.03669-0.78281 0.14817-0.16298 0.59126-0.32914 0.8756-0.42369 0.12453-0.04092 0.22684 0.05186 0.19791 0.17956-0.06562 0.2921-0.18732 0.74965-0.33514 0.91299"
/>
<path
class="rain"
d="m8.432 15.878c-0.15452 0.17039-0.3937 0.20567-0.53446 0.07867-0.14041-0.12735-0.12876-0.36865 0.025753-0.53975 0.10195-0.11218 0.40711-0.22684 0.60325-0.29175 0.085725-0.02858 0.15628 0.03563 0.13652 0.12382-0.045508 0.20108-0.12912 0.51647-0.23107 0.629"
/>
<path
class="rain"
d="m7.9991 14.118c-0.19226 0.21237-0.49001 0.25612-0.66499 0.09737-0.17462-0.15804-0.16051-0.45861 0.03175-0.67098 0.12665-0.14005 0.50729-0.28293 0.75071-0.36336 0.10689-0.03563 0.19473 0.0441 0.17004 0.15346-0.056092 0.25082-0.16051 0.64347-0.28751 0.78352"
/>
`
: ""
}
${
state === "pouring"
? svg`
<path
class="rain"
d="m10.648 16.448c-0.19226 0.21449-0.49001 0.25894-0.66499 0.09878-0.17498-0.16016-0.16087-0.4639 0.03175-0.67874 0.12665-0.14146 0.50694-0.2854 0.75071-0.36724 0.10689-0.03563 0.19473 0.0448 0.17004 0.15558-0.05645 0.25365-0.16051 0.65017-0.28751 0.79163"
/>
<path
class="rain"
d="m5.9383 16.658c-0.22437 0.25012-0.5715 0.30162-0.77505 0.11501-0.20391-0.18627-0.18768-0.54046 0.036689-0.79093 0.14817-0.1651 0.59126-0.33267 0.87559-0.42827 0.12418-0.04127 0.22648 0.05221 0.19791 0.18168-0.065617 0.29528-0.18732 0.75741-0.33514 0.92251"
/>
`
: ""
}
${
windyStates.has(state)
? svg`
<path
class="cloud-back"
d="m 13.59616,15.30968 c 0,0 -0.09137,-0.0071 -0.250472,-0.0187 -0.158045,-0.01235 -0.381353,-0.02893 -0.64382,-0.05715 -0.262466,-0.02716 -0.564444,-0.06385 -0.877358,-0.124531 -0.156986,-0.03034 -0.315383,-0.06844 -0.473781,-0.111478 -0.157691,-0.04551 -0.313266,-0.09842 -0.463902,-0.161219 l -0.267406,-0.0949 c -0.09984,-0.02646 -0.205669,-0.04904 -0.305153,-0.06738 -0.193322,-0.02716 -0.3838218,-0.03316 -0.5640912,-0.02011 -0.3626556,0.02611 -0.6847417,0.119239 -0.94615,0.226483 -0.2617611,0.108656 -0.4642556,0.230364 -0.600075,0.324203 -0.1358195,0.09419 -0.2049639,0.160514 -0.2049639,0.160514 0,0 0.089958,-0.01623 0.24765,-0.04445 0.1559278,-0.02575 0.3764139,-0.06174 0.6367639,-0.08714 0.2596444,-0.02646 0.5591527,-0.0441 0.8678333,-0.02328 0.076905,0.0035 0.1538111,0.01658 0.2321278,0.02293 0.077611,0.01058 0.1534581,0.02893 0.2314221,0.04022 0.07267,0.01834 0.1397,0.03986 0.213078,0.05644 l 0.238125,0.08925 c 0.09207,0.03281 0.183444,0.07055 0.275872,0.09878 0.09243,0.0261 0.185208,0.05327 0.277636,0.07161 0.184856,0.0388 0.367947,0.06174 0.543983,0.0702 0.353131,0.01905 0.678745,-0.01341 0.951442,-0.06456 0.27305,-0.05292 0.494595,-0.123119 0.646642,-0.181681 0.152047,-0.05785 0.234597,-0.104069 0.234597,-0.104069"
/>
<path
class="cloud-back"
d="m 4.7519154,13.905801 c 0,0 0.091369,-0.0032 0.2511778,-0.0092 0.1580444,-0.0064 0.3820583,-0.01446 0.6455833,-0.03281 0.2631722,-0.01729 0.5662083,-0.04269 0.8812389,-0.09137 0.1576916,-0.02434 0.3175,-0.05609 0.4776611,-0.09384 0.1591027,-0.03951 0.3167944,-0.08643 0.4699,-0.14358 l 0.2702277,-0.08467 c 0.1008945,-0.02222 0.2074334,-0.04127 0.3072695,-0.05574 0.1943805,-0.01976 0.3848805,-0.0187 0.5651499,0.0014 0.3608917,0.03951 0.67945,0.144639 0.936625,0.261761 0.2575278,0.118534 0.4554364,0.247297 0.5873754,0.346781 0.132291,0.09913 0.198966,0.168275 0.198966,0.168275 0,0 -0.08925,-0.01976 -0.245886,-0.05397 C 9.9423347,14.087088 9.7232597,14.042988 9.4639681,14.00736 9.2057347,13.97173 8.9072848,13.94245 8.5978986,13.95162 c -0.077258,7.06e-4 -0.1541638,0.01058 -0.2328333,0.01411 -0.077964,0.0078 -0.1545166,0.02328 -0.2331861,0.03175 -0.073025,0.01588 -0.1404055,0.03422 -0.2141361,0.04798 l -0.2420055,0.08008 c -0.093486,0.02963 -0.1859139,0.06421 -0.2794,0.0889 C 7.3028516,14.23666 7.2093653,14.2603 7.116232,14.27512 6.9303181,14.30722 6.7465209,14.3231 6.5697792,14.32486 6.2166487,14.33046 5.8924459,14.28605 5.6218654,14.224318 5.3505793,14.161565 5.1318571,14.082895 4.9822793,14.01869 4.8327015,13.95519 4.7519154,13.905801 4.7519154,13.905801"
/>
`
: ""
}
${
snowyStates.has(state)
? svg`
<path
class="rain"
d="m 8.4319893,15.348341 c 0,0.257881 -0.209197,0.467079 -0.467078,0.467079 -0.258586,0 -0.46743,-0.209198 -0.46743,-0.467079 0,-0.258233 0.208844,-0.467431 0.46743,-0.467431 0.257881,0 0.467078,0.209198 0.467078,0.467431"
/>
<path
class="rain"
d="m 11.263878,14.358553 c 0,0.364067 -0.295275,0.659694 -0.659695,0.659694 -0.364419,0 -0.6596937,-0.295627 -0.6596937,-0.659694 0,-0.364419 0.2952747,-0.659694 0.6596937,-0.659694 0.36442,0 0.659695,0.295275 0.659695,0.659694"
/>
<path
class="rain"
d="m 5.3252173,13.69847 c 0,0.364419 -0.295275,0.660047 -0.659695,0.660047 -0.364067,0 -0.659694,-0.295628 -0.659694,-0.660047 0,-0.364067 0.295627,-0.659694 0.659694,-0.659694 0.36442,0 0.659695,0.295627 0.659695,0.659694"
/>
`
: ""
}
${
lightningStates.has(state)
? svg`
<path
class="sun"
d="m 9.9252695,10.935875 -1.6483986,2.341014 1.1170184,0.05929 -1.2169864,2.02141 3.0450261,-2.616159 H 9.8864918 L 10.97937,11.294651 10.700323,10.79794 h -0.508706 l -0.2663475,0.137936"
/>
`
: ""
}
</svg>`;
};
export const getWeatherStateIcon = (
state: string,
element: HTMLElement
): TemplateResult | undefined => {
const userDefinedIcon = getComputedStyle(element).getPropertyValue(
`--weather-icon-${state}`
);
if (userDefinedIcon) {
return html`
<div
style="background-size: cover;${styleMap({
"background-image": userDefinedIcon,
})}"
></div>
`;
}
if (weatherSVGs.has(state)) {
return html`${getWeatherStateSVG(state)}`;
}
if (state in weatherIcons) {
return html`
<ha-icon class="weather-icon" .icon=${weatherIcons[state]}></ha-icon>
`;
}
return undefined;
};

View File

@ -3,6 +3,7 @@ import { HomeAssistant } from "../types";
export interface ZHAEntityReference extends HassEntity { export interface ZHAEntityReference extends HassEntity {
name: string; name: string;
original_name?: string;
} }
export interface ZHADevice { export interface ZHADevice {
@ -26,6 +27,12 @@ export interface ZHADevice {
signature: any; signature: any;
} }
export interface ZHADeviceEndpoint {
device: ZHADevice;
endpoint_id: number;
entities: ZHAEntityReference[];
}
export interface Attribute { export interface Attribute {
name: string; name: string;
id: number; id: number;
@ -56,7 +63,12 @@ export interface ReadAttributeServiceData {
export interface ZHAGroup { export interface ZHAGroup {
name: string; name: string;
group_id: number; group_id: number;
members: ZHADevice[]; members: ZHADeviceEndpoint[];
}
export interface ZHAGroupMember {
ieee: string;
endpoint_id: string;
} }
export const reconfigureNode = ( export const reconfigureNode = (
@ -213,7 +225,7 @@ export const fetchGroup = (
export const fetchGroupableDevices = ( export const fetchGroupableDevices = (
hass: HomeAssistant hass: HomeAssistant
): Promise<ZHADevice[]> => ): Promise<ZHADeviceEndpoint[]> =>
hass.callWS({ hass.callWS({
type: "zha/devices/groupable", type: "zha/devices/groupable",
}); });
@ -221,7 +233,7 @@ export const fetchGroupableDevices = (
export const addMembersToGroup = ( export const addMembersToGroup = (
hass: HomeAssistant, hass: HomeAssistant,
groupId: number, groupId: number,
membersToAdd: string[] membersToAdd: ZHAGroupMember[]
): Promise<ZHAGroup> => ): Promise<ZHAGroup> =>
hass.callWS({ hass.callWS({
type: "zha/group/members/add", type: "zha/group/members/add",
@ -232,7 +244,7 @@ export const addMembersToGroup = (
export const removeMembersFromGroup = ( export const removeMembersFromGroup = (
hass: HomeAssistant, hass: HomeAssistant,
groupId: number, groupId: number,
membersToRemove: string[] membersToRemove: ZHAGroupMember[]
): Promise<ZHAGroup> => ): Promise<ZHAGroup> =>
hass.callWS({ hass.callWS({
type: "zha/group/members/remove", type: "zha/group/members/remove",
@ -243,7 +255,7 @@ export const removeMembersFromGroup = (
export const addGroup = ( export const addGroup = (
hass: HomeAssistant, hass: HomeAssistant,
groupName: string, groupName: string,
membersToAdd?: string[] membersToAdd?: ZHAGroupMember[]
): Promise<ZHAGroup> => ): Promise<ZHAGroup> =>
hass.callWS({ hass.callWS({
type: "zha/group/add", type: "zha/group/add",

View File

@ -104,6 +104,11 @@ window.hassConnection.then(({ conn }) => {
}); });
window.addEventListener("error", (e) => { window.addEventListener("error", (e) => {
if (!__DEV__ && e.message === "ResizeObserver loop limit exceeded") {
e.stopImmediatePropagation();
e.stopPropagation();
return;
}
const homeAssistant = document.querySelector("home-assistant") as any; const homeAssistant = document.querySelector("home-assistant") as any;
if ( if (
homeAssistant && homeAssistant &&

View File

@ -74,7 +74,7 @@ class HassTabsSubpage extends LitElement {
html` html`
<ha-tab <ha-tab
.hass=${this.hass} .hass=${this.hass}
@activated=${this._tabTapped} @click=${this._tabTapped}
.path=${page.path} .path=${page.path}
.active=${page === activeTab} .active=${page === activeTab}
.narrow=${this.narrow} .narrow=${this.narrow}

View File

@ -60,6 +60,7 @@ import type {
import { HASSDomEvent } from "../../../common/dom/fire_event"; import { HASSDomEvent } from "../../../common/dom/fire_event";
import "../../../components/ha-svg-icon"; import "../../../components/ha-svg-icon";
import { mdiPlus } from "@mdi/js"; import { mdiPlus } from "@mdi/js";
import { LocalizeFunc } from "../../../common/translations/localize";
interface DataEntryFlowProgressExtended extends DataEntryFlowProgress { interface DataEntryFlowProgressExtended extends DataEntryFlowProgress {
localized_title?: string; localized_title?: string;
@ -121,7 +122,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
this._deviceRegistryEntries = entries; this._deviceRegistryEntries = entries;
}), }),
subscribeConfigFlowInProgress(this.hass, async (flowsInProgress) => { subscribeConfigFlowInProgress(this.hass, async (flowsInProgress) => {
const translationsPromisses: Promise<void>[] = []; const translationsPromisses: Promise<LocalizeFunc>[] = [];
flowsInProgress.forEach((flow) => { flowsInProgress.forEach((flow) => {
// To render title placeholders // To render title placeholders
if (flow.context.title_placeholders) { if (flow.context.title_placeholders) {

View File

@ -18,31 +18,31 @@ import type { SelectionChangedEvent } from "../../../components/data-table/ha-da
import { import {
addGroup, addGroup,
fetchGroupableDevices, fetchGroupableDevices,
ZHADevice,
ZHAGroup, ZHAGroup,
ZHADeviceEndpoint,
} from "../../../data/zha"; } from "../../../data/zha";
import "../../../layouts/hass-error-screen"; import "../../../layouts/hass-error-screen";
import "../../../layouts/hass-subpage"; import "../../../layouts/hass-subpage";
import type { PolymerChangedEvent } from "../../../polymer-types"; import type { PolymerChangedEvent } from "../../../polymer-types";
import type { HomeAssistant } from "../../../types"; import type { HomeAssistant } from "../../../types";
import "../ha-config-section"; import "../ha-config-section";
import "./zha-devices-data-table"; import "./zha-device-endpoint-data-table";
import type { ZHADevicesDataTable } from "./zha-devices-data-table"; import type { ZHADeviceEndpointDataTable } from "./zha-device-endpoint-data-table";
@customElement("zha-add-group-page") @customElement("zha-add-group-page")
export class ZHAAddGroupPage extends LitElement { export class ZHAAddGroupPage extends LitElement {
@property() public hass!: HomeAssistant; @property({ type: Object }) public hass!: HomeAssistant;
@property() public narrow!: boolean; @property({ type: Boolean }) public narrow!: boolean;
@property() public devices: ZHADevice[] = []; @property({ type: Array }) public deviceEndpoints: ZHADeviceEndpoint[] = [];
@property() private _processingAdd = false; @property() private _processingAdd = false;
@property() private _groupName = ""; @property() private _groupName = "";
@query("zha-devices-data-table") @query("zha-device-endpoint-data-table")
private _zhaDevicesDataTable!: ZHADevicesDataTable; private _zhaDevicesDataTable!: ZHADeviceEndpointDataTable;
private _firstUpdatedCalled = false; private _firstUpdatedCalled = false;
@ -87,14 +87,14 @@ export class ZHAAddGroupPage extends LitElement {
${this.hass.localize("ui.panel.config.zha.groups.add_members")} ${this.hass.localize("ui.panel.config.zha.groups.add_members")}
</div> </div>
<zha-devices-data-table <zha-device-endpoint-data-table
.hass=${this.hass} .hass=${this.hass}
.devices=${this.devices} .deviceEndpoints=${this.deviceEndpoints}
.narrow=${this.narrow} .narrow=${this.narrow}
selectable selectable
@selection-changed=${this._handleAddSelectionChanged} @selection-changed=${this._handleAddSelectionChanged}
> >
</zha-devices-data-table> </zha-device-endpoint-data-table>
<div class="paper-dialog-buttons"> <div class="paper-dialog-buttons">
<mwc-button <mwc-button
@ -121,7 +121,7 @@ export class ZHAAddGroupPage extends LitElement {
} }
private async _fetchData() { private async _fetchData() {
this.devices = await fetchGroupableDevices(this.hass!); this.deviceEndpoints = await fetchGroupableDevices(this.hass!);
} }
private _handleAddSelectionChanged( private _handleAddSelectionChanged(
@ -132,11 +132,11 @@ export class ZHAAddGroupPage extends LitElement {
private async _createGroup(): Promise<void> { private async _createGroup(): Promise<void> {
this._processingAdd = true; this._processingAdd = true;
const group: ZHAGroup = await addGroup( const members = this._selectedDevicesToAdd.map((member) => {
this.hass, const memberParts = member.split("_");
this._groupName, return { ieee: memberParts[0], endpoint_id: memberParts[1] };
this._selectedDevicesToAdd });
); const group: ZHAGroup = await addGroup(this.hass, this._groupName, members);
this._selectedDevicesToAdd = []; this._selectedDevicesToAdd = [];
this._processingAdd = false; this._processingAdd = false;
this._groupName = ""; this._groupName = "";

View File

@ -16,6 +16,7 @@ import "../../../components/data-table/ha-data-table";
import type { import type {
DataTableColumnContainer, DataTableColumnContainer,
RowClickedEvent, RowClickedEvent,
DataTableRowData,
} from "../../../components/data-table/ha-data-table"; } from "../../../components/data-table/ha-data-table";
import "../../../components/ha-card"; import "../../../components/ha-card";
import "../../../components/ha-icon-next"; import "../../../components/ha-icon-next";
@ -27,19 +28,19 @@ import type { HomeAssistant, Route } from "../../../types";
import "../ha-config-section"; import "../ha-config-section";
import { formatAsPaddedHex, sortZHADevices } from "./functions"; import { formatAsPaddedHex, sortZHADevices } from "./functions";
export interface DeviceRowData extends ZHADevice { export interface DeviceRowData extends DataTableRowData {
device?: DeviceRowData; device?: DeviceRowData;
} }
@customElement("zha-config-dashboard") @customElement("zha-config-dashboard")
class ZHAConfigDashboard extends LitElement { class ZHAConfigDashboard extends LitElement {
@property() public hass!: HomeAssistant; @property({ type: Object }) public hass!: HomeAssistant;
@property() public route!: Route; @property({ type: Object }) public route!: Route;
@property() public narrow!: boolean; @property({ type: Boolean }) public narrow!: boolean;
@property() public isWide!: boolean; @property({ type: Boolean }) public isWide!: boolean;
@property() private _devices: ZHADevice[] = []; @property() private _devices: ZHADevice[] = [];
@ -91,7 +92,7 @@ class ZHAConfigDashboard extends LitElement {
title: "IEEE", title: "IEEE",
sortable: true, sortable: true,
filterable: true, filterable: true,
width: "25%", width: "30%",
}, },
} }
); );

View File

@ -0,0 +1,182 @@
import {
customElement,
html,
LitElement,
property,
query,
TemplateResult,
css,
CSSResult,
} from "lit-element";
import memoizeOne from "memoize-one";
import "../../../components/data-table/ha-data-table";
import type {
DataTableColumnContainer,
HaDataTable,
DataTableRowData,
} from "../../../components/data-table/ha-data-table";
import "../../../components/entity/ha-state-icon";
import type { ZHADeviceEndpoint, ZHAEntityReference } from "../../../data/zha";
import { showZHADeviceInfoDialog } from "../../../dialogs/zha-device-info-dialog/show-dialog-zha-device-info";
import type { HomeAssistant } from "../../../types";
export interface DeviceEndpointRowData extends DataTableRowData {
id: string;
name: string;
model: string;
manufacturer: string;
endpoint_id: number;
entities: ZHAEntityReference[];
}
@customElement("zha-device-endpoint-data-table")
export class ZHADeviceEndpointDataTable extends LitElement {
@property({ type: Object }) public hass!: HomeAssistant;
@property({ type: Boolean }) public narrow = false;
@property({ type: Boolean }) public selectable = false;
@property({ type: Array }) public deviceEndpoints: ZHADeviceEndpoint[] = [];
@query("ha-data-table") private _dataTable!: HaDataTable;
private _deviceEndpoints = memoizeOne(
(deviceEndpoints: ZHADeviceEndpoint[]) => {
const outputDevices: DeviceEndpointRowData[] = [];
deviceEndpoints.forEach((deviceEndpoint) => {
outputDevices.push({
name:
deviceEndpoint.device.user_given_name || deviceEndpoint.device.name,
model: deviceEndpoint.device.model,
manufacturer: deviceEndpoint.device.manufacturer,
id: deviceEndpoint.device.ieee + "_" + deviceEndpoint.endpoint_id,
ieee: deviceEndpoint.device.ieee,
endpoint_id: deviceEndpoint.endpoint_id,
entities: deviceEndpoint.entities,
});
});
return outputDevices;
}
);
private _columns = memoizeOne(
(narrow: boolean): DataTableColumnContainer =>
narrow
? {
name: {
title: "Devices",
sortable: true,
filterable: true,
direction: "asc",
grows: true,
template: (name) => html`
<div
class="mdc-data-table__cell table-cell-text"
@click=${this._handleClicked}
style="cursor: pointer;"
>
${name}
</div>
`,
},
endpoint_id: {
title: "Endpoint",
sortable: true,
filterable: true,
},
}
: {
name: {
title: "Name",
sortable: true,
filterable: true,
direction: "asc",
grows: true,
template: (name) => html`
<div
class="mdc-data-table__cell table-cell-text"
@click=${this._handleClicked}
style="cursor: pointer;"
>
${name}
</div>
`,
},
endpoint_id: {
title: "Endpoint",
sortable: true,
filterable: true,
},
entities: {
title: "Associated Entities",
sortable: false,
filterable: false,
width: "50%",
template: (entities) => html`
${entities.length
? entities.length > 3
? html`${entities.slice(0, 2).map(
(entity) =>
html`<div
style="overflow: hidden; text-overflow: ellipsis;"
>
${entity.name || entity.original_name}
</div>`
)}
<div>And ${entities.length - 2} more...</div>`
: entities.map(
(entity) =>
html`<div
style="overflow: hidden; text-overflow: ellipsis;"
>
${entity.name || entity.original_name}
</div>`
)
: "This endpoint has no associated entities"}
`,
},
}
);
public clearSelection() {
this._dataTable.clearSelection();
}
protected render(): TemplateResult {
return html`
<ha-data-table
.columns=${this._columns(this.narrow)}
.data=${this._deviceEndpoints(this.deviceEndpoints)}
.selectable=${this.selectable}
auto-height
></ha-data-table>
`;
}
private async _handleClicked(ev: CustomEvent) {
const rowId = ((ev.target as HTMLElement).closest(
".mdc-data-table__row"
) as any).rowId;
const ieee = rowId.substring(0, rowId.indexOf("_"));
showZHADeviceInfoDialog(this, { ieee });
}
static get styles(): CSSResult[] {
return [
css`
.table-cell-text {
word-break: break-word;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"zha-device-endpoint-data-table": ZHADeviceEndpointDataTable;
}
}

View File

@ -1,124 +0,0 @@
import {
customElement,
html,
LitElement,
property,
query,
TemplateResult,
} from "lit-element";
import memoizeOne from "memoize-one";
import "../../../components/data-table/ha-data-table";
import type {
DataTableColumnContainer,
HaDataTable,
} from "../../../components/data-table/ha-data-table";
import "../../../components/entity/ha-state-icon";
import type { ZHADevice } from "../../../data/zha";
import { showZHADeviceInfoDialog } from "../../../dialogs/zha-device-info-dialog/show-dialog-zha-device-info";
import type { HomeAssistant } from "../../../types";
export interface DeviceRowData extends ZHADevice {
device?: DeviceRowData;
}
@customElement("zha-devices-data-table")
export class ZHADevicesDataTable extends LitElement {
@property() public hass!: HomeAssistant;
@property() public narrow = false;
@property({ type: Boolean }) public selectable = false;
@property() public devices: ZHADevice[] = [];
@query("ha-data-table") private _dataTable!: HaDataTable;
private _devices = memoizeOne((devices: ZHADevice[]) => {
let outputDevices: DeviceRowData[] = devices;
outputDevices = outputDevices.map((device) => {
return {
...device,
name: device.user_given_name || device.name,
model: device.model,
manufacturer: device.manufacturer,
id: device.ieee,
};
});
return outputDevices;
});
private _columns = memoizeOne(
(narrow: boolean): DataTableColumnContainer =>
narrow
? {
name: {
title: "Devices",
sortable: true,
filterable: true,
direction: "asc",
grows: true,
template: (name) => html`
<div @click=${this._handleClicked} style="cursor: pointer;">
${name}
</div>
`,
},
}
: {
name: {
title: "Name",
sortable: true,
filterable: true,
direction: "asc",
grows: true,
template: (name) => html`
<div @click=${this._handleClicked} style="cursor: pointer;">
${name}
</div>
`,
},
manufacturer: {
title: "Manufacturer",
sortable: true,
filterable: true,
width: "20%",
},
model: {
title: "Model",
sortable: true,
filterable: true,
width: "20%",
},
}
);
public clearSelection() {
this._dataTable.clearSelection();
}
protected render(): TemplateResult {
return html`
<ha-data-table
.columns=${this._columns(this.narrow)}
.data=${this._devices(this.devices)}
.selectable=${this.selectable}
auto-height
></ha-data-table>
`;
}
private async _handleClicked(ev: CustomEvent) {
const ieee = ((ev.target as HTMLElement).closest(
".mdc-data-table__row"
) as any).rowId;
showZHADeviceInfoDialog(this, { ieee });
}
}
declare global {
interface HTMLElementTagNameMap {
"zha-devices-data-table": ZHADevicesDataTable;
}
}

View File

@ -9,8 +9,8 @@ import {
LitElement, LitElement,
property, property,
PropertyValues, PropertyValues,
query,
} from "lit-element"; } from "lit-element";
import memoizeOne from "memoize-one";
import { HASSDomEvent } from "../../../common/dom/fire_event"; import { HASSDomEvent } from "../../../common/dom/fire_event";
import { navigate } from "../../../common/navigate"; import { navigate } from "../../../common/navigate";
import { SelectionChangedEvent } from "../../../components/data-table/ha-data-table"; import { SelectionChangedEvent } from "../../../components/data-table/ha-data-table";
@ -20,8 +20,8 @@ import {
fetchGroupableDevices, fetchGroupableDevices,
removeGroups, removeGroups,
removeMembersFromGroup, removeMembersFromGroup,
ZHADevice,
ZHAGroup, ZHAGroup,
ZHADeviceEndpoint,
} from "../../../data/zha"; } from "../../../data/zha";
import "../../../layouts/hass-error-screen"; import "../../../layouts/hass-error-screen";
import "../../../layouts/hass-subpage"; import "../../../layouts/hass-subpage";
@ -29,37 +29,40 @@ import { HomeAssistant } from "../../../types";
import "../ha-config-section"; import "../ha-config-section";
import { formatAsPaddedHex } from "./functions"; import { formatAsPaddedHex } from "./functions";
import "./zha-device-card"; import "./zha-device-card";
import "./zha-devices-data-table"; import "./zha-device-endpoint-data-table";
import type { ZHADeviceEndpointDataTable } from "./zha-device-endpoint-data-table";
@customElement("zha-group-page") @customElement("zha-group-page")
export class ZHAGroupPage extends LitElement { export class ZHAGroupPage extends LitElement {
@property() public hass!: HomeAssistant; @property({ type: Object }) public hass!: HomeAssistant;
@property() public group?: ZHAGroup; @property({ type: Object }) public group?: ZHAGroup;
@property() public groupId!: number; @property({ type: Number }) public groupId!: number;
@property() public narrow!: boolean; @property({ type: Boolean }) public narrow!: boolean;
@property() public isWide!: boolean; @property({ type: Boolean }) public isWide!: boolean;
@property() public devices: ZHADevice[] = []; @property({ type: Array }) public deviceEndpoints: ZHADeviceEndpoint[] = [];
@property() private _processingAdd = false; @property() private _processingAdd = false;
@property() private _processingRemove = false; @property() private _processingRemove = false;
@property() private _filteredDevices: ZHADevice[] = []; @property() private _filteredDeviceEndpoints: ZHADeviceEndpoint[] = [];
@property() private _selectedDevicesToAdd: string[] = []; @property() private _selectedDevicesToAdd: string[] = [];
@property() private _selectedDevicesToRemove: string[] = []; @property() private _selectedDevicesToRemove: string[] = [];
private _firstUpdatedCalled = false; @query("#addMembers")
private _zhaAddMembersDataTable!: ZHADeviceEndpointDataTable;
private _members = memoizeOne( @query("#removeMembers")
(group: ZHAGroup): ZHADevice[] => group.members private _zhaRemoveMembersDataTable!: ZHADeviceEndpointDataTable;
);
private _firstUpdatedCalled = false;
public connectedCallback(): void { public connectedCallback(): void {
super.connectedCallback(); super.connectedCallback();
@ -74,8 +77,8 @@ export class ZHAGroupPage extends LitElement {
this._processingRemove = false; this._processingRemove = false;
this._selectedDevicesToRemove = []; this._selectedDevicesToRemove = [];
this._selectedDevicesToAdd = []; this._selectedDevicesToAdd = [];
this.devices = []; this.deviceEndpoints = [];
this._filteredDevices = []; this._filteredDeviceEndpoints = [];
} }
protected firstUpdated(changedProperties: PropertyValues): void { protected firstUpdated(changedProperties: PropertyValues): void {
@ -97,8 +100,6 @@ export class ZHAGroupPage extends LitElement {
`; `;
} }
const members = this._members(this.group);
return html` return html`
<hass-subpage .header=${this.group.name}> <hass-subpage .header=${this.group.name}>
<ha-icon-button <ha-icon-button
@ -122,13 +123,13 @@ export class ZHAGroupPage extends LitElement {
${this.hass.localize("ui.panel.config.zha.groups.members")} ${this.hass.localize("ui.panel.config.zha.groups.members")}
</div> </div>
${members.length ${this.group.members.length
? members.map( ? this.group.members.map(
(member) => html` (member) => html`
<zha-device-card <zha-device-card
class="card" class="card"
.hass=${this.hass} .hass=${this.hass}
.device=${member} .device=${member.device}
.narrow=${this.narrow} .narrow=${this.narrow}
.showActions=${false} .showActions=${false}
.showEditableInfo=${false} .showEditableInfo=${false}
@ -140,7 +141,7 @@ export class ZHAGroupPage extends LitElement {
This group has no members This group has no members
</p> </p>
`} `}
${members.length ${this.group.members.length
? html` ? html`
<div class="header"> <div class="header">
${this.hass.localize( ${this.hass.localize(
@ -148,14 +149,15 @@ export class ZHAGroupPage extends LitElement {
)} )}
</div> </div>
<zha-devices-data-table <zha-device-endpoint-data-table
id="removeMembers"
.hass=${this.hass} .hass=${this.hass}
.devices=${members} .deviceEndpoints=${this.group.members}
.narrow=${this.narrow} .narrow=${this.narrow}
selectable selectable
@selection-changed=${this._handleRemoveSelectionChanged} @selection-changed=${this._handleRemoveSelectionChanged}
> >
</zha-devices-data-table> </zha-device-endpoint-data-table>
<div class="paper-dialog-buttons"> <div class="paper-dialog-buttons">
<mwc-button <mwc-button
@ -182,14 +184,15 @@ export class ZHAGroupPage extends LitElement {
${this.hass.localize("ui.panel.config.zha.groups.add_members")} ${this.hass.localize("ui.panel.config.zha.groups.add_members")}
</div> </div>
<zha-devices-data-table <zha-device-endpoint-data-table
id="addMembers"
.hass=${this.hass} .hass=${this.hass}
.devices=${this._filteredDevices} .deviceEndpoints=${this._filteredDeviceEndpoints}
.narrow=${this.narrow} .narrow=${this.narrow}
selectable selectable
@selection-changed=${this._handleAddSelectionChanged} @selection-changed=${this._handleAddSelectionChanged}
> >
</zha-devices-data-table> </zha-device-endpoint-data-table>
<div class="paper-dialog-buttons"> <div class="paper-dialog-buttons">
<mwc-button <mwc-button
@ -218,16 +221,22 @@ export class ZHAGroupPage extends LitElement {
if (this.groupId !== null && this.groupId !== undefined) { if (this.groupId !== null && this.groupId !== undefined) {
this.group = await fetchGroup(this.hass!, this.groupId); this.group = await fetchGroup(this.hass!, this.groupId);
} }
this.devices = await fetchGroupableDevices(this.hass!); this.deviceEndpoints = await fetchGroupableDevices(this.hass!);
// filter the groupable devices so we only show devices that aren't already in the group // filter the groupable devices so we only show devices that aren't already in the group
this._filterDevices(); this._filterDevices();
} }
private _filterDevices() { private _filterDevices() {
// filter the groupable devices so we only show devices that aren't already in the group // filter the groupable devices so we only show devices that aren't already in the group
this._filteredDevices = this.devices.filter((device) => { this._filteredDeviceEndpoints = this.deviceEndpoints.filter(
return !this.group!.members.some((member) => member.ieee === device.ieee); (deviceEndpoint) => {
}); return !this.group!.members.some(
(member) =>
member.device.ieee === deviceEndpoint.device.ieee &&
member.endpoint_id === deviceEndpoint.endpoint_id
);
}
);
} }
private _handleAddSelectionChanged( private _handleAddSelectionChanged(
@ -244,25 +253,27 @@ export class ZHAGroupPage extends LitElement {
private async _addMembersToGroup(): Promise<void> { private async _addMembersToGroup(): Promise<void> {
this._processingAdd = true; this._processingAdd = true;
this.group = await addMembersToGroup( const members = this._selectedDevicesToAdd.map((member) => {
this.hass, const memberParts = member.split("_");
this.groupId, return { ieee: memberParts[0], endpoint_id: memberParts[1] };
this._selectedDevicesToAdd });
); this.group = await addMembersToGroup(this.hass, this.groupId, members);
this._filterDevices(); this._filterDevices();
this._selectedDevicesToAdd = []; this._selectedDevicesToAdd = [];
this._zhaAddMembersDataTable.clearSelection();
this._processingAdd = false; this._processingAdd = false;
} }
private async _removeMembersFromGroup(): Promise<void> { private async _removeMembersFromGroup(): Promise<void> {
this._processingRemove = true; this._processingRemove = true;
this.group = await removeMembersFromGroup( const members = this._selectedDevicesToRemove.map((member) => {
this.hass, const memberParts = member.split("_");
this.groupId, return { ieee: memberParts[0], endpoint_id: memberParts[1] };
this._selectedDevicesToRemove });
); this.group = await removeMembersFromGroup(this.hass, this.groupId, members);
this._filterDevices(); this._filterDevices();
this._selectedDevicesToRemove = []; this._selectedDevicesToRemove = [];
this._zhaRemoveMembersDataTable.clearSelection();
this._processingRemove = false; this._processingRemove = false;
} }

View File

@ -34,7 +34,11 @@ class IntegrationsCard extends LitElement {
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
<ha-card header="Integrations"> <ha-card
.header=${this.hass.localize(
"ui.panel.developer-tools.tabs.info.integrations"
)}
>
<table class="card-content"> <table class="card-content">
<tbody> <tbody>
${this._sortedIntegrations(this.hass!.config.components).map( ${this._sortedIntegrations(this.hass!.config.components).map(
@ -62,22 +66,29 @@ class IntegrationsCard extends LitElement {
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
> >
Documentation ${this.hass.localize(
"ui.panel.developer-tools.tabs.info.documentation"
)}
</a> </a>
</td> </td>
${!manifest.is_built_in ${manifest.is_built_in || manifest.issue_tracker
? "" ? html`
: html`
<td> <td>
<a <a
href=${integrationIssuesUrl(domain)} href=${integrationIssuesUrl(
domain,
manifest
)}
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
> >
Issues ${this.hass.localize(
"ui.panel.developer-tools.tabs.info.issues"
)}
</a> </a>
</td> </td>
`} `
: ""}
`} `}
</tr> </tr>
`; `;

View File

@ -84,15 +84,19 @@ class DialogSystemLogDetail extends LitElement {
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
>documentation</a >documentation</a
>${!this._manifest.is_built_in >${this._manifest.is_built_in ||
? "" this._manifest.issue_tracker
: html`, ? html`,
<a <a
href=${integrationIssuesUrl(integration)} href=${integrationIssuesUrl(
integration,
this._manifest
)}
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
>issues</a >issues</a
>`}) >`
: ""})
`} `}
` `
: ""} : ""}

View File

@ -231,7 +231,7 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
stateLabel === "triggered" || stateLabel === "triggered" ||
!stateLabel !stateLabel
? "" ? ""
: stateLabel; : this._stateDisplay(state);
} }
private _actionDisplay(state: string): string { private _actionDisplay(state: string): string {

View File

@ -10,17 +10,21 @@ import {
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { styleMap } from "lit-html/directives/style-map"; import { styleMap } from "lit-html/directives/style-map";
import "@thomasloven/round-slider";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { fireEvent } from "../../../common/dom/fire_event"; import { fireEvent } from "../../../common/dom/fire_event";
import { computeStateName } from "../../../common/entity/compute_state_name"; import { computeStateName } from "../../../common/entity/compute_state_name";
import { isValidEntityId } from "../../../common/entity/valid_entity_id"; import { isValidEntityId } from "../../../common/entity/valid_entity_id";
import "../../../components/ha-card"; import "../../../components/ha-card";
import { HomeAssistant } from "../../../types"; import type { HomeAssistant } from "../../../types";
import { findEntities } from "../common/find-entites"; import { findEntities } from "../common/find-entites";
import { hasConfigOrEntityChanged } from "../common/has-changed"; import { hasConfigOrEntityChanged } from "../common/has-changed";
import "../components/hui-warning"; import "../components/hui-warning";
import { LovelaceCard, LovelaceCardEditor } from "../types"; import type { LovelaceCard, LovelaceCardEditor } from "../types";
import { GaugeCardConfig } from "./types"; import type { GaugeCardConfig } from "./types";
import { debounce } from "../../../common/util/debounce";
import { installResizeObserver } from "../common/install-resize-observer";
export const severityMap = { export const severityMap = {
red: "var(--label-badge-red)", red: "var(--label-badge-red)",
@ -63,11 +67,20 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
@property() public hass?: HomeAssistant; @property() public hass?: HomeAssistant;
@property() private _baseUnit = "50px";
@property() private _config?: GaugeCardConfig; @property() private _config?: GaugeCardConfig;
private _updated?: boolean; private _resizeObserver?: ResizeObserver;
public connectedCallback(): void {
super.connectedCallback();
this.updateComplete.then(() => this._attachObserver());
}
public disconnectedCallback(): void {
if (this._resizeObserver) {
this._resizeObserver.disconnect();
}
}
public getCardSize(): number { public getCardSize(): number {
return 2; return 2;
@ -83,11 +96,6 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
this._config = { min: 0, max: 100, ...config }; this._config = { min: 0, max: 100, ...config };
} }
public connectedCallback(): void {
super.connectedCallback();
this._setBaseUnit();
}
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this._config || !this.hass) { if (!this._config || !this.hass) {
return html``; return html``;
@ -121,33 +129,32 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
`; `;
} }
const sliderBarColor = this._computeSeverity(state);
return html` return html`
<ha-card <ha-card
@click="${this._handleClick}" @click=${this._handleClick}
tabindex="0" tabindex="0"
style=${styleMap({ style=${styleMap({
"--base-unit": this._baseUnit, "--round-slider-bar-color": sliderBarColor,
})} })}
> >
<div class="container"> <round-slider
<div class="gauge-a"></div> readonly
<div arcLength="180"
class="gauge-c" startAngle="180"
style=${styleMap({ .value=${state}
transform: `rotate(${this._translateTurn(state)}turn)`, .min=${this._config.min}
"background-color": this._computeSeverity(state), .max=${this._config.max}
})} ></round-slider>
></div>
<div class="gauge-b"></div>
</div>
<div class="gauge-data"> <div class="gauge-data">
<div id="percent"> <div class="percent">
${stateObj.state} ${stateObj.state}
${this._config.unit || ${this._config.unit ||
stateObj.attributes.unit_of_measurement || stateObj.attributes.unit_of_measurement ||
""} ""}
</div> </div>
<div id="name"> <div class="name">
${this._config.name || computeStateName(stateObj)} ${this._config.name || computeStateName(stateObj)}
</div> </div>
</div> </div>
@ -160,10 +167,7 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
} }
protected firstUpdated(): void { protected firstUpdated(): void {
this._updated = true; this._attachObserver();
this._setBaseUnit();
// eslint-disable-next-line wc/no-self-class
this.classList.add("init");
} }
protected updated(changedProps: PropertyValues): void { protected updated(changedProps: PropertyValues): void {
@ -187,16 +191,6 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
} }
} }
private _setBaseUnit(): void {
if (!this.isConnected || !this._updated) {
return;
}
const baseUnit = this._computeBaseUnit();
if (baseUnit !== "0px") {
this._baseUnit = baseUnit;
}
}
private _computeSeverity(numberValue: number): string { private _computeSeverity(numberValue: number): string {
const sections = this._config!.severity; const sections = this._config!.severity;
@ -229,95 +223,122 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
return severityMap.normal; return severityMap.normal;
} }
private _translateTurn(value: number): number {
const { min, max } = this._config!;
const maxTurnValue = Math.min(Math.max(value, min!), max!);
return (5 * (maxTurnValue - min!)) / (max! - min!) / 10;
}
private _computeBaseUnit(): string {
return this.clientWidth < 200 ? this.clientWidth / 5 + "px" : "50px";
}
private _handleClick(): void { private _handleClick(): void {
fireEvent(this, "hass-more-info", { entityId: this._config!.entity }); fireEvent(this, "hass-more-info", { entityId: this._config!.entity });
} }
private async _attachObserver(): Promise<void> {
await installResizeObserver();
this._resizeObserver = new ResizeObserver(
debounce(() => this._measureCard(), 250, false)
);
const card = this.shadowRoot!.querySelector("ha-card");
// If we show an error or warning there is no ha-card
if (!card) {
return;
}
this._resizeObserver.observe(card);
}
private _measureCard() {
if (this.offsetWidth < 200) {
this.setAttribute("narrow", "");
} else {
this.removeAttribute("narrow");
}
if (this.offsetWidth < 150) {
this.setAttribute("veryNarrow", "");
} else {
this.removeAttribute("veryNarrow");
}
}
static get styles(): CSSResult { static get styles(): CSSResult {
return css` return css`
ha-card { ha-card {
cursor: pointer; cursor: pointer;
padding: 16px 16px 0 16px;
height: 100%; height: 100%;
overflow: hidden;
padding: 16px 16px 0 16px;
display: flex; display: flex;
align-items: center;
justify-content: center;
flex-direction: column; flex-direction: column;
box-sizing: border-box; box-sizing: border-box;
justify-content: center;
align-items: center;
} }
ha-card:focus { ha-card:focus {
outline: none; outline: none;
background: var(--divider-color); background: var(--divider-color);
} }
.container {
width: calc(var(--base-unit) * 4); round-slider {
height: calc(var(--base-unit) * 2); max-width: 200px;
overflow: hidden; --round-slider-path-width: 35px;
position: relative; --round-slider-path-color: var(--disabled-text-color);
} --round-slider-linecap: "butt";
.gauge-a {
position: absolute;
background-color: var(--primary-background-color);
width: calc(var(--base-unit) * 4);
height: calc(var(--base-unit) * 2);
top: 0%;
border-radius: calc(var(--base-unit) * 2.5) calc(var(--base-unit) * 2.5)
0px 0px;
}
.gauge-b {
position: absolute;
background-color: var(--paper-card-background-color);
width: calc(var(--base-unit) * 2.5);
height: calc(var(--base-unit) * 1.25);
top: calc(var(--base-unit) * 0.75);
margin-left: calc(var(--base-unit) * 0.75);
margin-right: auto;
border-radius: calc(var(--base-unit) * 2.5) calc(var(--base-unit) * 2.5)
0px 0px;
}
.gauge-c {
position: absolute;
background-color: var(--label-badge-blue);
width: calc(var(--base-unit) * 4);
height: calc(var(--base-unit) * 2);
top: calc(var(--base-unit) * 2);
margin-left: auto;
margin-right: auto;
border-radius: 0px 0px calc(var(--base-unit) * 2)
calc(var(--base-unit) * 2);
transform-origin: center top;
}
.init .gauge-c {
transition: all 1.3s ease-in-out;
} }
.gauge-data { .gauge-data {
line-height: 1;
text-align: center; text-align: center;
color: var(--primary-text-color);
line-height: calc(var(--base-unit) * 0.3);
width: 100%;
position: relative; position: relative;
top: calc(var(--base-unit) * -0.5); color: var(--primary-text-color);
margin-top: -28px;
margin-bottom: 14px;
} }
.init .gauge-data {
transition: all 1s ease-out; .gauge-data .percent {
font-size: 28px;
} }
.gauge-data #percent {
font-size: calc(var(--base-unit) * 0.55); .gauge-data .name {
line-height: calc(var(--base-unit) * 0.55); padding-top: 6px;
font-size: 14px;
} }
.gauge-data #name {
padding-top: calc(var(--base-unit) * 0.15); /* ============= NARROW ============= */
font-size: calc(var(--base-unit) * 0.3);
:host([narrow]) round-slider {
--round-slider-path-width: 22px;
}
:host([narrow]) .gauge-data {
margin-top: -24px;
margin-bottom: 12px;
}
:host([narrow]) .gauge-data .percent {
font-size: 24px;
}
:host([narrow]) .gauge-data .name {
font-size: 12px;
}
/* ============= VERY NARROW ============= */
:host([veryNarrow]) round-slider {
--round-slider-path-width: 15px;
}
:host([veryNarrow]) ha-card {
padding-bottom: 16px;
}
:host([veryNarrow]) .gauge-data {
margin-top: 0;
margin-bottom: 0;
}
:host([veryNarrow]) .gauge-data .percent {
font-size: 20px;
}
:host([veryNarrow]) .gauge-data .name {
font-size: 10px;
} }
`; `;
} }

View File

@ -46,6 +46,7 @@ import "../components/hui-marquee";
import type { LovelaceCard, LovelaceCardEditor } from "../types"; import type { LovelaceCard, LovelaceCardEditor } from "../types";
import "../components/hui-warning"; import "../components/hui-warning";
import { MediaControlCardConfig } from "./types"; import { MediaControlCardConfig } from "./types";
import { installResizeObserver } from "../common/install-resize-observer";
function getContrastRatio( function getContrastRatio(
rgb1: [number, number, number], rgb1: [number, number, number],
@ -223,7 +224,7 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
public connectedCallback(): void { public connectedCallback(): void {
super.connectedCallback(); super.connectedCallback();
this.updateComplete.then(() => this._measureCard()); this.updateComplete.then(() => this._attachObserver());
if (!this.hass || !this._config) { if (!this.hass || !this._config) {
return; return;
@ -252,6 +253,9 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
clearInterval(this._progressInterval); clearInterval(this._progressInterval);
this._progressInterval = undefined; this._progressInterval = undefined;
} }
if (this._resizeObserver) {
this._resizeObserver.disconnect();
}
} }
protected render(): TemplateResult { protected render(): TemplateResult {
@ -624,15 +628,8 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
this._cardHeight = card.offsetHeight; this._cardHeight = card.offsetHeight;
} }
private _attachObserver(): void { private async _attachObserver(): Promise<void> {
if (typeof ResizeObserver !== "function") { await installResizeObserver();
import("resize-observer").then((modules) => {
modules.install();
this._attachObserver();
});
return;
}
this._resizeObserver = new ResizeObserver( this._resizeObserver = new ResizeObserver(
debounce(() => this._measureCard(), 250, false) debounce(() => this._measureCard(), 250, false)
); );

View File

@ -21,16 +21,17 @@ import { UNAVAILABLE } from "../../../data/entity";
import { import {
getSecondaryWeatherAttribute, getSecondaryWeatherAttribute,
getWeatherUnit, getWeatherUnit,
weatherIcons, getWeatherStateIcon,
weatherImages, weatherSVGStyles,
} from "../../../data/weather"; } from "../../../data/weather";
import { HomeAssistant, WeatherEntity } from "../../../types"; import type { HomeAssistant, WeatherEntity } from "../../../types";
import { actionHandler } from "../common/directives/action-handler-directive"; import { actionHandler } from "../common/directives/action-handler-directive";
import { findEntities } from "../common/find-entites"; import { findEntities } from "../common/find-entites";
import { hasConfigOrEntityChanged } from "../common/has-changed"; import { hasConfigOrEntityChanged } from "../common/has-changed";
import "../components/hui-warning"; import "../components/hui-warning";
import { LovelaceCard, LovelaceCardEditor } from "../types"; import type { LovelaceCard, LovelaceCardEditor } from "../types";
import { WeatherForecastCardConfig } from "./types"; import type { WeatherForecastCardConfig } from "./types";
import { installResizeObserver } from "../common/install-resize-observer";
const DAY_IN_MILLISECONDS = 86400000; const DAY_IN_MILLISECONDS = 86400000;
@ -72,7 +73,13 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
public connectedCallback(): void { public connectedCallback(): void {
super.connectedCallback(); super.connectedCallback();
this.updateComplete.then(() => this._measureCard()); this.updateComplete.then(() => this._attachObserver());
}
public disconnectedCallback(): void {
if (this._resizeObserver) {
this._resizeObserver.disconnect();
}
} }
public getCardSize(): number { public getCardSize(): number {
@ -158,6 +165,8 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
hourly = timeDiff < DAY_IN_MILLISECONDS; hourly = timeDiff < DAY_IN_MILLISECONDS;
} }
const weatherStateIcon = getWeatherStateIcon(stateObj.state, this);
return html` return html`
<ha-card <ha-card
@action=${this._handleAction} @action=${this._handleAction}
@ -166,25 +175,16 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
> >
<div class="content"> <div class="content">
<div class="icon-image"> <div class="icon-image">
${stateObj.state in weatherImages ${weatherStateIcon ||
? html` html`
<img
class="weather-image"
src="${weatherImages[stateObj.state]}"
/>
`
: html`
<ha-icon <ha-icon
class="weather-icon" class="weather-icon"
.icon=${weatherIcons[stateObj.state] || stateIcon(stateObj)} .icon=${stateIcon(stateObj)}
></ha-icon> ></ha-icon>
`} `}
</div> </div>
<div class="info"> <div class="info">
<div class="name-state"> <div class="name-state">
<div class="name">
${this._config.name || computeStateName(stateObj)}
</div>
<div class="state"> <div class="state">
${computeStateDisplay( ${computeStateDisplay(
this.hass.localize, this.hass.localize,
@ -192,6 +192,9 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
this.hass.language this.hass.language
)} )}
</div> </div>
<div class="name">
${this._config.name || computeStateName(stateObj)}
</div>
</div> </div>
<div class="temp-attribute"> <div class="temp-attribute">
<div class="temp"> <div class="temp">
@ -200,7 +203,20 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
> >
</div> </div>
<div class="attribute"> <div class="attribute">
${getSecondaryWeatherAttribute(this.hass, stateObj)} ${this._config.secondary_info_attribute !== undefined
? html`
${this.hass!.localize(
`ui.card.weather.attributes.${this._config.secondary_info_attribute}`
)}
${stateObj.attributes[
this._config.secondary_info_attribute
]}
${getWeatherUnit(
this.hass,
this._config.secondary_info_attribute
)}
`
: getSecondaryWeatherAttribute(this.hass, stateObj)}
</div> </div>
</div> </div>
</div> </div>
@ -231,21 +247,7 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
${item.condition !== undefined && item.condition !== null ${item.condition !== undefined && item.condition !== null
? html` ? html`
<div class="forecast-image-icon"> <div class="forecast-image-icon">
${item.condition in weatherImages ${getWeatherStateIcon(item.condition, this)}
? html`
<img
class="forecast-image"
src="${weatherImages[item.condition]}"
/>
`
: item.condition in weatherIcons
? html`
<ha-icon
class="forecast-icon"
.icon=${weatherIcons[item.condition]}
></ha-icon>
`
: ""}
</div> </div>
` `
: ""} : ""}
@ -286,15 +288,8 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
fireEvent(this, "hass-more-info", { entityId: this._config!.entity }); fireEvent(this, "hass-more-info", { entityId: this._config!.entity });
} }
private _attachObserver(): void { private async _attachObserver(): Promise<void> {
if (typeof ResizeObserver !== "function") { await installResizeObserver();
import("resize-observer").then((modules) => {
modules.install();
this._attachObserver();
});
return;
}
this._resizeObserver = new ResizeObserver( this._resizeObserver = new ResizeObserver(
debounce(() => this._measureCard(), 250, false) debounce(() => this._measureCard(), 250, false)
); );
@ -321,8 +316,10 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
} }
} }
static get styles(): CSSResult { static get styles(): CSSResult[] {
return css` return [
weatherSVGStyles,
css`
:host { :host {
display: block; display: block;
} }
@ -346,9 +343,9 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
margin-right: 16px; margin-right: 16px;
} }
.weather-image, .icon-image > * {
.weather-icon {
flex: 0 0 64px; flex: 0 0 64px;
height: 64px;
} }
.weather-icon { .weather-icon {
@ -377,13 +374,13 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
top: 1px; top: 1px;
} }
.name, .state,
.temp-attribute .temp { .temp-attribute .temp {
font-size: 28px; font-size: 28px;
line-height: 1.2; line-height: 1.2;
} }
.state, .name,
.attribute { .attribute {
font-size: 14px; font-size: 14px;
line-height: 1; line-height: 1;
@ -428,9 +425,10 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
.forecast-image-icon { .forecast-image-icon {
padding-top: 4px; padding-top: 4px;
padding-bottom: 4px; padding-bottom: 4px;
display: flex;
} }
.forecast-image { .forecast-image-icon > * {
width: 40px; width: 40px;
} }
@ -440,7 +438,7 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
.attribute, .attribute,
.templow, .templow,
.state { .name {
color: var(--secondary-text-color); color: var(--secondary-text-color);
} }
@ -469,7 +467,7 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
--mdc-icon-size: 52px; --mdc-icon-size: 52px;
} }
:host([narrow]) .name, :host([narrow]) .state,
:host([narrow]) .temp-attribute .temp { :host([narrow]) .temp-attribute .temp {
font-size: 22px; font-size: 22px;
} }
@ -485,7 +483,7 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
/* ============= VERY NARROW ============= */ /* ============= VERY NARROW ============= */
:host([veryNarrow]) .state, :host([veryNarrow]) .name,
:host([veryNarrow]) .attribute { :host([veryNarrow]) .attribute {
display: none; display: none;
} }
@ -515,7 +513,8 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
:host([veryVeryNarrow]) .icon-image { :host([veryVeryNarrow]) .icon-image {
margin-right: 0; margin-right: 0;
} }
`; `,
];
} }
} }

View File

@ -34,7 +34,8 @@ export interface EntitiesCardEntityConfig extends EntityConfig {
| "last-changed" | "last-changed"
| "last-triggered" | "last-triggered"
| "position" | "position"
| "tilt-position"; | "tilt-position"
| "brightness";
action_name?: string; action_name?: string;
service?: string; service?: string;
service_data?: object; service_data?: object;
@ -275,4 +276,5 @@ export interface WeatherForecastCardConfig extends LovelaceCardConfig {
entity: string; entity: string;
name?: string; name?: string;
show_forecast?: boolean; show_forecast?: boolean;
secondary_info_attribute?: string;
} }

View File

@ -465,7 +465,8 @@ export const generateLovelaceConfigFromData = async (
}; };
export const generateLovelaceConfigFromHass = async ( export const generateLovelaceConfigFromHass = async (
hass: HomeAssistant hass: HomeAssistant,
localize?: LocalizeFunc
): Promise<LovelaceConfig> => { ): Promise<LovelaceConfig> => {
// We want to keep the registry subscriptions alive after generating the UI // We want to keep the registry subscriptions alive after generating the UI
// so that we don't serve up stale data after changing areas. // so that we don't serve up stale data after changing areas.
@ -488,6 +489,6 @@ export const generateLovelaceConfigFromHass = async (
deviceEntries, deviceEntries,
entityEntries, entityEntries,
hass.states, hass.states,
hass.localize localize || hass.localize
); );
}; };

View File

@ -0,0 +1,6 @@
export const installResizeObserver = async () => {
if (typeof ResizeObserver !== "function") {
const modules = await import("resize-observer");
modules.install();
}
};

View File

@ -10,25 +10,30 @@ import {
LitElement, LitElement,
property, property,
TemplateResult, TemplateResult,
queryAssignedNodes,
} from "lit-element"; } from "lit-element";
import { LovelaceCardConfig } from "../../../data/lovelace";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { showEditCardDialog } from "../editor/card-editor/show-edit-card-dialog"; import { showEditCardDialog } from "../editor/card-editor/show-edit-card-dialog";
import { showMoveCardViewDialog } from "../editor/card-editor/show-move-card-view-dialog"; import { showMoveCardViewDialog } from "../editor/card-editor/show-move-card-view-dialog";
import { swapCard } from "../editor/config-util"; import { swapCard } from "../editor/config-util";
import { confDeleteCard } from "../editor/delete-card"; import { confDeleteCard } from "../editor/delete-card";
import { Lovelace } from "../types"; import { Lovelace, LovelaceCard } from "../types";
import { computeCardSize } from "../common/compute-card-size";
@customElement("hui-card-options") @customElement("hui-card-options")
export class HuiCardOptions extends LitElement { export class HuiCardOptions extends LitElement {
public cardConfig?: LovelaceCardConfig;
@property() public hass?: HomeAssistant; @property() public hass?: HomeAssistant;
@property() public lovelace?: Lovelace; @property() public lovelace?: Lovelace;
@property() public path?: [number, number]; @property() public path?: [number, number];
@queryAssignedNodes() private _assignedNodes?: NodeListOf<LovelaceCard>;
public getCardSize() {
return this._assignedNodes ? computeCardSize(this._assignedNodes[0]) : 1;
}
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
<slot></slot> <slot></slot>

View File

@ -122,6 +122,11 @@ class HuiGenericEntityRow extends LitElement {
? `${this.hass.localize("ui.card.cover.tilt_position")}: ${ ? `${this.hass.localize("ui.card.cover.tilt_position")}: ${
stateObj.attributes.current_tilt_position stateObj.attributes.current_tilt_position
}` }`
: this.config.secondary_info === "brightness" &&
stateObj.attributes.brightness
? html`${Math.round(
(stateObj.attributes.brightness / 255) * 100
)}%`
: "")} : "")}
</div> </div>
` `

View File

@ -22,6 +22,7 @@ const cardConfigStruct = struct({
name: "string?", name: "string?",
theme: "string?", theme: "string?",
show_forecast: "boolean?", show_forecast: "boolean?",
secondary_info_attribute: "string?",
}); });
const includeDomains = ["weather"]; const includeDomains = ["weather"];
@ -54,6 +55,10 @@ export class HuiWeatherForecastCardEditor extends LitElement
return this._config!.show_forecast || true; return this._config!.show_forecast || true;
} }
get _secondary_info_attribute(): string {
return this._config!.secondary_info_attribute || "";
}
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this.hass || !this._config) { if (!this.hass || !this._config) {
return html``; return html``;
@ -93,13 +98,27 @@ export class HuiWeatherForecastCardEditor extends LitElement
@value-changed=${this._valueChanged} @value-changed=${this._valueChanged}
></hui-theme-select-editor> ></hui-theme-select-editor>
</div> </div>
<div class="side-by-side">
<paper-input
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.secondary_info_attribute"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
.value=${this._secondary_info_attribute}
.configValue=${"secondary_info_attribute"}
@value-changed=${this._valueChanged}
></paper-input>
<ha-switch <ha-switch
.checked=${this._config!.show_forecast !== false} .checked=${this._config!.show_forecast !== false}
.configValue=${"show_forecast"} .configValue=${"show_forecast"}
@change=${this._valueChanged} @change=${this._valueChanged}
>Show forecast</ha-switch >${this.hass.localize(
"ui.panel.lovelace.editor.card.weather.show_forecast"
)}</ha-switch
> >
</div> </div>
</div>
`; `;
} }

View File

@ -31,6 +31,7 @@ import { hasConfigOrEntityChanged } from "../common/has-changed";
import "../components/hui-generic-entity-row"; import "../components/hui-generic-entity-row";
import "../components/hui-warning"; import "../components/hui-warning";
import { EntityConfig, LovelaceRow } from "./types"; import { EntityConfig, LovelaceRow } from "./types";
import { installResizeObserver } from "../common/install-resize-observer";
@customElement("hui-media-player-entity-row") @customElement("hui-media-player-entity-row")
class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow { class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow {
@ -209,19 +210,13 @@ class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow {
} }
private _attachObserver(): void { private _attachObserver(): void {
if (typeof ResizeObserver !== "function") { installResizeObserver().then(() => {
import("resize-observer").then((modules) => {
modules.install();
this._attachObserver();
});
return;
}
this._resizeObserver = new ResizeObserver(() => this._resizeObserver = new ResizeObserver(() =>
this._debouncedResizeListener() this._debouncedResizeListener()
); );
this._resizeObserver.observe(this); this._resizeObserver.observe(this);
});
} }
private _computeControlIcon(stateObj: HassEntity): string { private _computeControlIcon(stateObj: HassEntity): string {

View File

@ -8,21 +8,32 @@ import {
PropertyValues, PropertyValues,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import { ifDefined } from "lit-html/directives/if-defined";
import { computeStateDisplay } from "../../../common/entity/compute_state_display"; import { computeStateDisplay } from "../../../common/entity/compute_state_display";
import "../../../components/entity/state-badge"; import "../../../components/entity/state-badge";
import { UNAVAILABLE_STATES } from "../../../data/entity"; import { UNAVAILABLE_STATES } from "../../../data/entity";
import { import {
getSecondaryWeatherAttribute, getSecondaryWeatherAttribute,
getWeatherUnit, getWeatherUnit,
weatherIcons, getWeatherStateIcon,
weatherImages, weatherSVGStyles,
} from "../../../data/weather"; } from "../../../data/weather";
import { HomeAssistant, WeatherEntity } from "../../../types"; import type { HomeAssistant, WeatherEntity } from "../../../types";
import { EntitiesCardEntityConfig } from "../cards/types"; import type { EntitiesCardEntityConfig } from "../cards/types";
import { hasConfigOrEntityChanged } from "../common/has-changed"; import { hasConfigOrEntityChanged } from "../common/has-changed";
import "../components/hui-generic-entity-row"; import "../components/hui-generic-entity-row";
import "../components/hui-warning"; import "../components/hui-warning";
import { LovelaceRow } from "./types"; import type { LovelaceRow } from "./types";
import { DOMAINS_HIDE_MORE_INFO } from "../../../common/const";
import { computeDomain } from "../../../common/entity/compute_domain";
import { actionHandler } from "../common/directives/action-handler-directive";
import { hasAction } from "../common/has-action";
import { computeStateName } from "../../../common/entity/compute_state_name";
import { ActionHandlerEvent } from "../../../data/lovelace";
import { handleAction } from "../common/handle-action";
import { stateIcon } from "../../../common/entity/state_icon";
@customElement("hui-weather-entity-row") @customElement("hui-weather-entity-row")
class HuiWeatherEntityRow extends LitElement implements LovelaceRow { class HuiWeatherEntityRow extends LitElement implements LovelaceRow {
@ -61,14 +72,42 @@ class HuiWeatherEntityRow extends LitElement implements LovelaceRow {
`; `;
} }
const weatherRowConfig = { const pointer =
...this._config, (this._config.tap_action && this._config.tap_action.action !== "none") ||
icon: weatherIcons[stateObj.state], (this._config.entity &&
image: weatherImages[stateObj.state], !DOMAINS_HIDE_MORE_INFO.includes(computeDomain(this._config.entity)));
};
const weatherStateIcon = getWeatherStateIcon(stateObj.state, this);
return html` return html`
<hui-generic-entity-row .hass=${this.hass} .config=${weatherRowConfig}> <div
class="icon-image${classMap({
pointer,
})}"
@action=${this._handleAction}
.actionHandler=${actionHandler({
hasHold: hasAction(this._config!.hold_action),
hasDoubleClick: hasAction(this._config!.double_tap_action),
})}
tabindex=${ifDefined(pointer ? "0" : undefined)}
>
${weatherStateIcon ||
html`
<ha-icon class="weather-icon" .icon=${stateIcon(stateObj)}></ha-icon>
`}
</div>
<div
class="info ${classMap({
pointer,
})}"
@action=${this._handleAction}
.actionHandler=${actionHandler({
hasHold: hasAction(this._config!.hold_action),
hasDoubleClick: hasAction(this._config!.double_tap_action),
})}
>
${this._config.name || computeStateName(stateObj)}
</div>
<div class="attributes"> <div class="attributes">
<div> <div>
${UNAVAILABLE_STATES.includes(stateObj.state) ${UNAVAILABLE_STATES.includes(stateObj.state)
@ -86,23 +125,73 @@ class HuiWeatherEntityRow extends LitElement implements LovelaceRow {
${getSecondaryWeatherAttribute(this.hass!, stateObj)} ${getSecondaryWeatherAttribute(this.hass!, stateObj)}
</div> </div>
</div> </div>
</hui-generic-entity-row>
`; `;
} }
static get styles(): CSSResult { private _handleAction(ev: ActionHandlerEvent) {
return css` handleAction(this, this.hass!, this._config!, ev.detail.action!);
}
static get styles(): CSSResult[] {
return [
weatherSVGStyles,
css`
:host {
display: flex;
align-items: center;
flex-direction: row;
}
.info {
margin-left: 16px;
flex: 1 0 60px;
}
.info,
.info > * {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.icon-image {
display: flex;
align-items: center;
min-width: 40px;
}
.icon-image > * {
flex: 0 0 40px;
height: 40px;
}
.weather-icon {
--iron-icon-width: 40px;
--iron-icon-height: 40px;
}
:host([rtl]) .flex {
margin-left: 0;
margin-right: 16px;
}
.pointer {
cursor: pointer;
}
.attributes { .attributes {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
text-align: right; text-align: right;
margin-left: 8px;
} }
.secondary { .secondary {
color: var(--secondary-text-color); color: var(--secondary-text-color);
} }
`; `,
];
} }
} }

View File

@ -288,7 +288,8 @@ class LovelacePanel extends LitElement {
this._errorMsg = err.message; this._errorMsg = err.message;
return; return;
} }
conf = await generateLovelaceConfigFromHass(this.hass!); const localize = await this.hass!.loadBackendTranslation("title");
conf = await generateLovelaceConfigFromHass(this.hass!, localize);
confMode = "generated"; confMode = "generated";
} finally { } finally {
// Ignore updates for another 2 seconds. // Ignore updates for another 2 seconds.
@ -370,8 +371,9 @@ class LovelacePanel extends LitElement {
const { config: previousConfig, mode: previousMode } = this.lovelace!; const { config: previousConfig, mode: previousMode } = this.lovelace!;
try { try {
// Optimistic update // Optimistic update
const localize = await this.hass!.loadBackendTranslation("title");
this._updateLovelace({ this._updateLovelace({
config: await generateLovelaceConfigFromHass(this.hass!), config: await generateLovelaceConfigFromHass(this.hass!, localize),
mode: "generated", mode: "generated",
editMode: false, editMode: false,
}); });

View File

@ -369,100 +369,6 @@ class HUIRoot extends LitElement {
`; `;
} }
static get styles(): CSSResult[] {
return [
haStyle,
css`
:host {
--dark-color: #455a64;
--text-dark-color: #fff;
-ms-user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
}
ha-app-layout {
min-height: 100%;
}
paper-menu-button {
padding: 0;
}
paper-tabs {
margin-left: 12px;
--paper-tabs-selection-bar-color: var(--text-primary-color, #fff);
text-transform: uppercase;
}
.edit-mode {
background-color: var(--dark-color, #455a64);
color: var(--text-dark-color);
}
.edit-mode div[main-title] {
pointer-events: auto;
}
paper-tab.iron-selected .edit-icon {
display: inline-flex;
}
.edit-icon {
color: var(--accent-color);
padding-left: 8px;
vertical-align: middle;
--mdc-theme-text-disabled-on-light: var(--disabled-text-color);
}
.edit-icon.view {
display: none;
}
#add-view {
position: absolute;
height: 44px;
}
#add-view ha-icon {
background-color: var(--accent-color);
border-radius: 5px;
margin-top: 4px;
}
app-toolbar a {
color: var(--text-primary-color, white);
}
mwc-button.warning:not([disabled]) {
color: var(--google-red-500);
}
#view {
min-height: calc(100vh - 112px);
/**
* Since we only set min-height, if child nodes need percentage
* heights they must use absolute positioning so we need relative
* positioning here.
*
* https://www.w3.org/TR/CSS2/visudet.html#the-height-property
*/
position: relative;
display: flex;
}
#view > * {
/**
* The view could get larger than the window in Firefox
* to prevent that we set the max-width to 100%
* flex-grow: 1 and flex-basis: 100% should make sure the view
* stays full width.
*
* https://github.com/home-assistant/home-assistant-polymer/pull/3806
*/
flex: 1 1 100%;
max-width: 100%;
}
#view.tabs-hidden {
min-height: calc(100vh - 64px);
}
paper-item {
cursor: pointer;
}
.hide-tab {
display: none;
}
`,
];
}
protected updated(changedProperties: PropertyValues): void { protected updated(changedProperties: PropertyValues): void {
super.updated(changedProperties); super.updated(changedProperties);
@ -527,8 +433,10 @@ class HUIRoot extends LitElement {
navigate(this, `${this.route?.prefix}/${views[0]?.path || 0}`); navigate(this, `${this.route?.prefix}/${views[0]?.path || 0}`);
newSelectView = 0; newSelectView = 0;
} }
// On edit mode change, recreate the current view from scratch }
force = true;
if (!force) {
huiView.lovelace = this.lovelace;
} }
} }
@ -734,6 +642,100 @@ class HUIRoot extends LitElement {
// Recalculate to see if we need to adjust content area for tab bar // Recalculate to see if we need to adjust content area for tab bar
fireEvent(this, "iron-resize"); fireEvent(this, "iron-resize");
} }
static get styles(): CSSResult[] {
return [
haStyle,
css`
:host {
--dark-color: #455a64;
--text-dark-color: #fff;
-ms-user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
}
ha-app-layout {
min-height: 100%;
}
paper-menu-button {
padding: 0;
}
paper-tabs {
margin-left: 12px;
--paper-tabs-selection-bar-color: var(--text-primary-color, #fff);
text-transform: uppercase;
}
.edit-mode {
background-color: var(--dark-color, #455a64);
color: var(--text-dark-color);
}
.edit-mode div[main-title] {
pointer-events: auto;
}
paper-tab.iron-selected .edit-icon {
display: inline-flex;
}
.edit-icon {
color: var(--accent-color);
padding-left: 8px;
vertical-align: middle;
--mdc-theme-text-disabled-on-light: var(--disabled-text-color);
}
.edit-icon.view {
display: none;
}
#add-view {
position: absolute;
height: 44px;
}
#add-view ha-icon {
background-color: var(--accent-color);
border-radius: 5px;
margin-top: 4px;
}
app-toolbar a {
color: var(--text-primary-color, white);
}
mwc-button.warning:not([disabled]) {
color: var(--google-red-500);
}
#view {
min-height: calc(100vh - 112px);
/**
* Since we only set min-height, if child nodes need percentage
* heights they must use absolute positioning so we need relative
* positioning here.
*
* https://www.w3.org/TR/CSS2/visudet.html#the-height-property
*/
position: relative;
display: flex;
}
#view > * {
/**
* The view could get larger than the window in Firefox
* to prevent that we set the max-width to 100%
* flex-grow: 1 and flex-basis: 100% should make sure the view
* stays full width.
*
* https://github.com/home-assistant/home-assistant-polymer/pull/3806
*/
flex: 1 1 100%;
max-width: 100%;
}
#view.tabs-hidden {
min-height: calc(100vh - 64px);
}
paper-item {
cursor: pointer;
}
.hide-tab {
display: none;
}
`,
];
}
} }
declare global { declare global {

View File

@ -34,6 +34,7 @@ export interface LovelaceCard extends HTMLElement {
hass?: HomeAssistant; hass?: HomeAssistant;
isPanel?: boolean; isPanel?: boolean;
editMode?: boolean; editMode?: boolean;
index?: number;
getCardSize(): number; getCardSize(): number;
setConfig(config: LovelaceCardConfig): void; setConfig(config: LovelaceCardConfig): void;
} }

View File

@ -4,8 +4,9 @@ import {
property, property,
PropertyValues, PropertyValues,
TemplateResult, TemplateResult,
CSSResult,
css,
} from "lit-element"; } from "lit-element";
// This one is for types
import { classMap } from "lit-html/directives/class-map"; import { classMap } from "lit-html/directives/class-map";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { computeRTL } from "../../../common/util/compute_rtl"; import { computeRTL } from "../../../common/util/compute_rtl";
@ -92,7 +93,6 @@ export class HUIView extends LitElement {
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
${this.renderStyles()}
<div id="badges"></div> <div id="badges"></div>
<div id="columns"></div> <div id="columns"></div>
${this.lovelace!.editMode ${this.lovelace!.editMode
@ -113,9 +113,203 @@ export class HUIView extends LitElement {
`; `;
} }
protected renderStyles(): TemplateResult { protected updated(changedProperties: PropertyValues): void {
return html` super.updated(changedProperties);
<style>
const hass = this.hass!;
const lovelace = this.lovelace!;
if (lovelace.editMode && !editCodeLoaded) {
editCodeLoaded = true;
import(/* webpackChunkName: "hui-view-editable" */ "./hui-view-editable");
}
const hassChanged = changedProperties.has("hass");
let editModeChanged = false;
let configChanged = false;
if (changedProperties.has("index")) {
configChanged = true;
} else if (changedProperties.has("lovelace")) {
const oldLovelace = changedProperties.get("lovelace") as Lovelace;
editModeChanged =
oldLovelace && lovelace.editMode !== oldLovelace.editMode;
configChanged = !oldLovelace || lovelace.config !== oldLovelace.config;
}
if (configChanged) {
this._createBadges(lovelace.config.views[this.index!]);
} else if (hassChanged) {
this._badges.forEach((badge) => {
badge.hass = hass;
});
}
if (configChanged) {
this._createCards(lovelace.config.views[this.index!]);
} else if (editModeChanged) {
this._switchEditMode();
} else if (changedProperties.has("columns")) {
this._recreateColumns();
}
if (hassChanged && !configChanged) {
this._cards.forEach((element) => {
element.hass = this.hass;
});
}
const oldHass = changedProperties.get("hass") as this["hass"] | undefined;
if (
configChanged ||
editModeChanged ||
(hassChanged &&
oldHass &&
(hass.themes !== oldHass.themes ||
hass.selectedTheme !== oldHass.selectedTheme))
) {
applyThemesOnElement(
this,
hass.themes,
lovelace.config.views[this.index!].theme
);
}
}
private _addCard(): void {
showEditCardDialog(this, {
lovelaceConfig: this.lovelace!.config,
saveConfig: this.lovelace!.saveConfig,
path: [this.index!],
});
}
private _createBadges(config: LovelaceViewConfig): void {
const root = this.shadowRoot!.getElementById("badges")!;
while (root.lastChild) {
root.removeChild(root.lastChild);
}
if (!config || !config.badges || !Array.isArray(config.badges)) {
root.style.display = "none";
this._badges = [];
return;
}
const elements: HUIView["_badges"] = [];
const badges = processConfigEntities(config.badges as any);
badges.forEach((badge) => {
const element = createBadgeElement(badge);
element.hass = this.hass;
elements.push(element);
root.appendChild(element);
});
this._badges = elements;
root.style.display = elements.length > 0 ? "block" : "none";
}
private _switchEditMode() {
if (this.lovelace!.editMode) {
const wrappedCards = this._cards.map((element) => {
const wrapper = document.createElement("hui-card-options");
wrapper.hass = this.hass;
wrapper.lovelace = this.lovelace;
wrapper.path = [this.index!, (element as LovelaceCard).index!];
(element as LovelaceCard).editMode = true;
wrapper.appendChild(element);
return wrapper;
});
this._createColumns(wrappedCards);
} else {
this._createColumns(this._cards);
}
}
private _recreateColumns() {
this._createColumns(this._cards);
}
private _createColumns(elements: HTMLElement[]) {
const root = this.shadowRoot!.getElementById("columns")!;
while (root.lastChild) {
root.removeChild(root.lastChild);
}
let columns: HTMLElement[][] = [];
const columnEntityCount: number[] = [];
for (let i = 0; i < this.columns!; i++) {
columns.push([]);
columnEntityCount.push(0);
}
elements.forEach((el) => {
const cardSize = computeCardSize(
(el.tagName === "HUI-CARD-OPTIONS" ? el.firstChild : el) as LovelaceCard
);
columns[getColumnIndex(columnEntityCount, cardSize)].push(el);
});
// Remove empty columns
columns = columns.filter((val) => val.length > 0);
columns.forEach((column) => {
const columnEl = document.createElement("div");
columnEl.classList.add("column");
column.forEach((el) => columnEl.appendChild(el));
root.appendChild(columnEl);
});
}
private _createCards(config: LovelaceViewConfig): void {
if (!config || !config.cards || !Array.isArray(config.cards)) {
this._cards = [];
return;
}
const elements: LovelaceCard[] = [];
config.cards.forEach((cardConfig, index) => {
const element = this.createCardElement(cardConfig);
element.index = index;
elements.push(element);
});
this._cards = elements;
if (this.lovelace!.editMode) {
this._switchEditMode();
} else {
this._createColumns(this._cards);
}
}
private _rebuildCard(
cardElToReplace: LovelaceCard,
config: LovelaceCardConfig
): void {
const newCardEl = this.createCardElement(config);
cardElToReplace.parentElement!.replaceChild(newCardEl, cardElToReplace);
this._cards = this._cards!.map((curCardEl) =>
curCardEl === cardElToReplace ? newCardEl : curCardEl
);
}
private _rebuildBadge(
badgeElToReplace: LovelaceBadge,
config: LovelaceBadgeConfig
): void {
const newBadgeEl = this.createBadgeElement(config);
badgeElToReplace.parentElement!.replaceChild(newBadgeEl, badgeElToReplace);
this._badges = this._cards!.map((curBadgeEl) =>
curBadgeEl === badgeElToReplace ? newBadgeEl : curBadgeEl
);
}
static get styles(): CSSResult {
return css`
:host { :host {
display: block; display: block;
box-sizing: border-box; box-sizing: border-box;
@ -123,10 +317,7 @@ export class HUIView extends LitElement {
transform: translateZ(0); transform: translateZ(0);
position: relative; position: relative;
color: var(--primary-text-color); color: var(--primary-text-color);
background: var( background: var(--lovelace-background, var(--primary-background-color));
--lovelace-background,
var(--primary-background-color)
);
} }
#badges { #badges {
@ -185,182 +376,8 @@ export class HUIView extends LitElement {
max-width: 600px; max-width: 600px;
} }
} }
</style>
`; `;
} }
protected updated(changedProperties: PropertyValues): void {
super.updated(changedProperties);
const hass = this.hass!;
const lovelace = this.lovelace!;
if (lovelace.editMode && !editCodeLoaded) {
editCodeLoaded = true;
import(/* webpackChunkName: "hui-view-editable" */ "./hui-view-editable");
}
const hassChanged = changedProperties.has("hass");
let editModeChanged = false;
let configChanged = false;
if (changedProperties.has("index")) {
configChanged = true;
} else if (changedProperties.has("lovelace")) {
const oldLovelace = changedProperties.get("lovelace") as Lovelace;
editModeChanged =
!oldLovelace || lovelace.editMode !== oldLovelace.editMode;
configChanged = !oldLovelace || lovelace.config !== oldLovelace.config;
}
if (configChanged) {
this._createBadges(lovelace.config.views[this.index!]);
} else if (hassChanged) {
this._badges.forEach((badge) => {
badge.hass = hass;
});
}
if (configChanged || editModeChanged || changedProperties.has("columns")) {
this._createCards(lovelace.config.views[this.index!]);
} else if (hassChanged) {
this._cards.forEach((element) => {
element.hass = this.hass;
});
}
const oldHass = changedProperties.get("hass") as this["hass"] | undefined;
if (
configChanged ||
editModeChanged ||
(hassChanged &&
oldHass &&
(hass.themes !== oldHass.themes ||
hass.selectedTheme !== oldHass.selectedTheme))
) {
applyThemesOnElement(
this,
hass.themes,
lovelace.config.views[this.index!].theme
);
}
}
private _addCard(): void {
showEditCardDialog(this, {
lovelaceConfig: this.lovelace!.config,
saveConfig: this.lovelace!.saveConfig,
path: [this.index!],
});
}
private _createBadges(config: LovelaceViewConfig): void {
const root = this.shadowRoot!.getElementById("badges")!;
while (root.lastChild) {
root.removeChild(root.lastChild);
}
if (!config || !config.badges || !Array.isArray(config.badges)) {
root.style.display = "none";
this._badges = [];
return;
}
const elements: HUIView["_badges"] = [];
const badges = processConfigEntities(config.badges as any);
badges.forEach((badge) => {
const element = createBadgeElement(badge);
element.hass = this.hass;
elements.push(element);
root.appendChild(element);
});
this._badges = elements;
root.style.display = elements.length > 0 ? "block" : "none";
}
private _createCards(config: LovelaceViewConfig): void {
const root = this.shadowRoot!.getElementById("columns")!;
while (root.lastChild) {
root.removeChild(root.lastChild);
}
if (!config || !config.cards || !Array.isArray(config.cards)) {
this._cards = [];
return;
}
const elements: LovelaceCard[] = [];
const elementsToAppend: HTMLElement[] = [];
config.cards.forEach((cardConfig, cardIndex) => {
const element = this.createCardElement(cardConfig);
elements.push(element);
if (!this.lovelace!.editMode) {
elementsToAppend.push(element);
return;
}
const wrapper = document.createElement("hui-card-options");
wrapper.hass = this.hass;
wrapper.lovelace = this.lovelace;
wrapper.path = [this.index!, cardIndex];
element.editMode = true;
wrapper.appendChild(element);
elementsToAppend.push(wrapper);
});
let columns: HTMLElement[][] = [];
const columnEntityCount: number[] = [];
for (let i = 0; i < this.columns!; i++) {
columns.push([]);
columnEntityCount.push(0);
}
elements.forEach((el, index) => {
const cardSize = computeCardSize(el);
// Element to append might be the wrapped card when we're editing.
columns[getColumnIndex(columnEntityCount, cardSize)].push(
elementsToAppend[index]
);
});
// Remove empty columns
columns = columns.filter((val) => val.length > 0);
columns.forEach((column) => {
const columnEl = document.createElement("div");
columnEl.classList.add("column");
column.forEach((el) => columnEl.appendChild(el));
root.appendChild(columnEl);
});
this._cards = elements;
}
private _rebuildCard(
cardElToReplace: LovelaceCard,
config: LovelaceCardConfig
): void {
const newCardEl = this.createCardElement(config);
cardElToReplace.parentElement!.replaceChild(newCardEl, cardElToReplace);
this._cards = this._cards!.map((curCardEl) =>
curCardEl === cardElToReplace ? newCardEl : curCardEl
);
}
private _rebuildBadge(
badgeElToReplace: LovelaceBadge,
config: LovelaceBadgeConfig
): void {
const newBadgeEl = this.createBadgeElement(config);
badgeElToReplace.parentElement!.replaceChild(newBadgeEl, badgeElToReplace);
this._badges = this._cards!.map((curBadgeEl) =>
curBadgeEl === badgeElToReplace ? newBadgeEl : curBadgeEl
);
}
} }
declare global { declare global {

View File

@ -1,5 +1,5 @@
import { atLeastVersion } from "../common/config/version"; import { atLeastVersion } from "../common/config/version";
import { computeLocalize } from "../common/translations/localize"; import { computeLocalize, LocalizeFunc } from "../common/translations/localize";
import { computeRTL } from "../common/util/compute_rtl"; import { computeRTL } from "../common/util/compute_rtl";
import { debounce } from "../common/util/debounce"; import { debounce } from "../common/util/debounce";
import { import {
@ -104,29 +104,37 @@ export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
this._loadFragmentTranslations(hass.language, hass.panelUrl); this._loadFragmentTranslations(hass.language, hass.panelUrl);
} }
/**
* Load translations from the backend
* @param language language to fetch
* @param category category to fetch
* @param integration optional, if having to fetch for specific integration
* @param configFlow optional, if having to fetch for all integrations with a config flow
* @param force optional, load even if already cached
*/
private async _loadHassTranslations( private async _loadHassTranslations(
language: string, language: string,
category: Parameters<typeof getHassTranslations>[2], category: Parameters<typeof getHassTranslations>[2],
integration?: Parameters<typeof getHassTranslations>[3], integration?: Parameters<typeof getHassTranslations>[3],
configFlow?: Parameters<typeof getHassTranslations>[4], configFlow?: Parameters<typeof getHassTranslations>[4],
force = false force = false
) { ): Promise<LocalizeFunc> {
if ( if (
__BACKWARDS_COMPAT__ && __BACKWARDS_COMPAT__ &&
!atLeastVersion(this.hass!.connection.haVersion, 0, 109) !atLeastVersion(this.hass!.connection.haVersion, 0, 109)
) { ) {
if (category !== "state") { if (category !== "state") {
return; return this.hass!.localize;
} }
const resources = await getHassTranslationsPre109(this.hass!, language); const resources = await getHassTranslationsPre109(this.hass!, language);
// Ignore the repsonse if user switched languages before we got response // Ignore the repsonse if user switched languages before we got response
if (this.hass!.language !== language) { if (this.hass!.language !== language) {
return; return this.hass!.localize;
} }
this._updateResources(language, resources); this._updateResources(language, resources);
return; return this.hass!.localize;
} }
let alreadyLoaded: LoadedTranslationCategory; let alreadyLoaded: LoadedTranslationCategory;
@ -145,12 +153,12 @@ export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
if (!force) { if (!force) {
if (integration) { if (integration) {
if (alreadyLoaded.integrations.includes(integration)) { if (alreadyLoaded.integrations.includes(integration)) {
return; return this.hass!.localize;
} }
} else if ( } else if (
configFlow ? alreadyLoaded.configFlow : alreadyLoaded.setup configFlow ? alreadyLoaded.configFlow : alreadyLoaded.setup
) { ) {
return; return this.hass!.localize;
} }
} }
@ -176,10 +184,11 @@ export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
// Ignore the repsonse if user switched languages before we got response // Ignore the repsonse if user switched languages before we got response
if (this.hass!.language !== language) { if (this.hass!.language !== language) {
return; return this.hass!.localize;
} }
this._updateResources(language, resources); this._updateResources(language, resources);
return this.hass!.localize;
} }
private async _loadFragmentTranslations( private async _loadFragmentTranslations(
@ -214,9 +223,7 @@ export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
// multiple fragments. // multiple fragments.
const resources = { const resources = {
[language]: { [language]: {
...(this.hass && ...this.hass?.resources?.[language],
this.hass.resources &&
this.hass.resources[language]),
...data, ...data,
}, },
}; };

View File

@ -1841,7 +1841,8 @@
"no_theme": "No theme", "no_theme": "No theme",
"unit": "Unit", "unit": "Unit",
"url": "Url", "url": "Url",
"state": "State" "state": "State",
"secondary_info_attribute": "Secondary Info Attribute"
}, },
"map": { "map": {
"name": "Map", "name": "Map",
@ -1902,7 +1903,8 @@
}, },
"weather-forecast": { "weather-forecast": {
"name": "Weather Forecast", "name": "Weather Forecast",
"description": "The Weather Forecast card displays the weather. Very useful to include on interfaces that people display on the wall." "description": "The Weather Forecast card displays the weather. Very useful to include on interfaces that people display on the wall.",
"show_forecast": "Show Forecast"
} }
}, },
"view": { "view": {
@ -2186,7 +2188,10 @@
"icons_by": "Icons by", "icons_by": "Icons by",
"frontend_version": "Frontend version: {version} - {type}", "frontend_version": "Frontend version: {version} - {type}",
"custom_uis": "Custom UIs:", "custom_uis": "Custom UIs:",
"system_health_error": "System Health component is not loaded. Add 'system_health:' to configuration.yaml" "system_health_error": "System Health component is not loaded. Add 'system_health:' to configuration.yaml",
"integrations": "Integrations",
"documentation": "Documentation",
"issues": "Issues"
}, },
"logs": { "logs": {
"title": "Logs", "title": "Logs",

View File

@ -224,7 +224,7 @@ export interface HomeAssistant {
category: Parameters<typeof getHassTranslations>[2], category: Parameters<typeof getHassTranslations>[2],
integration?: Parameters<typeof getHassTranslations>[3], integration?: Parameters<typeof getHassTranslations>[3],
configFlow?: Parameters<typeof getHassTranslations>[4] configFlow?: Parameters<typeof getHassTranslations>[4]
): Promise<void>; ): Promise<LocalizeFunc>;
} }
export type LightEntity = HassEntityBase & { export type LightEntity = HassEntityBase & {

View File

@ -473,6 +473,7 @@
} }
}, },
"common": { "common": {
"and": "og",
"cancel": "Annuller", "cancel": "Annuller",
"close": "Luk", "close": "Luk",
"delete": "Slet", "delete": "Slet",
@ -1180,7 +1181,9 @@
"edit_requires_storage": "Editor er deaktiveret, fordi config er gemt i 'configuration.yaml'.", "edit_requires_storage": "Editor er deaktiveret, fordi config er gemt i 'configuration.yaml'.",
"elevation": "Højde", "elevation": "Højde",
"elevation_meters": "meter", "elevation_meters": "meter",
"external_url": "Ekstern webadresse",
"imperial_example": "Fahrenheit, pund", "imperial_example": "Fahrenheit, pund",
"internal_url": "Intern webadresse",
"latitude": "Breddegrad", "latitude": "Breddegrad",
"location_name": "Navn på din Home Assistant-installation", "location_name": "Navn på din Home Assistant-installation",
"longitude": "Længdegrad", "longitude": "Længdegrad",

View File

@ -473,6 +473,7 @@
} }
}, },
"common": { "common": {
"and": "und",
"cancel": "Abbrechen", "cancel": "Abbrechen",
"close": "Schließen", "close": "Schließen",
"delete": "Löschen", "delete": "Löschen",
@ -1180,7 +1181,9 @@
"edit_requires_storage": "Editor deaktiviert, da die Konfiguration in configuration.yaml gespeichert ist.", "edit_requires_storage": "Editor deaktiviert, da die Konfiguration in configuration.yaml gespeichert ist.",
"elevation": "Höhe", "elevation": "Höhe",
"elevation_meters": "Meter", "elevation_meters": "Meter",
"external_url": "Externe Adresse",
"imperial_example": "Fahrenheit, Pfund", "imperial_example": "Fahrenheit, Pfund",
"internal_url": "Interne Adresse",
"latitude": "Breitengrad", "latitude": "Breitengrad",
"location_name": "Name deiner Home Assistant Installation", "location_name": "Name deiner Home Assistant Installation",
"longitude": "Längengrad", "longitude": "Längengrad",

View File

@ -1878,10 +1878,13 @@
"built_using": "Built using", "built_using": "Built using",
"custom_uis": "Custom UIs:", "custom_uis": "Custom UIs:",
"developed_by": "Developed by a bunch of awesome people.", "developed_by": "Developed by a bunch of awesome people.",
"documentation": "Documentation",
"frontend": "frontend-ui", "frontend": "frontend-ui",
"frontend_version": "Frontend version: {version} - {type}", "frontend_version": "Frontend version: {version} - {type}",
"home_assistant_logo": "Home Assistant logo", "home_assistant_logo": "Home Assistant logo",
"icons_by": "Icons by", "icons_by": "Icons by",
"integrations": "Integrations",
"issues": "Issues",
"license": "Published under the Apache 2.0 license", "license": "Published under the Apache 2.0 license",
"path_configuration": "Path to configuration.yaml: {path}", "path_configuration": "Path to configuration.yaml: {path}",
"server": "server", "server": "server",
@ -2072,6 +2075,7 @@
"name": "Name", "name": "Name",
"no_theme": "No theme", "no_theme": "No theme",
"refresh_interval": "Refresh Interval", "refresh_interval": "Refresh Interval",
"secondary_info_attribute": "Secondary Info Attribute",
"show_icon": "Show Icon?", "show_icon": "Show Icon?",
"show_name": "Show Name?", "show_name": "Show Name?",
"show_state": "Show State?", "show_state": "Show State?",
@ -2162,7 +2166,8 @@
}, },
"weather-forecast": { "weather-forecast": {
"description": "The Weather Forecast card displays the weather. Very useful to include on interfaces that people display on the wall.", "description": "The Weather Forecast card displays the weather. Very useful to include on interfaces that people display on the wall.",
"name": "Weather Forecast" "name": "Weather Forecast",
"show_forecast": "Show Forecast"
} }
}, },
"cardpicker": { "cardpicker": {

View File

@ -473,6 +473,7 @@
} }
}, },
"common": { "common": {
"and": "y",
"cancel": "Cancelar", "cancel": "Cancelar",
"close": "Cerrar", "close": "Cerrar",
"delete": "Eliminar", "delete": "Eliminar",
@ -1180,7 +1181,9 @@
"edit_requires_storage": "Editor deshabilitado debido a la configuración almacenada en configuration.yaml.", "edit_requires_storage": "Editor deshabilitado debido a la configuración almacenada en configuration.yaml.",
"elevation": "Altitud", "elevation": "Altitud",
"elevation_meters": "metros", "elevation_meters": "metros",
"external_url": "URL externa",
"imperial_example": "Fahrenheit, libras", "imperial_example": "Fahrenheit, libras",
"internal_url": "URL interna",
"latitude": "Latitud", "latitude": "Latitud",
"location_name": "Nombre de tu instalación de Home Assistant", "location_name": "Nombre de tu instalación de Home Assistant",
"longitude": "Longitud", "longitude": "Longitud",

View File

@ -473,6 +473,7 @@
} }
}, },
"common": { "common": {
"and": "ja",
"cancel": "Peruuta", "cancel": "Peruuta",
"close": "Sulje", "close": "Sulje",
"delete": "Poista", "delete": "Poista",
@ -1180,7 +1181,9 @@
"edit_requires_storage": "Editori on poistettu käytöstä, koska asetuksia on annettu configuration.yaml:ssa.", "edit_requires_storage": "Editori on poistettu käytöstä, koska asetuksia on annettu configuration.yaml:ssa.",
"elevation": "Korkeus merenpinnasta", "elevation": "Korkeus merenpinnasta",
"elevation_meters": "metriä", "elevation_meters": "metriä",
"external_url": "Ulkoinen URL-osoite",
"imperial_example": "Fahrenheit, paunaa", "imperial_example": "Fahrenheit, paunaa",
"internal_url": "Sisäinen URL-osoite",
"latitude": "Leveysaste", "latitude": "Leveysaste",
"location_name": "Home Assistant -järjestelmäsi nimi", "location_name": "Home Assistant -järjestelmäsi nimi",
"longitude": "Pituusaste", "longitude": "Pituusaste",
@ -1247,6 +1250,7 @@
}, },
"delete": "Poista", "delete": "Poista",
"description": "Hallitse yhdistettyjä laitteita.", "description": "Hallitse yhdistettyjä laitteita.",
"device_info": "Laitteen tiedot",
"device_not_found": "Laitetta ei löydy.", "device_not_found": "Laitetta ei löydy.",
"entities": { "entities": {
"add_entities_lovelace": "Lisää Lovelace näkymään", "add_entities_lovelace": "Lisää Lovelace näkymään",
@ -1602,7 +1606,7 @@
"core": "Lataa ydin uudelleen", "core": "Lataa ydin uudelleen",
"group": "Lataa ryhmät uudelleen", "group": "Lataa ryhmät uudelleen",
"heading": "Asetusten uudelleenlataus", "heading": "Asetusten uudelleenlataus",
"introduction": "Jotkut kotiassistentin osat voidaan ladata uudelleen ilman, että tarvitaan uudelleenkäynnistystä. Painamalla uudelleenlatausta uudet asetukset luetaan yaml tiedostosta.", "introduction": "Jotkut Home Assistantin osat voidaan ladata uudelleen ilman, että tarvitaan uudelleenkäynnistystä. Painamalla uudelleenlatausta yaml-tiedosto luetaan uudelleen.",
"person": "Lataa henkilöt uudelleen", "person": "Lataa henkilöt uudelleen",
"scene": "Lataa tilanteet uudelleen", "scene": "Lataa tilanteet uudelleen",
"script": "Lataa skriptit uudelleen", "script": "Lataa skriptit uudelleen",
@ -1612,7 +1616,7 @@
"confirm_restart": "Haluatko varmasti käynnistää Home Assistantin uudelleen?", "confirm_restart": "Haluatko varmasti käynnistää Home Assistantin uudelleen?",
"confirm_stop": "Haluatko varmasti pysäyttää Home Assistantin?", "confirm_stop": "Haluatko varmasti pysäyttää Home Assistantin?",
"heading": "Palvelimen hallinta", "heading": "Palvelimen hallinta",
"introduction": "Hallitse Home Assistantia... suoraan Home Assistantista", "introduction": "Hallitse Home Assistantia suoraan käyttöliittymästä.",
"restart": "Käynnistä uudelleen", "restart": "Käynnistä uudelleen",
"stop": "Pysäytä" "stop": "Pysäytä"
}, },

View File

@ -473,6 +473,7 @@
} }
}, },
"common": { "common": {
"and": "És",
"cancel": "Mégse", "cancel": "Mégse",
"close": "Bezárás", "close": "Bezárás",
"delete": "Törlés", "delete": "Törlés",
@ -1180,7 +1181,9 @@
"edit_requires_storage": "A szerkesztő le van tiltva, mert a konfiguráció a configuration.yaml fájlban van tárolva.", "edit_requires_storage": "A szerkesztő le van tiltva, mert a konfiguráció a configuration.yaml fájlban van tárolva.",
"elevation": "Magasság", "elevation": "Magasság",
"elevation_meters": "méter", "elevation_meters": "méter",
"external_url": "Externe URL",
"imperial_example": "Fahrenheit, font", "imperial_example": "Fahrenheit, font",
"internal_url": "Érvénytelen URL",
"latitude": "Szélesség", "latitude": "Szélesség",
"location_name": "A Home Assistant rendszered neve", "location_name": "A Home Assistant rendszered neve",
"longitude": "Hosszúság", "longitude": "Hosszúság",
@ -1247,6 +1250,7 @@
}, },
"delete": "Törlés", "delete": "Törlés",
"description": "Csatlakoztatott eszközök kezelése", "description": "Csatlakoztatott eszközök kezelése",
"device_info": "Eszköz információ",
"device_not_found": "Eszköz nem található.", "device_not_found": "Eszköz nem található.",
"entities": { "entities": {
"add_entities_lovelace": "Hozzáadás a Lovelace-hez", "add_entities_lovelace": "Hozzáadás a Lovelace-hez",

View File

@ -473,6 +473,7 @@
} }
}, },
"common": { "common": {
"and": "e",
"cancel": "Annulla", "cancel": "Annulla",
"close": "Chiudi", "close": "Chiudi",
"delete": "Elimina", "delete": "Elimina",
@ -1180,7 +1181,9 @@
"edit_requires_storage": "Editor disabilitato perché la configurazione è memorizzata in configuration.yaml.", "edit_requires_storage": "Editor disabilitato perché la configurazione è memorizzata in configuration.yaml.",
"elevation": "Altitudine", "elevation": "Altitudine",
"elevation_meters": "metri", "elevation_meters": "metri",
"external_url": "URL esterno",
"imperial_example": "Fahrenheit, libbre", "imperial_example": "Fahrenheit, libbre",
"internal_url": "URL interno",
"latitude": "Latitudine", "latitude": "Latitudine",
"location_name": "Nome della tua installazione di Home Assistant", "location_name": "Nome della tua installazione di Home Assistant",
"longitude": "Longitudine", "longitude": "Longitudine",
@ -1247,6 +1250,7 @@
}, },
"delete": "Elimina", "delete": "Elimina",
"description": "Gestisci i dispositivi collegati", "description": "Gestisci i dispositivi collegati",
"device_info": "Informazioni sul dispositivo",
"device_not_found": "Dispositivo non trovato.", "device_not_found": "Dispositivo non trovato.",
"entities": { "entities": {
"add_entities_lovelace": "Aggiungi a Lovelace", "add_entities_lovelace": "Aggiungi a Lovelace",

View File

@ -1602,7 +1602,7 @@
"automation": "Automatisme nei lueden", "automation": "Automatisme nei lueden",
"core": "Standuert and Personnalisatioun néi lueden", "core": "Standuert and Personnalisatioun néi lueden",
"group": "Gruppe nei lueden", "group": "Gruppe nei lueden",
"heading": "YAML Konfiguratioun gëtt frësch gelueden", "heading": "YAML Konfiguratioun frësch lueden",
"introduction": "E puer Deeler vum Home Assistant kënne frësch geluede ginn ouni datt een Neistart néideg ass. Klick op nei luede fir di aktuell Konfiguratioun z'entlueden an di nei Konfiguratioun ze lueden.", "introduction": "E puer Deeler vum Home Assistant kënne frësch geluede ginn ouni datt een Neistart néideg ass. Klick op nei luede fir di aktuell Konfiguratioun z'entlueden an di nei Konfiguratioun ze lueden.",
"person": "Persoune frësch lueden", "person": "Persoune frësch lueden",
"scene": "Szeene néi lueden", "scene": "Szeene néi lueden",

View File

@ -473,6 +473,7 @@
} }
}, },
"common": { "common": {
"and": "og",
"cancel": "Avbryt", "cancel": "Avbryt",
"close": "Lukk", "close": "Lukk",
"delete": "Slett", "delete": "Slett",
@ -1180,7 +1181,9 @@
"edit_requires_storage": "Redigering deaktivert da konfigurasjonen er lagret i configuration.yaml.", "edit_requires_storage": "Redigering deaktivert da konfigurasjonen er lagret i configuration.yaml.",
"elevation": "Høyde", "elevation": "Høyde",
"elevation_meters": "meter", "elevation_meters": "meter",
"external_url": "Ekstern URL",
"imperial_example": "Fahrenheit, pund", "imperial_example": "Fahrenheit, pund",
"internal_url": "Intern URL",
"latitude": "Breddegrad", "latitude": "Breddegrad",
"location_name": "Navn på installasjonen av Home Assistant", "location_name": "Navn på installasjonen av Home Assistant",
"longitude": "Lengdegrad", "longitude": "Lengdegrad",

View File

@ -473,11 +473,15 @@
} }
}, },
"common": { "common": {
"and": "en",
"cancel": "Annuleren", "cancel": "Annuleren",
"close": "Sluiten", "close": "Sluiten",
"delete": "Verwijderen", "delete": "Verwijderen",
"loading": "Bezig met laden", "loading": "Bezig met laden",
"next": "Volgende",
"no": "Nee", "no": "Nee",
"previous": "Vorige",
"refresh": "Vernieuwen",
"save": "Opslaan", "save": "Opslaan",
"successfully_deleted": "Succesvol verwijderd", "successfully_deleted": "Succesvol verwijderd",
"successfully_saved": "Succesvol opgeslagen", "successfully_saved": "Succesvol opgeslagen",
@ -737,6 +741,10 @@
"triggered": "Geactiveerd {name}" "triggered": "Geactiveerd {name}"
}, },
"panel": { "panel": {
"calendar": {
"my_calendars": "Mijn agenda's",
"today": "Vandaag"
},
"config": { "config": {
"advanced_mode": { "advanced_mode": {
"hint_enable": "Ontbrekende configuratie-opties? Schakel geavanceerde modus in", "hint_enable": "Ontbrekende configuratie-opties? Schakel geavanceerde modus in",
@ -839,6 +847,9 @@
}, },
"label": "Apparaat" "label": "Apparaat"
}, },
"not": {
"label": "Niet"
},
"numeric_state": { "numeric_state": {
"above": "Boven", "above": "Boven",
"below": "Onder", "below": "Onder",
@ -1170,7 +1181,9 @@
"edit_requires_storage": "Editor uitgeschakeld omdat de configuratie is opgeslagen in configuration.yaml", "edit_requires_storage": "Editor uitgeschakeld omdat de configuratie is opgeslagen in configuration.yaml",
"elevation": "Hoogte", "elevation": "Hoogte",
"elevation_meters": "meter", "elevation_meters": "meter",
"external_url": "Externe URL",
"imperial_example": "Fahrenheit, ponden", "imperial_example": "Fahrenheit, ponden",
"internal_url": "Interne URL",
"latitude": "Breedtegraad", "latitude": "Breedtegraad",
"location_name": "Naam van Home Assistant installatie", "location_name": "Naam van Home Assistant installatie",
"longitude": "Lengtegraad", "longitude": "Lengtegraad",
@ -1237,6 +1250,7 @@
}, },
"delete": "Verwijderen", "delete": "Verwijderen",
"description": "Beheer verbonden apparaten", "description": "Beheer verbonden apparaten",
"device_info": "Apparaat info",
"device_not_found": "Apparaat niet gevonden.", "device_not_found": "Apparaat niet gevonden.",
"entities": { "entities": {
"add_entities_lovelace": "Voeg toe aan de Lovelace gebruikersinterface", "add_entities_lovelace": "Voeg toe aan de Lovelace gebruikersinterface",

View File

@ -473,6 +473,7 @@
} }
}, },
"common": { "common": {
"and": "i",
"cancel": "Anuluj", "cancel": "Anuluj",
"close": "Zamknij", "close": "Zamknij",
"delete": "Usuń", "delete": "Usuń",
@ -1180,7 +1181,9 @@
"edit_requires_storage": "Edytor wyłączony, ponieważ konfiguracja jest przechowywana w pliku configuration.yaml.", "edit_requires_storage": "Edytor wyłączony, ponieważ konfiguracja jest przechowywana w pliku configuration.yaml.",
"elevation": "Wysokość", "elevation": "Wysokość",
"elevation_meters": "metry/-ów", "elevation_meters": "metry/-ów",
"external_url": "Publiczny adres URL",
"imperial_example": "stopnie Fahrenheita, funty", "imperial_example": "stopnie Fahrenheita, funty",
"internal_url": "Lokalny adres URL",
"latitude": "Szerokość geograficzna", "latitude": "Szerokość geograficzna",
"location_name": "Nazwa instalacji Home Assistant", "location_name": "Nazwa instalacji Home Assistant",
"longitude": "Długość geograficzna", "longitude": "Długość geograficzna",

View File

@ -331,8 +331,7 @@
"ui": { "ui": {
"auth_store": { "auth_store": {
"ask": "Deseja continuar com sessão iniciada?", "ask": "Deseja continuar com sessão iniciada?",
"confirm": "Guardar login", "decline": "Não"
"decline": "Não, obrigado"
}, },
"card": { "card": {
"alarm_control_panel": { "alarm_control_panel": {
@ -473,6 +472,7 @@
} }
}, },
"common": { "common": {
"and": "e",
"cancel": "Cancelar", "cancel": "Cancelar",
"close": "Fechar", "close": "Fechar",
"delete": "Apagar", "delete": "Apagar",
@ -761,8 +761,8 @@
"editor": { "editor": {
"create": "Criar", "create": "Criar",
"default_name": "Nova área", "default_name": "Nova área",
"delete": "APAGAR", "delete": "Apagar",
"update": "ATUALIZAR" "update": "Atualizar"
}, },
"picker": { "picker": {
"create_area": "Criar área", "create_area": "Criar área",
@ -1178,7 +1178,9 @@
"edit_requires_storage": "Editor desativado por causa da configuração existente em configuration.yaml.", "edit_requires_storage": "Editor desativado por causa da configuração existente em configuration.yaml.",
"elevation": "Elevação", "elevation": "Elevação",
"elevation_meters": "metros", "elevation_meters": "metros",
"external_url": "URL externo",
"imperial_example": "Fahrenheit, libras", "imperial_example": "Fahrenheit, libras",
"internal_url": "URL interno",
"latitude": "Latitude", "latitude": "Latitude",
"location_name": "Nome da instalação do seu Home Assistant", "location_name": "Nome da instalação do seu Home Assistant",
"longitude": "Longitude", "longitude": "Longitude",
@ -1204,7 +1206,7 @@
"description": "Personalizar as suas entidades", "description": "Personalizar as suas entidades",
"pick_attribute": "Escolha um atributo para substituir.", "pick_attribute": "Escolha um atributo para substituir.",
"picker": { "picker": {
"header": "Personalização", "header": "Personalizações",
"introduction": "Ajustar atributos por entidade. Personalizações acrescentadas/editadas terão efeitos imediatos. Remoção de personalizações terão efeito quando a entidade for atualizada." "introduction": "Ajustar atributos por entidade. Personalizações acrescentadas/editadas terão efeitos imediatos. Remoção de personalizações terão efeito quando a entidade for atualizada."
}, },
"warning": { "warning": {
@ -1244,6 +1246,7 @@
}, },
"delete": "Apagar", "delete": "Apagar",
"description": "Gerir dispositivos ligados", "description": "Gerir dispositivos ligados",
"device_info": "Informação do dispositivo",
"device_not_found": "Dispositivo não encontrado.", "device_not_found": "Dispositivo não encontrado.",
"entities": { "entities": {
"add_entities_lovelace": "Adicionar ao Lovelace", "add_entities_lovelace": "Adicionar ao Lovelace",
@ -1368,6 +1371,7 @@
"aborted": "Abortado", "aborted": "Abortado",
"close": "Fechar", "close": "Fechar",
"created_config": "Configuração criada para {name}.", "created_config": "Configuração criada para {name}.",
"dismiss": "Descartar diálogo",
"error_saving_area": "Erro ao salvar a área: {error}", "error_saving_area": "Erro ao salvar a área: {error}",
"external_step": { "external_step": {
"description": "Para ser concluída, esta etapa exige que visite um sítio web externo.", "description": "Para ser concluída, esta etapa exige que visite um sítio web externo.",
@ -1592,8 +1596,8 @@
"automation": "Recarregar automações", "automation": "Recarregar automações",
"core": "Recarregar localização e personalizações", "core": "Recarregar localização e personalizações",
"group": "Recarregar grupos", "group": "Recarregar grupos",
"heading": "A recarregar configuração", "heading": "A recarregar a configuração YAML",
"introduction": "Algumas partes do Home Assistant podem ser recarregadas sem necessidade de reiniciar. Ao carregar em Recarregar configuração irá descarregar a configuração atual e carregar a nova.", "introduction": "Algumas partes do Home Assistant podem ser recarregadas sem a necessidade de reiniciar. Ao carregar em Recarregar a configuração irá descartar a configuração atual e carregar a nova.",
"person": "Recarregar pessoas", "person": "Recarregar pessoas",
"scene": "Recarregar cenas", "scene": "Recarregar cenas",
"script": "Recarregar scripts", "script": "Recarregar scripts",
@ -1661,7 +1665,8 @@
"spinner": "À procura de dispositivos ZHA Zigbee..." "spinner": "À procura de dispositivos ZHA Zigbee..."
}, },
"add": { "add": {
"caption": "Adicionar Dispositivos" "caption": "Adicionar Dispositivos",
"description": "Adicionar dispositivos à rede Zigbee"
}, },
"caption": "ZHA", "caption": "ZHA",
"cluster_attributes": { "cluster_attributes": {
@ -1709,8 +1714,10 @@
"caption": "Grupos", "caption": "Grupos",
"create": "Criar grupo", "create": "Criar grupo",
"create_group": "Zigbee Home Automation - Criar grupo", "create_group": "Zigbee Home Automation - Criar grupo",
"create_group_details": "Digite os detalhes necessários para criar um novo grupo zigbee",
"creating_group": "Criação de grupo", "creating_group": "Criação de grupo",
"description": "Criar e modificar grupos Zigbee", "description": "Criar e modificar grupos Zigbee",
"group_details": "Aqui estão todos os detalhes do grupo Zigbee selecionado.",
"group_id": "ID do grupo", "group_id": "ID do grupo",
"group_info": "Informações sobre o grupo", "group_info": "Informações sobre o grupo",
"group_name_placeholder": "Nome do Grupo", "group_name_placeholder": "Nome do Grupo",
@ -1718,6 +1725,7 @@
"group-header": "Zigbee Home Automation - Detalhes do grupo", "group-header": "Zigbee Home Automation - Detalhes do grupo",
"groups": "Grupos", "groups": "Grupos",
"groups-header": "Zigbee Home Automation - Gestão de Grupos", "groups-header": "Zigbee Home Automation - Gestão de Grupos",
"header": "Zigbee Home Automation - Gestão de Grupos",
"introduction": "Criar e modificar grupos zigbee", "introduction": "Criar e modificar grupos zigbee",
"manage_groups": "Gerir Grupos Zigbee", "manage_groups": "Gerir Grupos Zigbee",
"members": "Membros", "members": "Membros",
@ -1728,6 +1736,7 @@
"zha_zigbee_groups": "Grupos ZHA Zigbee" "zha_zigbee_groups": "Grupos ZHA Zigbee"
}, },
"header": "Configurar a automação residencial Zigbee", "header": "Configurar a automação residencial Zigbee",
"introduction": "Aqui é possível configurar o componente ZHA. Ainda não é possível configurar tudo a partir do IU, mas estamos a trabalhar nisso.",
"network_management": { "network_management": {
"header": "Gestão ", "header": "Gestão ",
"introduction": "Comandos que afetam toda a rede" "introduction": "Comandos que afetam toda a rede"
@ -1796,7 +1805,7 @@
"config_parameter": "Parâmetro de configuração", "config_parameter": "Parâmetro de configuração",
"config_value": "Valor de Configuração", "config_value": "Valor de Configuração",
"false": "Falso", "false": "Falso",
"header": "Configurar opçoes do nó", "header": "Opções de configuração de nó",
"seconds": "Segundos", "seconds": "Segundos",
"set_config_parameter": "Definir o Parâmetro de Configuração", "set_config_parameter": "Definir o Parâmetro de Configuração",
"set_wakeup": "Definir intervalo de acordar", "set_wakeup": "Definir intervalo de acordar",
@ -1973,7 +1982,7 @@
} }
}, },
"changed_toast": { "changed_toast": {
"message": "A configuração do Lovelace foi atualizada, gostaria de atualizar?", "message": "A configuração do Lovelace foi atualizada para este painel, gostaria de atualizar?",
"refresh": "Atualizar" "refresh": "Atualizar"
}, },
"editor": { "editor": {
@ -2147,7 +2156,7 @@
"header": "Configuração do cartão", "header": "Configuração do cartão",
"move": "Mover para Vista", "move": "Mover para Vista",
"options": "Mais opções", "options": "Mais opções",
"pick_card": "Escolha o cartão que deseja adicionar.", "pick_card": "Que cartão gostaria de adicionar?",
"pick_card_view_title": "Que cartão você gostaria de adicionar à sua vista {name}?", "pick_card_view_title": "Que cartão você gostaria de adicionar à sua vista {name}?",
"show_code_editor": "Mostrar Editor de Código", "show_code_editor": "Mostrar Editor de Código",
"show_visual_editor": "Mostrar Editor Visual", "show_visual_editor": "Mostrar Editor Visual",
@ -2185,13 +2194,14 @@
"para_no_id": "Este elemento não possui um ID. Por favor adicione um ID a este elemento em 'ui-lovelace.yaml'." "para_no_id": "Este elemento não possui um ID. Por favor adicione um ID a este elemento em 'ui-lovelace.yaml'."
}, },
"raw_editor": { "raw_editor": {
"confirm_remove_config_text": "Iremos gerar automaticamente as suas vistas do Lovelace UI com as suas áreas e dispositivos se você remover a sua configuração do Lovelace UI.",
"confirm_unsaved_changes": "Existem alterações não guardadas. De certeza de que quer sair?", "confirm_unsaved_changes": "Existem alterações não guardadas. De certeza de que quer sair?",
"confirm_unsaved_comments": "A sua configuração contém comentário(s), eles não serão salvos. Deseja continuar?", "confirm_unsaved_comments": "A sua configuração contém comentário(s), eles não serão salvos. Deseja continuar?",
"error_invalid_config": "A sua configuração não é válida: {error}", "error_invalid_config": "A sua configuração não é válida: {error}",
"error_parse_yaml": "Não foi possível analisar o YAML: {error}", "error_parse_yaml": "Não foi possível analisar o YAML: {error}",
"error_remove": "Não foi possível remover a configuração: {error}", "error_remove": "Não foi possível remover a configuração: {error}",
"error_save_yaml": "Não é possível salvar o YAML: {error}", "error_save_yaml": "Não é possível salvar o YAML: {error}",
"header": "Editar configuração", "header": "Editar Configuração",
"resources_moved": "Os recursos não devem mais ser adicionados ao editor de configuração do código fonte do Lovelace, mas podem ser adicionados no painel de configuração do Lovelace.", "resources_moved": "Os recursos não devem mais ser adicionados ao editor de configuração do código fonte do Lovelace, mas podem ser adicionados no painel de configuração do Lovelace.",
"save": "Guardar", "save": "Guardar",
"saved": "Guardada", "saved": "Guardada",
@ -2229,7 +2239,7 @@
"refresh": "Atualizar", "refresh": "Atualizar",
"reload_resources": "Recarregar recursos" "reload_resources": "Recarregar recursos"
}, },
"reload_lovelace": "Recarregar Lovelace", "reload_lovelace": "Recarregar UI",
"reload_resources": { "reload_resources": {
"refresh_body": "É preciso atualizar a página para concluir o carregamento. Deseja atualizar agora?", "refresh_body": "É preciso atualizar a página para concluir o carregamento. Deseja atualizar agora?",
"refresh_header": "Deseja atualizar?" "refresh_header": "Deseja atualizar?"
@ -2392,6 +2402,7 @@
"mirror": "Espelho", "mirror": "Espelho",
"patio": "Pátio", "patio": "Pátio",
"right": "Direita", "right": "Direita",
"temperature_study": "Estudo da Temperatura",
"upstairs": "Andar de cima" "upstairs": "Andar de cima"
}, },
"unit": { "unit": {

View File

@ -330,8 +330,8 @@
}, },
"ui": { "ui": {
"auth_store": { "auth_store": {
"ask": "Doriți să salvați aceste date de conectare?", "ask": "Vrei să rămâi autentificat?",
"confirm": "Salvați datele de conectare", "confirm": "Da",
"decline": "Nu" "decline": "Nu"
}, },
"card": { "card": {
@ -459,17 +459,20 @@
} }
}, },
"common": { "common": {
"and": "Și",
"cancel": "Revocare", "cancel": "Revocare",
"close": "Închide", "close": "Închide",
"delete": "Șterge", "delete": "Șterge",
"loading": "Se încarcă", "loading": "Se încarcă",
"next": "Următorul", "next": "Următorul",
"no": "Nu",
"previous": "Anterior", "previous": "Anterior",
"refresh": "Reîmprospătare", "refresh": "Reîmprospătare",
"save": "Salvați", "save": "Salvați",
"successfully_deleted": "Șters cu succes", "successfully_deleted": "Șters cu succes",
"successfully_saved": "Opțiunile salvate cu succes.", "successfully_saved": "Opțiunile salvate cu succes.",
"undo": "Undo" "undo": "Undo",
"yes": "Da"
}, },
"components": { "components": {
"area-picker": { "area-picker": {
@ -536,12 +539,15 @@
"title": "Opțiuni de sistem pentru {integration}", "title": "Opțiuni de sistem pentru {integration}",
"update": "Actualizare" "update": "Actualizare"
}, },
"domain_toggler": {
"title": "Comutare domenii"
},
"entity_registry": { "entity_registry": {
"control": "Control", "control": "Control",
"dismiss": "Ascunde", "dismiss": "Ascunde",
"editor": { "editor": {
"confirm_delete": "Sigur dorești să ștergi această intrare?", "confirm_delete": "Sigur dorești să ștergi această intrare?",
"delete": TERGE", "delete": terge",
"enabled_cause": "Dezactivat de către {cause}.", "enabled_cause": "Dezactivat de către {cause}.",
"enabled_description": "Entitățile dezactivate nu vof fi adăugate in Home Assistant", "enabled_description": "Entitățile dezactivate nu vof fi adăugate in Home Assistant",
"enabled_label": "Activează entitatea", "enabled_label": "Activează entitatea",
@ -550,7 +556,7 @@
"name": "Suprascriere nume", "name": "Suprascriere nume",
"note": "Notă: este posibil să nu funcționeze încă cu toate integrările.", "note": "Notă: este posibil să nu funcționeze încă cu toate integrările.",
"unavailable": "Această entitate nu este disponibilă momentan.", "unavailable": "Această entitate nu este disponibilă momentan.",
"update": "ACTUALIZARE" "update": "Actualizare"
}, },
"no_unique_id": "Această entitate nu are un ID unic, prin urmare, setările sale nu pot fi gestionate de la interfața cu utilizatorul.", "no_unique_id": "Această entitate nu are un ID unic, prin urmare, setările sale nu pot fi gestionate de la interfața cu utilizatorul.",
"related": "În legătură cu", "related": "În legătură cu",
@ -574,6 +580,7 @@
"time": "Timp" "time": "Timp"
}, },
"input_number": { "input_number": {
"box": "Introdu textul",
"max": "Valoare maximă", "max": "Valoare maximă",
"min": "Valoare minimă", "min": "Valoare minimă",
"mode": "Mod de afișare", "mode": "Mod de afișare",
@ -592,9 +599,12 @@
"min": "Lungime minimă", "min": "Lungime minimă",
"mode": "Mod de afișare", "mode": "Mod de afișare",
"password": "Parola", "password": "Parola",
"pattern": "Modelul Regex pentru validarea de partea clientului",
"text": "Text" "text": "Text"
}, },
"required_error_msg": "Acest câmp este obligatoriu" "platform_not_loaded": "Integrarea {platform} nu este încărcată. Vă rugăm să-i adăugați configurația dvs. fie adăugând 'default_config:' sau '{platform}:'.",
"required_error_msg": "Acest câmp este obligatoriu",
"yaml_not_editable": "Setările acestei entități nu se pot edita din interfața grafica. Numai entitățile configurate in interfața grafica sunt configurabile din interfața grafica."
}, },
"more_info_control": { "more_info_control": {
"dismiss": "Se respinge dialogul", "dismiss": "Se respinge dialogul",
@ -653,6 +663,10 @@
} }
}, },
"voice_command": { "voice_command": {
"did_not_hear": "Home Assistant nu a auzit nimic",
"error": "Oops, a apărut o eroare",
"found": "Am găsit următoarele pentru tine:",
"how_can_i_help": "Cu ce vă pot ajuta?",
"label": "Scrieți o întrebare și apăsați „Enter”", "label": "Scrieți o întrebare și apăsați „Enter”",
"label_voice": "Tastați și apăsați „Enter” sau atingeți microfonul pentru a vorbi" "label_voice": "Tastați și apăsați „Enter” sau atingeți microfonul pentru a vorbi"
}, },
@ -723,11 +737,12 @@
}, },
"description": "Privire de ansamblu asupra tuturor zonelor din casa ta.", "description": "Privire de ansamblu asupra tuturor zonelor din casa ta.",
"editor": { "editor": {
"create": "CREAȚI", "create": "Creează",
"delete": TERGEȚI", "delete": terge",
"update": "ACTUALIZAȚI" "update": "Actualizare"
}, },
"picker": { "picker": {
"create_area": "Creare zonă",
"header": "Zone", "header": "Zone",
"integrations_page": "Pagina de integrari", "integrations_page": "Pagina de integrari",
"introduction": "Zonele sunt folosite pentru a organiza unde sunt dispozitivele. Aceste informații vor fi utilizate de către Home Assistant pentru a vă ajuta să vă organizați interfața, permisiunile și integrarea cu alte sisteme.", "introduction": "Zonele sunt folosite pentru a organiza unde sunt dispozitivele. Aceste informații vor fi utilizate de către Home Assistant pentru a vă ajuta să vă organizați interfața, permisiunile și integrarea cu alte sisteme.",
@ -851,7 +866,7 @@
"triggers": { "triggers": {
"add": "Adăugați acțiune", "add": "Adăugați acțiune",
"delete": "Ștergeți", "delete": "Ștergeți",
"delete_confirm": "Sigur doriți să ștergeți?", "delete_confirm": "Sigur dorești să ștergi această intrare?",
"duplicate": "Dublura", "duplicate": "Dublura",
"header": "Declanșatoare", "header": "Declanșatoare",
"introduction": "Declanșatoarele sunt cele ce încep procesarea unei reguli de automatizare. Este posibil să specificați mai multe declanșatoare pentru aceeași regulă. Odată ce începe declanșarea, Home Assistant o să valideze condițiile, dacă este cazul, și va apela acțiunea. \n\n [Aflați mai multe despre declanșatoare.] (https://home-assistant.io/docs/automation/trigger/)", "introduction": "Declanșatoarele sunt cele ce încep procesarea unei reguli de automatizare. Este posibil să specificați mai multe declanșatoare pentru aceeași regulă. Odată ce începe declanșarea, Home Assistant o să valideze condițiile, dacă este cazul, și va apela acțiunea. \n\n [Aflați mai multe despre declanșatoare.] (https://home-assistant.io/docs/automation/trigger/)",
@ -994,14 +1009,22 @@
} }
}, },
"customize": { "customize": {
"attributes_customize": "Următoarele atribute sunt deja setate în customize.yaml",
"attributes_not_set": "Următoarele atribute nu au fost setate. Setați-le dacă doriți.", "attributes_not_set": "Următoarele atribute nu au fost setate. Setați-le dacă doriți.",
"attributes_outside": "Următoarele atribute sunt personalizate din afara customize.yaml",
"attributes_override": "Poți să le suprascrii dacă vrei.", "attributes_override": "Poți să le suprascrii dacă vrei.",
"attributes_set": "Următoarele atribute ale entității sunt setate programatic.",
"caption": "Personalizare", "caption": "Personalizare",
"description": "Personalizați-vă entitățile", "description": "Personalizați-vă entitățile",
"different_include": "Posibil printr-un domeniu, un glob sau alta includere",
"pick_attribute": "Alegeți un atribut pentru suprascriere", "pick_attribute": "Alegeți un atribut pentru suprascriere",
"picker": { "picker": {
"header": "Personalizări", "header": "Personalizări",
"introduction": "Atributele per entitate. Particularizările adăugate/editate vor avea efect imediat. Particularizările eliminate vor avea efect atunci când entitatea este actualizată." "introduction": "Atributele per entitate. Particularizările adăugate/editate vor avea efect imediat. Particularizările eliminate vor avea efect atunci când entitatea este actualizată."
},
"warning": {
"include_link": "include customize.yaml",
"include_sentence": "Se pare că configurația dvs..yaml nu corespunde"
} }
}, },
"devices": { "devices": {
@ -1172,7 +1195,7 @@
"ignore": { "ignore": {
"confirm_delete_ignore": "Acest lucru va face ca integrarea să apară din nou în integrările descoperite atunci când este descoperită. Acest lucru ar putea necesita o repornire sau să dureze ceva timp.", "confirm_delete_ignore": "Acest lucru va face ca integrarea să apară din nou în integrările descoperite atunci când este descoperită. Acest lucru ar putea necesita o repornire sau să dureze ceva timp.",
"confirm_delete_ignore_title": "Încetați să ignorați {name} ?", "confirm_delete_ignore_title": "Încetați să ignorați {name} ?",
"confirm_ignore": "Sunteți sigur că nu doriți să configurați această integrare? Puteți anula acest lucru făcând clic pe „Afișați integrări ignorate” din meniul de preaplin din partea dreaptă sus.", "confirm_ignore": "Sunteți sigur că nu doriți să configurați această integrare? Puteți anula acest lucru făcând clic pe „Afișați integrări ignorate” din meniul overflow din partea dreaptă sus.",
"confirm_ignore_title": "Ignorați descoperirea {name} ?", "confirm_ignore_title": "Ignorați descoperirea {name} ?",
"hide_ignored": "Ascundeți integrările ignorate", "hide_ignored": "Ascundeți integrările ignorate",
"ignore": "Ignora", "ignore": "Ignora",
@ -1194,6 +1217,7 @@
"lovelace": { "lovelace": {
"caption": "Panouri de bord Lovelace", "caption": "Panouri de bord Lovelace",
"dashboards": { "dashboards": {
"cant_edit_default": "Tabloul de bord standard Lovelace nu poate fi editat din UI. Îl puteți ascunde setând un alt tablou de bord ca implicit.",
"cant_edit_yaml": "Tablourile de bord definite în YAML nu pot fi editate din UI. Schimbați-le în configuration.yaml.", "cant_edit_yaml": "Tablourile de bord definite în YAML nu pot fi editate din UI. Schimbați-le în configuration.yaml.",
"caption": "Tablouri de bord", "caption": "Tablouri de bord",
"conf_mode": { "conf_mode": {
@ -1207,13 +1231,13 @@
"delete": "Șterge", "delete": "Șterge",
"dismiss": "Închide", "dismiss": "Închide",
"edit_dashboard": "Editează tabloul de bord", "edit_dashboard": "Editează tabloul de bord",
"icon": "Pictograma barei laterale", "icon": "Pictograma",
"new_dashboard": "Adăugați un nou tablou de bord", "new_dashboard": "Adăugați un nou tablou de bord",
"remove_default": "Eliminare ca implicit pe acest dispozitiv", "remove_default": "Eliminare ca implicit pe acest dispozitiv",
"require_admin": "Doar administrator", "require_admin": "Doar administrator",
"set_default": "Setare ca implicită pe acest dispozitiv", "set_default": "Setare ca implicită pe acest dispozitiv",
"show_sidebar": "Afișați în bara laterală", "show_sidebar": "Afișați în bara laterală",
"title": "Titlul barei laterale", "title": "Titlu",
"title_required": "Titlul este necesar.", "title_required": "Titlul este necesar.",
"update": "Actualizare", "update": "Actualizare",
"url": "Url", "url": "Url",
@ -1229,7 +1253,7 @@
"sidebar": "Afișați în bara laterală", "sidebar": "Afișați în bara laterală",
"title": "Titlu" "title": "Titlu"
}, },
"open": "Deschide tabloul de bord" "open": "Deschide"
} }
}, },
"resources": { "resources": {
@ -1313,6 +1337,7 @@
}, },
"learn_more": "Aflați mai multe despre scene", "learn_more": "Aflați mai multe despre scene",
"no_scenes": "Nu am putut găsi nici o scenă editabilă", "no_scenes": "Nu am putut găsi nici o scenă editabilă",
"only_editable": "Numai scenele definite în scene.yaml sunt editabile.",
"pick_scene": "Alege scena pentru a edita", "pick_scene": "Alege scena pentru a edita",
"show_info_scene": "Afișează informații despre scenă" "show_info_scene": "Afișează informații despre scenă"
} }
@ -1547,7 +1572,13 @@
"developer-tools": { "developer-tools": {
"tabs": { "tabs": {
"events": { "events": {
"alert_event_type": "Tipul de eveniment este un câmp obligatoriu",
"available_events": "Evenimente disponibile", "available_events": "Evenimente disponibile",
"fire_event": "Declansare eveniment",
"listening_to": "Ascultand",
"start_listening": "Incepe sa asculti",
"stop_listening": "Nu mai asculta",
"subscribe_to": "Eveniment la care să vă abonați",
"title": "Evenimente" "title": "Evenimente"
}, },
"info": { "info": {
@ -1703,6 +1734,9 @@
"history-graph": { "history-graph": {
"description": "Fișa History Graph vă permite să afișați un grafic pentru fiecare dintre entitățile listate." "description": "Fișa History Graph vă permite să afișați un grafic pentru fiecare dintre entitățile listate."
}, },
"iframe": {
"name": "Pagină web"
},
"light": { "light": {
"description": "Cardul Light vă permite să schimbați luminozitatea luminii." "description": "Cardul Light vă permite să schimbați luminozitatea luminii."
}, },
@ -1835,6 +1869,7 @@
"title": "Entități neutilizate" "title": "Entități neutilizate"
}, },
"views": { "views": {
"confirm_delete": "Ștergeți vizualizarea?",
"confirm_delete_existing_cards": "Ștergerea acestei vizualizări va elimina și cardurile", "confirm_delete_existing_cards": "Ștergerea acestei vizualizări va elimina și cardurile",
"confirm_delete_text": "Sigur doriți să ștergeți vizualizarea „{name}”?" "confirm_delete_text": "Sigur doriți să ștergeți vizualizarea „{name}”?"
}, },

View File

@ -473,11 +473,15 @@
} }
}, },
"common": { "common": {
"and": "och",
"cancel": "Avbryt", "cancel": "Avbryt",
"close": "Stäng", "close": "Stäng",
"delete": "Radera", "delete": "Radera",
"loading": "Läser in", "loading": "Läser in",
"next": "Nästa",
"no": "Nej", "no": "Nej",
"previous": "Föregående",
"refresh": "Uppdatera",
"save": "Spara", "save": "Spara",
"successfully_deleted": "Har raderats", "successfully_deleted": "Har raderats",
"successfully_saved": "Inställningar sparades", "successfully_saved": "Inställningar sparades",
@ -736,6 +740,10 @@
"triggered": "Utlöst {name}" "triggered": "Utlöst {name}"
}, },
"panel": { "panel": {
"calendar": {
"my_calendars": "Mina Kalendrar",
"today": "Idag"
},
"config": { "config": {
"advanced_mode": { "advanced_mode": {
"hint_enable": "Saknas konfigurationsalternativ? Aktivera avancerat läge på", "hint_enable": "Saknas konfigurationsalternativ? Aktivera avancerat läge på",
@ -838,6 +846,9 @@
}, },
"label": "Enhet" "label": "Enhet"
}, },
"not": {
"label": "Inte"
},
"numeric_state": { "numeric_state": {
"above": "Över", "above": "Över",
"below": "Under", "below": "Under",
@ -1169,7 +1180,9 @@
"edit_requires_storage": "Redigeraren är inaktiverad eftersom konfigurationen lagras i configuration.yaml.", "edit_requires_storage": "Redigeraren är inaktiverad eftersom konfigurationen lagras i configuration.yaml.",
"elevation": "Höjd över havet", "elevation": "Höjd över havet",
"elevation_meters": "meter", "elevation_meters": "meter",
"external_url": "Extern URL",
"imperial_example": "Fahrenheit, pounds", "imperial_example": "Fahrenheit, pounds",
"internal_url": "Intern URL",
"latitude": "Latitud", "latitude": "Latitud",
"location_name": "Namn på din Home Assistant-installation", "location_name": "Namn på din Home Assistant-installation",
"longitude": "Longitud", "longitude": "Longitud",
@ -1236,6 +1249,7 @@
}, },
"delete": "Ta bort", "delete": "Ta bort",
"description": "Hantera anslutna enheter", "description": "Hantera anslutna enheter",
"device_info": "Enhetsinformation",
"device_not_found": "Enheten hittades inte.", "device_not_found": "Enheten hittades inte.",
"entities": { "entities": {
"add_entities_lovelace": "Lägg till i Lovelace", "add_entities_lovelace": "Lägg till i Lovelace",
@ -2095,21 +2109,27 @@
"name": "Markdown" "name": "Markdown"
}, },
"media-control": { "media-control": {
"description": "Mediakontrolkortet används för att visa mediaspelarenheter på ett gränssnitt med lättanvända kontroller.",
"name": "Mediaspelare" "name": "Mediaspelare"
}, },
"picture-elements": { "picture-elements": {
"description": "Bildlementkortet är en av de mest mångsidiga typerna av kort. Korten låter dig placera ikoner eller text och till och med tjänster! På en bild baserad på koordinater.",
"name": "Bildelement" "name": "Bildelement"
}, },
"picture-entity": { "picture-entity": {
"description": "Bildentitetskortet visar en enhet i form av en bild. I stället för bilder från URL kan den också visa bilden av kameraenheter.",
"name": "Bildentitet" "name": "Bildentitet"
}, },
"picture-glance": { "picture-glance": {
"description": "Picture Glance-kortet visar en bild och motsvarande enhetstillstånd som en ikon. Enheterna på höger sida tillåter att växla åtgärder, andra visar dialogrutan för mer information.",
"name": "Bildblick" "name": "Bildblick"
}, },
"picture": { "picture": {
"description": "Bildkortet låter dig ställa in en bild som ska användas för att navigera till olika banor i ditt gränssnitt eller för att ringa en tjänst.",
"name": "Bild" "name": "Bild"
}, },
"plant-status": { "plant-status": {
"description": "Plant Status-kortet är för alla de härliga botanikerna där ute.",
"name": "Växtstatus" "name": "Växtstatus"
}, },
"sensor": { "sensor": {
@ -2125,6 +2145,7 @@
"name": "Termostat" "name": "Termostat"
}, },
"vertical-stack": { "vertical-stack": {
"description": "Med Vertical Stack-kortet kan du gruppera flera kort så att de alltid sitter i samma kolumn.",
"name": "Vertikal trave" "name": "Vertikal trave"
}, },
"weather-forecast": { "weather-forecast": {

View File

@ -473,6 +473,7 @@
} }
}, },
"common": { "common": {
"and": "及",
"cancel": "取消", "cancel": "取消",
"close": "關閉", "close": "關閉",
"delete": "刪除", "delete": "刪除",
@ -1180,7 +1181,9 @@
"edit_requires_storage": "由於 configuration.yaml 內已儲存設定,編輯功能已關閉。", "edit_requires_storage": "由於 configuration.yaml 內已儲存設定,編輯功能已關閉。",
"elevation": "海拔", "elevation": "海拔",
"elevation_meters": "公尺", "elevation_meters": "公尺",
"external_url": "外部 URL",
"imperial_example": "華氏、磅", "imperial_example": "華氏、磅",
"internal_url": "內部 URL",
"latitude": "緯度", "latitude": "緯度",
"location_name": "Home Assistant 安裝名稱", "location_name": "Home Assistant 安裝名稱",
"longitude": "經度", "longitude": "經度",

View File

@ -2613,10 +2613,10 @@
resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5" resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5"
integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ== integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==
"@thomasloven/round-slider@0.3.7": "@thomasloven/round-slider@0.4.1":
version "0.3.7" version "0.4.1"
resolved "https://registry.yarnpkg.com/@thomasloven/round-slider/-/round-slider-0.3.7.tgz#3f8f16f90296e1062d932f5ea8ebf244aa7e58f6" resolved "https://registry.yarnpkg.com/@thomasloven/round-slider/-/round-slider-0.4.1.tgz#42ddd28abb25c378dce35c4c0ccdd72ea63b3b25"
integrity sha512-rIdEvyLt4YNahpAp1Ibk7qOn9mdgP3Qo2gORyojHqaBTV+t29N1zlTo/G0SbKTLDUtSGDslQWD3/nAdD3yBOYA== integrity sha512-Z6jrXG5vowKQkOwdsyGDLi8ZT9lUfcYjFsaQe8djhDE8+x41GYp5lkJ4uCwT787A8WcODbtQfYtuxPOlZcizTw==
dependencies: dependencies:
lit-element "^2.2.1" lit-element "^2.2.1"