Merge pull request #5848 from home-assistant/dev

This commit is contained in:
Bram Kragten 2020-05-13 13:14:23 +02:00 committed by GitHub
commit 51e7aaa805
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 980 additions and 342 deletions

View File

@ -20,7 +20,7 @@ gulp.task(
},
"clean",
gulp.parallel(
"gen-service-worker-dev",
"gen-service-worker-app-dev",
"gen-icons-json",
"gen-pages-dev",
"gen-index-app-dev",
@ -46,7 +46,7 @@ gulp.task(
gulp.parallel(
"gen-pages-prod",
"gen-index-app-prod",
"gen-service-worker-prod"
"gen-service-worker-app-prod"
)
)
);

View File

@ -5,13 +5,15 @@
const gulp = require("gulp");
const path = require("path");
const fs = require("fs-extra");
const config = require("../paths.js");
const workboxBuild = require("workbox-build");
const sourceMapUrl = require("source-map-url");
const paths = require("../paths.js");
const swPath = path.resolve(config.root, "service_worker.js");
const swDest = path.resolve(paths.root, "service_worker.js");
const writeSW = (content) => fs.outputFileSync(swPath, content.trim() + "\n");
const writeSW = (content) => fs.outputFileSync(swDest, content.trim() + "\n");
gulp.task("gen-service-worker-dev", (done) => {
gulp.task("gen-service-worker-app-dev", (done) => {
writeSW(
`
console.debug('Service worker disabled in development');
@ -24,10 +26,58 @@ self.addEventListener('install', (event) => {
done();
});
gulp.task("gen-service-worker-prod", (done) => {
fs.copySync(
path.resolve(config.output, "service_worker.js"),
path.resolve(config.root, "service_worker.js")
gulp.task("gen-service-worker-app-prod", async () => {
const workboxManifest = await workboxBuild.getManifest({
// Files that mach this pattern will be considered unique and skip revision check
// ignore JS files + translation files
dontCacheBustURLsMatching: /(frontend_latest\/.+|static\/translations\/.+)/,
globDirectory: paths.root,
globPatterns: [
"frontend_latest/*.js",
// Cache all English translations because we catch them as fallback
// Using pattern to match hash instead of * to avoid caching en-GB
"static/translations/**/en-+([a-f0-9]).json",
// Icon shown on splash screen
"static/icons/favicon-192x192.png",
"static/icons/favicon.ico",
// Common fonts
"static/fonts/roboto/Roboto-Light.woff2",
"static/fonts/roboto/Roboto-Medium.woff2",
"static/fonts/roboto/Roboto-Regular.woff2",
"static/fonts/roboto/Roboto-Bold.woff2",
],
});
for (const warning of workboxManifest.warnings) {
console.warn(warning);
}
// Replace `null` with 0 for better compression
for (const entry of workboxManifest.manifestEntries) {
if (entry.revision === null) {
entry.revision = 0;
}
}
const manifest = require(path.resolve(paths.output, "manifest.json"));
// Write bundled source file
let serviceWorkerContent = fs.readFileSync(
paths.root + manifest["service_worker.js"],
"utf-8"
);
done();
// remove source map and add WB manifest
serviceWorkerContent = sourceMapUrl.removeFrom(serviceWorkerContent);
serviceWorkerContent = serviceWorkerContent.replace(
"WB_MANIFEST",
JSON.stringify(workboxManifest.manifestEntries)
);
// Write new file to root
fs.writeFileSync(swDest, serviceWorkerContent);
// Delete old file from frontend_latest
fs.removeSync(paths.root + manifest["service_worker.js"]);
fs.removeSync(paths.root + manifest["service_worker.js.map"]);
});

View File

@ -1,7 +1,6 @@
const webpack = require("webpack");
const path = require("path");
const TerserPlugin = require("terser-webpack-plugin");
const WorkboxPlugin = require("workbox-webpack-plugin");
const ManifestPlugin = require("webpack-manifest-plugin");
const paths = require("./paths.js");
const env = require("./env.js");
@ -107,8 +106,9 @@ const createWebpackConfig = ({
};
const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => {
const config = createWebpackConfig({
return createWebpackConfig({
entry: {
service_worker: "./src/entrypoints/service_worker.ts",
app: "./src/entrypoints/app.ts",
authorize: "./src/entrypoints/authorize.ts",
onboarding: "./src/entrypoints/onboarding.ts",
@ -121,48 +121,6 @@ const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => {
latestBuild,
isStatsBuild,
});
if (latestBuild) {
// Create an object mapping browser urls to their paths during build
const translationMetadata = require("../build-translations/translationMetadata.json");
const workBoxTranslationsTemplatedURLs = {};
const englishFilename = `en-${translationMetadata.translations.en.hash}.json`;
// core
workBoxTranslationsTemplatedURLs[
`/static/translations/${englishFilename}`
] = `build-translations/output/${englishFilename}`;
translationMetadata.fragments.forEach((fragment) => {
workBoxTranslationsTemplatedURLs[
`/static/translations/${fragment}/${englishFilename}`
] = `build-translations/output/${fragment}/${englishFilename}`;
});
config.plugins.push(
new WorkboxPlugin.InjectManifest({
swSrc: "./src/entrypoints/service-worker-hass.js",
swDest: "service_worker.js",
importWorkboxFrom: "local",
include: [/\.js$/],
templatedURLs: {
...workBoxTranslationsTemplatedURLs,
"/static/icons/favicon-192x192.png":
"public/icons/favicon-192x192.png",
"/static/fonts/roboto/Roboto-Light.woff2":
"node_modules/roboto-fontface/fonts/roboto/Roboto-Light.woff2",
"/static/fonts/roboto/Roboto-Medium.woff2":
"node_modules/roboto-fontface/fonts/roboto/Roboto-Medium.woff2",
"/static/fonts/roboto/Roboto-Regular.woff2":
"node_modules/roboto-fontface/fonts/roboto/Roboto-Regular.woff2",
"/static/fonts/roboto/Roboto-Bold.woff2":
"node_modules/roboto-fontface/fonts/roboto/Roboto-Bold.woff2",
},
})
);
}
return config;
};
const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => {

View File

@ -114,6 +114,10 @@
"tslib": "^1.10.0",
"unfetch": "^4.1.0",
"web-animations-js": "^2.3.2",
"workbox-core": "^5.1.3",
"workbox-precaching": "^5.1.3",
"workbox-routing": "^5.1.3",
"workbox-strategies": "^5.1.3",
"xss": "^1.0.6"
},
"devDependencies": {
@ -177,6 +181,7 @@
"reify": "^0.18.1",
"require-dir": "^1.2.0",
"sinon": "^7.3.1",
"source-map-url": "^0.4.0",
"terser-webpack-plugin": "^1.2.3",
"ts-lit-plugin": "^1.1.10",
"ts-mocha": "^6.0.0",
@ -188,7 +193,7 @@
"webpack-cli": "^3.3.9",
"webpack-dev-server": "^3.10.3",
"webpack-manifest-plugin": "^2.0.4",
"workbox-webpack-plugin": "^4.1.1",
"workbox-build": "^5.1.3",
"workerize-loader": "^1.1.0"
},
"_comment": "Polymer fixed to 3.1 because 3.2 throws on logbook page",

View File

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

View File

@ -1,4 +1,5 @@
import type { HassEntity } from "home-assistant-js-websocket";
import { styleMap } from "lit-html/directives/style-map";
import {
css,
CSSResult,
@ -6,7 +7,6 @@ import {
LitElement,
property,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { ifDefined } from "lit-html/directives/if-defined";
@ -16,7 +16,6 @@ import { stateIcon } from "../../common/entity/state_icon";
import { iconColorCSS } from "../../common/style/icon_color_css";
import type { HomeAssistant } from "../../types";
import "../ha-icon";
import type { HaIcon } from "../ha-icon";
export class StateBadge extends LitElement {
public hass?: HomeAssistant;
@ -29,12 +28,15 @@ export class StateBadge extends LitElement {
@property({ type: Boolean }) public stateColor?: boolean;
@query("ha-icon") private _icon!: HaIcon;
@property({ type: Boolean, reflect: true, attribute: "icon" })
private _showIcon = true;
@property() private _iconStyle: { [name: string]: string } = {};
protected render(): TemplateResult {
const stateObj = this.stateObj;
if (!stateObj) {
if (!stateObj || !this._showIcon) {
return html``;
}
@ -42,7 +44,7 @@ export class StateBadge extends LitElement {
return html`
<ha-icon
id="icon"
style=${styleMap(this._iconStyle)}
data-domain=${ifDefined(
this.stateColor || (domain === "light" && this.stateColor !== false)
? domain
@ -60,14 +62,13 @@ export class StateBadge extends LitElement {
}
const stateObj = this.stateObj;
const iconStyle: Partial<CSSStyleDeclaration> = {
color: "",
filter: "",
display: "",
};
const iconStyle: { [name: string]: string } = {};
const hostStyle: Partial<CSSStyleDeclaration> = {
backgroundImage: "",
};
this._showIcon = true;
if (stateObj) {
// hide icon if we have entity picture
if (
@ -79,7 +80,7 @@ export class StateBadge extends LitElement {
imageUrl = this.hass.hassUrl(imageUrl);
}
hostStyle.backgroundImage = `url(${imageUrl})`;
iconStyle.display = "none";
this._showIcon = false;
} else if (stateObj.state === "on") {
if (stateObj.attributes.hs_color && this.stateColor !== false) {
const hue = stateObj.attributes.hs_color[0];
@ -102,7 +103,7 @@ export class StateBadge extends LitElement {
}
}
}
Object.assign(this._icon.style, iconStyle);
this._iconStyle = iconStyle;
Object.assign(this.style, hostStyle);
}
@ -119,8 +120,17 @@ export class StateBadge extends LitElement {
background-size: cover;
line-height: 40px;
vertical-align: middle;
box-sizing: border-box;
}
:host(:focus) {
outline: none;
}
:host(:not([icon]):focus) {
border: 2px solid var(--divider-color);
}
:host([icon]:focus) {
background: var(--divider-color);
}
ha-icon {
transition: color 0.3s ease-in-out, filter 0.3s ease-in-out;
}

View File

@ -1,40 +1,51 @@
/*
This file is not run through webpack, but instead is directly manipulated
by Workbox Webpack plugin. So we cannot use __DEV__ or other constants.
*/
/* global workbox clients */
/* eslint-disable @typescript-eslint/triple-slash-reference */
// eslint-disable-next-line spaced-comment
/// <reference path="../types/service-worker.d.ts" />
/* eslint-env serviceworker */
import {
CacheFirst,
StaleWhileRevalidate,
NetworkOnly,
} from "workbox-strategies";
import { cleanupOutdatedCaches, precacheAndRoute } from "workbox-precaching";
import { registerRoute } from "workbox-routing";
import { cacheNames } from "workbox-core";
// Clean up caches from older workboxes and old service workers.
// Will help with cleaning up Workbox v4 stuff
cleanupOutdatedCaches();
function initRouting() {
workbox.precaching.precacheAndRoute(self.__precacheManifest || []);
precacheAndRoute(
// @ts-ignore
WB_MANIFEST
);
// Cache static content (including translations) on first access.
workbox.routing.registerRoute(
registerRoute(
new RegExp(`${location.host}/(static|frontend_latest|frontend_es5)/.+`),
new workbox.strategies.CacheFirst()
new CacheFirst()
);
// Get api from network.
workbox.routing.registerRoute(
registerRoute(
new RegExp(`${location.host}/(api|auth)/.*`),
new workbox.strategies.NetworkOnly()
new NetworkOnly()
);
// Get manifest, service worker, onboarding from network.
workbox.routing.registerRoute(
registerRoute(
new RegExp(
`${location.host}/(service_worker.js|manifest.json|onboarding.html)`
),
new workbox.strategies.NetworkOnly()
new NetworkOnly()
);
// For rest of the files (on Home Assistant domain only) try both cache and network.
// This includes the root "/" or "/states" response and user files from "/local".
// First access might bring stale data from cache, but a single refresh will bring updated
// file.
workbox.routing.registerRoute(
new RegExp(`${location.host}/.*`),
new workbox.strategies.StaleWhileRevalidate()
);
registerRoute(new RegExp(`${location.host}/.*`), new StaleWhileRevalidate());
}
function initPushNotifications() {
@ -149,7 +160,7 @@ function initPushNotifications() {
self.addEventListener("install", (event) => {
// Delete all runtime caching, so that index.html has to be refetched.
const cacheName = workbox.core.cacheNames.runtime;
const cacheName = cacheNames.runtime;
event.waitUntil(caches.delete(cacheName));
});
@ -160,9 +171,5 @@ self.addEventListener("message", (message) => {
}
});
workbox.setConfig({
debug: false,
});
initRouting();
initPushNotifications();

View File

@ -18,7 +18,6 @@ import "../../../components/ha-card";
import "../../../components/ha-icon";
import { UNAVAILABLE_STATES } from "../../../data/entity";
import { HomeAssistant } from "../../../types";
import { actionHandler } from "../common/directives/action-handler-directive";
import { findEntities } from "../common/find-entites";
import { hasConfigOrEntityChanged } from "../common/has-changed";
import "../components/hui-warning";
@ -108,46 +107,40 @@ export class HuiEntityCard extends LitElement implements LovelaceCard {
: !UNAVAILABLE_STATES.includes(stateObj.state);
return html`
<ha-card>
<div
@action=${this._handleClick}
.actionHandler=${actionHandler()}
tabindex="0"
>
<div class="header">
<div class="name">
${this._config.name || computeStateName(stateObj)}
</div>
<div class="icon">
<ha-icon
.icon=${this._config.icon || stateIcon(stateObj)}
></ha-icon>
</div>
<ha-card @click=${this._handleClick} tabindex="0">
<div class="header">
<div class="name">
${this._config.name || computeStateName(stateObj)}
</div>
<div class="info">
<span class="value"
>${"attribute" in this._config
? stateObj.attributes[this._config.attribute!] ||
this.hass.localize("state.default.unknown")
: stateObj.attributes.unit_of_measurement
? stateObj.state
: computeStateDisplay(
this.hass.localize,
stateObj,
this.hass.language
)}</span
>${showUnit
? html`
<span class="measurement"
>${this._config.unit ||
(this._config.attribute
? ""
: stateObj.attributes.unit_of_measurement)}</span
>
`
: ""}
<div class="icon">
<ha-icon
.icon=${this._config.icon || stateIcon(stateObj)}
></ha-icon>
</div>
</div>
<div class="info">
<span class="value"
>${"attribute" in this._config
? stateObj.attributes[this._config.attribute!] ||
this.hass.localize("state.default.unknown")
: stateObj.attributes.unit_of_measurement
? stateObj.state
: computeStateDisplay(
this.hass.localize,
stateObj,
this.hass.language
)}</span
>${showUnit
? html`
<span class="measurement"
>${this._config.unit ||
(this._config.attribute
? ""
: stateObj.attributes.unit_of_measurement)}</span
>
`
: ""}
</div>
${this._footerElement}
</ha-card>
`;
@ -194,9 +187,8 @@ export class HuiEntityCard extends LitElement implements LovelaceCard {
display: flex;
flex-direction: column;
justify-content: space-between;
}
ha-card > div {
cursor: pointer;
outline: none;
}
.header {

View File

@ -327,6 +327,7 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
ha-card {
cursor: pointer;
padding: 16px;
outline: none;
}
.content {

View File

@ -184,11 +184,6 @@ class HuiGenericEntityRow extends LitElement {
state-badge {
flex: 0 0 40px;
}
state-badge:focus {
outline: none;
background: var(--divider-color);
border-radius: 100%;
}
:host([rtl]) .flex {
margin-left: 0;
margin-right: 16px;

View File

@ -13,15 +13,23 @@ import { HomeAssistant } from "../../../types";
import { hasConfigOrEntityChanged } from "../common/has-changed";
import "../components/hui-generic-entity-row";
import "../components/hui-warning";
import { EntityConfig, LovelaceRow } from "./types";
import { 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 { ActionHandlerEvent } from "../../../data/lovelace";
import { handleAction } from "../common/handle-action";
import { classMap } from "lit-html/directives/class-map";
import { EntitiesCardEntityConfig } from "../cards/types";
@customElement("hui-text-entity-row")
class HuiTextEntityRow extends LitElement implements LovelaceRow {
@property() public hass?: HomeAssistant;
@property() private _config?: EntityConfig;
@property() private _config?: EntitiesCardEntityConfig;
public setConfig(config: EntityConfig): void {
public setConfig(config: EntitiesCardEntityConfig): void {
if (!config) {
throw new Error("Configuration error");
}
@ -51,9 +59,23 @@ class HuiTextEntityRow extends LitElement implements LovelaceRow {
`;
}
const pointer =
(this._config.tap_action && this._config.tap_action.action !== "none") ||
(this._config.entity &&
!DOMAINS_HIDE_MORE_INFO.includes(computeDomain(this._config.entity)));
return html`
<hui-generic-entity-row .hass=${this.hass} .config=${this._config}>
<div class="text-content">
<div
class="text-content ${classMap({
pointer,
})}"
@action=${this._handleAction}
.actionHandler=${actionHandler({
hasHold: hasAction(this._config.hold_action),
hasDoubleClick: hasAction(this._config.double_tap_action),
})}
>
${computeStateDisplay(
this.hass!.localize,
stateObj,
@ -64,11 +86,18 @@ class HuiTextEntityRow extends LitElement implements LovelaceRow {
`;
}
private _handleAction(ev: ActionHandlerEvent) {
handleAction(this, this.hass!, this._config!, ev.detail.action);
}
static get styles(): CSSResult {
return css`
div {
text-align: right;
}
.pointer {
cursor: pointer;
}
`;
}
}

View File

@ -81,7 +81,7 @@ class HuiWeatherEntityRow extends LitElement implements LovelaceRow {
return html`
<div
class="icon-image${classMap({
class="icon-image ${classMap({
pointer,
})}"
@action=${this._handleAction}
@ -165,6 +165,12 @@ class HuiWeatherEntityRow extends LitElement implements LovelaceRow {
height: 40px;
}
.icon-image:focus {
outline: none;
background-color: var(--divider-color);
border-radius: 50%;
}
.weather-icon {
--iron-icon-width: 40px;
--iron-icon-height: 40px;

View File

@ -75,7 +75,7 @@ export class HuiGraphHeaderFooter extends LitElement
`;
}
if (this._coordinates.length < 1) {
if (!this._coordinates.length) {
return html`
<div class="container">
<div class="info">
@ -146,12 +146,13 @@ export class HuiGraphHeaderFooter extends LitElement
this._stateHistory!.push(...stateHistory[0]);
}
this._coordinates = coordinates(
this._stateHistory,
this._config!.hours_to_show!,
500,
this._config!.detail!
);
this._coordinates =
coordinates(
this._stateHistory,
this._config!.hours_to_show!,
500,
this._config!.detail!
) || [];
this._date = endTime;
this._fetching = false;

View File

@ -224,6 +224,9 @@ export class HUIView extends LitElement {
});
this._createColumns(wrappedCards);
} else {
this._cards.forEach((card) => {
(card as LovelaceCard).editMode = false;
});
this._createColumns(this._cards);
}
}

126
src/types/service-worker.d.ts vendored Normal file
View File

@ -0,0 +1,126 @@
// From https://gist.githubusercontent.com/tiernan/c18a380935e45a6d942ac1e88c5bbaf3/raw/1d103eb5882504505ccc84cbc9398ac20418ef8a/serviceworker.d.ts
/**
* Copyright (c) 2018, Tiernan Cridland
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby
* granted, provided that the above copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*
* Service Worker Typings to supplement lib.webworker.ts
* @author Tiernan Cridland
* @email tiernanc@gmail.com
* @license: ISC
*
* lib.webworker.d.ts as well as an es5+ library (es5, es2015, etc) are required.
* Recommended to be used with a triple slash directive in the files requiring the typings only.
* e.g. your-service-worker.js, register-service-worker.js
* e.g. /// <reference path="path/to/serviceworker.d.ts" />
*/
/* eslint-disable */
// Registration
interface WorkerNavigator {
readonly serviceWorker: ServiceWorkerContainer;
}
interface ServiceWorkerContainer {
readonly controller: ServiceWorker;
readonly ready: Promise<ServiceWorkerRegistration>;
oncontrollerchange:
| ((this: ServiceWorkerContainer, event: Event) => any)
| null;
onerror: ((this: ServiceWorkerContainer, event?: Event) => any) | null;
onmessage:
| ((this: ServiceWorkerContainer, event: ServiceWorkerMessageEvent) => any)
| null;
getRegistration(scope?: string): Promise<ServiceWorkerRegistration>;
getRegistrations(): Promise<ServiceWorkerRegistration[]>;
register(
url: string,
options?: ServiceWorkerRegistrationOptions
): Promise<ServiceWorkerRegistration>;
}
interface ServiceWorkerMessageEvent extends Event {
readonly data: any;
readonly lastEventId: string;
readonly origin: string;
readonly ports: ReadonlyArray<MessagePort> | null;
readonly source: ServiceWorker | MessagePort | null;
}
interface ServiceWorkerRegistrationOptions {
scope?: string;
}
// Client API
interface Client {
readonly frameType: ClientFrameType;
}
type ClientFrameType = "auxiliary" | "top-level" | "nested" | "none";
// Events
interface ActivateEvent extends ExtendableEvent {}
interface InstallEvent extends ExtendableEvent {
readonly activeWorker: ServiceWorker;
}
// Fetch API
interface Body {
readonly body: ReadableStream;
}
interface Headers {
entries(): string[][];
keys(): string[];
values(): string[];
}
interface Response extends Body {
readonly useFinalURL: boolean;
clone(): Response;
error(): Response;
redirect(): Response;
}
// Notification API
interface Notification {
readonly actions: NotificationAction[];
readonly requireInteraction: boolean;
readonly silent: boolean;
readonly tag: string;
readonly renotify: boolean;
readonly timestamp: number;
readonly title: string;
readonly vibrate: number[];
close(): void;
requestPermission(): Promise<string>;
}
interface NotificationAction {}
// ServiceWorkerGlobalScope
declare var clients: Clients;
declare var onactivate: ((event?: ActivateEvent) => any) | null;
declare var onfetch: ((event?: FetchEvent) => any) | null;
declare var oninstall: ((event?: InstallEvent) => any) | null;
declare var onnotificationclick: ((event?: NotificationEvent) => any) | null;
declare var onnotificationclose: ((event?: NotificationEvent) => any) | null;
declare var onpush: ((event?: PushEvent) => any) | null;
declare var onpushsubscriptionchange: (() => any) | null;
declare var onsync: ((event?: SyncEvent) => any) | null;
declare var registration: ServiceWorkerRegistration;
declare function skipWaiting(): void;

View File

@ -1878,10 +1878,13 @@
"built_using": "Creat utilitzant",
"custom_uis": "Interfícies d'usuari personalitzades:",
"developed_by": "Desenvolupat per un munt de gent fantàstica.",
"documentation": "Documentació",
"frontend": "frontend-ui",
"frontend_version": "Versió de la interfície web d'usuari: {version} - {type}",
"home_assistant_logo": "Logotip de Home Assistant",
"icons_by": "Icones de",
"integrations": "Integracions",
"issues": "Problemes",
"license": "Publicat amb la llicència Apache 2.0",
"path_configuration": "Ruta al fitxer configuration.yaml: {path}",
"server": "servidor",
@ -2072,6 +2075,7 @@
"name": "Nom",
"no_theme": "Sense tema",
"refresh_interval": "Interval d'actualització",
"secondary_info_attribute": "Atribut dinformació secundària",
"show_icon": "Mostra icona?",
"show_name": "Mostra nom?",
"show_state": "Mostra estat?",
@ -2162,7 +2166,8 @@
},
"weather-forecast": {
"description": "La targeta de previsió meteorològica, és molt útil per incloure en interfícies integrades en parets.",
"name": "Previsió meteorològica"
"name": "Previsió meteorològica",
"show_forecast": "Mostra la previsió"
}
},
"cardpicker": {

View File

@ -1181,9 +1181,9 @@
"edit_requires_storage": "Editor deaktiviert, da die Konfiguration in configuration.yaml gespeichert ist.",
"elevation": "Höhe",
"elevation_meters": "Meter",
"external_url": "Externe Adresse",
"external_url": "Externe URL",
"imperial_example": "Fahrenheit, Pfund",
"internal_url": "Interne Adresse",
"internal_url": "Interne URL",
"latitude": "Breitengrad",
"location_name": "Name deiner Home Assistant Installation",
"longitude": "Längengrad",
@ -1878,10 +1878,13 @@
"built_using": "Gebaut mit",
"custom_uis": "Benutzerdefinierte UIs:",
"developed_by": "Entwickelt von einem Haufen toller Leute.",
"documentation": "Dokumentation",
"frontend": "Frontend-UI",
"frontend_version": "Frontend-Version: {version} - {type}",
"home_assistant_logo": "Home Assistant-Logo",
"icons_by": "Icons von",
"integrations": "Integrationen",
"issues": "Probleme",
"license": "Veröffentlicht unter der Apache 2.0 Lizenz",
"path_configuration": "Pfad zu configuration.yaml: {path}",
"server": "Server",
@ -2072,6 +2075,7 @@
"name": "Name",
"no_theme": "Kein Theme",
"refresh_interval": "Aktualisierungsintervall",
"secondary_info_attribute": "Zweites Infoattribut",
"show_icon": "Symbol anzeigen?",
"show_name": "Namen anzeigen?",
"show_state": "Status anzeigen?",
@ -2162,7 +2166,8 @@
},
"weather-forecast": {
"description": "Die Wettervorhersagekarte zeigt das vorhergesagte Wetter an. Sehr nützlich für wandmontierte Displays.",
"name": "Wettervorhersage"
"name": "Wettervorhersage",
"show_forecast": "Wettervorhersage anzeigen"
}
},
"cardpicker": {

View File

@ -40,7 +40,7 @@
},
"preset_mode": {
"activity": "Actividad",
"away": "Fuera de casa",
"away": "Ausente",
"boost": "Impulso",
"comfort": "Confort",
"eco": "Eco",
@ -52,11 +52,11 @@
},
"state_badge": {
"alarm_control_panel": {
"armed": "Armado",
"armed_away": "Armado",
"armed_custom_bypass": "Armado",
"armed_home": "Armado",
"armed_night": "Armado",
"armed": "Armada",
"armed_away": "Armada",
"armed_custom_bypass": "Armada",
"armed_home": "Armada",
"armed_night": "Armada",
"arming": "Armando",
"disarmed": "Desarmar",
"disarming": "Desarmar",
@ -80,13 +80,13 @@
},
"state": {
"alarm_control_panel": {
"armed": "Armado",
"armed_away": "Armado fuera de casa",
"armed": "Armada",
"armed_away": "Armada ausente",
"armed_custom_bypass": "Armada Zona Específica",
"armed_home": "Armado en casa",
"armed_night": "Armado noche",
"armed_home": "Armada en casa",
"armed_night": "Armada noche",
"arming": "Armando",
"disarmed": "Desarmado",
"disarmed": "Desarmada",
"disarming": "Desarmando",
"pending": "Pendiente",
"triggered": "Disparada"
@ -311,7 +311,7 @@
"rainy": "Lluvioso",
"snowy": "Nevado",
"snowy-rainy": "Nevado, lluvioso",
"sunny": "Soleado",
"sunny": "Despejado",
"windy": "Ventoso",
"windy-variant": "Ventoso"
},
@ -336,7 +336,7 @@
},
"card": {
"alarm_control_panel": {
"arm_away": "Armar fuera de casa",
"arm_away": "Armar ausente",
"arm_custom_bypass": "Bypass personalizada",
"arm_home": "Armar en casa",
"arm_night": "Armar noche",
@ -1878,10 +1878,13 @@
"built_using": "Construido usando",
"custom_uis": "IU personalizadas:",
"developed_by": "Desarrollado por un montón de gente impresionante.",
"documentation": "Documentación",
"frontend": "interfaz de usuario",
"frontend_version": "Versión del frontend: {version} - {type}",
"home_assistant_logo": "Logotipo de Home Assistant",
"icons_by": "Iconos por",
"integrations": "Integraciones",
"issues": "Incidencias",
"license": "Publicado bajo la licencia Apache 2.0",
"path_configuration": "Ruta a configuration.yaml: {path}",
"server": "servidor",
@ -2162,7 +2165,8 @@
},
"weather-forecast": {
"description": "La tarjeta de Pronóstico del tiempo muestra el clima. Muy útil para incluir en las interfaces que las personas muestran en la pared.",
"name": "Pronóstico del tiempo"
"name": "Pronóstico del tiempo",
"show_forecast": "Mostrar pronóstico"
}
},
"cardpicker": {

View File

@ -1878,10 +1878,13 @@
"built_using": "Construit en utilisant",
"custom_uis": "Interfaces utilisateur personnalisées :",
"developed_by": "Développé par un groupe de personnes formidables.",
"documentation": "Documentation",
"frontend": "interface utilisateur",
"frontend_version": "Version interface utilisateur : {version} - {type}",
"home_assistant_logo": "Logo de Home Assistant",
"icons_by": "Icônes par",
"integrations": "Intégrations",
"issues": "Problèmes",
"license": "Publié sous la licence Apache 2.0",
"path_configuration": "Chemin vers configuration.yaml : {path}",
"server": "serveur",
@ -2072,6 +2075,7 @@
"name": "Nom",
"no_theme": "Pas de thème",
"refresh_interval": "Intervalle de rafraîchissement",
"secondary_info_attribute": "Attribut d'informations secondaires",
"show_icon": "Afficher l'icône ?",
"show_name": "Afficher le nom ?",
"show_state": "Afficher l'état ?",
@ -2162,7 +2166,8 @@
},
"weather-forecast": {
"description": "La carte Prévisions météorologiques affiche la météo. Très utile pour les écrans placés au mur.",
"name": "Prévisions Météo"
"name": "Prévisions Météo",
"show_forecast": "Afficher les prévisions"
}
},
"cardpicker": {

View File

@ -1878,10 +1878,13 @@
"built_using": "Costruito usando",
"custom_uis": "Interfacce Utente personalizzate:",
"developed_by": "Sviluppato da un gruppo di persone fantastiche.",
"documentation": "Documentazione",
"frontend": "frontend-ui",
"frontend_version": "Versione Frontend: {version} - {type}",
"home_assistant_logo": "Logo Home Assistant",
"icons_by": "Icone di",
"integrations": "integrazioni",
"issues": "Problemi",
"license": "Pubblicato sotto la licenza Apache 2.0",
"path_configuration": "Percorso del file configuration.yaml: {path}",
"server": "server",
@ -2072,6 +2075,7 @@
"name": "Nome",
"no_theme": "Nessun tema",
"refresh_interval": "Intervallo di aggiornamento",
"secondary_info_attribute": "Attributo informazioni secondarie",
"show_icon": "Mostrare l'icona?",
"show_name": "Mostrare il nome?",
"show_state": "Mostrare lo stato?",
@ -2162,7 +2166,8 @@
},
"weather-forecast": {
"description": "La scheda Previsioni Meteo mostra le condizioni meteorologiche. Molto utili da includere nelle interfacce che le persone visualizzano sulla parete.",
"name": "Previsioni del tempo"
"name": "Previsioni del tempo",
"show_forecast": "Mostra Previsioni"
}
},
"cardpicker": {

View File

@ -473,6 +473,7 @@
}
},
"common": {
"and": "an",
"cancel": "Ofbriechen",
"close": "Zoumaachen",
"delete": "Läschen",
@ -1180,7 +1181,9 @@
"edit_requires_storage": "Editeur ass desaktivéiert well d'Konfiguratioun an der configuration.yaml gespäichert ass.",
"elevation": "Héicht",
"elevation_meters": "Meter",
"external_url": "Externe URL",
"imperial_example": "Fahrenheit, Pënner",
"internal_url": "Interne URL",
"latitude": "Breedegrad",
"location_name": "Numm vun der Home Assistant Installatioun",
"longitude": "Längegrad",
@ -1875,10 +1878,13 @@
"built_using": "Gebaut mat",
"custom_uis": "Personaliséierte Benotzer Interface:",
"developed_by": "Entwéckelt vun enger ganzer Rei fantastesche Leit.",
"documentation": "Dokumentatioun",
"frontend": "frontend-ui",
"frontend_version": "Frontend Versioun: {version} - {type}",
"home_assistant_logo": "Home Assistant logo",
"icons_by": "Ikonen vun",
"integrations": "Integratioune",
"issues": "Problemer",
"license": "Verëffentlecht ënnert der Apache 2.0 Lizenz",
"path_configuration": "Pad zur configuration.yaml: {path}",
"server": "server",
@ -2069,6 +2075,7 @@
"name": "Numm",
"no_theme": "Kee Thema",
"refresh_interval": "Aktualiséierungs Intervall",
"secondary_info_attribute": "Sekundär Informatiouns Attribut",
"show_icon": "Ikon uweisen?",
"show_name": "Numm uweisen?",
"show_state": "Zoustand uweisen?",
@ -2159,7 +2166,8 @@
},
"weather-forecast": {
"description": "Wieder Previsoune Kaart weist d'Météo un. Immens nëtzlech fir Interfacen déi Benotzer un d'Mauer hänken.",
"name": "Wiederprevisioune"
"name": "Wiederprevisioune",
"show_forecast": "Prévisioun uweisen"
}
},
"cardpicker": {

View File

@ -1878,10 +1878,13 @@
"built_using": "Zbudowany przy użyciu",
"custom_uis": "Niestandardowe interfejsy użytkownika:",
"developed_by": "Opracowany przez grono wspaniałych ludzi.",
"documentation": "Dokumentacja",
"frontend": "frontend-ui",
"frontend_version": "Wersja interfejsu użytkownika: {version} - {type}",
"home_assistant_logo": "Logo Home Assistant",
"icons_by": "ikon",
"integrations": "Integracje",
"issues": "Problemy",
"license": "Opublikowany na licencji Apache 2.0",
"path_configuration": "Ścieżka do pliku configuration.yaml: {path}",
"server": "serwer",
@ -2072,6 +2075,7 @@
"name": "Nazwa",
"no_theme": "Bez motywu",
"refresh_interval": "Częstotliwość odświeżania",
"secondary_info_attribute": "Dodatkowy atrybut informacyjny",
"show_icon": "Wyświetlanie ikony",
"show_name": "Wyświetlanie nazwy",
"show_state": "Wyświetlanie stanu",
@ -2162,7 +2166,8 @@
},
"weather-forecast": {
"description": "Karta prognozy pogody wyświetla pogodę. Bardzo przydatna w przypadku interfejsów wyświetlanych na urządzeniach zawieszonych na ścianie.",
"name": "Prognoza pogody"
"name": "Prognoza pogody",
"show_forecast": "Wyświetlanie prognozy"
}
},
"cardpicker": {

View File

@ -331,6 +331,7 @@
"ui": {
"auth_store": {
"ask": "Deseja continuar com sessão iniciada?",
"confirm": "Sim",
"decline": "Não"
},
"card": {
@ -1863,10 +1864,13 @@
"built_using": "Construído com",
"custom_uis": "IUs personalizados:",
"developed_by": "Desenvolvido por um punhado de pessoas incríveis.",
"documentation": "Documentação",
"frontend": "",
"frontend_version": "Versão frontend: {version} - {type}",
"home_assistant_logo": "Logotipo do Home Assistant",
"icons_by": "Ícones por",
"integrations": "Integrações",
"issues": "Problemas",
"license": "Publicado sob a licença de Apache 2.0",
"path_configuration": "Caminho para configuration.yaml: {path}",
"server": "servidor",
@ -1934,7 +1938,7 @@
"editor": "Editor de Templates",
"jinja_documentation": "Documentação do template Jinja2",
"template_extensions": "Extensões de templates do Home Assistant",
"title": "Modelos",
"title": "Modelo",
"unknown_error_template": "Erro desconhecido ao processar o template"
}
}
@ -1950,6 +1954,7 @@
},
"lovelace": {
"add_entities": {
"saving_failed": "Falha ao salvar a configuração da interface Lovelace.",
"yaml_unsupported": "Você não pode usar esta função ao usar o Lovelace IU no modo YAML."
},
"cards": {
@ -2053,6 +2058,7 @@
"name": "Nome",
"no_theme": "Nenhum tema",
"refresh_interval": "Intervalo entre atualizações",
"secondary_info_attribute": "Atributo de informação secundária",
"show_icon": "Mostrar Ícone?",
"show_name": "Mostrar nome?",
"show_state": "Mostrar Estado?",
@ -2065,7 +2071,8 @@
},
"glance": {
"columns": "Colunas",
"description": "O cartão Glance é útil para agrupar vários sensores numa visão geral compacta."
"description": "O cartão Glance é útil para agrupar vários sensores numa visão geral compacta.",
"name": "Relance"
},
"history-graph": {
"description": "O cartão Gráfico de histórico permite exibir um gráfico para cada uma das entidades listadas.",
@ -2141,7 +2148,8 @@
},
"weather-forecast": {
"description": "O cartão Previsão do tempo exibe o tempo. Muito útil para incluir nos interfaces que coloca-mos na parede.",
"name": "Previsão do tempo"
"name": "Previsão do tempo",
"show_forecast": "Mostrar previsão do tempo"
}
},
"cardpicker": {
@ -2221,6 +2229,7 @@
},
"suggest_card": {
"add": "Adicionar à Lovelace UI",
"create_own": "Escolha um cartão diferente",
"header": "Criamos uma sugestão para você."
},
"view": {

View File

@ -1878,10 +1878,13 @@
"built_using": "Создано с использованием",
"custom_uis": "Кастомные интерфейсы:",
"developed_by": "Разработано множеством замечательных людей.",
"documentation": "Документация",
"frontend": "пользовательский интерфейс",
"frontend_version": "Версия интерфейса: {version} - {type}",
"home_assistant_logo": "Логотип Home Assistant",
"icons_by": "Значки от",
"integrations": "Интеграции",
"issues": "Вопросы",
"license": "Опубликовано под лицензией Apache 2.0",
"path_configuration": "Путь к configuration.yaml: {path}",
"server": "сервер",
@ -2072,6 +2075,7 @@
"name": "Название",
"no_theme": "Нет темы",
"refresh_interval": "Интервал обновления",
"secondary_info_attribute": "Дополнительная информация",
"show_icon": "Значок",
"show_name": "Название",
"show_state": "Состояние",
@ -2162,7 +2166,8 @@
},
"weather-forecast": {
"description": "Отображает погоду. Может быть полезна на устройствах, размещаемых на стене.",
"name": "Прогноз погоды"
"name": "Прогноз погоды",
"show_forecast": "Показывать прогноз"
}
},
"cardpicker": {

View File

@ -1878,10 +1878,13 @@
"built_using": "建于",
"custom_uis": "自定义用户界面:",
"developed_by": "由一帮很 Awesome~~~ 的人开发。",
"documentation": "文档",
"frontend": "前端用户界面",
"frontend_version": "前端版本: {version} - {type}",
"home_assistant_logo": "Home Assistant logo",
"icons_by": "图标来自",
"integrations": "集成",
"issues": "问题",
"license": "根据 Apache 2.0 许可发布",
"path_configuration": "configuration.yaml 路径:{path}",
"server": "服务器",
@ -2072,6 +2075,7 @@
"name": "名称",
"no_theme": "没有主题",
"refresh_interval": "刷新间隔",
"secondary_info_attribute": "次要信息属性",
"show_icon": "显示图标?",
"show_name": "显示名称?",
"show_state": "显示状态?",
@ -2162,7 +2166,8 @@
},
"weather-forecast": {
"description": "“天气预报”卡片用于显示天气。对于将界面挂墙显示的情况非常有用。",
"name": "天气预报"
"name": "天气预报",
"show_forecast": "显示预报"
}
},
"cardpicker": {

View File

@ -1878,10 +1878,13 @@
"built_using": "建置使用",
"custom_uis": "自定介面:",
"developed_by": "由一群充滿熱情的人們所開發。",
"documentation": "相關文件",
"frontend": "frontend-ui",
"frontend_version": "Frontend 版本:{version} - {type}",
"home_assistant_logo": "Home Assistant logo",
"icons_by": "圖示使用",
"integrations": "整合",
"issues": "問題",
"license": "依據 Apache 2.0 授權許可發行",
"path_configuration": "configuration.yaml 路徑:{path}",
"server": "伺服器",
@ -2072,6 +2075,7 @@
"name": "名稱",
"no_theme": "沒有主題",
"refresh_interval": "更新間隔",
"secondary_info_attribute": "次要資訊屬性",
"show_icon": "顯示圖示?",
"show_name": "顯示名稱?",
"show_state": "顯示狀態?",
@ -2162,7 +2166,8 @@
},
"weather-forecast": {
"description": "天氣預報面板顯示天氣狀態。對於放置於牆上顯示非常實用。",
"name": "天氣預報面板"
"name": "天氣預報面板",
"show_forecast": "顯示預報"
}
},
"cardpicker": {

View File

@ -3,7 +3,7 @@
"target": "es2017",
"module": "esnext",
"moduleResolution": "node",
"lib": ["es2017", "dom", "dom.iterable"],
"lib": ["es2017", "dom", "dom.iterable", "WebWorker"],
"noEmit": true,
"noUnusedLocals": true,
"noUnusedParameters": true,

724
yarn.lock

File diff suppressed because it is too large Load Diff