Compare commits

...

17 Commits

Author SHA1 Message Date
Joakim Sørensen
09f4922ad3 change 2021-06-15 15:44:59 +00:00
Joakim Sørensen
36831d26e4 fix typing 2021-06-15 09:51:27 +00:00
Joakim Sørensen
5829660894 Better event handling 2021-06-15 09:48:19 +00:00
Joakim Sørensen
4e3fbc1169 Add applying update "screen" 2021-06-14 18:18:54 +00:00
GitHub Action
38640c99e3 Translation update 2021-06-14 00:48:26 +00:00
GitHub Action
d6df8bddea Translation update 2021-06-13 00:48:28 +00:00
GitHub Action
ddfc4bd98e Translation update 2021-06-12 00:48:12 +00:00
Shane Qi
3d6674325c Fix the issue that logbook card doesn't translate context.user_id to name if it's a user's id. (#9383)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-06-10 14:51:24 +02:00
Bram Kragten
194829f5b1 Generalize map (#9331)
* Generalize map

* Fix path opacity

* Add fitZones
2021-06-10 14:22:44 +02:00
GitHub Action
11a77253f4 Translation update 2021-06-10 00:49:02 +00:00
GitHub Action
67be2343f8 Translation update 2021-06-09 00:48:19 +00:00
Joakim Sørensen
e9b1b3d853 Fix issues with restoring snapshot during onboarding (#9385)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-06-08 17:57:53 +02:00
Bram Kragten
8a33d174d7 FIx selecting service/url path action after choosing default action (#9376)
Co-authored-by: Joakim Sørensen <joasoe@gmail.com>
2021-06-08 14:24:58 +02:00
Joakim Sørensen
226d6216b7 Build wheels for 3.9-alpine3.13 (#9390) 2021-06-08 13:07:02 +02:00
GitHub Action
1925bb01be Translation update 2021-06-08 00:49:18 +00:00
Bram Kragten
82a4806e01 Change line logic 2021-06-07 10:45:57 +02:00
Joakim Sørensen
ce419fae7b Add password confirmation to snapshot creation (#9349)
* Add password confirmation to snapshot creation

* Remove confirm_password before sending

* change layout

* style changes

* Adjust styling

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-06-07 10:45:20 +02:00
61 changed files with 1829 additions and 1737 deletions

View File

@@ -6,7 +6,6 @@ on:
- published - published
env: env:
WHEELS_TAG: 3.8-alpine3.12
PYTHON_VERSION: 3.8 PYTHON_VERSION: 3.8
NODE_VERSION: 12.1 NODE_VERSION: 12.1
@@ -64,6 +63,9 @@ jobs:
strategy: strategy:
matrix: matrix:
arch: ["aarch64", "armhf", "armv7", "amd64", "i386"] arch: ["aarch64", "armhf", "armv7", "amd64", "i386"]
tag:
- "3.8-alpine3.12"
- "3.9-alpine3.13"
steps: steps:
- name: Download requirements.txt - name: Download requirements.txt
uses: actions/download-artifact@v2 uses: actions/download-artifact@v2
@@ -73,7 +75,7 @@ jobs:
- name: Build wheels - name: Build wheels
uses: home-assistant/wheels@master uses: home-assistant/wheels@master
with: with:
tag: ${{ env.WHEELS_TAG }} tag: ${{ matrix.tag }}
arch: ${{ matrix.arch }} arch: ${{ matrix.arch }}
wheels-host: ${{ secrets.WHEELS_HOST }} wheels-host: ${{ secrets.WHEELS_HOST }}
wheels-key: ${{ secrets.WHEELS_KEY }} wheels-key: ${{ secrets.WHEELS_KEY }}

View File

@@ -29,7 +29,6 @@ class SupervisorFormfieldLabel extends LitElement {
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return css` return css`
:host { :host {
cursor: pointer;
display: flex; display: flex;
align-items: center; align-items: center;
} }

View File

@@ -5,6 +5,7 @@ import { customElement, property } from "lit/decorators";
import { atLeastVersion } from "../../../src/common/config/version"; import { atLeastVersion } from "../../../src/common/config/version";
import { formatDate } from "../../../src/common/datetime/format_date"; import { formatDate } from "../../../src/common/datetime/format_date";
import { formatDateTime } from "../../../src/common/datetime/format_date_time"; import { formatDateTime } from "../../../src/common/datetime/format_date_time";
import { LocalizeFunc } from "../../../src/common/translations/localize";
import "../../../src/components/ha-checkbox"; import "../../../src/components/ha-checkbox";
import "../../../src/components/ha-formfield"; import "../../../src/components/ha-formfield";
import "../../../src/components/ha-radio"; import "../../../src/components/ha-radio";
@@ -67,6 +68,8 @@ const _computeAddons = (addons): AddonCheckboxItem[] =>
export class SupervisorSnapshotContent extends LitElement { export class SupervisorSnapshotContent extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property() public localize?: LocalizeFunc;
@property({ attribute: false }) public supervisor?: Supervisor; @property({ attribute: false }) public supervisor?: Supervisor;
@property({ attribute: false }) public snapshot?: HassioSnapshotDetail; @property({ attribute: false }) public snapshot?: HassioSnapshotDetail;
@@ -81,10 +84,14 @@ export class SupervisorSnapshotContent extends LitElement {
@property({ type: Boolean }) public snapshotHasPassword = false; @property({ type: Boolean }) public snapshotHasPassword = false;
@property({ type: Boolean }) public onboarding = false;
@property() public snapshotName = ""; @property() public snapshotName = "";
@property() public snapshotPassword = ""; @property() public snapshotPassword = "";
@property() public confirmSnapshotPassword = "";
public willUpdate(changedProps) { public willUpdate(changedProps) {
super.willUpdate(changedProps); super.willUpdate(changedProps);
if (!this.hasUpdated) { if (!this.hasUpdated) {
@@ -104,8 +111,12 @@ export class SupervisorSnapshotContent extends LitElement {
} }
} }
private _localize = (string: string) =>
this.supervisor?.localize(`snapshot.${string}`) ||
this.localize!(`ui.panel.page-onboarding.restore.${string}`);
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this.supervisor) { if (!this.onboarding && !this.supervisor) {
return html``; return html``;
} }
const foldersSection = const foldersSection =
@@ -117,14 +128,16 @@ export class SupervisorSnapshotContent extends LitElement {
${this.snapshot ${this.snapshot
? html`<div class="details"> ? html`<div class="details">
${this.snapshot.type === "full" ${this.snapshot.type === "full"
? this.supervisor.localize("snapshot.full_snapshot") ? this._localize("full_snapshot")
: this.supervisor.localize("snapshot.partial_snapshot")} : this._localize("partial_snapshot")}
(${Math.ceil(this.snapshot.size * 10) / 10 + " MB"})<br /> (${Math.ceil(this.snapshot.size * 10) / 10 + " MB"})<br />
${formatDateTime(new Date(this.snapshot.date), this.hass.locale)} ${this.hass
? formatDateTime(new Date(this.snapshot.date), this.hass.locale)
: this.snapshot.date}
</div>` </div>`
: html`<paper-input : html`<paper-input
name="snapshotName" name="snapshotName"
.label=${this.supervisor.localize("snapshot.name")} .label=${this.supervisor?.localize("snapshot.name") || "Name"}
.value=${this.snapshotName} .value=${this.snapshotName}
@value-changed=${this._handleTextValueChanged} @value-changed=${this._handleTextValueChanged}
> >
@@ -132,13 +145,11 @@ export class SupervisorSnapshotContent extends LitElement {
${!this.snapshot || this.snapshot.type === "full" ${!this.snapshot || this.snapshot.type === "full"
? html`<div class="sub-header"> ? html`<div class="sub-header">
${!this.snapshot ${!this.snapshot
? this.supervisor.localize("snapshot.type") ? this._localize("type")
: this.supervisor.localize("snapshot.select_type")} : this._localize("select_type")}
</div> </div>
<div class="snapshot-types"> <div class="snapshot-types">
<ha-formfield <ha-formfield .label=${this._localize("full_snapshot")}>
.label=${this.supervisor.localize("snapshot.full_snapshot")}
>
<ha-radio <ha-radio
@change=${this._handleRadioValueChanged} @change=${this._handleRadioValueChanged}
value="full" value="full"
@@ -147,9 +158,7 @@ export class SupervisorSnapshotContent extends LitElement {
> >
</ha-radio> </ha-radio>
</ha-formfield> </ha-formfield>
<ha-formfield <ha-formfield .label=${this._localize("partial_snapshot")}>
.label=${this.supervisor!.localize("snapshot.partial_snapshot")}
>
<ha-radio <ha-radio
@change=${this._handleRadioValueChanged} @change=${this._handleRadioValueChanged}
value="partial" value="partial"
@@ -160,9 +169,9 @@ export class SupervisorSnapshotContent extends LitElement {
</ha-formfield> </ha-formfield>
</div>` </div>`
: ""} : ""}
${this.snapshot && this.snapshotType === "partial" ${this.snapshotType === "partial"
? html` ? html`<div class="partial-picker">
${this.snapshot.homeassistant ${this.snapshot && this.snapshot.homeassistant
? html` ? html`
<ha-formfield <ha-formfield
.label=${html`<supervisor-formfield-label .label=${html`<supervisor-formfield-label
@@ -182,15 +191,11 @@ export class SupervisorSnapshotContent extends LitElement {
</ha-formfield> </ha-formfield>
` `
: ""} : ""}
`
: ""}
${this.snapshotType === "partial"
? html`
${foldersSection?.templates.length ${foldersSection?.templates.length
? html` ? html`
<ha-formfield <ha-formfield
.label=${html`<supervisor-formfield-label .label=${html`<supervisor-formfield-label
.label=${this.supervisor.localize("snapshot.folders")} .label=${this._localize("folders")}
.iconPath=${mdiFolder} .iconPath=${mdiFolder}
> >
</supervisor-formfield-label>`} </supervisor-formfield-label>`}
@@ -210,7 +215,7 @@ export class SupervisorSnapshotContent extends LitElement {
? html` ? html`
<ha-formfield <ha-formfield
.label=${html`<supervisor-formfield-label .label=${html`<supervisor-formfield-label
.label=${this.supervisor.localize("snapshot.addons")} .label=${this._localize("addons")}
.iconPath=${mdiPuzzle} .iconPath=${mdiPuzzle}
> >
</supervisor-formfield-label>`} </supervisor-formfield-label>`}
@@ -226,29 +231,44 @@ export class SupervisorSnapshotContent extends LitElement {
<div class="section-content">${addonsSection.templates}</div> <div class="section-content">${addonsSection.templates}</div>
` `
: ""} : ""}
` </div> `
: ""}
${this.snapshotType === "partial" &&
(!this.snapshot || this.snapshotHasPassword)
? html`<hr />`
: ""} : ""}
${!this.snapshot ${!this.snapshot
? html`<ha-formfield ? html`<ha-formfield
.label=${this.supervisor.localize("snapshot.password_protection")} class="password"
.label=${this._localize("password_protection")}
> >
<ha-checkbox <ha-checkbox
.checked=${this.snapshotHasPassword} .checked=${this.snapshotHasPassword}
@change=${this._toggleHasPassword} @change=${this._toggleHasPassword}
> >
</ha-checkbox </ha-checkbox>
></ha-formfield>` </ha-formfield>`
: ""} : ""}
${this.snapshotHasPassword ${this.snapshotHasPassword
? html` ? html`
<paper-input <paper-input
.label=${this.supervisor.localize("snapshot.password")} .label=${this._localize("password")}
type="password" type="password"
name="snapshotPassword" name="snapshotPassword"
.value=${this.snapshotPassword} .value=${this.snapshotPassword}
@value-changed=${this._handleTextValueChanged} @value-changed=${this._handleTextValueChanged}
> >
</paper-input> </paper-input>
${!this.snapshot
? html` <paper-input
.label=${this.supervisor?.localize("confirm_password")}
type="password"
name="confirmSnapshotPassword"
.value=${this.confirmSnapshotPassword}
@value-changed=${this._handleTextValueChanged}
>
</paper-input>`
: ""}
` `
: ""} : ""}
`; `;
@@ -256,21 +276,24 @@ export class SupervisorSnapshotContent extends LitElement {
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return css` return css`
ha-checkbox { .partial-picker ha-formfield {
--mdc-checkbox-touch-target-size: 16px;
display: block; display: block;
margin: 4px 12px 8px 0;
} }
ha-formfield { .partial-picker ha-checkbox {
display: contents; --mdc-checkbox-touch-target-size: 32px;
}
.partial-picker {
display: block;
margin: 0px -6px;
} }
supervisor-formfield-label { supervisor-formfield-label {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
} }
paper-input[type="password"] { hr {
display: block; border-color: var(--divider-color);
margin: 4px 0 4px 16px; border-bottom: none;
margin: 16px 0;
} }
.details { .details {
color: var(--secondary-text-color); color: var(--secondary-text-color);
@@ -278,13 +301,15 @@ export class SupervisorSnapshotContent extends LitElement {
.section-content { .section-content {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin-left: 16px; margin-left: 30px;
} }
.security { ha-formfield.password {
margin-top: 16px; display: block;
margin: 0 -14px -16px;
} }
.snapshot-types { .snapshot-types {
display: flex; display: flex;
margin-left: -13px;
} }
.sub-header { .sub-header {
margin-top: 8px; margin-top: 8px;
@@ -303,6 +328,9 @@ export class SupervisorSnapshotContent extends LitElement {
if (this.snapshotHasPassword) { if (this.snapshotHasPassword) {
data.password = this.snapshotPassword; data.password = this.snapshotPassword;
if (!this.snapshot) {
data.confirm_password = this.confirmSnapshotPassword;
}
} }
if (this.snapshotType === "full") { if (this.snapshotType === "full") {
@@ -334,7 +362,7 @@ export class SupervisorSnapshotContent extends LitElement {
const addons = const addons =
section === "addons" section === "addons"
? new Map( ? new Map(
this.supervisor!.addon.addons.map((item) => [item.slug, item]) this.supervisor?.addon.addons.map((item) => [item.slug, item])
) )
: undefined; : undefined;
let checkedItems = 0; let checkedItems = 0;
@@ -344,6 +372,7 @@ export class SupervisorSnapshotContent extends LitElement {
.label=${item.name} .label=${item.name}
.iconPath=${section === "addons" ? mdiPuzzle : mdiFolder} .iconPath=${section === "addons" ? mdiPuzzle : mdiFolder}
.imageUrl=${section === "addons" && .imageUrl=${section === "addons" &&
!this.onboarding &&
atLeastVersion(this.hass.config.version, 0, 105) && atLeastVersion(this.hass.config.version, 0, 105) &&
addons?.get(item.slug)?.icon addons?.get(item.slug)?.icon
? `/api/hassio/addons/${item.slug}/icon` ? `/api/hassio/addons/${item.slug}/icon`

View File

@@ -220,10 +220,29 @@ export class HassioUpdate extends LitElement {
} }
private async _updateCore(): Promise<void> { private async _updateCore(): Promise<void> {
await updateCore(this.hass); try {
await updateCore(this.hass);
} catch (err) {
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
showAlertDialog(this, {
title: this.supervisor.localize(
"common.failed_to_update_name",
"name",
"Home Assistant Core"
),
text: extractApiErrorMessage(err),
});
return;
}
}
fireEvent(this, "supervisor-collection-refresh", { fireEvent(this, "supervisor-collection-refresh", {
collection: "core", collection: "core",
}); });
fireEvent(this, "supervisor-applying-update", {
name: "Home Assistant Core",
version: this.supervisor.core.version_latest,
});
} }
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {

View File

@@ -95,16 +95,25 @@ class HassioCreateSnapshotDialog extends LitElement {
this._creatingSnapshot = true; this._creatingSnapshot = true;
this._error = ""; this._error = "";
if ( if (snapshotDetails.password && !snapshotDetails.password.length) {
this._snapshotContent.snapshotHasPassword &&
!this._snapshotContent.snapshotPassword.length
) {
this._error = this._dialogParams!.supervisor.localize( this._error = this._dialogParams!.supervisor.localize(
"snapshot.enter_password" "snapshot.enter_password"
); );
this._creatingSnapshot = false; this._creatingSnapshot = false;
return; return;
} }
if (
snapshotDetails.password &&
snapshotDetails.password !== snapshotDetails.confirm_password
) {
this._error = this._dialogParams!.supervisor.localize(
"snapshot.passwords_not_matching"
);
this._creatingSnapshot = false;
return;
}
delete snapshotDetails.confirm_password;
try { try {
if (this._snapshotContent.snapshotType === "full") { if (this._snapshotContent.snapshotType === "full") {

View File

@@ -1,13 +1,13 @@
import { ActionDetail } from "@material/mwc-list"; import { ActionDetail } from "@material/mwc-list";
import "@material/mwc-list/mwc-list-item"; import "@material/mwc-list/mwc-list-item";
import { mdiDotsVertical } from "@mdi/js"; import { mdiClose, mdiDotsVertical } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, query, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import { slugify } from "../../../../src/common/string/slugify"; import { slugify } from "../../../../src/common/string/slugify";
import "../../../../src/components/buttons/ha-progress-button"; import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-button-menu"; import "../../../../src/components/ha-button-menu";
import { createCloseHeading } from "../../../../src/components/ha-dialog"; import "../../../../src/components/ha-header-bar";
import "../../../../src/components/ha-svg-icon"; import "../../../../src/components/ha-svg-icon";
import { getSignedPath } from "../../../../src/data/auth"; import { getSignedPath } from "../../../../src/data/auth";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common"; import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
@@ -67,14 +67,24 @@ class HassioSnapshotDialog
open open
scrimClickAction scrimClickAction
@closed=${this.closeDialog} @closed=${this.closeDialog}
.heading=${createCloseHeading(this.hass, this._computeName)} .heading=${true}
> >
<div slot="heading">
<ha-header-bar>
<span slot="title">${this._snapshot.name}</span>
<mwc-icon-button slot="actionItems" dialogAction="cancel">
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button>
</ha-header-bar>
</div>
${this._restoringSnapshot ${this._restoringSnapshot
? html` <ha-circular-progress active></ha-circular-progress>` ? html` <ha-circular-progress active></ha-circular-progress>`
: html`<supervisor-snapshot-content : html`<supervisor-snapshot-content
.hass=${this.hass} .hass=${this.hass}
.supervisor=${this._dialogParams.supervisor} .supervisor=${this._dialogParams.supervisor}
.snapshot=${this._snapshot} .snapshot=${this._snapshot}
.onboarding=${this._dialogParams.onboarding || false}
.localize=${this._dialogParams.localize}
> >
</supervisor-snapshot-content>`} </supervisor-snapshot-content>`}
${this._error ? html`<p class="error">Error: ${this._error}</p>` : ""} ${this._error ? html`<p class="error">Error: ${this._error}</p>` : ""}
@@ -87,18 +97,20 @@ class HassioSnapshotDialog
Restore Restore
</mwc-button> </mwc-button>
<ha-button-menu ${!this._dialogParams.onboarding
fixed ? html`<ha-button-menu
slot="primaryAction" fixed
@action=${this._handleMenuAction} slot="primaryAction"
@closed=${(ev: Event) => ev.stopPropagation()} @action=${this._handleMenuAction}
> @closed=${(ev: Event) => ev.stopPropagation()}
<mwc-icon-button slot="trigger" alt="menu"> >
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon> <mwc-icon-button slot="trigger" alt="menu">
</mwc-icon-button> <ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
<mwc-list-item>Download Snapshot</mwc-list-item> </mwc-icon-button>
<mwc-list-item class="error">Delete Snapshot</mwc-list-item> <mwc-list-item>Download Snapshot</mwc-list-item>
</ha-button-menu> <mwc-list-item class="error">Delete Snapshot</mwc-list-item>
</ha-button-menu>`
: ""}
</ha-dialog> </ha-dialog>
`; `;
} }
@@ -115,6 +127,12 @@ class HassioSnapshotDialog
display: block; display: block;
text-align: center; text-align: center;
} }
ha-header-bar {
--mdc-theme-on-primary: var(--primary-text-color);
--mdc-theme-primary: var(--mdc-theme-surface);
flex-shrink: 0;
display: block;
}
`, `,
]; ];
} }

View File

@@ -1,4 +1,5 @@
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import { LocalizeFunc } from "../../../../src/common/translations/localize";
import { Supervisor } from "../../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../../src/data/supervisor/supervisor";
export interface HassioSnapshotDialogParams { export interface HassioSnapshotDialogParams {
@@ -6,6 +7,7 @@ export interface HassioSnapshotDialogParams {
onDelete?: () => void; onDelete?: () => void;
onboarding?: boolean; onboarding?: boolean;
supervisor?: Supervisor; supervisor?: Supervisor;
localize?: LocalizeFunc;
} }
export const showHassioSnapshotDialog = ( export const showHassioSnapshotDialog = (

View File

@@ -7,10 +7,7 @@ import "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-settings-row"; import "../../../../src/components/ha-settings-row";
import "../../../../src/components/ha-svg-icon"; import "../../../../src/components/ha-svg-icon";
import "../../../../src/components/ha-switch"; import "../../../../src/components/ha-switch";
import { import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
extractApiErrorMessage,
ignoreSupervisorError,
} from "../../../../src/data/hassio/common";
import { createHassioPartialSnapshot } from "../../../../src/data/hassio/snapshot"; import { createHassioPartialSnapshot } from "../../../../src/data/hassio/snapshot";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles"; import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import type { HomeAssistant } from "../../../../src/types"; import type { HomeAssistant } from "../../../../src/types";
@@ -153,16 +150,7 @@ class DialogSupervisorUpdate extends LitElement {
} }
this._action = "update"; this._action = "update";
try { await this._dialogParams!.updateHandler!();
await this._dialogParams!.updateHandler!();
} catch (err) {
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
this._error = extractApiErrorMessage(err);
}
this._action = null;
return;
}
this.closeDialog(); this.closeDialog();
} }

View File

@@ -1,17 +1,18 @@
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import { HassioPartialSnapshotCreateParams } from "../../../../src/data/hassio/snapshot";
import { Supervisor } from "../../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../../src/data/supervisor/supervisor";
export interface SupervisorDialogSupervisorUpdateParams { export interface SupervisorDialogSupervisorUpdateParams {
supervisor: Supervisor; supervisor: Supervisor;
name: string; name: string;
version: string; version: string;
snapshotParams: any; snapshotParams: HassioPartialSnapshotCreateParams;
updateHandler: () => Promise<void>; updateHandler: () => Promise<void>;
} }
export const showDialogSupervisorUpdate = ( export const showDialogSupervisorUpdate = (
element: HTMLElement, element: HTMLElement,
dialogParams: SupervisorDialogSupervisorUpdateParams dialogParams: Partial<SupervisorDialogSupervisorUpdateParams>
): void => { ): void => {
fireEvent(element, "show-dialog", { fireEvent(element, "show-dialog", {
dialogTag: "dialog-supervisor-update", dialogTag: "dialog-supervisor-update",

View File

@@ -1,12 +1,26 @@
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import {
import { customElement, property } from "lit/decorators"; css,
CSSResultGroup,
html,
LitElement,
PropertyValues,
TemplateResult,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { import {
Supervisor, Supervisor,
supervisorApplyUpdateDetails,
supervisorCollection, supervisorCollection,
} from "../../src/data/supervisor/supervisor"; } from "../../src/data/supervisor/supervisor";
import { HomeAssistant, Route } from "../../src/types"; import { HomeAssistant, Route } from "../../src/types";
import "./hassio-panel-router"; import "./hassio-panel-router";
declare global {
interface HASSDomEvents {
"supervisor-applying-update": supervisorApplyUpdateDetails;
}
}
@customElement("hassio-panel") @customElement("hassio-panel")
class HassioPanel extends LitElement { class HassioPanel extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@@ -17,6 +31,16 @@ class HassioPanel extends LitElement {
@property({ attribute: false }) public route!: Route; @property({ attribute: false }) public route!: Route;
@state() private _applyingUpdate?: supervisorApplyUpdateDetails;
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
this._applyingUpdate = undefined;
this.addEventListener("supervisor-applying-update", (ev) => {
this._applyingUpdate = ev.detail;
});
}
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this.hass) { if (!this.hass) {
return html`<hass-loading-screen></hass-loading-screen>`; return html`<hass-loading-screen></hass-loading-screen>`;
@@ -29,6 +53,16 @@ class HassioPanel extends LitElement {
) { ) {
return html`<hass-loading-screen></hass-loading-screen>`; return html`<hass-loading-screen></hass-loading-screen>`;
} }
if (this._applyingUpdate !== undefined) {
return html`<hass-loading-screen no-toolbar>
${this.supervisor.localize("dialog.update.updating", {
name: this._applyingUpdate.name,
version: this._applyingUpdate.version,
})}
</hass-loading-screen>`;
}
return html` return html`
<hassio-panel-router <hassio-panel-router
.hass=${this.hass} .hass=${this.hass}

View File

@@ -11,6 +11,7 @@ import {
extractApiErrorMessage, extractApiErrorMessage,
fetchHassioStats, fetchHassioStats,
HassioStats, HassioStats,
ignoreSupervisorError,
} from "../../../src/data/hassio/common"; } from "../../../src/data/hassio/common";
import { restartCore, updateCore } from "../../../src/data/supervisor/core"; import { restartCore, updateCore } from "../../../src/data/supervisor/core";
import { Supervisor } from "../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../src/data/supervisor/supervisor";
@@ -150,7 +151,7 @@ class HassioCoreInfo extends LitElement {
title: this.supervisor.localize( title: this.supervisor.localize(
"common.failed_to_restart_name", "common.failed_to_restart_name",
"name", "name",
"Home AssistantCore" "Home Assistant Core"
), ),
text: extractApiErrorMessage(err), text: extractApiErrorMessage(err),
}); });
@@ -175,10 +176,29 @@ class HassioCoreInfo extends LitElement {
} }
private async _updateCore(): Promise<void> { private async _updateCore(): Promise<void> {
await updateCore(this.hass); try {
await updateCore(this.hass);
} catch (err) {
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
showAlertDialog(this, {
title: this.supervisor.localize(
"common.failed_to_update_name",
"name",
"Home Assistant Core"
),
text: extractApiErrorMessage(err),
});
return;
}
}
fireEvent(this, "supervisor-collection-refresh", { fireEvent(this, "supervisor-collection-refresh", {
collection: "core", collection: "core",
}); });
fireEvent(this, "supervisor-applying-update", {
name: "Home Assistant Core",
version: this.supervisor.core.version_latest,
});
} }
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {

View File

@@ -66,9 +66,7 @@
"@polymer/iron-autogrow-textarea": "^3.0.1", "@polymer/iron-autogrow-textarea": "^3.0.1",
"@polymer/iron-flex-layout": "^3.0.1", "@polymer/iron-flex-layout": "^3.0.1",
"@polymer/iron-icon": "^3.0.1", "@polymer/iron-icon": "^3.0.1",
"@polymer/iron-image": "^3.0.1",
"@polymer/iron-input": "^3.0.1", "@polymer/iron-input": "^3.0.1",
"@polymer/iron-label": "^3.0.1",
"@polymer/iron-overlay-behavior": "^3.0.2", "@polymer/iron-overlay-behavior": "^3.0.2",
"@polymer/iron-resizable-behavior": "^3.0.1", "@polymer/iron-resizable-behavior": "^3.0.1",
"@polymer/paper-checkbox": "^3.1.0", "@polymer/paper-checkbox": "^3.1.0",

View File

@@ -6,8 +6,7 @@ export type LeafletDrawModuleType = typeof import("leaflet-draw");
export const setupLeafletMap = async ( export const setupLeafletMap = async (
mapElement: HTMLElement, mapElement: HTMLElement,
darkMode?: boolean, darkMode?: boolean
draw = false
): Promise<[Map, LeafletModuleType, TileLayer]> => { ): Promise<[Map, LeafletModuleType, TileLayer]> => {
if (!mapElement.parentNode) { if (!mapElement.parentNode) {
throw new Error("Cannot setup Leaflet map on disconnected element"); throw new Error("Cannot setup Leaflet map on disconnected element");
@@ -17,10 +16,6 @@ export const setupLeafletMap = async (
.default as LeafletModuleType; .default as LeafletModuleType;
Leaflet.Icon.Default.imagePath = "/static/images/leaflet/images/"; Leaflet.Icon.Default.imagePath = "/static/images/leaflet/images/";
if (draw) {
await import("leaflet-draw");
}
const map = Leaflet.map(mapElement); const map = Leaflet.map(mapElement);
const style = document.createElement("link"); const style = document.createElement("link");
style.setAttribute("href", "/static/images/leaflet/leaflet.css"); style.setAttribute("href", "/static/images/leaflet/leaflet.css");

View File

@@ -0,0 +1,69 @@
import { LitElement, html, css } from "lit";
import { property } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
import { fireEvent } from "../../common/dom/fire_event";
import { HomeAssistant } from "../../types";
class HaEntityMarker extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: "entity-id" }) public entityId?: string;
@property({ attribute: "entity-name" }) public entityName?: string;
@property({ attribute: "entity-picture" }) public entityPicture?: string;
@property({ attribute: "entity-color" }) public entityColor?: string;
protected render() {
return html`
<div
class="marker"
style=${styleMap({ "border-color": this.entityColor })}
@click=${this._badgeTap}
>
${this.entityPicture
? html`<div
class="entity-picture"
style=${styleMap({
"background-image": `url(${this.entityPicture})`,
})}
></div>`
: this.entityName}
</div>
`;
}
private _badgeTap(ev: Event) {
ev.stopPropagation();
if (this.entityId) {
fireEvent(this, "hass-more-info", { entityId: this.entityId });
}
}
static get styles() {
return css`
.marker {
display: flex;
justify-content: center;
align-items: center;
box-sizing: border-box;
overflow: hidden;
width: 48px;
height: 48px;
font-size: var(--ha-marker-font-size, 1.5em);
border-radius: 50%;
border: 1px solid var(--ha-marker-color, var(--primary-color));
color: var(--primary-text-color);
background-color: var(--card-background-color);
}
.entity-picture {
background-size: cover;
height: 100%;
width: 100%;
}
`;
}
}
customElements.define("ha-entity-marker", HaEntityMarker);

View File

@@ -1,299 +0,0 @@
import {
Circle,
DivIcon,
DragEndEvent,
LatLng,
LeafletMouseEvent,
Map,
Marker,
TileLayer,
} from "leaflet";
import {
css,
CSSResultGroup,
html,
LitElement,
PropertyValues,
TemplateResult,
} from "lit";
import { customElement, property } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event";
import {
LeafletModuleType,
replaceTileLayer,
setupLeafletMap,
} from "../../common/dom/setup-leaflet-map";
import { nextRender } from "../../common/util/render-status";
import { defaultRadiusColor } from "../../data/zone";
import { HomeAssistant } from "../../types";
@customElement("ha-location-editor")
class LocationEditor extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Array }) public location?: [number, number];
@property({ type: Number }) public radius?: number;
@property() public radiusColor?: string;
@property() public icon?: string;
@property({ type: Boolean }) public darkMode?: boolean;
public fitZoom = 16;
private _iconEl?: DivIcon;
private _ignoreFitToMap?: [number, number];
// eslint-disable-next-line
private Leaflet?: LeafletModuleType;
private _leafletMap?: Map;
private _tileLayer?: TileLayer;
private _locationMarker?: Marker | Circle;
public fitMap(): void {
if (!this._leafletMap || !this.location) {
return;
}
if (this._locationMarker && "getBounds" in this._locationMarker) {
this._leafletMap.fitBounds(this._locationMarker.getBounds());
} else {
this._leafletMap.setView(this.location, this.fitZoom);
}
this._ignoreFitToMap = this.location;
}
protected render(): TemplateResult {
return html` <div id="map"></div> `;
}
protected firstUpdated(changedProps: PropertyValues): void {
super.firstUpdated(changedProps);
this._initMap();
}
protected updated(changedProps: PropertyValues): void {
super.updated(changedProps);
// Still loading.
if (!this.Leaflet) {
return;
}
if (changedProps.has("location")) {
this._updateMarker();
if (
this.location &&
(!this._ignoreFitToMap ||
this._ignoreFitToMap[0] !== this.location[0] ||
this._ignoreFitToMap[1] !== this.location[1])
) {
this.fitMap();
}
}
if (changedProps.has("radius")) {
this._updateRadius();
}
if (changedProps.has("radiusColor")) {
this._updateRadiusColor();
}
if (changedProps.has("icon")) {
this._updateIcon();
}
if (changedProps.has("hass")) {
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
if (!oldHass || oldHass.themes.darkMode === this.hass.themes.darkMode) {
return;
}
if (!this._leafletMap || !this._tileLayer) {
return;
}
this._tileLayer = replaceTileLayer(
this.Leaflet,
this._leafletMap,
this._tileLayer,
this.hass.themes.darkMode
);
}
}
private get _mapEl(): HTMLDivElement {
return this.shadowRoot!.querySelector("div")!;
}
private async _initMap(): Promise<void> {
[this._leafletMap, this.Leaflet, this._tileLayer] = await setupLeafletMap(
this._mapEl,
this.darkMode ?? this.hass.themes.darkMode,
Boolean(this.radius)
);
this._leafletMap.addEventListener(
"click",
// @ts-ignore
(ev: LeafletMouseEvent) => this._locationUpdated(ev.latlng)
);
this._updateIcon();
this._updateMarker();
this.fitMap();
this._leafletMap.invalidateSize();
}
private _locationUpdated(latlng: LatLng) {
let longitude = latlng.lng;
if (Math.abs(longitude) > 180.0) {
// Normalize longitude if map provides values beyond -180 to +180 degrees.
longitude = (((longitude % 360.0) + 540.0) % 360.0) - 180.0;
}
this.location = this._ignoreFitToMap = [latlng.lat, longitude];
fireEvent(this, "change", undefined, { bubbles: false });
}
private _radiusUpdated() {
this._ignoreFitToMap = this.location;
this.radius = (this._locationMarker as Circle).getRadius();
fireEvent(this, "change", undefined, { bubbles: false });
}
private _updateIcon() {
if (!this.icon) {
this._iconEl = undefined;
return;
}
// create icon
let iconHTML = "";
const el = document.createElement("ha-icon");
el.setAttribute("icon", this.icon);
iconHTML = el.outerHTML;
this._iconEl = this.Leaflet!.divIcon({
html: iconHTML,
iconSize: [24, 24],
className: "light leaflet-edit-move",
});
this._setIcon();
}
private _setIcon() {
if (!this._locationMarker || !this._iconEl) {
return;
}
if (!this.radius) {
(this._locationMarker as Marker).setIcon(this._iconEl);
return;
}
// @ts-ignore
const moveMarker = this._locationMarker.editing._moveMarker;
moveMarker.setIcon(this._iconEl);
}
private _setupEdit() {
// @ts-ignore
this._locationMarker.editing.enable();
// @ts-ignore
const moveMarker = this._locationMarker.editing._moveMarker;
// @ts-ignore
const resizeMarker = this._locationMarker.editing._resizeMarkers[0];
this._setIcon();
moveMarker.addEventListener(
"dragend",
// @ts-ignore
(ev: DragEndEvent) => this._locationUpdated(ev.target.getLatLng())
);
resizeMarker.addEventListener(
"dragend",
// @ts-ignore
(ev: DragEndEvent) => this._radiusUpdated(ev)
);
}
private async _updateMarker(): Promise<void> {
if (!this.location) {
if (this._locationMarker) {
this._locationMarker.remove();
this._locationMarker = undefined;
}
return;
}
if (this._locationMarker) {
this._locationMarker.setLatLng(this.location);
if (this.radius) {
// @ts-ignore
this._locationMarker.editing.disable();
await nextRender();
this._setupEdit();
}
return;
}
if (!this.radius) {
this._locationMarker = this.Leaflet!.marker(this.location, {
draggable: true,
});
this._setIcon();
this._locationMarker.addEventListener(
"dragend",
// @ts-ignore
(ev: DragEndEvent) => this._locationUpdated(ev.target.getLatLng())
);
this._leafletMap!.addLayer(this._locationMarker);
} else {
this._locationMarker = this.Leaflet!.circle(this.location, {
color: this.radiusColor || defaultRadiusColor,
radius: this.radius,
});
this._leafletMap!.addLayer(this._locationMarker);
this._setupEdit();
}
}
private _updateRadius(): void {
if (!this._locationMarker || !this.radius) {
return;
}
(this._locationMarker as Circle).setRadius(this.radius);
}
private _updateRadiusColor(): void {
if (!this._locationMarker || !this.radius) {
return;
}
(this._locationMarker as Circle).setStyle({ color: this.radiusColor });
}
static get styles(): CSSResultGroup {
return css`
:host {
display: block;
height: 300px;
}
#map {
height: 100%;
background: inherit;
}
.leaflet-edit-move {
border-radius: 50%;
cursor: move !important;
}
.leaflet-edit-resize {
border-radius: 50%;
cursor: nesw-resize !important;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-location-editor": LocationEditor;
}
}

View File

@@ -3,10 +3,8 @@ import {
DivIcon, DivIcon,
DragEndEvent, DragEndEvent,
LatLng, LatLng,
Map,
Marker, Marker,
MarkerOptions, MarkerOptions,
TileLayer,
} from "leaflet"; } from "leaflet";
import { import {
css, css,
@@ -16,15 +14,13 @@ import {
PropertyValues, PropertyValues,
TemplateResult, TemplateResult,
} from "lit"; } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../common/dom/fire_event"; import { fireEvent } from "../../common/dom/fire_event";
import { import type { LeafletModuleType } from "../../common/dom/setup-leaflet-map";
LeafletModuleType, import type { HomeAssistant } from "../../types";
replaceTileLayer, import "./ha-map";
setupLeafletMap, import type { HaMap } from "./ha-map";
} from "../../common/dom/setup-leaflet-map";
import { defaultRadiusColor } from "../../data/zone";
import { HomeAssistant } from "../../types";
declare global { declare global {
// for fire event // for fire event
@@ -51,38 +47,40 @@ export interface MarkerLocation {
export class HaLocationsEditor extends LitElement { export class HaLocationsEditor extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property() public locations?: MarkerLocation[]; @property({ attribute: false }) public locations?: MarkerLocation[];
public fitZoom = 16; @property({ type: Boolean }) public autoFit = false;
@property({ type: Number }) public zoom = 16;
@property({ type: Boolean }) public darkMode?: boolean;
@state() private _locationMarkers?: Record<string, Marker | Circle>;
@state() private _circles: Record<string, Circle> = {};
@query("ha-map", true) private map!: HaMap;
// eslint-disable-next-line
private Leaflet?: LeafletModuleType; private Leaflet?: LeafletModuleType;
// eslint-disable-next-line constructor() {
private _leafletMap?: Map; super();
private _tileLayer?: TileLayer; import("leaflet").then((module) => {
import("leaflet-draw").then(() => {
private _locationMarkers?: { [key: string]: Marker | Circle }; this.Leaflet = module.default as LeafletModuleType;
this._updateMarkers();
private _circles: Record<string, Circle> = {}; this.updateComplete.then(() => this.fitMap());
});
});
}
public fitMap(): void { public fitMap(): void {
if ( this.map.fitMap();
!this._leafletMap ||
!this._locationMarkers ||
!Object.keys(this._locationMarkers).length
) {
return;
}
const bounds = this.Leaflet!.latLngBounds(
Object.values(this._locationMarkers).map((item) => item.getLatLng())
);
this._leafletMap.fitBounds(bounds.pad(0.5));
} }
public fitMarker(id: string): void { public fitMarker(id: string): void {
if (!this._leafletMap || !this._locationMarkers) { if (!this.map.leafletMap || !this._locationMarkers) {
return; return;
} }
const marker = this._locationMarkers[id]; const marker = this._locationMarkers[id];
@@ -90,29 +88,44 @@ export class HaLocationsEditor extends LitElement {
return; return;
} }
if ("getBounds" in marker) { if ("getBounds" in marker) {
this._leafletMap.fitBounds(marker.getBounds()); this.map.leafletMap.fitBounds(marker.getBounds());
(marker as Circle).bringToFront(); (marker as Circle).bringToFront();
} else { } else {
const circle = this._circles[id]; const circle = this._circles[id];
if (circle) { if (circle) {
this._leafletMap.fitBounds(circle.getBounds()); this.map.leafletMap.fitBounds(circle.getBounds());
} else { } else {
this._leafletMap.setView(marker.getLatLng(), this.fitZoom); this.map.leafletMap.setView(marker.getLatLng(), this.zoom);
} }
} }
} }
protected render(): TemplateResult { protected render(): TemplateResult {
return html` <div id="map"></div> `; return html`<ha-map
.hass=${this.hass}
.layers=${this._getLayers(this._circles, this._locationMarkers)}
.zoom=${this.zoom}
.autoFit=${this.autoFit}
.darkMode=${this.darkMode}
></ha-map>`;
} }
protected firstUpdated(changedProps: PropertyValues): void { private _getLayers = memoizeOne(
super.firstUpdated(changedProps); (
this._initMap(); circles: Record<string, Circle>,
} markers?: Record<string, Marker | Circle>
): Array<Marker | Circle> => {
const layers: Array<Marker | Circle> = [];
Array.prototype.push.apply(layers, Object.values(circles));
if (markers) {
Array.prototype.push.apply(layers, Object.values(markers));
}
return layers;
}
);
protected updated(changedProps: PropertyValues): void { public willUpdate(changedProps: PropertyValues): void {
super.updated(changedProps); super.willUpdate(changedProps);
// Still loading. // Still loading.
if (!this.Leaflet) { if (!this.Leaflet) {
@@ -122,37 +135,6 @@ export class HaLocationsEditor extends LitElement {
if (changedProps.has("locations")) { if (changedProps.has("locations")) {
this._updateMarkers(); this._updateMarkers();
} }
if (changedProps.has("hass")) {
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
if (!oldHass || oldHass.themes.darkMode === this.hass.themes.darkMode) {
return;
}
if (!this._leafletMap || !this._tileLayer) {
return;
}
this._tileLayer = replaceTileLayer(
this.Leaflet,
this._leafletMap,
this._tileLayer,
this.hass.themes.darkMode
);
}
}
private get _mapEl(): HTMLDivElement {
return this.shadowRoot!.querySelector("div")!;
}
private async _initMap(): Promise<void> {
[this._leafletMap, this.Leaflet, this._tileLayer] = await setupLeafletMap(
this._mapEl,
this.hass.themes.darkMode,
true
);
this._updateMarkers();
this.fitMap();
this._leafletMap.invalidateSize();
} }
private _updateLocation(ev: DragEndEvent) { private _updateLocation(ev: DragEndEvent) {
@@ -189,21 +171,18 @@ export class HaLocationsEditor extends LitElement {
} }
private _updateMarkers(): void { private _updateMarkers(): void {
if (this._locationMarkers) {
Object.values(this._locationMarkers).forEach((marker) => {
marker.remove();
});
this._locationMarkers = undefined;
Object.values(this._circles).forEach((circle) => circle.remove());
this._circles = {};
}
if (!this.locations || !this.locations.length) { if (!this.locations || !this.locations.length) {
this._circles = {};
this._locationMarkers = undefined;
return; return;
} }
this._locationMarkers = {}; const locationMarkers = {};
const circles = {};
const defaultZoneRadiusColor = getComputedStyle(this).getPropertyValue(
"--accent-color"
);
this.locations.forEach((location: MarkerLocation) => { this.locations.forEach((location: MarkerLocation) => {
let icon: DivIcon | undefined; let icon: DivIcon | undefined;
@@ -228,45 +207,46 @@ export class HaLocationsEditor extends LitElement {
const circle = this.Leaflet!.circle( const circle = this.Leaflet!.circle(
[location.latitude, location.longitude], [location.latitude, location.longitude],
{ {
color: location.radius_color || defaultRadiusColor, color: location.radius_color || defaultZoneRadiusColor,
radius: location.radius, radius: location.radius,
} }
); );
circle.addTo(this._leafletMap!);
if (location.radius_editable || location.location_editable) { if (location.radius_editable || location.location_editable) {
// @ts-ignore // @ts-ignore
circle.editing.enable(); circle.editing.enable();
// @ts-ignore circle.addEventListener("add", () => {
const moveMarker = circle.editing._moveMarker; // @ts-ignore
// @ts-ignore const moveMarker = circle.editing._moveMarker;
const resizeMarker = circle.editing._resizeMarkers[0]; // @ts-ignore
if (icon) { const resizeMarker = circle.editing._resizeMarkers[0];
moveMarker.setIcon(icon); if (icon) {
} moveMarker.setIcon(icon);
resizeMarker.id = moveMarker.id = location.id; }
moveMarker resizeMarker.id = moveMarker.id = location.id;
.addEventListener( moveMarker
"dragend", .addEventListener(
// @ts-ignore "dragend",
(ev: DragEndEvent) => this._updateLocation(ev) // @ts-ignore
) (ev: DragEndEvent) => this._updateLocation(ev)
.addEventListener( )
"click", .addEventListener(
// @ts-ignore "click",
(ev: MouseEvent) => this._markerClicked(ev) // @ts-ignore
); (ev: MouseEvent) => this._markerClicked(ev)
if (location.radius_editable) { );
resizeMarker.addEventListener( if (location.radius_editable) {
"dragend", resizeMarker.addEventListener(
// @ts-ignore "dragend",
(ev: DragEndEvent) => this._updateRadius(ev) // @ts-ignore
); (ev: DragEndEvent) => this._updateRadius(ev)
} else { );
resizeMarker.remove(); } else {
} resizeMarker.remove();
this._locationMarkers![location.id] = circle; }
});
locationMarkers[location.id] = circle;
} else { } else {
this._circles[location.id] = circle; circles[location.id] = circle;
} }
} }
if ( if (
@@ -275,6 +255,7 @@ export class HaLocationsEditor extends LitElement {
) { ) {
const options: MarkerOptions = { const options: MarkerOptions = {
title: location.name, title: location.name,
draggable: location.location_editable,
}; };
if (icon) { if (icon) {
@@ -293,13 +274,14 @@ export class HaLocationsEditor extends LitElement {
"click", "click",
// @ts-ignore // @ts-ignore
(ev: MouseEvent) => this._markerClicked(ev) (ev: MouseEvent) => this._markerClicked(ev)
) );
.addTo(this._leafletMap!);
(marker as any).id = location.id; (marker as any).id = location.id;
this._locationMarkers![location.id] = marker; locationMarkers[location.id] = marker;
} }
}); });
this._circles = circles;
this._locationMarkers = locationMarkers;
} }
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
@@ -308,23 +290,9 @@ export class HaLocationsEditor extends LitElement {
display: block; display: block;
height: 300px; height: 300px;
} }
#map { ha-map {
height: 100%; height: 100%;
} }
.leaflet-marker-draggable {
cursor: move !important;
}
.leaflet-edit-resize {
border-radius: 50%;
cursor: nesw-resize !important;
}
.named-icon {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
text-align: center;
}
`; `;
} }
} }

View File

@@ -1,13 +1,15 @@
import { Circle, Layer, Map, Marker, TileLayer } from "leaflet";
import { import {
css, Circle,
CSSResultGroup, CircleMarker,
html, LatLngTuple,
LitElement, Layer,
PropertyValues, Map,
TemplateResult, Marker,
} from "lit"; Polyline,
import { customElement, property } from "lit/decorators"; TileLayer,
} from "leaflet";
import { css, CSSResultGroup, PropertyValues, ReactiveElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { import {
LeafletModuleType, LeafletModuleType,
replaceTileLayer, replaceTileLayer,
@@ -15,194 +17,324 @@ import {
} from "../../common/dom/setup-leaflet-map"; } from "../../common/dom/setup-leaflet-map";
import { computeStateDomain } from "../../common/entity/compute_state_domain"; import { computeStateDomain } from "../../common/entity/compute_state_domain";
import { computeStateName } from "../../common/entity/compute_state_name"; import { computeStateName } from "../../common/entity/compute_state_name";
import { debounce } from "../../common/util/debounce"; import "./ha-entity-marker";
import "../../panels/map/ha-entity-marker";
import { HomeAssistant } from "../../types"; import { HomeAssistant } from "../../types";
import "../ha-icon-button"; import "../ha-icon-button";
import { installResizeObserver } from "../../panels/lovelace/common/install-resize-observer";
const getEntityId = (entity: string | HaMapEntity): string =>
typeof entity === "string" ? entity : entity.entity_id;
export interface HaMapPaths {
points: LatLngTuple[];
color?: string;
gradualOpacity?: number;
}
export interface HaMapEntity {
entity_id: string;
color: string;
}
@customElement("ha-map") @customElement("ha-map")
class HaMap extends LitElement { export class HaMap extends ReactiveElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property() public entities?: string[]; @property({ attribute: false }) public entities?: string[] | HaMapEntity[];
@property() public darkMode?: boolean; @property({ attribute: false }) public paths?: HaMapPaths[];
@property() public zoom?: number; @property({ attribute: false }) public layers?: Layer[];
@property({ type: Boolean }) public autoFit = false;
@property({ type: Boolean }) public fitZones?: boolean;
@property({ type: Boolean }) public darkMode?: boolean;
@property({ type: Number }) public zoom = 14;
@state() private _loaded = false;
public leafletMap?: Map;
// eslint-disable-next-line
private Leaflet?: LeafletModuleType; private Leaflet?: LeafletModuleType;
private _leafletMap?: Map;
private _tileLayer?: TileLayer; private _tileLayer?: TileLayer;
// @ts-ignore
private _resizeObserver?: ResizeObserver; private _resizeObserver?: ResizeObserver;
private _debouncedResizeListener = debounce(
() => {
if (!this._leafletMap) {
return;
}
this._leafletMap.invalidateSize();
},
100,
false
);
private _mapItems: Array<Marker | Circle> = []; private _mapItems: Array<Marker | Circle> = [];
private _mapZones: Array<Marker | Circle> = []; private _mapZones: Array<Marker | Circle> = [];
private _connected = false; private _mapPaths: Array<Polyline | CircleMarker> = [];
public connectedCallback(): void { public connectedCallback(): void {
super.connectedCallback(); super.connectedCallback();
this._connected = true; this._loadMap();
if (this.hasUpdated) { this._attachObserver();
this.loadMap();
this._attachObserver();
}
} }
public disconnectedCallback(): void { public disconnectedCallback(): void {
super.disconnectedCallback(); super.disconnectedCallback();
this._connected = false; if (this.leafletMap) {
this.leafletMap.remove();
if (this._leafletMap) { this.leafletMap = undefined;
this._leafletMap.remove();
this._leafletMap = undefined;
this.Leaflet = undefined; this.Leaflet = undefined;
} }
this._loaded = false;
if (this._resizeObserver) { if (this._resizeObserver) {
this._resizeObserver.unobserve(this._mapEl); this._resizeObserver.unobserve(this);
} else {
window.removeEventListener("resize", this._debouncedResizeListener);
} }
} }
protected render(): TemplateResult { protected update(changedProps: PropertyValues) {
if (!this.entities) { super.update(changedProps);
return html``;
}
return html` <div id="map"></div> `;
}
protected firstUpdated(changedProps: PropertyValues): void { if (!this._loaded) {
super.firstUpdated(changedProps);
this.loadMap();
if (this._connected) {
this._attachObserver();
}
}
protected shouldUpdate(changedProps) {
if (!changedProps.has("hass") || changedProps.size > 1) {
return true;
}
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
if (!oldHass || !this.entities) {
return true;
}
// Check if any state has changed
for (const entity of this.entities) {
if (oldHass.states[entity] !== this.hass!.states[entity]) {
return true;
}
}
return false;
}
protected updated(changedProps: PropertyValues): void {
if (changedProps.has("hass")) {
this._drawEntities();
this._fitMap();
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
if (!oldHass || oldHass.themes.darkMode === this.hass.themes.darkMode) {
return;
}
if (!this.Leaflet || !this._leafletMap || !this._tileLayer) {
return;
}
this._tileLayer = replaceTileLayer(
this.Leaflet,
this._leafletMap,
this._tileLayer,
this.hass.themes.darkMode
);
}
}
private get _mapEl(): HTMLDivElement {
return this.shadowRoot!.getElementById("map") as HTMLDivElement;
}
private async loadMap(): Promise<void> {
[this._leafletMap, this.Leaflet, this._tileLayer] = await setupLeafletMap(
this._mapEl,
this.darkMode ?? this.hass.themes.darkMode
);
this._drawEntities();
this._leafletMap.invalidateSize();
this._fitMap();
}
private _fitMap(): void {
if (!this._leafletMap || !this.Leaflet || !this.hass) {
return; return;
} }
if (this._mapItems.length === 0) { const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
this._leafletMap.setView(
if (changedProps.has("_loaded") || changedProps.has("entities")) {
this._drawEntities();
} else if (this._loaded && oldHass && this.entities) {
// Check if any state has changed
for (const entity of this.entities) {
if (
oldHass.states[getEntityId(entity)] !==
this.hass!.states[getEntityId(entity)]
) {
this._drawEntities();
break;
}
}
}
if (changedProps.has("_loaded") || changedProps.has("paths")) {
this._drawPaths();
}
if (changedProps.has("_loaded") || changedProps.has("layers")) {
this._drawLayers(changedProps.get("layers") as Layer[] | undefined);
}
if (
changedProps.has("_loaded") ||
((changedProps.has("entities") || changedProps.has("layers")) &&
this.autoFit)
) {
this.fitMap();
}
if (changedProps.has("zoom")) {
this.leafletMap!.setZoom(this.zoom);
}
if (
!changedProps.has("darkMode") &&
(!changedProps.has("hass") ||
(oldHass && oldHass.themes.darkMode === this.hass.themes.darkMode))
) {
return;
}
const darkMode = this.darkMode ?? this.hass.themes.darkMode;
this._tileLayer = replaceTileLayer(
this.Leaflet!,
this.leafletMap!,
this._tileLayer!,
darkMode
);
this.shadowRoot!.getElementById("map")!.classList.toggle("dark", darkMode);
}
private async _loadMap(): Promise<void> {
let map = this.shadowRoot!.getElementById("map");
if (!map) {
map = document.createElement("div");
map.id = "map";
this.shadowRoot!.append(map);
}
const darkMode = this.darkMode ?? this.hass.themes.darkMode;
[this.leafletMap, this.Leaflet, this._tileLayer] = await setupLeafletMap(
map,
darkMode
);
this.shadowRoot!.getElementById("map")!.classList.toggle("dark", darkMode);
this._loaded = true;
}
public fitMap(): void {
if (!this.leafletMap || !this.Leaflet || !this.hass) {
return;
}
if (!this._mapItems.length && !this.layers?.length) {
this.leafletMap.setView(
new this.Leaflet.LatLng( new this.Leaflet.LatLng(
this.hass.config.latitude, this.hass.config.latitude,
this.hass.config.longitude this.hass.config.longitude
), ),
this.zoom || 14 this.zoom
); );
return; return;
} }
const bounds = this.Leaflet.latLngBounds( let bounds = this.Leaflet.latLngBounds(
this._mapItems ? this._mapItems.map((item) => item.getLatLng()) : [] this._mapItems ? this._mapItems.map((item) => item.getLatLng()) : []
); );
this._leafletMap.fitBounds(bounds.pad(0.5));
if (this.zoom && this._leafletMap.getZoom() > this.zoom) { if (this.fitZones) {
this._leafletMap.setZoom(this.zoom); this._mapZones?.forEach((zone) => {
bounds.extend(
"getBounds" in zone ? zone.getBounds() : zone.getLatLng()
);
});
} }
this.layers?.forEach((layer: any) => {
bounds.extend(
"getBounds" in layer ? layer.getBounds() : layer.getLatLng()
);
});
if (!this.layers) {
bounds = bounds.pad(0.5);
}
this.leafletMap.fitBounds(bounds, { maxZoom: this.zoom });
}
private _drawLayers(prevLayers: Layer[] | undefined): void {
if (prevLayers) {
prevLayers.forEach((layer) => layer.remove());
}
if (!this.layers) {
return;
}
const map = this.leafletMap!;
this.layers.forEach((layer) => {
map.addLayer(layer);
});
}
private _drawPaths(): void {
const hass = this.hass;
const map = this.leafletMap;
const Leaflet = this.Leaflet;
if (!hass || !map || !Leaflet) {
return;
}
if (this._mapPaths.length) {
this._mapPaths.forEach((marker) => marker.remove());
this._mapPaths = [];
}
if (!this.paths) {
return;
}
const darkPrimaryColor = getComputedStyle(this).getPropertyValue(
"--dark-primary-color"
);
this.paths.forEach((path) => {
let opacityStep: number;
let baseOpacity: number;
if (path.gradualOpacity) {
opacityStep = path.gradualOpacity / (path.points.length - 2);
baseOpacity = 1 - path.gradualOpacity;
}
for (
let pointIndex = 0;
pointIndex < path.points.length - 1;
pointIndex++
) {
const opacity = path.gradualOpacity
? baseOpacity! + pointIndex * opacityStep!
: undefined;
// DRAW point
this._mapPaths.push(
Leaflet!.circleMarker(path.points[pointIndex], {
radius: 3,
color: path.color || darkPrimaryColor,
opacity,
fillOpacity: opacity,
interactive: false,
})
);
// DRAW line between this and next point
this._mapPaths.push(
Leaflet!.polyline(
[path.points[pointIndex], path.points[pointIndex + 1]],
{
color: path.color || darkPrimaryColor,
opacity,
interactive: false,
}
)
);
}
const pointIndex = path.points.length - 1;
if (pointIndex >= 0) {
const opacity = path.gradualOpacity
? baseOpacity! + pointIndex * opacityStep!
: undefined;
// DRAW end path point
this._mapPaths.push(
Leaflet!.circleMarker(path.points[pointIndex], {
radius: 3,
color: path.color || darkPrimaryColor,
opacity,
fillOpacity: opacity,
interactive: false,
})
);
}
this._mapPaths.forEach((marker) => map.addLayer(marker));
});
} }
private _drawEntities(): void { private _drawEntities(): void {
const hass = this.hass; const hass = this.hass;
const map = this._leafletMap; const map = this.leafletMap;
const Leaflet = this.Leaflet; const Leaflet = this.Leaflet;
if (!hass || !map || !Leaflet) { if (!hass || !map || !Leaflet) {
return; return;
} }
if (this._mapItems) { if (this._mapItems.length) {
this._mapItems.forEach((marker) => marker.remove()); this._mapItems.forEach((marker) => marker.remove());
this._mapItems = [];
} }
const mapItems: Layer[] = (this._mapItems = []);
if (this._mapZones) { if (this._mapZones.length) {
this._mapZones.forEach((marker) => marker.remove()); this._mapZones.forEach((marker) => marker.remove());
this._mapZones = [];
} }
const mapZones: Layer[] = (this._mapZones = []);
const allEntities = this.entities!.concat(); if (!this.entities) {
return;
}
for (const entity of allEntities) { const computedStyles = getComputedStyle(this);
const entityId = entity; const zoneColor = computedStyles.getPropertyValue("--accent-color");
const stateObj = hass.states[entityId]; const darkPrimaryColor = computedStyles.getPropertyValue(
"--dark-primary-color"
);
const className =
this.darkMode ?? this.hass.themes.darkMode ? "dark" : "light";
for (const entity of this.entities) {
const stateObj = hass.states[getEntityId(entity)];
if (!stateObj) { if (!stateObj) {
continue; continue;
} }
@@ -240,13 +372,12 @@ class HaMap extends LitElement {
} }
// create marker with the icon // create marker with the icon
mapZones.push( this._mapZones.push(
Leaflet.marker([latitude, longitude], { Leaflet.marker([latitude, longitude], {
icon: Leaflet.divIcon({ icon: Leaflet.divIcon({
html: iconHTML, html: iconHTML,
iconSize: [24, 24], iconSize: [24, 24],
className: className,
this.darkMode ?? this.hass.themes.darkMode ? "dark" : "light",
}), }),
interactive: false, interactive: false,
title, title,
@@ -254,10 +385,10 @@ class HaMap extends LitElement {
); );
// create circle around it // create circle around it
mapZones.push( this._mapZones.push(
Leaflet.circle([latitude, longitude], { Leaflet.circle([latitude, longitude], {
interactive: false, interactive: false,
color: "#FF9800", color: zoneColor,
radius, radius,
}) })
); );
@@ -273,17 +404,20 @@ class HaMap extends LitElement {
.join("") .join("")
.substr(0, 3); .substr(0, 3);
// create market with the icon // create marker with the icon
mapItems.push( this._mapItems.push(
Leaflet.marker([latitude, longitude], { Leaflet.marker([latitude, longitude], {
icon: Leaflet.divIcon({ icon: Leaflet.divIcon({
// Leaflet clones this element before adding it to the map. This messes up
// our Polymer object and we can't pass data through. Thus we hack like this.
html: ` html: `
<ha-entity-marker <ha-entity-marker
entity-id="${entityId}" entity-id="${getEntityId(entity)}"
entity-name="${entityName}" entity-name="${entityName}"
entity-picture="${entityPicture || ""}" entity-picture="${entityPicture || ""}"
${
typeof entity !== "string"
? `entity-color="${entity.color}"`
: ""
}
></ha-entity-marker> ></ha-entity-marker>
`, `,
iconSize: [48, 48], iconSize: [48, 48],
@@ -295,10 +429,10 @@ class HaMap extends LitElement {
// create circle around if entity has accuracy // create circle around if entity has accuracy
if (gpsAccuracy) { if (gpsAccuracy) {
mapItems.push( this._mapItems.push(
Leaflet.circle([latitude, longitude], { Leaflet.circle([latitude, longitude], {
interactive: false, interactive: false,
color: "#0288D1", color: darkPrimaryColor,
radius: gpsAccuracy, radius: gpsAccuracy,
}) })
); );
@@ -309,20 +443,14 @@ class HaMap extends LitElement {
this._mapZones.forEach((marker) => map.addLayer(marker)); this._mapZones.forEach((marker) => map.addLayer(marker));
} }
private _attachObserver(): void { private async _attachObserver(): Promise<void> {
// Observe changes to map size and invalidate to prevent broken rendering if (!this._resizeObserver) {
// Uses ResizeObserver in Chrome, otherwise window resize event await installResizeObserver();
this._resizeObserver = new ResizeObserver(() => {
// @ts-ignore this.leafletMap?.invalidateSize({ debounceMoveend: true });
if (typeof ResizeObserver === "function") { });
// @ts-ignore
this._resizeObserver = new ResizeObserver(() =>
this._debouncedResizeListener()
);
this._resizeObserver.observe(this._mapEl);
} else {
window.addEventListener("resize", this._debouncedResizeListener);
} }
this._resizeObserver.observe(this);
} }
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
@@ -337,13 +465,25 @@ class HaMap extends LitElement {
#map.dark { #map.dark {
background: #090909; background: #090909;
} }
.light {
color: #000000;
}
.dark { .dark {
color: #ffffff; color: #ffffff;
} }
.leaflet-marker-draggable {
.light { cursor: move !important;
color: #000000; }
.leaflet-edit-resize {
border-radius: 50%;
cursor: nesw-resize !important;
}
.named-icon {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
text-align: center;
} }
`; `;
} }

View File

@@ -41,6 +41,7 @@ export interface HassioSnapshotDetail extends HassioSnapshot {
export interface HassioFullSnapshotCreateParams { export interface HassioFullSnapshotCreateParams {
name: string; name: string;
password?: string; password?: string;
confirm_password?: string;
} }
export interface HassioPartialSnapshotCreateParams export interface HassioPartialSnapshotCreateParams
extends HassioFullSnapshotCreateParams { extends HassioFullSnapshotCreateParams {

View File

@@ -13,6 +13,11 @@ import {
} from "../hassio/supervisor"; } from "../hassio/supervisor";
import { SupervisorStore } from "./store"; import { SupervisorStore } from "./store";
export interface supervisorApplyUpdateDetails {
name: string;
version: string;
}
export const supervisorWSbaseCommand = { export const supervisorWSbaseCommand = {
type: "supervisor/api", type: "supervisor/api",
method: "GET", method: "GET",

View File

@@ -1,14 +1,6 @@
import { navigate } from "../common/navigate"; import { navigate } from "../common/navigate";
import {
DEFAULT_ACCENT_COLOR,
DEFAULT_PRIMARY_COLOR,
} from "../resources/ha-style";
import { HomeAssistant } from "../types"; import { HomeAssistant } from "../types";
export const defaultRadiusColor = DEFAULT_ACCENT_COLOR;
export const homeRadiusColor = DEFAULT_PRIMARY_COLOR;
export const passiveRadiusColor = "#9b9b9b";
export interface Zone { export interface Zone {
id: string; id: string;
name: string; name: string;

View File

@@ -28,6 +28,7 @@ class MoreInfoPerson extends LitElement {
<ha-map <ha-map
.hass=${this.hass} .hass=${this.hass}
.entities=${this._entityArray(this.stateObj.entity_id)} .entities=${this._entityArray(this.stateObj.entity_id)}
autoFit
></ha-map> ></ha-map>
` `
: ""} : ""}

View File

@@ -5,6 +5,7 @@ import { computeStateDomain } from "../../common/entity/compute_state_domain";
import { throttle } from "../../common/util/throttle"; import { throttle } from "../../common/util/throttle";
import "../../components/ha-circular-progress"; import "../../components/ha-circular-progress";
import "../../components/state-history-charts"; import "../../components/state-history-charts";
import { fetchUsers } from "../../data/user";
import { getLogbookData, LogbookEntry } from "../../data/logbook"; import { getLogbookData, LogbookEntry } from "../../data/logbook";
import { loadTraceContexts, TraceContexts } from "../../data/trace"; import { loadTraceContexts, TraceContexts } from "../../data/trace";
import "../../panels/logbook/ha-logbook"; import "../../panels/logbook/ha-logbook";
@@ -22,10 +23,12 @@ export class MoreInfoLogbook extends LitElement {
@state() private _traceContexts?: TraceContexts; @state() private _traceContexts?: TraceContexts;
@state() private _persons = {}; @state() private _userIdToName = {};
private _lastLogbookDate?: Date; private _lastLogbookDate?: Date;
private _fetchUserPromise?: Promise<void>;
private _throttleGetLogbookEntries = throttle(() => { private _throttleGetLogbookEntries = throttle(() => {
this._getLogBookData(); this._getLogBookData();
}, 10000); }, 10000);
@@ -59,7 +62,7 @@ export class MoreInfoLogbook extends LitElement {
.hass=${this.hass} .hass=${this.hass}
.entries=${this._logbookEntries} .entries=${this._logbookEntries}
.traceContexts=${this._traceContexts} .traceContexts=${this._traceContexts}
.userIdToName=${this._persons} .userIdToName=${this._userIdToName}
></ha-logbook> ></ha-logbook>
` `
: html`<div class="no-entries"> : html`<div class="no-entries">
@@ -70,7 +73,7 @@ export class MoreInfoLogbook extends LitElement {
} }
protected firstUpdated(): void { protected firstUpdated(): void {
this._fetchPersonNames(); this._fetchUserPromise = this._fetchUserNames();
this.addEventListener("click", (ev) => { this.addEventListener("click", (ev) => {
if ((ev.composedPath()[0] as HTMLElement).tagName === "A") { if ((ev.composedPath()[0] as HTMLElement).tagName === "A") {
setTimeout(() => closeDialog("ha-more-info-dialog"), 500); setTimeout(() => closeDialog("ha-more-info-dialog"), 500);
@@ -125,6 +128,7 @@ export class MoreInfoLogbook extends LitElement {
true true
), ),
loadTraceContexts(this.hass), loadTraceContexts(this.hass),
this._fetchUserPromise,
]); ]);
this._logbookEntries = this._logbookEntries this._logbookEntries = this._logbookEntries
? [...newEntries, ...this._logbookEntries] ? [...newEntries, ...this._logbookEntries]
@@ -133,16 +137,34 @@ export class MoreInfoLogbook extends LitElement {
this._traceContexts = traceContexts; this._traceContexts = traceContexts;
} }
private _fetchPersonNames() { private async _fetchUserNames() {
const userIdToName = {};
// Start loading users
const userProm = this.hass.user?.is_admin && fetchUsers(this.hass);
// Process persons
Object.values(this.hass.states).forEach((entity) => { Object.values(this.hass.states).forEach((entity) => {
if ( if (
entity.attributes.user_id && entity.attributes.user_id &&
computeStateDomain(entity) === "person" computeStateDomain(entity) === "person"
) { ) {
this._persons[entity.attributes.user_id] = this._userIdToName[entity.attributes.user_id] =
entity.attributes.friendly_name; entity.attributes.friendly_name;
} }
}); });
// Process users
if (userProm) {
const users = await userProm;
for (const user of users) {
if (!(user.id in userIdToName)) {
userIdToName[user.id] = user.name;
}
}
}
this._userIdToName = userIdToName;
} }
static get styles() { static get styles() {

View File

@@ -39,6 +39,7 @@ class HassLoadingScreen extends LitElement {
</div>`} </div>`}
<div class="content"> <div class="content">
<ha-circular-progress active></ha-circular-progress> <ha-circular-progress active></ha-circular-progress>
<slot></slot>
</div> </div>
`; `;
} }
@@ -76,6 +77,7 @@ class HassLoadingScreen extends LitElement {
.content { .content {
height: calc(100% - var(--header-height)); height: calc(100% - var(--header-height));
display: flex; display: flex;
flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }

View File

@@ -12,7 +12,10 @@ import { HASSDomEvent } from "../common/dom/fire_event";
import { extractSearchParamsObject } from "../common/url/search-params"; import { extractSearchParamsObject } from "../common/url/search-params";
import { subscribeOne } from "../common/util/subscribe-one"; import { subscribeOne } from "../common/util/subscribe-one";
import { AuthUrlSearchParams, hassUrl } from "../data/auth"; import { AuthUrlSearchParams, hassUrl } from "../data/auth";
import { fetchDiscoveryInformation } from "../data/discovery"; import {
DiscoveryInformation,
fetchDiscoveryInformation,
} from "../data/discovery";
import { import {
fetchOnboardingOverview, fetchOnboardingOverview,
OnboardingResponses, OnboardingResponses,
@@ -68,6 +71,8 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
@state() private _steps?: OnboardingStep[]; @state() private _steps?: OnboardingStep[];
@state() private _discoveryInformation?: DiscoveryInformation;
protected render(): TemplateResult { protected render(): TemplateResult {
const step = this._curStep()!; const step = this._curStep()!;
@@ -87,6 +92,7 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
? html`<onboarding-restore-snapshot ? html`<onboarding-restore-snapshot
.localize=${this.localize} .localize=${this.localize}
.restoring=${this._restoring} .restoring=${this._restoring}
.discoveryInformation=${this._discoveryInformation}
@restoring=${this._restoringSnapshot} @restoring=${this._restoringSnapshot}
> >
</onboarding-restore-snapshot>` </onboarding-restore-snapshot>`

View File

@@ -5,9 +5,11 @@ import "@polymer/paper-radio-button/paper-radio-button";
import "@polymer/paper-radio-group/paper-radio-group"; import "@polymer/paper-radio-group/paper-radio-group";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../common/dom/fire_event"; import { fireEvent } from "../common/dom/fire_event";
import type { LocalizeFunc } from "../common/translations/localize"; import type { LocalizeFunc } from "../common/translations/localize";
import "../components/map/ha-location-editor"; import "../components/map/ha-locations-editor";
import type { MarkerLocation } from "../components/map/ha-locations-editor";
import { createTimezoneListEl } from "../components/timezone-datalist"; import { createTimezoneListEl } from "../components/timezone-datalist";
import { import {
ConfigUpdateValues, ConfigUpdateValues,
@@ -81,14 +83,14 @@ class OnboardingCoreConfig extends LitElement {
</div> </div>
<div class="row"> <div class="row">
<ha-location-editor <ha-locations-editor
class="flex" class="flex"
.hass=${this.hass} .hass=${this.hass}
.location=${this._locationValue} .locations=${this._markerLocation(this._locationValue)}
.fitZoom=${14} zoom="14"
.darkMode=${mql.matches} .darkMode=${mql.matches}
@change=${this._locationChanged} @location-updated=${this._locationChanged}
></ha-location-editor> ></ha-locations-editor>
</div> </div>
<div class="row"> <div class="row">
@@ -208,13 +210,24 @@ class OnboardingCoreConfig extends LitElement {
return this._unitSystem !== undefined ? this._unitSystem : "metric"; return this._unitSystem !== undefined ? this._unitSystem : "metric";
} }
private _markerLocation = memoizeOne(
(location: [number, number]): MarkerLocation[] => [
{
id: "location",
latitude: location[0],
longitude: location[1],
location_editable: true,
},
]
);
private _handleChange(ev: PolymerChangedEvent<string>) { private _handleChange(ev: PolymerChangedEvent<string>) {
const target = ev.currentTarget as PaperInputElement; const target = ev.currentTarget as PaperInputElement;
this[`_${target.name}`] = target.value; this[`_${target.name}`] = target.value;
} }
private _locationChanged(ev) { private _locationChanged(ev) {
this._location = ev.currentTarget.location; this._location = ev.detail.location;
} }
private _unitSystemChanged( private _unitSystemChanged(

View File

@@ -4,9 +4,12 @@ import { customElement, property } from "lit/decorators";
import "../../hassio/src/components/hassio-ansi-to-html"; import "../../hassio/src/components/hassio-ansi-to-html";
import { showHassioSnapshotDialog } from "../../hassio/src/dialogs/snapshot/show-dialog-hassio-snapshot"; import { showHassioSnapshotDialog } from "../../hassio/src/dialogs/snapshot/show-dialog-hassio-snapshot";
import { showSnapshotUploadDialog } from "../../hassio/src/dialogs/snapshot/show-dialog-snapshot-upload"; import { showSnapshotUploadDialog } from "../../hassio/src/dialogs/snapshot/show-dialog-snapshot-upload";
import { navigate } from "../common/navigate";
import type { LocalizeFunc } from "../common/translations/localize"; import type { LocalizeFunc } from "../common/translations/localize";
import "../components/ha-card"; import "../components/ha-card";
import {
DiscoveryInformation,
fetchDiscoveryInformation,
} from "../data/discovery";
import { makeDialogManager } from "../dialogs/make-dialog-manager"; import { makeDialogManager } from "../dialogs/make-dialog-manager";
import { ProvideHassLitMixin } from "../mixins/provide-hass-lit-mixin"; import { ProvideHassLitMixin } from "../mixins/provide-hass-lit-mixin";
import { haStyle } from "../resources/styles"; import { haStyle } from "../resources/styles";
@@ -26,6 +29,9 @@ class OnboardingRestoreSnapshot extends ProvideHassLitMixin(LitElement) {
@property({ type: Boolean }) public restoring = false; @property({ type: Boolean }) public restoring = false;
@property({ attribute: false })
public discoveryInformation?: DiscoveryInformation;
protected render(): TemplateResult { protected render(): TemplateResult {
return this.restoring return this.restoring
? html`<ha-card ? html`<ha-card
@@ -58,13 +64,14 @@ class OnboardingRestoreSnapshot extends ProvideHassLitMixin(LitElement) {
private async _checkRestoreStatus(): Promise<void> { private async _checkRestoreStatus(): Promise<void> {
if (this.restoring) { if (this.restoring) {
try { try {
const response = await fetch("/api/hassio/supervisor/info", { const response = await fetchDiscoveryInformation();
method: "GET",
}); if (
if (response.status === 401) { !this.discoveryInformation ||
// If we get a unauthorized response, the restore is done this.discoveryInformation.uuid !== response.uuid
navigate("/", { replace: true }); ) {
location.reload(); // When the UUID changes, the restore is complete
window.location.replace("/");
} }
} catch (err) { } catch (err) {
// We fully expected issues with fetching info untill restore is complete. // We fully expected issues with fetching info untill restore is complete.
@@ -76,6 +83,7 @@ class OnboardingRestoreSnapshot extends ProvideHassLitMixin(LitElement) {
showHassioSnapshotDialog(this, { showHassioSnapshotDialog(this, {
slug, slug,
onboarding: true, onboarding: true,
localize: this.localize,
}); });
} }

View File

@@ -8,7 +8,8 @@ import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { UNIT_C } from "../../../common/const"; import { UNIT_C } from "../../../common/const";
import "../../../components/ha-card"; import "../../../components/ha-card";
import "../../../components/map/ha-location-editor"; import "../../../components/map/ha-locations-editor";
import type { MarkerLocation } from "../../../components/map/ha-locations-editor";
import { createTimezoneListEl } from "../../../components/timezone-datalist"; import { createTimezoneListEl } from "../../../components/timezone-datalist";
import { ConfigUpdateValues, saveCoreConfig } from "../../../data/core"; import { ConfigUpdateValues, saveCoreConfig } from "../../../data/core";
import type { PolymerChangedEvent } from "../../../polymer-types"; import type { PolymerChangedEvent } from "../../../polymer-types";
@@ -20,13 +21,13 @@ class ConfigCoreForm extends LitElement {
@state() private _working = false; @state() private _working = false;
@state() private _location!: [number, number]; @state() private _location?: [number, number];
@state() private _elevation!: string; @state() private _elevation?: string;
@state() private _unitSystem!: ConfigUpdateValues["unit_system"]; @state() private _unitSystem?: ConfigUpdateValues["unit_system"];
@state() private _timeZone!: string; @state() private _timeZone?: string;
protected render(): TemplateResult { protected render(): TemplateResult {
const canEdit = ["storage", "default"].includes( const canEdit = ["storage", "default"].includes(
@@ -52,16 +53,16 @@ class ConfigCoreForm extends LitElement {
: ""} : ""}
<div class="row"> <div class="row">
<ha-location-editor <ha-locations-editor
class="flex" class="flex"
.hass=${this.hass} .hass=${this.hass}
.location=${this._locationValue( .locations=${this._markerLocation(
this._location,
this.hass.config.latitude, this.hass.config.latitude,
this.hass.config.longitude this.hass.config.longitude,
this._location
)} )}
@change=${this._locationChanged} @location-updated=${this._locationChanged}
></ha-location-editor> ></ha-locations-editor>
</div> </div>
<div class="row"> <div class="row">
@@ -162,8 +163,19 @@ class ConfigCoreForm extends LitElement {
input.inputElement.appendChild(createTimezoneListEl()); input.inputElement.appendChild(createTimezoneListEl());
} }
private _locationValue = memoizeOne( private _markerLocation = memoizeOne(
(location, lat, lng) => location || [Number(lat), Number(lng)] (
lat: number,
lng: number,
location?: [number, number]
): MarkerLocation[] => [
{
id: "location",
latitude: location ? location[0] : lat,
longitude: location ? location[1] : lng,
location_editable: true,
},
]
); );
private get _elevationValue() { private get _elevationValue() {
@@ -192,7 +204,7 @@ class ConfigCoreForm extends LitElement {
} }
private _locationChanged(ev) { private _locationChanged(ev) {
this._location = ev.currentTarget.location; this._location = ev.detail.location;
} }
private _unitSystemChanged( private _unitSystemChanged(
@@ -204,11 +216,10 @@ class ConfigCoreForm extends LitElement {
private async _save() { private async _save() {
this._working = true; this._working = true;
try { try {
const location = this._locationValue( const location = this._location || [
this._location,
this.hass.config.latitude, this.hass.config.latitude,
this.hass.config.longitude this.hass.config.longitude,
); ];
await saveCoreConfig(this.hass, { await saveCoreConfig(this.hass, {
latitude: location[0], latitude: location[0],
longitude: location[1], longitude: location[1],

View File

@@ -9,13 +9,9 @@ import { computeRTLDirection } from "../../../common/util/compute_rtl";
import { createCloseHeading } from "../../../components/ha-dialog"; import { createCloseHeading } from "../../../components/ha-dialog";
import "../../../components/ha-formfield"; import "../../../components/ha-formfield";
import "../../../components/ha-switch"; import "../../../components/ha-switch";
import "../../../components/map/ha-location-editor"; import "../../../components/map/ha-locations-editor";
import { import type { MarkerLocation } from "../../../components/map/ha-locations-editor";
defaultRadiusColor, import { getZoneEditorInitData, ZoneMutableParams } from "../../../data/zone";
getZoneEditorInitData,
passiveRadiusColor,
ZoneMutableParams,
} from "../../../data/zone";
import { haStyleDialog } from "../../../resources/styles"; import { haStyleDialog } from "../../../resources/styles";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { ZoneDetailDialogParams } from "./show-dialog-zone-detail"; import { ZoneDetailDialogParams } from "./show-dialog-zone-detail";
@@ -132,17 +128,19 @@ class DialogZoneDetail extends LitElement {
)}" )}"
.invalid=${iconValid} .invalid=${iconValid}
></paper-input> ></paper-input>
<ha-location-editor <ha-locations-editor
class="flex" class="flex"
.hass=${this.hass} .hass=${this.hass}
.location=${this._locationValue(this._latitude, this._longitude)} .locations=${this._location(
.radius=${this._radius} this._latitude,
.radiusColor=${this._passive this._longitude,
? passiveRadiusColor this._radius,
: defaultRadiusColor} this._passive,
.icon=${this._icon} this._icon
@change=${this._locationChanged} )}
></ha-location-editor> @location-updated=${this._locationChanged}
@radius-updated=${this._radiusChanged}
></ha-locations-editor>
<div class="location"> <div class="location">
<paper-input <paper-input
.value=${this._latitude} .value=${this._latitude}
@@ -222,11 +220,40 @@ class DialogZoneDetail extends LitElement {
`; `;
} }
private _locationValue = memoizeOne((lat, lng) => [Number(lat), Number(lng)]); private _location = memoizeOne(
(
lat: number,
lng: number,
radius: number,
passive: boolean,
icon: string
): MarkerLocation[] => {
const computedStyles = getComputedStyle(this);
const zoneRadiusColor = computedStyles.getPropertyValue("--accent-color");
const passiveRadiusColor = computedStyles.getPropertyValue(
"--secondary-text-color"
);
return [
{
id: "location",
latitude: Number(lat),
longitude: Number(lng),
radius,
radius_color: passive ? passiveRadiusColor : zoneRadiusColor,
icon,
location_editable: true,
radius_editable: true,
},
];
}
);
private _locationChanged(ev) { private _locationChanged(ev: CustomEvent) {
[this._latitude, this._longitude] = ev.currentTarget.location; [this._latitude, this._longitude] = ev.detail.location;
this._radius = ev.currentTarget.radius; }
private _radiusChanged(ev: CustomEvent) {
this._radius = ev.detail.radius;
} }
private _passiveChanged(ev) { private _passiveChanged(ev) {
@@ -292,7 +319,7 @@ class DialogZoneDetail extends LitElement {
.location > *:last-child { .location > *:last-child {
margin-left: 4px; margin-left: 4px;
} }
ha-location-editor { ha-locations-editor {
margin-top: 16px; margin-top: 16px;
} }
a { a {

View File

@@ -31,11 +31,8 @@ import { saveCoreConfig } from "../../../data/core";
import { subscribeEntityRegistry } from "../../../data/entity_registry"; import { subscribeEntityRegistry } from "../../../data/entity_registry";
import { import {
createZone, createZone,
defaultRadiusColor,
deleteZone, deleteZone,
fetchZones, fetchZones,
homeRadiusColor,
passiveRadiusColor,
updateZone, updateZone,
Zone, Zone,
ZoneMutableParams, ZoneMutableParams,
@@ -73,6 +70,15 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
private _getZones = memoizeOne( private _getZones = memoizeOne(
(storageItems: Zone[], stateItems: HassEntity[]): MarkerLocation[] => { (storageItems: Zone[], stateItems: HassEntity[]): MarkerLocation[] => {
const computedStyles = getComputedStyle(this);
const zoneRadiusColor = computedStyles.getPropertyValue("--accent-color");
const passiveRadiusColor = computedStyles.getPropertyValue(
"--secondary-text-color"
);
const homeRadiusColor = computedStyles.getPropertyValue(
"--primary-color"
);
const stateLocations: MarkerLocation[] = stateItems.map( const stateLocations: MarkerLocation[] = stateItems.map(
(entityState) => ({ (entityState) => ({
id: entityState.entity_id, id: entityState.entity_id,
@@ -86,7 +92,7 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
? homeRadiusColor ? homeRadiusColor
: entityState.attributes.passive : entityState.attributes.passive
? passiveRadiusColor ? passiveRadiusColor
: defaultRadiusColor, : zoneRadiusColor,
location_editable: location_editable:
entityState.entity_id === "zone.home" && this._canEditCore, entityState.entity_id === "zone.home" && this._canEditCore,
radius_editable: false, radius_editable: false,
@@ -94,7 +100,7 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
); );
const storageLocations: MarkerLocation[] = storageItems.map((zone) => ({ const storageLocations: MarkerLocation[] = storageItems.map((zone) => ({
...zone, ...zone,
radius_color: zone.passive ? passiveRadiusColor : defaultRadiusColor, radius_color: zone.passive ? passiveRadiusColor : zoneRadiusColor,
location_editable: true, location_editable: true,
radius_editable: true, radius_editable: true,
})); }));
@@ -274,7 +280,7 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
} }
} }
protected updated(changedProps: PropertyValues) { public willUpdate(changedProps: PropertyValues) {
super.updated(changedProps); super.updated(changedProps);
const oldHass = changedProps.get("hass") as HomeAssistant | undefined; const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
if (oldHass && this._stateItems) { if (oldHass && this._stateItems) {
@@ -410,8 +416,9 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
if (this.narrow) { if (this.narrow) {
return; return;
} }
await this.updateComplete;
this._activeEntry = created.id; this._activeEntry = created.id;
await this.updateComplete;
await this._map?.updateComplete;
this._map?.fitMarker(created.id); this._map?.fitMarker(created.id);
} }
@@ -427,8 +434,9 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
if (this.narrow || !fitMap) { if (this.narrow || !fitMap) {
return; return;
} }
await this.updateComplete;
this._activeEntry = entry.id; this._activeEntry = entry.id;
await this.updateComplete;
await this._map?.updateComplete;
this._map?.fitMarker(entry.id); this._map?.fitMarker(entry.id);
} }

View File

@@ -1,22 +1,22 @@
import { mdiRefresh } from "@mdi/js"; import { mdiRefresh } from "@mdi/js";
import "@material/mwc-icon-button";
import "@polymer/app-layout/app-header/app-header"; import "@polymer/app-layout/app-header/app-header";
import "@polymer/app-layout/app-toolbar/app-toolbar"; import "@polymer/app-layout/app-toolbar/app-toolbar";
import { css, html, LitElement, PropertyValues } from "lit"; import { css, html, LitElement, PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { isComponentLoaded } from "../../common/config/is_component_loaded"; import { isComponentLoaded } from "../../common/config/is_component_loaded";
import { computeStateDomain } from "../../common/entity/compute_state_domain";
import { computeRTL } from "../../common/util/compute_rtl"; import { computeRTL } from "../../common/util/compute_rtl";
import "../../components/entity/ha-entity-picker"; import "../../components/entity/ha-entity-picker";
import "../../components/ha-circular-progress"; import "../../components/ha-circular-progress";
import "../../components/ha-date-range-picker"; import "../../components/ha-date-range-picker";
import type { DateRangePickerRanges } from "../../components/ha-date-range-picker"; import type { DateRangePickerRanges } from "../../components/ha-date-range-picker";
import "../../components/ha-icon-button";
import "../../components/ha-menu-button"; import "../../components/ha-menu-button";
import { import {
clearLogbookCache, clearLogbookCache,
getLogbookData, getLogbookData,
LogbookEntry, LogbookEntry,
} from "../../data/logbook"; } from "../../data/logbook";
import { fetchPersons } from "../../data/person";
import { loadTraceContexts, TraceContexts } from "../../data/trace"; import { loadTraceContexts, TraceContexts } from "../../data/trace";
import { fetchUsers } from "../../data/user"; import { fetchUsers } from "../../data/user";
import "../../layouts/ha-app-layout"; import "../../layouts/ha-app-layout";
@@ -44,7 +44,7 @@ export class HaPanelLogbook extends LitElement {
@state() private _ranges?: DateRangePickerRanges; @state() private _ranges?: DateRangePickerRanges;
private _fetchUserDone?: Promise<unknown>; private _fetchUserPromise?: Promise<void>;
@state() private _userIdToName = {}; @state() private _userIdToName = {};
@@ -136,7 +136,7 @@ export class HaPanelLogbook extends LitElement {
super.firstUpdated(changedProps); super.firstUpdated(changedProps);
this.hass.loadBackendTranslation("title"); this.hass.loadBackendTranslation("title");
this._fetchUserDone = this._fetchUserNames(); this._fetchUserPromise = this._fetchUserNames();
const today = new Date(); const today = new Date();
today.setHours(0, 0, 0, 0); today.setHours(0, 0, 0, 0);
@@ -198,23 +198,19 @@ export class HaPanelLogbook extends LitElement {
private async _fetchUserNames() { private async _fetchUserNames() {
const userIdToName = {}; const userIdToName = {};
// Start loading all the data // Start loading users
const personProm = fetchPersons(this.hass); const userProm = this.hass.user?.is_admin && fetchUsers(this.hass);
const userProm = this.hass.user!.is_admin && fetchUsers(this.hass);
// Process persons // Process persons
const persons = await personProm; Object.values(this.hass.states).forEach((entity) => {
if (
for (const person of persons.storage) { entity.attributes.user_id &&
if (person.user_id) { computeStateDomain(entity) === "person"
userIdToName[person.user_id] = person.name; ) {
this._userIdToName[entity.attributes.user_id] =
entity.attributes.friendly_name;
} }
} });
for (const person of persons.config) {
if (person.user_id) {
userIdToName[person.user_id] = person.name;
}
}
// Process users // Process users
if (userProm) { if (userProm) {
@@ -262,7 +258,7 @@ export class HaPanelLogbook extends LitElement {
this._entityId this._entityId
), ),
isComponentLoaded(this.hass, "trace") ? loadTraceContexts(this.hass) : {}, isComponentLoaded(this.hass, "trace") ? loadTraceContexts(this.hass) : {},
this._fetchUserDone, this._fetchUserPromise,
]); ]);
this._entries = entries; this._entries = entries;

View File

@@ -9,11 +9,12 @@ import {
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map"; import { classMap } from "lit/directives/class-map";
import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { computeStateDomain } from "../../../common/entity/compute_state_domain"; import { computeStateDomain } from "../../../common/entity/compute_state_domain";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { throttle } from "../../../common/util/throttle"; import { throttle } from "../../../common/util/throttle";
import "../../../components/ha-card"; import "../../../components/ha-card";
import "../../../components/ha-circular-progress"; import "../../../components/ha-circular-progress";
import { fetchUsers } from "../../../data/user";
import { getLogbookData, LogbookEntry } from "../../../data/logbook"; import { getLogbookData, LogbookEntry } from "../../../data/logbook";
import type { HomeAssistant } from "../../../types"; import type { HomeAssistant } from "../../../types";
import "../../logbook/ha-logbook"; import "../../logbook/ha-logbook";
@@ -51,18 +52,20 @@ export class HuiLogbookCard extends LitElement implements LovelaceCard {
}; };
} }
@property({ attribute: false }) public hass?: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@state() private _config?: LogbookCardConfig; @state() private _config?: LogbookCardConfig;
@state() private _logbookEntries?: LogbookEntry[]; @state() private _logbookEntries?: LogbookEntry[];
@state() private _persons = {};
@state() private _configEntities?: EntityConfig[]; @state() private _configEntities?: EntityConfig[];
@state() private _userIdToName = {};
private _lastLogbookDate?: Date; private _lastLogbookDate?: Date;
private _fetchUserPromise?: Promise<void>;
private _throttleGetLogbookEntries = throttle(() => { private _throttleGetLogbookEntries = throttle(() => {
this._getLogBookData(); this._getLogBookData();
}, 10000); }, 10000);
@@ -114,7 +117,7 @@ export class HuiLogbookCard extends LitElement implements LovelaceCard {
} }
protected firstUpdated(): void { protected firstUpdated(): void {
this._fetchPersonNames(); this._fetchUserPromise = this._fetchUserNames();
} }
protected updated(changedProperties: PropertyValues) { protected updated(changedProperties: PropertyValues) {
@@ -199,7 +202,7 @@ export class HuiLogbookCard extends LitElement implements LovelaceCard {
virtualize virtualize
.hass=${this.hass} .hass=${this.hass}
.entries=${this._logbookEntries} .entries=${this._logbookEntries}
.userIdToName=${this._persons} .userIdToName=${this._userIdToName}
></ha-logbook> ></ha-logbook>
` `
: html` : html`
@@ -229,13 +232,16 @@ export class HuiLogbookCard extends LitElement implements LovelaceCard {
const lastDate = this._lastLogbookDate || hoursToShowDate; const lastDate = this._lastLogbookDate || hoursToShowDate;
const now = new Date(); const now = new Date();
const newEntries = await getLogbookData( const [newEntries] = await Promise.all([
this.hass, getLogbookData(
lastDate.toISOString(), this.hass,
now.toISOString(), lastDate.toISOString(),
this._configEntities!.map((entity) => entity.entity).toString(), now.toISOString(),
true this._configEntities!.map((entity) => entity.entity).toString(),
); true
),
this._fetchUserPromise,
]);
const logbookEntries = this._logbookEntries const logbookEntries = this._logbookEntries
? [...newEntries, ...this._logbookEntries] ? [...newEntries, ...this._logbookEntries]
@@ -248,20 +254,34 @@ export class HuiLogbookCard extends LitElement implements LovelaceCard {
this._lastLogbookDate = now; this._lastLogbookDate = now;
} }
private _fetchPersonNames() { private async _fetchUserNames() {
if (!this.hass) { const userIdToName = {};
return;
}
// Start loading users
const userProm = this.hass.user?.is_admin && fetchUsers(this.hass);
// Process persons
Object.values(this.hass!.states).forEach((entity) => { Object.values(this.hass!.states).forEach((entity) => {
if ( if (
entity.attributes.user_id && entity.attributes.user_id &&
computeStateDomain(entity) === "person" computeStateDomain(entity) === "person"
) { ) {
this._persons[entity.attributes.user_id] = this._userIdToName[entity.attributes.user_id] =
entity.attributes.friendly_name; entity.attributes.friendly_name;
} }
}); });
// Process users
if (userProm) {
const users = await userProm;
for (const user of users) {
if (!(user.id in userIdToName)) {
userIdToName[user.id] = user.name;
}
}
}
this._userIdToName = userIdToName;
} }
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {

View File

@@ -1,14 +1,5 @@
import { HassEntity } from "home-assistant-js-websocket"; import { HassEntities, HassEntity } from "home-assistant-js-websocket";
import { import { LatLngTuple } from "leaflet";
Circle,
CircleMarker,
LatLngTuple,
Layer,
Map,
Marker,
Polyline,
TileLayer,
} from "leaflet";
import { import {
css, css,
CSSResultGroup, CSSResultGroup,
@@ -17,32 +8,106 @@ import {
PropertyValues, PropertyValues,
TemplateResult, TemplateResult,
} from "lit"; } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import {
LeafletModuleType,
replaceTileLayer,
setupLeafletMap,
} from "../../../common/dom/setup-leaflet-map";
import { computeDomain } from "../../../common/entity/compute_domain"; import { computeDomain } from "../../../common/entity/compute_domain";
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
import { computeStateName } from "../../../common/entity/compute_state_name";
import { debounce } from "../../../common/util/debounce";
import parseAspectRatio from "../../../common/util/parse-aspect-ratio"; import parseAspectRatio from "../../../common/util/parse-aspect-ratio";
import "../../../components/ha-card"; import "../../../components/ha-card";
import "../../../components/ha-icon-button"; import "../../../components/ha-icon-button";
import { fetchRecent } from "../../../data/history"; import { fetchRecent } from "../../../data/history";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import "../../map/ha-entity-marker"; import "../../../components/map/ha-entity-marker";
import { findEntities } from "../common/find-entities"; import { findEntities } from "../common/find-entities";
import { installResizeObserver } from "../common/install-resize-observer";
import { processConfigEntities } from "../common/process-config-entities"; import { processConfigEntities } from "../common/process-config-entities";
import { EntityConfig } from "../entity-rows/types"; import { EntityConfig } from "../entity-rows/types";
import { LovelaceCard } from "../types"; import { LovelaceCard } from "../types";
import { MapCardConfig } from "./types"; import { MapCardConfig } from "./types";
import "../../../components/map/ha-map";
import { mdiImageFilterCenterFocus } from "@mdi/js";
import type { HaMap, HaMapPaths } from "../../../components/map/ha-map";
import memoizeOne from "memoize-one";
const MINUTE = 60000;
const COLORS = [
"#0288D1",
"#00AA00",
"#984ea3",
"#00d2d5",
"#ff7f00",
"#af8d00",
"#7f80cd",
"#b3e900",
"#c42e60",
"#a65628",
"#f781bf",
"#8dd3c7",
];
@customElement("hui-map-card") @customElement("hui-map-card")
class HuiMapCard extends LitElement implements LovelaceCard { class HuiMapCard extends LitElement implements LovelaceCard {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Boolean, reflect: true })
public isPanel = false;
@state()
private _history?: HassEntity[][];
@state()
private _config?: MapCardConfig;
@query("ha-map")
private _map?: HaMap;
private _date?: Date;
private _configEntities?: string[];
private _colorDict: Record<string, string> = {};
private _colorIndex = 0;
public setConfig(config: MapCardConfig): void {
if (!config) {
throw new Error("Error in card configuration.");
}
if (!config.entities?.length && !config.geo_location_sources) {
throw new Error(
"Either entities or geo_location_sources must be specified"
);
}
if (config.entities && !Array.isArray(config.entities)) {
throw new Error("Entities need to be an array");
}
if (
config.geo_location_sources &&
!Array.isArray(config.geo_location_sources)
) {
throw new Error("Geo_location_sources needs to be an array");
}
this._config = config;
this._configEntities = (config.entities
? processConfigEntities<EntityConfig>(config.entities)
: []
).map((entity) => entity.entity);
this._cleanupHistory();
}
public getCardSize(): number {
if (!this._config?.aspect_ratio) {
return 7;
}
const ratio = parseAspectRatio(this._config.aspect_ratio);
const ar =
ratio && ratio.w > 0 && ratio.h > 0
? `${((100 * ratio.h) / ratio.w).toFixed(2)}`
: "100";
return 1 + Math.floor(Number(ar) / 25) || 3;
}
public static async getConfigElement() { public static async getConfigElement() {
await import("../editor/config-elements/hui-map-card-editor"); await import("../editor/config-elements/hui-map-card-editor");
return document.createElement("hui-map-card-editor"); return document.createElement("hui-map-card-editor");
@@ -66,129 +131,6 @@ class HuiMapCard extends LitElement implements LovelaceCard {
return { type: "map", entities: foundEntities }; return { type: "map", entities: foundEntities };
} }
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Boolean, reflect: true })
public isPanel = false;
@property()
private _history?: HassEntity[][];
private _date?: Date;
@property()
private _config?: MapCardConfig;
private _configEntities?: EntityConfig[];
// eslint-disable-next-line
private Leaflet?: LeafletModuleType;
private _leafletMap?: Map;
private _tileLayer?: TileLayer;
private _resizeObserver?: ResizeObserver;
private _debouncedResizeListener = debounce(
() => {
if (!this.isConnected || !this._leafletMap) {
return;
}
this._leafletMap.invalidateSize();
},
250,
false
);
private _mapItems: Array<Marker | Circle> = [];
private _mapZones: Array<Marker | Circle> = [];
private _mapPaths: Array<Polyline | CircleMarker> = [];
private _colorDict: Record<string, string> = {};
private _colorIndex = 0;
private _colors: string[] = [
"#0288D1",
"#00AA00",
"#984ea3",
"#00d2d5",
"#ff7f00",
"#af8d00",
"#7f80cd",
"#b3e900",
"#c42e60",
"#a65628",
"#f781bf",
"#8dd3c7",
];
public setConfig(config: MapCardConfig): void {
if (!config) {
throw new Error("Error in card configuration.");
}
if (!config.entities?.length && !config.geo_location_sources) {
throw new Error(
"Either entities or geo_location_sources must be specified"
);
}
if (config.entities && !Array.isArray(config.entities)) {
throw new Error("Entities need to be an array");
}
if (
config.geo_location_sources &&
!Array.isArray(config.geo_location_sources)
) {
throw new Error("Geo_location_sources needs to be an array");
}
this._config = config;
this._configEntities = config.entities
? processConfigEntities(config.entities)
: [];
this._cleanupHistory();
}
public getCardSize(): number {
if (!this._config?.aspect_ratio) {
return 7;
}
const ratio = parseAspectRatio(this._config.aspect_ratio);
const ar =
ratio && ratio.w > 0 && ratio.h > 0
? `${((100 * ratio.h) / ratio.w).toFixed(2)}`
: "100";
return 1 + Math.floor(Number(ar) / 25) || 3;
}
public connectedCallback(): void {
super.connectedCallback();
this._attachObserver();
if (this.hasUpdated) {
this.loadMap();
}
}
public disconnectedCallback(): void {
super.disconnectedCallback();
if (this._leafletMap) {
this._leafletMap.remove();
this._leafletMap = undefined;
this.Leaflet = undefined;
}
if (this._resizeObserver) {
this._resizeObserver.unobserve(this._mapEl);
}
}
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this._config) { if (!this._config) {
return html``; return html``;
@@ -196,22 +138,29 @@ class HuiMapCard extends LitElement implements LovelaceCard {
return html` return html`
<ha-card id="card" .header=${this._config.title}> <ha-card id="card" .header=${this._config.title}>
<div id="root"> <div id="root">
<div <ha-map
id="map" .hass=${this.hass}
class=${classMap({ dark: this._config.dark_mode === true })} .entities=${this._getEntities(
></div> this.hass.states,
<ha-icon-button this._config,
this._configEntities
)}
.paths=${this._getHistoryPaths(this._config, this._history)}
.darkMode=${this._config.dark_mode}
></ha-map>
<mwc-icon-button
@click=${this._fitMap} @click=${this._fitMap}
tabindex="0" tabindex="0"
icon="hass:image-filter-center-focus"
title="Reset focus" title="Reset focus"
></ha-icon-button> >
<ha-svg-icon .path=${mdiImageFilterCenterFocus}></ha-svg-icon>
</mwc-icon-button>
</div> </div>
</ha-card> </ha-card>
`; `;
} }
protected shouldUpdate(changedProps) { protected shouldUpdate(changedProps: PropertyValues) {
if (!changedProps.has("hass") || changedProps.size > 1) { if (!changedProps.has("hass") || changedProps.size > 1) {
return true; return true;
} }
@@ -228,7 +177,7 @@ class HuiMapCard extends LitElement implements LovelaceCard {
// Check if any state has changed // Check if any state has changed
for (const entity of this._configEntities) { for (const entity of this._configEntities) {
if (oldHass.states[entity.entity] !== this.hass!.states[entity.entity]) { if (oldHass.states[entity] !== this.hass!.states[entity]) {
return true; return true;
} }
} }
@@ -238,17 +187,12 @@ class HuiMapCard extends LitElement implements LovelaceCard {
protected firstUpdated(changedProps: PropertyValues): void { protected firstUpdated(changedProps: PropertyValues): void {
super.firstUpdated(changedProps); super.firstUpdated(changedProps);
if (this.isConnected) {
this.loadMap();
}
const root = this.shadowRoot!.getElementById("root"); const root = this.shadowRoot!.getElementById("root");
if (!this._config || this.isPanel || !root) { if (!this._config || this.isPanel || !root) {
return; return;
} }
this._attachObserver();
if (!this._config.aspect_ratio) { if (!this._config.aspect_ratio) {
root.style.paddingBottom = "100%"; root.style.paddingBottom = "100%";
return; return;
@@ -263,172 +207,86 @@ class HuiMapCard extends LitElement implements LovelaceCard {
} }
protected updated(changedProps: PropertyValues): void { protected updated(changedProps: PropertyValues): void {
if (changedProps.has("hass") || changedProps.has("_history")) {
this._drawEntities();
this._fitMap();
}
if (changedProps.has("hass")) {
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
if (oldHass && oldHass.themes.darkMode !== this.hass.themes.darkMode) {
this._replaceTileLayer();
}
}
if (
changedProps.has("_config") &&
changedProps.get("_config") !== undefined
) {
this.updateMap(changedProps.get("_config") as MapCardConfig);
}
if (this._config?.hours_to_show && this._configEntities?.length) { if (this._config?.hours_to_show && this._configEntities?.length) {
const minute = 60000;
if (changedProps.has("_config")) { if (changedProps.has("_config")) {
this._getHistory(); this._getHistory();
} else if (Date.now() - this._date!.getTime() >= minute) { } else if (Date.now() - this._date!.getTime() >= MINUTE) {
this._getHistory(); this._getHistory();
} }
} }
} }
private get _mapEl(): HTMLDivElement { private _fitMap() {
return this.shadowRoot!.getElementById("map") as HTMLDivElement; this._map?.fitMap();
} }
private async loadMap(): Promise<void> { private _getColor(entityId: string): string {
[this._leafletMap, this.Leaflet, this._tileLayer] = await setupLeafletMap( let color = this._colorDict[entityId];
this._mapEl, if (color) {
this._config!.dark_mode ?? this.hass.themes.darkMode return color;
);
this._drawEntities();
this._leafletMap.invalidateSize();
this._fitMap();
}
private _replaceTileLayer() {
const map = this._leafletMap;
const config = this._config;
const Leaflet = this.Leaflet;
if (!map || !config || !Leaflet || !this._tileLayer) {
return;
}
this._tileLayer = replaceTileLayer(
Leaflet,
map,
this._tileLayer,
this._config!.dark_mode ?? this.hass.themes.darkMode
);
}
private updateMap(oldConfig: MapCardConfig): void {
const map = this._leafletMap;
const config = this._config;
const Leaflet = this.Leaflet;
if (!map || !config || !Leaflet || !this._tileLayer) {
return;
}
if (this._config!.dark_mode !== oldConfig.dark_mode) {
this._replaceTileLayer();
}
if (
config.entities !== oldConfig.entities ||
config.geo_location_sources !== oldConfig.geo_location_sources
) {
this._drawEntities();
}
map.invalidateSize();
this._fitMap();
}
private _fitMap(): void {
if (!this._leafletMap || !this.Leaflet || !this._config || !this.hass) {
return;
}
const zoom = this._config.default_zoom;
if (this._mapItems.length === 0) {
this._leafletMap.setView(
new this.Leaflet.LatLng(
this.hass.config.latitude,
this.hass.config.longitude
),
zoom || 14
);
return;
}
const bounds = this.Leaflet.featureGroup(this._mapItems).getBounds();
this._leafletMap.fitBounds(bounds.pad(0.5));
if (zoom && this._leafletMap.getZoom() > zoom) {
this._leafletMap.setZoom(zoom);
}
}
private _getColor(entityId: string) {
let color;
if (this._colorDict[entityId]) {
color = this._colorDict[entityId];
} else {
color = this._colors[this._colorIndex];
this._colorIndex = (this._colorIndex + 1) % this._colors.length;
this._colorDict[entityId] = color;
} }
color = COLORS[this._colorIndex % COLORS.length];
this._colorIndex++;
this._colorDict[entityId] = color;
return color; return color;
} }
private _drawEntities(): void { private _getEntities = memoizeOne(
const hass = this.hass; (
const map = this._leafletMap; states: HassEntities,
const config = this._config; config: MapCardConfig,
const Leaflet = this.Leaflet; configEntities?: string[]
if (!hass || !map || !config || !Leaflet) { ) => {
return; if (!states || !config) {
} return undefined;
if (this._mapItems) {
this._mapItems.forEach((marker) => marker.remove());
}
const mapItems: Layer[] = (this._mapItems = []);
if (this._mapZones) {
this._mapZones.forEach((marker) => marker.remove());
}
const mapZones: Layer[] = (this._mapZones = []);
if (this._mapPaths) {
this._mapPaths.forEach((marker) => marker.remove());
}
const mapPaths: Layer[] = (this._mapPaths = []);
const allEntities = this._configEntities!.concat();
// Calculate visible geo location sources
if (config.geo_location_sources) {
const includesAll = config.geo_location_sources.includes("all");
for (const entityId of Object.keys(hass.states)) {
const stateObj = hass.states[entityId];
if (
computeDomain(entityId) === "geo_location" &&
(includesAll ||
config.geo_location_sources.includes(stateObj.attributes.source))
) {
allEntities.push({ entity: entityId });
}
} }
}
// DRAW history let entities = configEntities || [];
if (this._config!.hours_to_show && this._history) {
for (const entityStates of this._history) { if (config.geo_location_sources) {
const geoEntities: string[] = [];
// Calculate visible geo location sources
const includesAll = config.geo_location_sources.includes("all");
for (const stateObj of Object.values(states)) {
if (
computeDomain(stateObj.entity_id) === "geo_location" &&
(includesAll ||
config.geo_location_sources.includes(stateObj.attributes.source))
) {
geoEntities.push(stateObj.entity_id);
}
}
entities = [...entities, ...geoEntities];
}
return entities.map((entity) => ({
entity_id: entity,
color: this._getColor(entity),
}));
}
);
private _getHistoryPaths = memoizeOne(
(
config: MapCardConfig,
history?: HassEntity[][]
): HaMapPaths[] | undefined => {
if (!config.hours_to_show || !history) {
return undefined;
}
const paths: HaMapPaths[] = [];
for (const entityStates of history) {
if (entityStates?.length <= 1) { if (entityStates?.length <= 1) {
continue; continue;
} }
const entityId = entityStates[0].entity_id;
// filter location data from states and remove all invalid locations // filter location data from states and remove all invalid locations
const path = entityStates.reduce( const points = entityStates.reduce(
(accumulator: LatLngTuple[], state) => { (accumulator: LatLngTuple[], entityState) => {
const latitude = state.attributes.latitude; const latitude = entityState.attributes.latitude;
const longitude = state.attributes.longitude; const longitude = entityState.attributes.longitude;
if (latitude && longitude) { if (latitude && longitude) {
accumulator.push([latitude, longitude] as LatLngTuple); accumulator.push([latitude, longitude] as LatLngTuple);
} }
@@ -437,162 +295,15 @@ class HuiMapCard extends LitElement implements LovelaceCard {
[] []
) as LatLngTuple[]; ) as LatLngTuple[];
// DRAW HISTORY paths.push({
for ( points,
let markerIndex = 0; color: this._getColor(entityStates[0].entity_id),
markerIndex < path.length - 1; gradualOpacity: 0.8,
markerIndex++ });
) {
const opacityStep = 0.8 / (path.length - 2);
const opacity = 0.2 + markerIndex * opacityStep;
// DRAW history path dots
mapPaths.push(
Leaflet.circleMarker(path[markerIndex], {
radius: 3,
color: this._getColor(entityId),
opacity,
interactive: false,
})
);
// DRAW history path lines
const line = [path[markerIndex], path[markerIndex + 1]];
mapPaths.push(
Leaflet.polyline(line, {
color: this._getColor(entityId),
opacity,
interactive: false,
})
);
}
} }
return paths;
} }
);
// DRAW entities
for (const entity of allEntities) {
const entityId = entity.entity;
const stateObj = hass.states[entityId];
if (!stateObj) {
continue;
}
const title = computeStateName(stateObj);
const {
latitude,
longitude,
passive,
icon,
radius,
entity_picture: entityPicture,
gps_accuracy: gpsAccuracy,
} = stateObj.attributes;
if (!(latitude && longitude)) {
continue;
}
if (computeStateDomain(stateObj) === "zone") {
// DRAW ZONE
if (passive) {
continue;
}
// create icon
let iconHTML = "";
if (icon) {
const el = document.createElement("ha-icon");
el.setAttribute("icon", icon);
iconHTML = el.outerHTML;
} else {
const el = document.createElement("span");
el.innerHTML = title;
iconHTML = el.outerHTML;
}
// create marker with the icon
mapZones.push(
Leaflet.marker([latitude, longitude], {
icon: Leaflet.divIcon({
html: iconHTML,
iconSize: [24, 24],
className: this._config!.dark_mode
? "dark"
: this._config!.dark_mode === false
? "light"
: "",
}),
interactive: false,
title,
})
);
// create circle around it
mapZones.push(
Leaflet.circle([latitude, longitude], {
interactive: false,
color: "#FF9800",
radius,
})
);
continue;
}
// DRAW ENTITY
// create icon
const entityName = title
.split(" ")
.map((part) => part[0])
.join("")
.substr(0, 3);
// create market with the icon
mapItems.push(
Leaflet.marker([latitude, longitude], {
icon: Leaflet.divIcon({
// Leaflet clones this element before adding it to the map. This messes up
// our Polymer object and we can't pass data through. Thus we hack like this.
html: `
<ha-entity-marker
entity-id="${entityId}"
entity-name="${entityName}"
entity-picture="${entityPicture || ""}"
entity-color="${this._getColor(entityId)}"
></ha-entity-marker>
`,
iconSize: [48, 48],
className: "",
}),
title: computeStateName(stateObj),
})
);
// create circle around if entity has accuracy
if (gpsAccuracy) {
mapItems.push(
Leaflet.circle([latitude, longitude], {
interactive: false,
color: this._getColor(entityId),
radius: gpsAccuracy,
})
);
}
}
this._mapItems.forEach((marker) => map.addLayer(marker));
this._mapZones.forEach((marker) => map.addLayer(marker));
this._mapPaths.forEach((marker) => map.addLayer(marker));
}
private async _attachObserver(): Promise<void> {
// Observe changes to map size and invalidate to prevent broken rendering
if (!this._resizeObserver) {
await installResizeObserver();
this._resizeObserver = new ResizeObserver(this._debouncedResizeListener);
}
this._resizeObserver.observe(this);
}
private async _getHistory(): Promise<void> { private async _getHistory(): Promise<void> {
this._date = new Date(); this._date = new Date();
@@ -601,9 +312,7 @@ class HuiMapCard extends LitElement implements LovelaceCard {
return; return;
} }
const entityIds = this._configEntities!.map((entity) => entity.entity).join( const entityIds = this._configEntities!.join(",");
","
);
const endTime = new Date(); const endTime = new Date();
const startTime = new Date(); const startTime = new Date();
startTime.setHours(endTime.getHours() - this._config!.hours_to_show!); startTime.setHours(endTime.getHours() - this._config!.hours_to_show!);
@@ -624,7 +333,6 @@ class HuiMapCard extends LitElement implements LovelaceCard {
if (stateHistory.length < 1) { if (stateHistory.length < 1) {
return; return;
} }
this._history = stateHistory; this._history = stateHistory;
} }
@@ -636,13 +344,10 @@ class HuiMapCard extends LitElement implements LovelaceCard {
this._history = undefined; this._history = undefined;
} else { } else {
// remove unused entities // remove unused entities
const configEntityIds = this._configEntities?.map(
(configEntity) => configEntity.entity
);
this._history = this._history!.reduce( this._history = this._history!.reduce(
(accumulator: HassEntity[][], entityStates) => { (accumulator: HassEntity[][], entityStates) => {
const entityId = entityStates[0].entity_id; const entityId = entityStates[0].entity_id;
if (configEntityIds?.includes(entityId)) { if (this._configEntities?.includes(entityId)) {
accumulator.push(entityStates); accumulator.push(entityStates);
} }
return accumulator; return accumulator;
@@ -660,7 +365,7 @@ class HuiMapCard extends LitElement implements LovelaceCard {
height: 100%; height: 100%;
} }
#map { ha-map {
z-index: 0; z-index: 0;
border: none; border: none;
position: absolute; position: absolute;
@@ -671,7 +376,7 @@ class HuiMapCard extends LitElement implements LovelaceCard {
background: inherit; background: inherit;
} }
ha-icon-button { mwc-icon-button {
position: absolute; position: absolute;
top: 75px; top: 75px;
left: 3px; left: 3px;
@@ -685,14 +390,6 @@ class HuiMapCard extends LitElement implements LovelaceCard {
:host([ispanel]) #root { :host([ispanel]) #root {
height: 100%; height: 100%;
} }
.dark {
color: #ffffff;
}
.light {
color: #000000;
}
`; `;
} }
} }

View File

@@ -33,18 +33,18 @@ export class HuiActionEditor extends LitElement {
@property() protected hass?: HomeAssistant; @property() protected hass?: HomeAssistant;
get _navigation_path(): string { get _navigation_path(): string {
const config = this.config as NavigateActionConfig; const config = this.config as NavigateActionConfig | undefined;
return config.navigation_path || ""; return config?.navigation_path || "";
} }
get _url_path(): string { get _url_path(): string {
const config = this.config as UrlActionConfig; const config = this.config as UrlActionConfig | undefined;
return config.url_path || ""; return config?.url_path || "";
} }
get _service(): string { get _service(): string {
const config = this.config as CallServiceActionConfig; const config = this.config as CallServiceActionConfig;
return config.service || ""; return config?.service || "";
} }
private _serviceAction = memoizeOne( private _serviceAction = memoizeOne(

View File

@@ -29,6 +29,7 @@ export class HuiInputListEditor extends LitElement {
.index=${index} .index=${index}
@value-changed=${this._valueChanged} @value-changed=${this._valueChanged}
@blur=${this._consolidateEntries} @blur=${this._consolidateEntries}
@keydown=${this._handleKeyDown}
><ha-icon-button ><ha-icon-button
slot="suffix" slot="suffix"
class="clear-button" class="clear-button"
@@ -70,6 +71,13 @@ export class HuiInputListEditor extends LitElement {
}); });
} }
private _handleKeyDown(ev: KeyboardEvent) {
if (ev.key === "Enter") {
ev.stopPropagation();
this._consolidateEntries(ev);
}
}
private _consolidateEntries(ev: Event): void { private _consolidateEntries(ev: Event): void {
const target = ev.target! as EditorTarget; const target = ev.target! as EditorTarget;
if (target.value === "") { if (target.value === "") {

View File

@@ -34,7 +34,7 @@ const cardConfigStruct = object({
dark_mode: optional(boolean()), dark_mode: optional(boolean()),
entities: array(entitiesConfigStruct), entities: array(entitiesConfigStruct),
hours_to_show: optional(number()), hours_to_show: optional(number()),
geo_location_sources: optional(array()), geo_location_sources: optional(array(string())),
}); });
@customElement("hui-map-card-editor") @customElement("hui-map-card-editor")

View File

@@ -1,88 +0,0 @@
import "@polymer/iron-image/iron-image";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { EventsMixin } from "../../mixins/events-mixin";
/*
* @appliesMixin EventsMixin
*/
class HaEntityMarker extends EventsMixin(PolymerElement) {
static get template() {
return html`
<style include="iron-positioning"></style>
<style>
.marker {
position: relative;
display: block;
margin: 0 auto;
width: 2.5em;
text-align: center;
height: 2.5em;
line-height: 2.5em;
font-size: 1.5em;
border-radius: 50%;
border: 0.1em solid var(--ha-marker-color, var(--primary-color));
color: var(--primary-text-color);
background-color: var(--card-background-color);
}
iron-image {
border-radius: 50%;
}
</style>
<div class="marker" style$="border-color:{{entityColor}}">
<template is="dom-if" if="[[entityName]]">[[entityName]]</template>
<template is="dom-if" if="[[entityPicture]]">
<iron-image
sizing="cover"
class="fit"
src="[[entityPicture]]"
></iron-image>
</template>
</div>
`;
}
static get properties() {
return {
hass: {
type: Object,
},
entityId: {
type: String,
value: "",
},
entityName: {
type: String,
value: null,
},
entityPicture: {
type: String,
value: null,
},
entityColor: {
type: String,
value: null,
},
};
}
ready() {
super.ready();
this.addEventListener("click", (ev) => this.badgeTap(ev));
}
badgeTap(ev) {
ev.stopPropagation();
if (this.entityId) {
this.fire("hass-more-info", { entityId: this.entityId });
}
}
}
customElements.define("ha-entity-marker", HaEntityMarker);

View File

@@ -1,263 +0,0 @@
import "@polymer/app-layout/app-toolbar/app-toolbar";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import {
replaceTileLayer,
setupLeafletMap,
} from "../../common/dom/setup-leaflet-map";
import { computeStateDomain } from "../../common/entity/compute_state_domain";
import { computeStateName } from "../../common/entity/compute_state_name";
import { navigate } from "../../common/navigate";
import "../../components/ha-icon";
import "../../components/ha-menu-button";
import { defaultRadiusColor } from "../../data/zone";
import "../../layouts/ha-app-layout";
import LocalizeMixin from "../../mixins/localize-mixin";
import "../../styles/polymer-ha-style";
import "./ha-entity-marker";
/*
* @appliesMixin LocalizeMixin
*/
class HaPanelMap extends LocalizeMixin(PolymerElement) {
static get template() {
return html`
<style include="ha-style">
#map {
height: calc(100vh - var(--header-height));
width: 100%;
z-index: 0;
background: inherit;
}
.icon {
color: var(--primary-text-color);
}
</style>
<ha-app-layout>
<app-header fixed slot="header">
<app-toolbar>
<ha-menu-button
hass="[[hass]]"
narrow="[[narrow]]"
></ha-menu-button>
<div main-title>[[localize('panel.map')]]</div>
<template is="dom-if" if="[[computeShowEditZone(hass)]]">
<ha-icon-button
icon="hass:pencil"
on-click="openZonesEditor"
></ha-icon-button>
</template>
</app-toolbar>
</app-header>
<div id="map"></div>
</ha-app-layout>
`;
}
static get properties() {
return {
hass: {
type: Object,
observer: "drawEntities",
},
narrow: Boolean,
};
}
connectedCallback() {
super.connectedCallback();
this.loadMap();
}
async loadMap() {
this._darkMode = this.hass.themes.darkMode;
[this._map, this.Leaflet, this._tileLayer] = await setupLeafletMap(
this.$.map,
this._darkMode
);
this.drawEntities(this.hass);
this._map.invalidateSize();
this.fitMap();
}
disconnectedCallback() {
if (this._map) {
this._map.remove();
}
}
computeShowEditZone(hass) {
return !__DEMO__ && hass.user.is_admin;
}
openZonesEditor() {
navigate("/config/zone");
}
fitMap() {
let bounds;
if (this._mapItems.length === 0) {
this._map.setView(
new this.Leaflet.LatLng(
this.hass.config.latitude,
this.hass.config.longitude
),
14
);
} else {
bounds = new this.Leaflet.latLngBounds(
this._mapItems.map((item) => item.getLatLng())
);
this._map.fitBounds(bounds.pad(0.5));
}
}
drawEntities(hass) {
/* eslint-disable vars-on-top */
const map = this._map;
if (!map) return;
if (this._darkMode !== this.hass.themes.darkMode) {
this._darkMode = this.hass.themes.darkMode;
this._tileLayer = replaceTileLayer(
this.Leaflet,
map,
this._tileLayer,
this.hass.themes.darkMode
);
}
if (this._mapItems) {
this._mapItems.forEach(function (marker) {
marker.remove();
});
}
const mapItems = (this._mapItems = []);
if (this._mapZones) {
this._mapZones.forEach(function (marker) {
marker.remove();
});
}
const mapZones = (this._mapZones = []);
Object.keys(hass.states).forEach((entityId) => {
const entity = hass.states[entityId];
if (
entity.state === "home" ||
!("latitude" in entity.attributes) ||
!("longitude" in entity.attributes)
) {
return;
}
const title = computeStateName(entity);
let icon;
if (computeStateDomain(entity) === "zone") {
// DRAW ZONE
if (entity.attributes.passive) return;
// create icon
let iconHTML = "";
if (entity.attributes.icon) {
const el = document.createElement("ha-icon");
el.setAttribute("icon", entity.attributes.icon);
iconHTML = el.outerHTML;
} else {
const el = document.createElement("span");
el.innerHTML = title;
iconHTML = el.outerHTML;
}
icon = this.Leaflet.divIcon({
html: iconHTML,
iconSize: [24, 24],
className: "icon",
});
// create marker with the icon
mapZones.push(
this.Leaflet.marker(
[entity.attributes.latitude, entity.attributes.longitude],
{
icon: icon,
interactive: false,
title: title,
}
).addTo(map)
);
// create circle around it
mapZones.push(
this.Leaflet.circle(
[entity.attributes.latitude, entity.attributes.longitude],
{
interactive: false,
color: defaultRadiusColor,
radius: entity.attributes.radius,
}
).addTo(map)
);
return;
}
// DRAW ENTITY
// create icon
const entityPicture = entity.attributes.entity_picture || "";
const entityName = title
.split(" ")
.map(function (part) {
return part.substr(0, 1);
})
.join("");
/* Leaflet clones this element before adding it to the map. This messes up
our Polymer object and we can't pass data through. Thus we hack like this. */
icon = this.Leaflet.divIcon({
html:
"<ha-entity-marker entity-id='" +
entity.entity_id +
"' entity-name='" +
entityName +
"' entity-picture='" +
entityPicture +
"'></ha-entity-marker>",
iconSize: [45, 45],
className: "",
});
// create market with the icon
mapItems.push(
this.Leaflet.marker(
[entity.attributes.latitude, entity.attributes.longitude],
{
icon: icon,
title: computeStateName(entity),
}
).addTo(map)
);
// create circle around if entity has accuracy
if (entity.attributes.gps_accuracy) {
mapItems.push(
this.Leaflet.circle(
[entity.attributes.latitude, entity.attributes.longitude],
{
interactive: false,
color: "#0288D1",
radius: entity.attributes.gps_accuracy,
}
).addTo(map)
);
}
});
}
}
customElements.define("ha-panel-map", HaPanelMap);

View File

@@ -0,0 +1,103 @@
import { mdiPencil } from "@mdi/js";
import "@material/mwc-icon-button";
import "@polymer/app-layout/app-toolbar/app-toolbar";
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
import { property } from "lit/decorators";
import { computeStateDomain } from "../../common/entity/compute_state_domain";
import { navigate } from "../../common/navigate";
import "../../components/ha-svg-icon";
import "../../components/ha-menu-button";
import "../../layouts/ha-app-layout";
import { HomeAssistant } from "../../types";
import "../../components/map/ha-map";
import { haStyle } from "../../resources/styles";
class HaPanelMap extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Boolean }) public narrow!: boolean;
private _entities: string[] = [];
protected render() {
return html`
<ha-app-layout>
<app-header fixed slot="header">
<app-toolbar>
<ha-menu-button
.hass=${this.hass}
.narrow=${this.narrow}
></ha-menu-button>
<div main-title>${this.hass.localize("panel.map")}</div>
${!__DEMO__ && this.hass.user?.is_admin
? html`<mwc-icon-button @click=${this._openZonesEditor}
><ha-svg-icon .path=${mdiPencil}></ha-svg-icon
></mwc-icon-button>`
: ""}
</app-toolbar>
</app-header>
<ha-map .hass=${this.hass} .entities=${this._entities} autoFit></ha-map>
</ha-app-layout>
`;
}
private _openZonesEditor() {
navigate("/config/zone");
}
public willUpdate(changedProps: PropertyValues) {
super.willUpdate(changedProps);
if (!changedProps.has("hass")) {
return;
}
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
this._getStates(oldHass);
}
private _getStates(oldHass?: HomeAssistant) {
let changed = false;
const personSources = new Set<string>();
const locationEntities: string[] = [];
Object.values(this.hass!.states).forEach((entity) => {
if (
entity.state === "home" ||
!("latitude" in entity.attributes) ||
!("longitude" in entity.attributes)
) {
return;
}
locationEntities.push(entity.entity_id);
if (computeStateDomain(entity) === "person" && entity.attributes.source) {
personSources.add(entity.attributes.source);
}
if (oldHass?.states[entity.entity_id] !== entity) {
changed = true;
}
});
if (changed) {
this._entities = locationEntities.filter(
(entity) => !personSources.has(entity)
);
}
}
static get styles(): CSSResultGroup {
return [
haStyle,
css`
ha-map {
height: calc(100vh - var(--header-height));
}
`,
];
}
}
customElements.define("ha-panel-map", HaPanelMap);
declare global {
interface HTMLElementTagNameMap {
"ha-panel-map": HaPanelMap;
}
}

View File

@@ -1,5 +1,4 @@
import "@polymer/iron-flex-layout/iron-flex-layout-classes"; import "@polymer/iron-flex-layout/iron-flex-layout-classes";
import "@polymer/iron-label/iron-label";
import { html } from "@polymer/polymer/lib/utils/html-tag"; import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */ /* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element"; import { PolymerElement } from "@polymer/polymer/polymer-element";

View File

@@ -29,10 +29,10 @@ documentContainer.innerHTML = `<custom-style>
--disabled-text-color: #bdbdbd; --disabled-text-color: #bdbdbd;
/* main interface colors */ /* main interface colors */
--primary-color: #03a9f4; --primary-color: ${DEFAULT_PRIMARY_COLOR};
--dark-primary-color: #0288d1; --dark-primary-color: #0288d1;
--light-primary-color: #b3e5fC; --light-primary-color: #b3e5fC;
--accent-color: #ff9800; --accent-color: ${DEFAULT_ACCENT_COLOR};
--divider-color: rgba(0, 0, 0, .12); --divider-color: rgba(0, 0, 0, .12);
--scrollbar-thumb-color: rgb(194, 194, 194); --scrollbar-thumb-color: rgb(194, 194, 194);

View File

@@ -3569,7 +3569,16 @@
"description": "Alternatively you can restore from a previous snapshot.", "description": "Alternatively you can restore from a previous snapshot.",
"in_progress": "Restore in progress", "in_progress": "Restore in progress",
"show_log": "Show full log", "show_log": "Show full log",
"hide_log": "Hide full log" "hide_log": "Hide full log",
"full_snapshot": "[%key:supervisor::snapshot::full_snapshot%]",
"partial_snapshot": "[%key:supervisor::snapshot::partial_snapshot%]",
"type": "[%key:supervisor::snapshot::type%]",
"select_type": "[%key:supervisor::snapshot::select_type%]",
"folders": "[%key:supervisor::snapshot::folders%]",
"addons": "[%key:supervisor::snapshot::addons%]",
"password_protection": "[%key:supervisor::snapshot::password_protection%]",
"password": "[%key:supervisor::snapshot::password%]",
"confirm_password": "[%key:supervisor::snapshot::confirm_password%]"
} }
}, },
"custom": { "custom": {
@@ -3924,9 +3933,11 @@
"addons": "Add-ons", "addons": "Add-ons",
"folders": "Folders", "folders": "Folders",
"password": "Snapshot password", "password": "Snapshot password",
"confirm_password": "Confirm Snapshot password",
"password_protection": "Password protection", "password_protection": "Password protection",
"password_protected": "password protected", "password_protected": "password protected",
"enter_password": "Please enter a password.", "enter_password": "Please enter a password.",
"passwords_not_matching": "The passwords does not match",
"folder": { "folder": {
"homeassistant": "Home Assistant configuration", "homeassistant": "Home Assistant configuration",
"ssl": "SSL", "ssl": "SSL",

View File

@@ -130,12 +130,12 @@
"action_error": { "action_error": {
"get_changelog": "Неуспешно получаване на списък с промени на добавката", "get_changelog": "Неуспешно получаване на списък с промени на добавката",
"go_to_config": "Грешка при стартиране на добавката - неуспешна проверка на конфигурацията!", "go_to_config": "Грешка при стартиране на добавката - неуспешна проверка на конфигурацията!",
"install": "Инсталирането на добавката не бе успешно", "install": "Инсталирането на добавката не е успешно",
"restart": "Рестартирането на добавката не бе успешно", "restart": "Рестартирането на добавката не е успешно",
"start": "Стартирането на добавката не бе успешно", "start": "Стартирането на добавката не е успешно",
"start_invalid_config": "Към конфигурацията", "start_invalid_config": "Към конфигурацията",
"stop": "Спирането на добавката не бе успешно", "stop": "Спирането на добавката не е успешно",
"uninstall": "Деинсталирането на добавката не бе успешно", "uninstall": "Деинсталирането на добавката не е успешно",
"validate_config": "Грешка при проверка на конфигурацията на добавката" "validate_config": "Грешка при проверка на конфигурацията на добавката"
}, },
"capability": { "capability": {
@@ -218,8 +218,8 @@
"documentation": { "documentation": {
"get_documentation": "Неуспешно получаване на документация за добавката, {error}" "get_documentation": "Неуспешно получаване на документация за добавката, {error}"
}, },
"failed_to_reset": "Нулирането на конфигурацията на добавката не бе успешно, {error}", "failed_to_reset": "Нулирането на конфигурацията на добавката не е успешно, {error}",
"failed_to_save": "Запазването на конфигурацията на добавката не бе успешно, {error}", "failed_to_save": "Запазването на конфигурацията на добавката не е успешно, {error}",
"logs": { "logs": {
"get_logs": "Неуспешно получаване журнали на добавка, {грешка}" "get_logs": "Неуспешно получаване журнали на добавка, {грешка}"
}, },
@@ -241,10 +241,10 @@
"description": "Описание", "description": "Описание",
"error": { "error": {
"unknown": "Неизвестна грешка", "unknown": "Неизвестна грешка",
"update_failed": "Актуализацията не бе успешна" "update_failed": "Актуализацията не е успешна"
}, },
"failed_to_restart_name": "Рестартирането на {name} не бе успешно", "failed_to_restart_name": "Рестартирането на {name} не е успешно",
"failed_to_update_name": "Актуализирането на {name} не бе успешно", "failed_to_update_name": "Актуализирането на {name} не е успешно",
"learn_more": "Научете повече", "learn_more": "Научете повече",
"new_version_available": "Налична е нова версия", "new_version_available": "Налична е нова версия",
"newest_version": "Последна версия", "newest_version": "Последна версия",
@@ -260,6 +260,7 @@
"save": "Запис", "save": "Запис",
"show_more": "Показване на повече информация за това", "show_more": "Показване на повече информация за това",
"update": "Актуализиране", "update": "Актуализиране",
"update_available": "{count}{count, plural,\n one {обновление изчаква}\n other {{count} обновления изчакват}\n}",
"version": "Версия", "version": "Версия",
"yes": "Да" "yes": "Да"
}, },
@@ -285,12 +286,20 @@
"no_addons": "Все още нямате инсталирани добавки. Насочете се към хранилището за добавки, за да започнете!" "no_addons": "Все още нямате инсталирани добавки. Насочете се към хранилището за добавки, за да започнете!"
}, },
"dialog": { "dialog": {
"hardware": {
"attributes": "Атрибути",
"device_path": "Път до устройството",
"id": "ID",
"search": "Търсене на хардуер",
"subsystem": "Подсистема",
"title": "Хардуер"
},
"network": { "network": {
"connected_to": "Свързан с {ssid}", "connected_to": "Свързан с {ssid}",
"dhcp": "DHCP", "dhcp": "DHCP",
"disabled": "Деактивиран", "disabled": "Деактивиран",
"dns_servers": "DNS Сървъри", "dns_servers": "DNS Сървъри",
"failed_to_change": "Промяната на мрежовите настройки не бе успешна", "failed_to_change": "Промяната на мрежовите настройки не е успешна",
"gateway": "Адрес на шлюза", "gateway": "Адрес на шлюза",
"ip_netmask": "IP адрес/Мрежова маска", "ip_netmask": "IP адрес/Мрежова маска",
"open": "Отворена", "open": "Отворена",
@@ -305,8 +314,8 @@
"registries": { "registries": {
"add_new_registry": "Добавяне на нов регистър", "add_new_registry": "Добавяне на нов регистър",
"add_registry": "Добавяне на регистър", "add_registry": "Добавяне на регистър",
"failed_to_add": "Добавянето на регистър не бе успешно", "failed_to_add": "Добавянето на регистър не е успешно",
"failed_to_remove": "Премахването на регистър не бе успешно", "failed_to_remove": "Премахването на регистър не е успешно",
"no_registries": "Няма конфигурирани регистри", "no_registries": "Няма конфигурирани регистри",
"password": "Парола", "password": "Парола",
"registry": "Регистър", "registry": "Регистър",
@@ -334,6 +343,8 @@
"my": { "my": {
"error": "Възникна неизвестна грешка", "error": "Възникна неизвестна грешка",
"error_addon_not_found": "Не е намерена добавка", "error_addon_not_found": "Не е намерена добавка",
"error_addon_not_installed": "Исканата добавка не е инсталирана. Моля, първо я инсталирайте",
"error_addon_not_started": "Заявените добавки не е стартирана. Моля, първо я стартирайте",
"faq_link": "My Home Assistant ЧЗВ", "faq_link": "My Home Assistant ЧЗВ",
"not_supported": "Тази препратка не се поддържа от вашата Home Assistant инсталация. Последвайте {link} за поддържани препратки както и версиите при тяхното пускане." "not_supported": "Тази препратка не се поддържа от вашата Home Assistant инсталация. Последвайте {link} за поддържани препратки както и версиите при тяхното пускане."
}, },
@@ -346,6 +357,7 @@
"snapshot": { "snapshot": {
"addons": "Добавки", "addons": "Добавки",
"available_snapshots": "Налични снапшоти", "available_snapshots": "Налични снапшоти",
"confirm_password": "Потвърдете паролата за снапшота",
"could_not_create": "Не можа да се създаде снапшот", "could_not_create": "Не можа да се създаде снапшот",
"create": "Създаване", "create": "Създаване",
"create_blocked_not_running": "Създаването на снапшот в момента не е възможно, тъй като системата е в състояние {state}.", "create_blocked_not_running": "Създаването на снапшот в момента не е възможно, тъй като системата е в състояние {state}.",
@@ -353,9 +365,11 @@
"created": "Създаден", "created": "Създаден",
"delete_selected": "Изтриване на избраните снапшоти", "delete_selected": "Изтриване на избраните снапшоти",
"delete_snapshot_confirm": "Изтрий", "delete_snapshot_confirm": "Изтрий",
"delete_snapshot_title": зтриване на снапшот", "delete_snapshot_text": скате ли да изтриете {number} {number, plural,\n one {резервно копие}\n other {резервни копия}\n}?",
"delete_snapshot_title": "Изтриване на снапшота",
"description": "Снапшотите ви позволяват лесно да архивирате и възстановявате всички данни от вашия екземпляр на Home Assistant.", "description": "Снапшотите ви позволяват лесно да архивирате и възстановявате всички данни от вашия екземпляр на Home Assistant.",
"enter_password": "Моля, въведете парола.", "enter_password": "Моля, въведете парола.",
"failed_to_delete": "Изтриването не е успешно",
"folder": { "folder": {
"addons/local": "Локални добавки", "addons/local": "Локални добавки",
"homeassistant": "Конфигурация на Home Assistant", "homeassistant": "Конфигурация на Home Assistant",
@@ -371,7 +385,9 @@
"password": "Парола", "password": "Парола",
"password_protected": "защитен с парола", "password_protected": "защитен с парола",
"password_protection": "Защита с парола", "password_protection": "Защита с парола",
"passwords_not_matching": "Паролите не съвпадат",
"security": "Сигурност", "security": "Сигурност",
"select_type": "Изберете какво да възстановите",
"selected": "{number} избрани", "selected": "{number} избрани",
"type": "Тип", "type": "Тип",
"upload_snapshot": "Качване на снапшот" "upload_snapshot": "Качване на снапшот"
@@ -439,7 +455,7 @@
"unhealthy_reason": { "unhealthy_reason": {
"docker": "Средата на Docker не работи правилно", "docker": "Средата на Docker не работи правилно",
"privileged": "Supervisor не е привилегирован", "privileged": "Supervisor не е привилегирован",
"setup": "Настройката на Supervisor не бе успешна", "setup": "Настройката на Supervisor не е успешна",
"supervisor": "Supervisor не успя да се актуализира", "supervisor": "Supervisor не успя да се актуализира",
"untrusted": "Открито e ненадежден съдържание" "untrusted": "Открито e ненадежден съдържание"
}, },
@@ -878,6 +894,8 @@
"config_entry_system_options": { "config_entry_system_options": {
"enable_new_entities_description": "Ако е изключено, новооткритите обекти за {integration} няма да бъдат автоматично добавяни в Home Assistant", "enable_new_entities_description": "Ако е изключено, новооткритите обекти за {integration} няма да бъдат автоматично добавяни в Home Assistant",
"enable_new_entities_label": "Активирай новодобавените обекти.", "enable_new_entities_label": "Активирай новодобавените обекти.",
"enable_polling_description": "Дали Home Assistant трябва да обновява автоматично обектите от {integration}.",
"enable_polling_label": "Включи автоматично опресняване",
"restart_home_assistant": "Трябва да рестартирате Home Assistant, за да влязат в сила промените.", "restart_home_assistant": "Трябва да рестартирате Home Assistant, за да влязат в сила промените.",
"title": "Системни опции за {integration}", "title": "Системни опции за {integration}",
"update": "Актуализация" "update": "Актуализация"
@@ -1126,6 +1144,7 @@
"cluster_header": "Клъстер", "cluster_header": "Клъстер",
"configuration_complete": "Преконфигурирането на устройството завърши.", "configuration_complete": "Преконфигурирането на устройството завърши.",
"configuration_failed": "Преконфигурирането на устройството не бе успешно. Допълнителна информация може да бъде налична в дневниците.", "configuration_failed": "Преконфигурирането на устройството не бе успешно. Допълнителна информация може да бъде налична в дневниците.",
"configuring_alt": "Конфигуриране",
"heading": "Преконфигуриране на устройство", "heading": "Преконфигуриране на устройство",
"in_progress": "Устройството се преконфигурира. Това може да отнеме известно време.", "in_progress": "Устройството се преконфигурира. Това може да отнеме известно време.",
"min_max_change": "мин/макс/промяна", "min_max_change": "мин/макс/промяна",
@@ -1642,7 +1661,8 @@
"link_learn_how_it_works": "Научете как работи", "link_learn_how_it_works": "Научете как работи",
"not_connected": "Не е свързан", "not_connected": "Не е свързан",
"remote_enabled": { "remote_enabled": {
"caption": "Автоматично свързване" "caption": "Автоматично свързване",
"description": "Активирайте тази опция, за да сте сигурни, че вашият Home Assistant е винаги достъпен от разстояние."
}, },
"title": "Дистанционен контрол" "title": "Дистанционен контрол"
}, },
@@ -1921,7 +1941,8 @@
"scripts": "Скриптове", "scripts": "Скриптове",
"unknown_error": "Неизвестна грешка", "unknown_error": "Неизвестна грешка",
"unnamed_device": "Устройство без име", "unnamed_device": "Устройство без име",
"update": "Актуализация" "update": "Актуализация",
"update_device_error": "Актуализирането на устройството не е успешно"
}, },
"entities": { "entities": {
"caption": "Обекти", "caption": "Обекти",
@@ -2033,6 +2054,7 @@
"depends_on_cloud": "Зависи от облака", "depends_on_cloud": "Зависи от облака",
"device_unavailable": "Устройството е недостъпно", "device_unavailable": "Устройството е недостъпно",
"devices": "{count} {count, plural,\n one {устройство}\n other {устройства}\n}", "devices": "{count} {count, plural,\n one {устройство}\n other {устройства}\n}",
"disable_error": "Активирането или деактивирането на интеграцията не бе успешно",
"disable_restart_confirm": "Рестартирайте Home Assistant за да завършите деактивирането на тази интеграция", "disable_restart_confirm": "Рестартирайте Home Assistant за да завършите деактивирането на тази интеграция",
"disable": { "disable": {
"disable_confirm": "Наистина ли искате да забраните този конфигурационен запис? Устройствата и обекти му ще бъдат деактивирани.", "disable_confirm": "Наистина ли искате да забраните този конфигурационен запис? Устройствата и обекти му ще бъдат деактивирани.",
@@ -2044,6 +2066,7 @@
}, },
"disabled_cause": "Деактивирано от {cause}" "disabled_cause": "Деактивирано от {cause}"
}, },
"disabled_polling": "Автоматичното обновяване за данни е забранено",
"documentation": "Документация", "documentation": "Документация",
"enable_restart_confirm": "Рестартирайте Home Assistant за да завършите активирането на тази интеграция", "enable_restart_confirm": "Рестартирайте Home Assistant за да завършите активирането на тази интеграция",
"entities": "{count} {count, plural,\n one {обект}\n other {обекта}\n}", "entities": "{count} {count, plural,\n one {обект}\n other {обекта}\n}",
@@ -2245,7 +2268,8 @@
"product_manual": "Ръководство за продукта" "product_manual": "Ръководство за продукта"
}, },
"node_query_stages": { "node_query_stages": {
"associations": "Обновяване на свързаните групи и членства" "associations": "Обновяване на свързаните групи и членства",
"complete": "Разпита приключи"
}, },
"node": { "node": {
"button": "Детайли за възела", "button": "Детайли за възела",
@@ -2261,6 +2285,7 @@
}, },
"refresh_node": { "refresh_node": {
"button": "Обнови възела", "button": "Обнови възела",
"description": "Това ще накара OpenZWave да разпита дадено устройство и да актуализира командните му класове, възможностите и стойностите му.",
"node_status": "Състояние на възела", "node_status": "Състояние на възела",
"refreshing_description": "Опресняване на информацията за възела...", "refreshing_description": "Опресняване на информацията за възела...",
"step": "Стъпка" "step": "Стъпка"
@@ -2330,6 +2355,8 @@
"add_scene": "Добавяне на сцена", "add_scene": "Добавяне на сцена",
"delete_confirm": "Сигурни ли сте, че искате да изтриете тази сцена?", "delete_confirm": "Сигурни ли сте, че искате да изтриете тази сцена?",
"delete_scene": "Изтриване на сцената", "delete_scene": "Изтриване на сцената",
"duplicate": "Дублиране",
"duplicate_scene": "Дублиране на сцената",
"edit_scene": "Редактиране на сцената", "edit_scene": "Редактиране на сцената",
"header": "Редактор на сцени", "header": "Редактор на сцени",
"headers": { "headers": {
@@ -2533,8 +2560,10 @@
"CONFIGURED_status_text": "Инициализация", "CONFIGURED_status_text": "Инициализация",
"INITIALIZED": "Инициализацията завърши", "INITIALIZED": "Инициализацията завърши",
"INITIALIZED_status_text": "Устройството е готово за употреба", "INITIALIZED_status_text": "Устройството е готово за употреба",
"INTERVIEW_COMPLETE": "Разпита приключи",
"INTERVIEW_COMPLETE_status_text": "Конфигуриране", "INTERVIEW_COMPLETE_status_text": "Конфигуриране",
"PAIRED": "Намерено устройство" "PAIRED": "Намерено устройство",
"PAIRED_status_text": "Започване на разпит"
}, },
"groups": { "groups": {
"add_group": "Добавяне на група", "add_group": "Добавяне на група",
@@ -2590,6 +2619,8 @@
"zwave_js": { "zwave_js": {
"add_node": { "add_node": {
"inclusion_failed": "Възелът не може да бъде добавен. Моля, проверете журналите за повече информация.", "inclusion_failed": "Възелът не може да бъде добавен. Моля, проверете журналите за повече информация.",
"interview_failed": "Разпита на устройството не бе успешен. Допълнителна информация може да е налична в логовете.",
"interview_started": "Устройството се разпитва. Това може да отнеме известно време.",
"title": "Добавяне на Z-Wave възел" "title": "Добавяне на Z-Wave възел"
}, },
"button": "Конфигуриране", "button": "Конфигуриране",
@@ -2633,6 +2664,16 @@
"node_status": { "node_status": {
"unknown": "Неизвестен" "unknown": "Неизвестен"
}, },
"reinterview_node": {
"battery_device_warning": "Ще трябва да събудите устройствата, захранвани с батерии, преди да започнете повторното интервю. Вижте ръководството на вашето устройство за инструкции как да го събудите.",
"in_progress": "Устройството се разпитва. Това може да отнеме известно време.",
"interview_complete": "Разпита на устройството приключи.",
"interview_failed": "Разпита на устройството не бе успешен. Допълнителна информация може да е налична в логовете.",
"introduction": "Повторно разпитване на устройство във вашата Z-Wave мрежа. Използвайте тази функция, ако устройството има липсваща или неправилна функционалност.",
"run_in_background": "Можете да затворите този диалогов прозорец и разпита ще продължи във фонов режим.",
"start_reinterview": "Започване на нов разпит",
"title": "Повторен разпит на Z-Wave устройство"
},
"remove_node": { "remove_node": {
"exclusion_failed": "Възелът не може да бъде премахнат. Моля, проверете журналите за повече информация.", "exclusion_failed": "Възелът не може да бъде премахнат. Моля, проверете журналите за повече информация.",
"title": "Премахване на Z-Wave възел" "title": "Премахване на Z-Wave възел"
@@ -2683,6 +2724,7 @@
"node_protection": "Защита на възела", "node_protection": "Защита на възела",
"nodes": "Възли", "nodes": "Възли",
"nodes_in_group": "Други възли в тази група:", "nodes_in_group": "Други възли в тази група:",
"pooling_intensity": "Честота на опресняване",
"protection": "Защита", "protection": "Защита",
"remove_from_group": "Премахване от групата" "remove_from_group": "Премахване от групата"
}, },
@@ -3351,10 +3393,19 @@
"intro": "Готови ли сте да събудите дома си, да отвоювате независимостта си и да се присъедините към световна общност от хора автоматизиращи домовете си?", "intro": "Готови ли сте да събудите дома си, да отвоювате независимостта си и да се присъедините към световна общност от хора автоматизиращи домовете си?",
"next": "Следващ", "next": "Следващ",
"restore": { "restore": {
"addons": "Добавки",
"confirm_password": "Потвърдете паролата за снапшота",
"description": "Като алтернатива можете да възстановите от предишен снапшот.", "description": "Като алтернатива можете да възстановите от предишен снапшот.",
"folders": "Папки",
"full_snapshot": "Пълен снапшот",
"hide_log": "Скриване на пълния дневник", "hide_log": "Скриване на пълния дневник",
"in_progress": "Възстановяването е в ход", "in_progress": "Възстановяването е в ход",
"show_log": "Показване на пълния дневник" "partial_snapshot": "Частичен снапшот",
"password": "Парола",
"password_protection": "Защита с парола",
"select_type": "Изберете какво да възстановите",
"show_log": "Показване на пълния дневник",
"type": "Тип"
}, },
"user": { "user": {
"create_account": "Създай акаунт", "create_account": "Създай акаунт",

View File

@@ -318,6 +318,14 @@
"no_addons": "Encara no tens cap complement instal·lat. Vés al directori de complements per començar!" "no_addons": "Encara no tens cap complement instal·lat. Vés al directori de complements per començar!"
}, },
"dialog": { "dialog": {
"hardware": {
"attributes": "Atributs",
"device_path": "Ruta del dispositiu",
"id": "ID",
"search": "Cerca maquinari",
"subsystem": "Subsistema",
"title": "Maquinari"
},
"network": { "network": {
"connected_to": "Connectat a {ssid}", "connected_to": "Connectat a {ssid}",
"dhcp": "DHCP", "dhcp": "DHCP",
@@ -382,6 +390,7 @@
"snapshot": { "snapshot": {
"addons": "Complements", "addons": "Complements",
"available_snapshots": "Instantànies disponibles", "available_snapshots": "Instantànies disponibles",
"confirm_password": "Confirma la contrasenya de la instantània",
"could_not_create": "No s'ha pogut crear la instantània", "could_not_create": "No s'ha pogut crear la instantània",
"create": "Crea", "create": "Crea",
"create_blocked_not_running": "Ara mateix no és possible crear una instantània perquè el sistema es troba en estat {state}.", "create_blocked_not_running": "Ara mateix no és possible crear una instantània perquè el sistema es troba en estat {state}.",
@@ -409,6 +418,7 @@
"password": "Contrasenya de la instantània", "password": "Contrasenya de la instantània",
"password_protected": "protegit amb contrasenya", "password_protected": "protegit amb contrasenya",
"password_protection": "Protecció amb contrasenya", "password_protection": "Protecció amb contrasenya",
"passwords_not_matching": "Les contrasenyes no coincideixen",
"security": "Seguretat", "security": "Seguretat",
"select_type": "Selecciona què vols restaurar", "select_type": "Selecciona què vols restaurar",
"selected": "{number} seleccionada/es", "selected": "{number} seleccionada/es",
@@ -3881,10 +3891,19 @@
"intro": "Estàs preparat donar vida pròpia a la teva llar, recuperar la teva privacitat i unir-te a una comunitat mundial de \"tinkerers\"?", "intro": "Estàs preparat donar vida pròpia a la teva llar, recuperar la teva privacitat i unir-te a una comunitat mundial de \"tinkerers\"?",
"next": "Següent", "next": "Següent",
"restore": { "restore": {
"addons": "Complements",
"confirm_password": "Confirma la contrasenya de la instantània",
"description": "També pots restaurar des d'una instantània anterior.", "description": "També pots restaurar des d'una instantània anterior.",
"folders": "Carpetes",
"full_snapshot": "Instantània completa",
"hide_log": "Amaga el registre complet", "hide_log": "Amaga el registre complet",
"in_progress": "Restauració en curs", "in_progress": "Restauració en curs",
"show_log": "Mostra el registre complet" "partial_snapshot": "Instantània parcial",
"password": "Contrasenya de la instantània",
"password_protection": "Protecció amb contrasenya",
"select_type": "Selecciona què vols restaurar",
"show_log": "Mostra el registre complet",
"type": "Tipus d'instantània"
}, },
"user": { "user": {
"create_account": "Crear compte", "create_account": "Crear compte",

View File

@@ -318,6 +318,14 @@
"no_addons": "Zatím nemáte nainstalované žádné doplňky. Chcete-li začít, přejděte do obchodu s doplňky." "no_addons": "Zatím nemáte nainstalované žádné doplňky. Chcete-li začít, přejděte do obchodu s doplňky."
}, },
"dialog": { "dialog": {
"hardware": {
"attributes": "Atributy",
"device_path": "Cesta k zařízení",
"id": "ID",
"search": "Hledat hardware",
"subsystem": "Subsystém",
"title": "Hardware"
},
"network": { "network": {
"connected_to": "Připojeno k {ssid}", "connected_to": "Připojeno k {ssid}",
"dhcp": "DHCP", "dhcp": "DHCP",
@@ -382,6 +390,7 @@
"snapshot": { "snapshot": {
"addons": "Doplňky", "addons": "Doplňky",
"available_snapshots": "Dostupné zálohy", "available_snapshots": "Dostupné zálohy",
"confirm_password": "Potvrďte heslo zálohy",
"could_not_create": "Nelze vytvořit zálohu", "could_not_create": "Nelze vytvořit zálohu",
"create": "Vytvořit", "create": "Vytvořit",
"create_blocked_not_running": "Vytvoření zálohy není momentálně možné, protože systém je ve \"{state}\".", "create_blocked_not_running": "Vytvoření zálohy není momentálně možné, protože systém je ve \"{state}\".",
@@ -409,6 +418,7 @@
"password": "Heslo zálohy", "password": "Heslo zálohy",
"password_protected": "chráněno heslem", "password_protected": "chráněno heslem",
"password_protection": "Ochrana heslem", "password_protection": "Ochrana heslem",
"passwords_not_matching": "Hesla se neshodují",
"security": "Zabezpečení", "security": "Zabezpečení",
"select_type": "Vyberte, co chcete obnovit", "select_type": "Vyberte, co chcete obnovit",
"selected": "{number} vybraných", "selected": "{number} vybraných",
@@ -3544,7 +3554,7 @@
"options": "Více možností", "options": "Více možností",
"pick_card": "Kterou kartu chcete přidat?", "pick_card": "Kterou kartu chcete přidat?",
"pick_card_view_title": "Kterou kartu byste chtěli přidat do svého {name} pohledu?", "pick_card_view_title": "Kterou kartu byste chtěli přidat do svého {name} pohledu?",
"search_cards": "Vyhledat karty", "search_cards": "Hledat karty",
"show_code_editor": "Zobrazit editor kódu", "show_code_editor": "Zobrazit editor kódu",
"show_visual_editor": "Zobrazit vizuální editor", "show_visual_editor": "Zobrazit vizuální editor",
"toggle_editor": "Přepnout Editor", "toggle_editor": "Přepnout Editor",
@@ -3876,10 +3886,19 @@
"intro": "Jste připraveni oživit svůj domov, zachovat si své soukromí a připojit se k celosvětové komunitě tvůrců?", "intro": "Jste připraveni oživit svůj domov, zachovat si své soukromí a připojit se k celosvětové komunitě tvůrců?",
"next": "Další", "next": "Další",
"restore": { "restore": {
"addons": "Doplňky",
"confirm_password": "Potvrďte heslo zálohy",
"description": "Případně můžete Home Assistant obnovit z poslední zálohy.", "description": "Případně můžete Home Assistant obnovit z poslední zálohy.",
"folders": "Složky",
"full_snapshot": "Úplná záloha",
"hide_log": "Skrýt celý log", "hide_log": "Skrýt celý log",
"in_progress": "Probíhá obnovení", "in_progress": "Probíhá obnovení",
"show_log": "Zobrazit celý log" "partial_snapshot": "Částečná záloha",
"password": "Heslo zálohy",
"password_protection": "Ochrana heslem",
"select_type": "Vyberte, co chcete obnovit",
"show_log": "Zobrazit celý log",
"type": "Typ zálohy"
}, },
"user": { "user": {
"create_account": "Vytvořit účet", "create_account": "Vytvořit účet",

View File

@@ -318,6 +318,14 @@
"no_addons": "Du har endnu ikke installeret nogen tilføjelsesprogrammer. Gå over til butikken for tilføjelsesprogrammer for at komme i gang!" "no_addons": "Du har endnu ikke installeret nogen tilføjelsesprogrammer. Gå over til butikken for tilføjelsesprogrammer for at komme i gang!"
}, },
"dialog": { "dialog": {
"hardware": {
"attributes": "Attributter",
"device_path": "Enhedssti",
"id": "Identifikationsnummer",
"search": "Søg efter hardware",
"subsystem": "Delsystem",
"title": "Hardware"
},
"network": { "network": {
"connected_to": "Forbundet til {ssid}", "connected_to": "Forbundet til {ssid}",
"dhcp": "DHCP", "dhcp": "DHCP",
@@ -368,6 +376,8 @@
"error": "Der opstod en ukendt fejl", "error": "Der opstod en ukendt fejl",
"error_addon_no_ingress": "Tilføjelsesprogrammet understøtter ikke ingress", "error_addon_no_ingress": "Tilføjelsesprogrammet understøtter ikke ingress",
"error_addon_not_found": "Tilføjelsesprogrammet blev ikke fundet", "error_addon_not_found": "Tilføjelsesprogrammet blev ikke fundet",
"error_addon_not_installed": "Tilføjelsesprogrammet er ikke installeret venligst installer det først",
"error_addon_not_started": "Tilføjelsesprogrammet kører ikke venligst start det først",
"faq_link": "Ofte stillede spørgsmål om Home Assistant", "faq_link": "Ofte stillede spørgsmål om Home Assistant",
"not_supported": "Denne omdirigering understøttes ikke af din Home Assistant installation. Kontroller {link} for de understøttede omdirigeringer og den version, de blev introduceret i." "not_supported": "Denne omdirigering understøttes ikke af din Home Assistant installation. Kontroller {link} for de understøttede omdirigeringer og den version, de blev introduceret i."
}, },
@@ -380,6 +390,7 @@
"snapshot": { "snapshot": {
"addons": "Tilføjelsesprogrammer", "addons": "Tilføjelsesprogrammer",
"available_snapshots": "Tilgængelige snapshots", "available_snapshots": "Tilgængelige snapshots",
"confirm_password": "Bekræft snapshot kodeord",
"could_not_create": "Kunne ikke oprette snapshot", "could_not_create": "Kunne ikke oprette snapshot",
"create": "Opret", "create": "Opret",
"create_blocked_not_running": "Det er ikke muligt at oprette et snapshot lige nu, fordi systemet er i tilstanden {state}.", "create_blocked_not_running": "Det er ikke muligt at oprette et snapshot lige nu, fordi systemet er i tilstanden {state}.",
@@ -407,6 +418,7 @@
"password": "Kodeord", "password": "Kodeord",
"password_protected": "beskyttet med kodeord", "password_protected": "beskyttet med kodeord",
"password_protection": "Kodeordsbeskyttelse", "password_protection": "Kodeordsbeskyttelse",
"passwords_not_matching": "Kodeordene er forskellige",
"security": "Sikkerhed", "security": "Sikkerhed",
"select_type": "Vælg, hvad der skal gendannes", "select_type": "Vælg, hvad der skal gendannes",
"selected": "{number} valgt", "selected": "{number} valgt",
@@ -931,6 +943,9 @@
"config_entry_system_options": { "config_entry_system_options": {
"enable_new_entities_description": "Hvis deaktiveret, tilføjes nyligt opdagede entiteter fra {integration} ikke automatisk til Home Assistant.", "enable_new_entities_description": "Hvis deaktiveret, tilføjes nyligt opdagede entiteter fra {integration} ikke automatisk til Home Assistant.",
"enable_new_entities_label": "Aktivér nyligt tilføjede entiteter.", "enable_new_entities_label": "Aktivér nyligt tilføjede entiteter.",
"enable_polling_description": "Hvis Home Assistant automatisk skal hente {integration} enheder for opdateringer.",
"enable_polling_label": "Aktiver automatisk henting af opdateringer",
"restart_home_assistant": "Du skal genstarte Home Assistant før dine ændringer aktiveres",
"title": "Systemindstillinger for {integration}", "title": "Systemindstillinger for {integration}",
"update": "Opdater" "update": "Opdater"
}, },
@@ -2073,7 +2088,8 @@
"scripts": "Scripts", "scripts": "Scripts",
"unknown_error": "Ukendt fejl", "unknown_error": "Ukendt fejl",
"unnamed_device": "Enhed uden navn", "unnamed_device": "Enhed uden navn",
"update": "Opdater" "update": "Opdater",
"update_device_error": "Enhedsopdateringen fejlede"
}, },
"entities": { "entities": {
"caption": "Entiteter", "caption": "Entiteter",
@@ -2206,6 +2222,7 @@
"depends_on_cloud": "Afhængig af Cloud tjenester", "depends_on_cloud": "Afhængig af Cloud tjenester",
"device_unavailable": "Enheden er utilgængelig", "device_unavailable": "Enheden er utilgængelig",
"devices": "{count} {count, plural,\n one {enhed}\n other {enheder}\n}", "devices": "{count} {count, plural,\n one {enhed}\n other {enheder}\n}",
"disable_error": "Aktivering eller deaktivering af integrationen fejlede",
"disable_restart_confirm": "Genstart Home Assistant for at fuldføre deaktivering af denne integration", "disable_restart_confirm": "Genstart Home Assistant for at fuldføre deaktivering af denne integration",
"disable": { "disable": {
"disable_confirm": "Er du sikker på, at du vil deaktivere denne integration? Integrationens enheder og entiteter vil blive deaktiveret.", "disable_confirm": "Er du sikker på, at du vil deaktivere denne integration? Integrationens enheder og entiteter vil blive deaktiveret.",
@@ -2217,6 +2234,7 @@
}, },
"disabled_cause": "Deaktiveret af {cause}" "disabled_cause": "Deaktiveret af {cause}"
}, },
"disabled_polling": "Den automatiske hentning af opdateret data er frakoblet",
"documentation": "Dokumentation", "documentation": "Dokumentation",
"enable_restart_confirm": "Genstart Home Assistant for at fuldføre aktivering af denne integration", "enable_restart_confirm": "Genstart Home Assistant for at fuldføre aktivering af denne integration",
"entities": "{count} {count, plural,\n one {entitet}\n other {entiteter}\n}", "entities": "{count} {count, plural,\n one {entitet}\n other {entiteter}\n}",
@@ -3873,10 +3891,19 @@
"intro": "Er du klar til at vække dit hjem til live, genvinde dit privatliv og blive medlem af et verdensomspændende fællesskab af tinkerers?", "intro": "Er du klar til at vække dit hjem til live, genvinde dit privatliv og blive medlem af et verdensomspændende fællesskab af tinkerers?",
"next": "Næste", "next": "Næste",
"restore": { "restore": {
"addons": "Tilføjelse",
"confirm_password": "Bekræft Snapshot kodeord",
"description": "Alternativt kan du gendanne fra et tidligere snapshot.", "description": "Alternativt kan du gendanne fra et tidligere snapshot.",
"folders": "Mappe",
"full_snapshot": "Fuld snapshot",
"hide_log": "Skjul fuld log", "hide_log": "Skjul fuld log",
"in_progress": "Gendannelse er i gang", "in_progress": "Gendannelse er i gang",
"show_log": "Vis den fulde log" "partial_snapshot": "Delvis snapshot",
"password": "Snapshot kodeord",
"password_protection": "Kodeordsbeskyttelse",
"select_type": "Vælg det der skal genetableres",
"show_log": "Vis den fulde log",
"type": "Snapshot type"
}, },
"user": { "user": {
"create_account": "Opret konto", "create_account": "Opret konto",

View File

@@ -314,10 +314,18 @@
"addon_new_version": "Neue Version verfügbar", "addon_new_version": "Neue Version verfügbar",
"addon_running": "Add-on wird ausgeführt", "addon_running": "Add-on wird ausgeführt",
"addon_stopped": "Add-on ist gestoppt", "addon_stopped": "Add-on ist gestoppt",
"addons": "Add-ons", "addons": "Installierte Add-ons",
"no_addons": "Du hast noch keine Add-ons installiert. Gehe zum Add-on Store, um loszulegen!" "no_addons": "Du hast noch keine Add-ons installiert. Gehe zum Add-on Store, um loszulegen!"
}, },
"dialog": { "dialog": {
"hardware": {
"attributes": "Attribute",
"device_path": "Gerätepfad",
"id": "ID",
"search": "Hardware suchen",
"subsystem": "Subsystem",
"title": "Hardware"
},
"network": { "network": {
"connected_to": "Verbunden mit {ssid}", "connected_to": "Verbunden mit {ssid}",
"dhcp": "DHCP", "dhcp": "DHCP",
@@ -382,6 +390,7 @@
"snapshot": { "snapshot": {
"addons": "Add-ons", "addons": "Add-ons",
"available_snapshots": "Verfügbare Datensicherungen", "available_snapshots": "Verfügbare Datensicherungen",
"confirm_password": "Snapshot-Passwort bestätigen",
"could_not_create": "Datensicherung konnte nicht erstellt werden", "could_not_create": "Datensicherung konnte nicht erstellt werden",
"create": "Erstellen", "create": "Erstellen",
"create_blocked_not_running": "Das Erstellen eines Snapshots ist derzeit nicht möglich, da sich das System im Zustand {state} befindet.", "create_blocked_not_running": "Das Erstellen eines Snapshots ist derzeit nicht möglich, da sich das System im Zustand {state} befindet.",
@@ -403,16 +412,17 @@
}, },
"folders": "Ordner", "folders": "Ordner",
"full_snapshot": "Vollständige Datensicherung", "full_snapshot": "Vollständige Datensicherung",
"name": "Name", "name": "Snapshot-Name",
"no_snapshots": "Du hast bisher keine Datensicherungen erstellt.", "no_snapshots": "Du hast bisher keine Datensicherungen erstellt.",
"partial_snapshot": "Selektive Datensicherung", "partial_snapshot": "Selektive Datensicherung",
"password": "Passwort", "password": "Snapshot-Passwort",
"password_protected": "Passwort geschützt", "password_protected": "Passwort geschützt",
"password_protection": "Passwortschutz", "password_protection": "Passwortschutz",
"passwords_not_matching": "Passwörter stimmen nicht überein",
"security": "Sicherheit", "security": "Sicherheit",
"select_type": "Wähle aus, was wiederhergestellt werden soll", "select_type": "Wähle aus, was wiederhergestellt werden soll",
"selected": "{number} ausgewählt", "selected": "{number} ausgewählt",
"type": "Typ", "type": "Snapshot-Typ",
"upload_snapshot": "Datensicherung hochladen" "upload_snapshot": "Datensicherung hochladen"
}, },
"store": { "store": {
@@ -3881,10 +3891,19 @@
"intro": "Sind Sie bereit, dein Zuhause zu wecken, Ihre Privatsphäre zurückzugewinnen und einer weltweiten Gemeinschaft von Tüftlern beizutreten?", "intro": "Sind Sie bereit, dein Zuhause zu wecken, Ihre Privatsphäre zurückzugewinnen und einer weltweiten Gemeinschaft von Tüftlern beizutreten?",
"next": "Weiter", "next": "Weiter",
"restore": { "restore": {
"addons": "Add-ons",
"confirm_password": "Snapshot-Passwort bestätigen",
"description": "Alternativ kannst du von einem vorherigen Snapshot wiederherstellen.", "description": "Alternativ kannst du von einem vorherigen Snapshot wiederherstellen.",
"folders": "Ordner",
"full_snapshot": "Vollständige Datensicherung",
"hide_log": "Vollständiges Protokoll ausblenden", "hide_log": "Vollständiges Protokoll ausblenden",
"in_progress": "Wiederherstellung im Gange", "in_progress": "Wiederherstellung im Gange",
"show_log": "Vollständiges Protokoll anzeigen" "partial_snapshot": "Selektive Datensicherung",
"password": "Snapshot-Passwort",
"password_protection": "Passwortschutz",
"select_type": "Wähle aus, was wiederhergestellt werden soll",
"show_log": "Vollständiges Protokoll anzeigen",
"type": "Snapshot-Typ"
}, },
"user": { "user": {
"create_account": "Benutzerkonto anlegen", "create_account": "Benutzerkonto anlegen",

View File

@@ -318,6 +318,14 @@
"no_addons": "You don't have any add-ons installed yet. Head over to the add-on store to get started!" "no_addons": "You don't have any add-ons installed yet. Head over to the add-on store to get started!"
}, },
"dialog": { "dialog": {
"hardware": {
"attributes": "Attributes",
"device_path": "Device path",
"id": "ID",
"search": "Search hardware",
"subsystem": "Subsystem",
"title": "Hardware"
},
"network": { "network": {
"connected_to": "Connected to {ssid}", "connected_to": "Connected to {ssid}",
"dhcp": "DHCP", "dhcp": "DHCP",
@@ -382,6 +390,7 @@
"snapshot": { "snapshot": {
"addons": "Add-ons", "addons": "Add-ons",
"available_snapshots": "Available Snapshots", "available_snapshots": "Available Snapshots",
"confirm_password": "Confirm Snapshot password",
"could_not_create": "Could not create snapshot", "could_not_create": "Could not create snapshot",
"create": "Create", "create": "Create",
"create_blocked_not_running": "Creating a snapshot is not possible right now because the system is in {state} state.", "create_blocked_not_running": "Creating a snapshot is not possible right now because the system is in {state} state.",
@@ -409,6 +418,7 @@
"password": "Snapshot password", "password": "Snapshot password",
"password_protected": "password protected", "password_protected": "password protected",
"password_protection": "Password protection", "password_protection": "Password protection",
"passwords_not_matching": "The passwords does not match",
"security": "Security", "security": "Security",
"select_type": "Select what to restore", "select_type": "Select what to restore",
"selected": "{number} selected", "selected": "{number} selected",
@@ -3881,10 +3891,19 @@
"intro": "Are you ready to awaken your home, reclaim your privacy and join a worldwide community of tinkerers?", "intro": "Are you ready to awaken your home, reclaim your privacy and join a worldwide community of tinkerers?",
"next": "Next", "next": "Next",
"restore": { "restore": {
"addons": "Add-ons",
"confirm_password": "Confirm Snapshot password",
"description": "Alternatively you can restore from a previous snapshot.", "description": "Alternatively you can restore from a previous snapshot.",
"folders": "Folders",
"full_snapshot": "Full snapshot",
"hide_log": "Hide full log", "hide_log": "Hide full log",
"in_progress": "Restore in progress", "in_progress": "Restore in progress",
"show_log": "Show full log" "partial_snapshot": "Partial snapshot",
"password": "Snapshot password",
"password_protection": "Password protection",
"select_type": "Select what to restore",
"show_log": "Show full log",
"type": "Snapshot type"
}, },
"user": { "user": {
"create_account": "Create Account", "create_account": "Create Account",

View File

@@ -318,6 +318,14 @@
"no_addons": "Aún no tienes ningún complemento instalado. ¡Dirígete a la tienda de complementos para comenzar!" "no_addons": "Aún no tienes ningún complemento instalado. ¡Dirígete a la tienda de complementos para comenzar!"
}, },
"dialog": { "dialog": {
"hardware": {
"attributes": "Atributos",
"device_path": "Ruta del dispositivo",
"id": "ID",
"search": "Buscar hardware",
"subsystem": "Subsistema",
"title": "Hardware"
},
"network": { "network": {
"connected_to": "Conectado a {ssid}", "connected_to": "Conectado a {ssid}",
"dhcp": "DHCP", "dhcp": "DHCP",
@@ -382,6 +390,7 @@
"snapshot": { "snapshot": {
"addons": "Complementos", "addons": "Complementos",
"available_snapshots": "Instantáneas disponibles", "available_snapshots": "Instantáneas disponibles",
"confirm_password": "Confirma la contraseña de la instantánea",
"could_not_create": "No se pudo crear la instantánea", "could_not_create": "No se pudo crear la instantánea",
"create": "Crear", "create": "Crear",
"create_blocked_not_running": "No es posible crear una instantánea en este momento porque el sistema está en el estado {state}.", "create_blocked_not_running": "No es posible crear una instantánea en este momento porque el sistema está en el estado {state}.",
@@ -409,6 +418,7 @@
"password": "Contraseña de la instantánea", "password": "Contraseña de la instantánea",
"password_protected": "protegida por contraseña", "password_protected": "protegida por contraseña",
"password_protection": "Protección con contraseña", "password_protection": "Protección con contraseña",
"passwords_not_matching": "Las contraseñas no coinciden",
"security": "Seguridad", "security": "Seguridad",
"select_type": "Selecciona qué restaurar", "select_type": "Selecciona qué restaurar",
"selected": "{number} {number, plural,\n one {seleccionada}\n other {seleccionadas}\n}", "selected": "{number} {number, plural,\n one {seleccionada}\n other {seleccionadas}\n}",
@@ -3881,10 +3891,19 @@
"intro": "¿Estás listo para despertar tu casa, reclamar tu privacidad y unirte a una comunidad mundial de pensadores?", "intro": "¿Estás listo para despertar tu casa, reclamar tu privacidad y unirte a una comunidad mundial de pensadores?",
"next": "Siguiente", "next": "Siguiente",
"restore": { "restore": {
"addons": "Complementos",
"confirm_password": "Confirma la contraseña de la instantánea",
"description": "Alternativamente, puedes restaurar desde una copia de seguridad anterior.", "description": "Alternativamente, puedes restaurar desde una copia de seguridad anterior.",
"folders": "Carpetas",
"full_snapshot": "Instantánea completa",
"hide_log": "Ocultar registro completo", "hide_log": "Ocultar registro completo",
"in_progress": "Restauración en curso", "in_progress": "Restauración en curso",
"show_log": "Mostrar registro completo" "partial_snapshot": "Instantánea parcial",
"password": "Contraseña de la instantánea",
"password_protection": "Protección con contraseña",
"select_type": "Selecciona qué restaurar",
"show_log": "Mostrar registro completo",
"type": "Tipo de instantánea"
}, },
"user": { "user": {
"create_account": "Crear una cuenta", "create_account": "Crear una cuenta",

View File

@@ -318,6 +318,14 @@
"no_addons": "Ühtegi lisandmoodulit pole veel paigaldatud. Alustamiseks mine lisandmoodulite hoidlasse!" "no_addons": "Ühtegi lisandmoodulit pole veel paigaldatud. Alustamiseks mine lisandmoodulite hoidlasse!"
}, },
"dialog": { "dialog": {
"hardware": {
"attributes": "Atribuudid",
"device_path": "Seadme asukoht",
"id": "ID",
"search": "Otsi riistvara",
"subsystem": "Alamsüsteem",
"title": "Riistvara"
},
"network": { "network": {
"connected_to": "Ühendatud pääsupunktiga {ssid}", "connected_to": "Ühendatud pääsupunktiga {ssid}",
"dhcp": "DHCP", "dhcp": "DHCP",
@@ -358,9 +366,9 @@
"text": "Kas soovid lisandmooduli taaskäivitada rakendades muudatused?" "text": "Kas soovid lisandmooduli taaskäivitada rakendades muudatused?"
}, },
"update": { "update": {
"create_snapshot": "Enne {name} värskendamist loo hetktõmmis", "create_snapshot": "Enne {name} värskendamist loo varukoopia",
"snapshot": "Hetktõmmis", "snapshot": "Varukoopia",
"snapshotting": "Üksuse {name} hetktõmmise loomine", "snapshotting": "Üksuse {name} varukoopia loomine",
"updating": "Üksuse {name} värskendamine versioonile {version}" "updating": "Üksuse {name} värskendamine versioonile {version}"
} }
}, },
@@ -381,17 +389,18 @@
}, },
"snapshot": { "snapshot": {
"addons": "Lisandmoodulid", "addons": "Lisandmoodulid",
"available_snapshots": "Saadaolevad hetktõmmised", "available_snapshots": "Saadaolevad varukoopiad",
"could_not_create": "Hetktõmmise loomine nurjus", "confirm_password": "Varukoopia salasõna kinnitamine",
"could_not_create": "Varukoopia loomine nurjus",
"create": "Loo", "create": "Loo",
"create_blocked_not_running": "Hetktõmmise loomine pole praegu võimalik kuna süsteem on olekus {state}.", "create_blocked_not_running": "Varukoopia loomine pole praegu võimalik kuna süsteem on olekus {state}.",
"create_snapshot": "Loo hetktõmmis", "create_snapshot": "Loo varukoopia",
"created": "Loodud", "created": "Loodud",
"delete_selected": "Kustuta valitud hetktõmmised", "delete_selected": "Kustuta valitud varukoopiad",
"delete_snapshot_confirm": "kustuta", "delete_snapshot_confirm": "kustuta",
"delete_snapshot_text": "Kas kustutada {number} {number, plural,\n one {hetktõmmis}\n other {hetktõmmist}\n}?", "delete_snapshot_text": "Kas kustutada {number} {number, plural,\n one {varukoopia}\n other {varukoopiat}\n}?",
"delete_snapshot_title": "Kustuta hetktõmmis", "delete_snapshot_title": "Kustuta varukoopia",
"description": "Hetktõmmised võimaldavad hõlpsalt varundada ja taastada kõik Home Assistanti andmed.", "description": "Varukoopiad võimaldavad hõlpsalt varundada ja taastada kõik Home Assistanti andmed.",
"enter_password": "Sisesta salasõna.", "enter_password": "Sisesta salasõna.",
"failed_to_delete": "Kustutamine nurjus", "failed_to_delete": "Kustutamine nurjus",
"folder": { "folder": {
@@ -402,18 +411,19 @@
"ssl": "SSL" "ssl": "SSL"
}, },
"folders": "Kaustad", "folders": "Kaustad",
"full_snapshot": "Täielik hetktõmmis", "full_snapshot": "Täielik varukoopia",
"name": "Hetktõmmise nimi", "name": "Varukoopia nimi",
"no_snapshots": "Sul pole veel ühtegi hetktõmmist.", "no_snapshots": "Sul pole veel ühtegi varukoopiat.",
"partial_snapshot": "Osaline hetktõmmis", "partial_snapshot": "Osaline varukoopia",
"password": "Hetktõmmise salasõna", "password": "Varukoopia salasõna",
"password_protected": "salasõnaha kaitstud", "password_protected": "salasõnaha kaitstud",
"password_protection": "Salasõnaga kaitstud", "password_protection": "Salasõnaga kaitstud",
"passwords_not_matching": "Salasõnad ei ühti",
"security": "Turvalisus", "security": "Turvalisus",
"select_type": "Vali mida taastada", "select_type": "Vali mida taastada",
"selected": "{number} valitud", "selected": "{number} valitud",
"type": "Hetktõmmise tüüp", "type": "Varukoopia tüüp",
"upload_snapshot": "Hetktõmmise üleslaadimine" "upload_snapshot": "Varukoopia üleslaadimine"
}, },
"store": { "store": {
"missing_addons": "Ei näe lisandmooduleid? Luba täpsem režiim oma kasutajaprofiili lehel", "missing_addons": "Ei näe lisandmooduleid? Luba täpsem režiim oma kasutajaprofiili lehel",
@@ -3881,10 +3891,19 @@
"intro": "Kas oled valmis oma kodu ellu äratama, oma privaatsust tagasi võitma ja ühinema ülemaailmse nokitsejate kogukonnaga?", "intro": "Kas oled valmis oma kodu ellu äratama, oma privaatsust tagasi võitma ja ühinema ülemaailmse nokitsejate kogukonnaga?",
"next": "Järgmine", "next": "Järgmine",
"restore": { "restore": {
"description": "Teise võimalusena saad taastada eelmise hetktõmmise.", "addons": "Lisandmoodulid",
"confirm_password": "Kinnita salasõna",
"description": "Teise võimalusena saad taastada eelmise varukoopia.",
"folders": "Kaustad",
"full_snapshot": "Täielik varukoopia",
"hide_log": "Peida täielik logi", "hide_log": "Peida täielik logi",
"in_progress": "Toimub taastamine", "in_progress": "Toimub taastamine",
"show_log": "Kuva täielik logi" "partial_snapshot": "Osaline varukoopia",
"password": "Salasõna",
"password_protection": "Salasõnaga kaitstud",
"select_type": "Vali andmestiku tüüp",
"show_log": "Kuva täielik logi",
"type": "Andmestiku tüüp"
}, },
"user": { "user": {
"create_account": "Loo konto", "create_account": "Loo konto",

View File

@@ -318,6 +318,14 @@
"no_addons": "עדיין לא מותקנות הרחבות. גשו לחנות ההרחבה כדי להתחיל!" "no_addons": "עדיין לא מותקנות הרחבות. גשו לחנות ההרחבה כדי להתחיל!"
}, },
"dialog": { "dialog": {
"hardware": {
"attributes": "תכונות",
"device_path": "נתיב התקן",
"id": "מזהה",
"search": "חפש חומרה",
"subsystem": "תת מערכת",
"title": "חומרה"
},
"network": { "network": {
"connected_to": "מחובר אל {ssid}", "connected_to": "מחובר אל {ssid}",
"dhcp": "DHCP", "dhcp": "DHCP",
@@ -382,6 +390,7 @@
"snapshot": { "snapshot": {
"addons": "תוספים", "addons": "תוספים",
"available_snapshots": "גיבויים זמינים", "available_snapshots": "גיבויים זמינים",
"confirm_password": "אישור סיסמת גיבוי",
"could_not_create": "לא ניתן ליצור גיבוי", "could_not_create": "לא ניתן ליצור גיבוי",
"create": "צור", "create": "צור",
"create_blocked_not_running": "יצירת גיבוי אינה אפשרית כרגע מכיוון שהמערכת במצב {state} .", "create_blocked_not_running": "יצירת גיבוי אינה אפשרית כרגע מכיוון שהמערכת במצב {state} .",
@@ -409,6 +418,7 @@
"password": "סיסמה", "password": "סיסמה",
"password_protected": "מוגן באמצעות סיסמה", "password_protected": "מוגן באמצעות סיסמה",
"password_protection": "הגנה באמצעות סיסמה", "password_protection": "הגנה באמצעות סיסמה",
"passwords_not_matching": "הסיסמאות אינן תואמות",
"security": "אבטחה", "security": "אבטחה",
"select_type": "בחר מה לשחזר", "select_type": "בחר מה לשחזר",
"selected": "{number} נבחרו", "selected": "{number} נבחרו",
@@ -3597,7 +3607,7 @@
"menu": { "menu": {
"manage_dashboards": "נהל לוחות מחוונים", "manage_dashboards": "נהל לוחות מחוונים",
"manage_resources": "נהל משאבים", "manage_resources": "נהל משאבים",
"open": "פתיחת את תפריט ממשק המשתמש Lovelace", "open": "פתיחת תפריט ממשק המשתמש Lovelace",
"raw_editor": "עורך תצורה גולמית" "raw_editor": "עורך תצורה גולמית"
}, },
"migrate": { "migrate": {
@@ -3881,10 +3891,19 @@
"intro": "האם אתה רוצה לקחת שליטה על הבית שלך ולהצטרף לקהילה הגדולה שלנו?", "intro": "האם אתה רוצה לקחת שליטה על הבית שלך ולהצטרף לקהילה הגדולה שלנו?",
"next": "הבא", "next": "הבא",
"restore": { "restore": {
"addons": "תוספים",
"confirm_password": "אישור סיסמת גיבוי",
"description": "לחלופין, באפשרותך לשחזר מגיבוי קודם.", "description": "לחלופין, באפשרותך לשחזר מגיבוי קודם.",
"folders": "תיקיות",
"full_snapshot": "גיבוי מלא",
"hide_log": "הסתר יומן מלא", "hide_log": "הסתר יומן מלא",
"in_progress": "שחזור מתבצע", "in_progress": "שחזור מתבצע",
"show_log": "הצג יומן מלא" "partial_snapshot": "גיבוי חלקי",
"password": "סיסמה",
"password_protection": "הגנה באמצעות סיסמה",
"select_type": "בחר מה לשחזר",
"show_log": "הצג יומן מלא",
"type": "סוג נקודת גיבוי"
}, },
"user": { "user": {
"create_account": "צור חשבון", "create_account": "צור חשבון",

View File

@@ -318,6 +318,14 @@
"no_addons": "Non hai ancora installato alcun componente aggiuntivo. Vai al negozio di componenti aggiuntivi per iniziare!" "no_addons": "Non hai ancora installato alcun componente aggiuntivo. Vai al negozio di componenti aggiuntivi per iniziare!"
}, },
"dialog": { "dialog": {
"hardware": {
"attributes": "Attributi",
"device_path": "Percorso del dispositivo",
"id": "ID",
"search": "Cerca hardware",
"subsystem": "Sottosistema",
"title": "Hardware"
},
"network": { "network": {
"connected_to": "Connesso a {ssid}", "connected_to": "Connesso a {ssid}",
"dhcp": "DHCP", "dhcp": "DHCP",
@@ -382,6 +390,7 @@
"snapshot": { "snapshot": {
"addons": "Componenti aggiuntivi", "addons": "Componenti aggiuntivi",
"available_snapshots": "Istantanee disponibili", "available_snapshots": "Istantanee disponibili",
"confirm_password": "Conferma la password dell'istantanea",
"could_not_create": "Impossibile creare l'istantanea", "could_not_create": "Impossibile creare l'istantanea",
"create": "Crea", "create": "Crea",
"create_blocked_not_running": "La creazione di un'istantanea non è al momento possibile perché il sistema è nello stato {state}.", "create_blocked_not_running": "La creazione di un'istantanea non è al momento possibile perché il sistema è nello stato {state}.",
@@ -409,6 +418,7 @@
"password": "Password dell'istantanea", "password": "Password dell'istantanea",
"password_protected": "protetto da password", "password_protected": "protetto da password",
"password_protection": "Protezione con password", "password_protection": "Protezione con password",
"passwords_not_matching": "Le password non corrispondono",
"security": "Sicurezza", "security": "Sicurezza",
"select_type": "Seleziona cosa ripristinare", "select_type": "Seleziona cosa ripristinare",
"selected": "{number} selezionato/i", "selected": "{number} selezionato/i",
@@ -3881,10 +3891,19 @@
"intro": "Sei pronto per risvegliare la tua casa, reclamare la tua privacy e far parte di una comunità mondiale di smanettoni?", "intro": "Sei pronto per risvegliare la tua casa, reclamare la tua privacy e far parte di una comunità mondiale di smanettoni?",
"next": "Avanti", "next": "Avanti",
"restore": { "restore": {
"addons": "Componenti aggiuntivi",
"confirm_password": "Conferma la password dell'istantanea",
"description": "In alternativa è possibile ripristinare da un'istantanea precedente.", "description": "In alternativa è possibile ripristinare da un'istantanea precedente.",
"folders": "Cartelle",
"full_snapshot": "Istantanea completa",
"hide_log": "Nascondi il registro completo", "hide_log": "Nascondi il registro completo",
"in_progress": "Ripristino in corso", "in_progress": "Ripristino in corso",
"show_log": "Mostra il registro completo" "partial_snapshot": "Istantanea parziale",
"password": "Password dell'istantanea",
"password_protection": "Protezione con password",
"select_type": "Seleziona cosa ripristinare",
"show_log": "Mostra il registro completo",
"type": "Tipo di istantanea"
}, },
"user": { "user": {
"create_account": "Crea un Account", "create_account": "Crea un Account",

View File

@@ -318,6 +318,14 @@
"no_addons": "아직 설치된 애드온이 없습니다. 시작하려면 애드온 스토어로 이동해보세요!" "no_addons": "아직 설치된 애드온이 없습니다. 시작하려면 애드온 스토어로 이동해보세요!"
}, },
"dialog": { "dialog": {
"hardware": {
"attributes": "속성",
"device_path": "장치 경로",
"id": "ID",
"search": "하드웨어 검색",
"subsystem": "서브 시스템",
"title": "하드웨어"
},
"network": { "network": {
"connected_to": "{ssid}에 연결되었습니다", "connected_to": "{ssid}에 연결되었습니다",
"dhcp": "자동 구성", "dhcp": "자동 구성",
@@ -382,6 +390,7 @@
"snapshot": { "snapshot": {
"addons": "애드온", "addons": "애드온",
"available_snapshots": "사용 가능한 스냅숏", "available_snapshots": "사용 가능한 스냅숏",
"confirm_password": "스냅샷 비밀번호 확인",
"could_not_create": "스냅숏을 만들 수 없습니다", "could_not_create": "스냅숏을 만들 수 없습니다",
"create": "생성하기", "create": "생성하기",
"create_blocked_not_running": "시스템이 {state} 상태이기 때문에 지금은 스냅숏을 생성할 수 없습니다.", "create_blocked_not_running": "시스템이 {state} 상태이기 때문에 지금은 스냅숏을 생성할 수 없습니다.",
@@ -409,6 +418,7 @@
"password": "비밀번호", "password": "비밀번호",
"password_protected": "비밀번호로 보호됨", "password_protected": "비밀번호로 보호됨",
"password_protection": "비밀번호 보호", "password_protection": "비밀번호 보호",
"passwords_not_matching": "비밀번호가 일치하지 않습니다",
"security": "보안", "security": "보안",
"select_type": "복원 할 항목 선택", "select_type": "복원 할 항목 선택",
"selected": "{number}개 선택됨", "selected": "{number}개 선택됨",
@@ -3881,10 +3891,19 @@
"intro": "잠들어 있는 집을 깨우고 개인정보를 보호하며 전세계의 공돌이 커뮤니티에 가입 할 준비가 되셨나요?", "intro": "잠들어 있는 집을 깨우고 개인정보를 보호하며 전세계의 공돌이 커뮤니티에 가입 할 준비가 되셨나요?",
"next": "다음", "next": "다음",
"restore": { "restore": {
"addons": "애드온",
"confirm_password": "스냅샷 비밀번호 확인",
"description": "이전 스냅숏에서 복원할 수 있습니다.", "description": "이전 스냅숏에서 복원할 수 있습니다.",
"folders": "폴더",
"full_snapshot": "전체 스냅샷",
"hide_log": "전체 로그 숨기기", "hide_log": "전체 로그 숨기기",
"in_progress": "복원 중", "in_progress": "복원 중",
"show_log": "전체 로그 표시하기" "partial_snapshot": "부분 스냅샷",
"password": "스냅샷 비밀번호",
"password_protection": "비밀번호 보호",
"select_type": "무엇을 복원할지 선택하세요",
"show_log": "전체 로그 표시하기",
"type": "스냅샷 종류"
}, },
"user": { "user": {
"create_account": "계정 만들기", "create_account": "계정 만들기",

View File

@@ -318,6 +318,14 @@
"no_addons": "Du har ikke installert tilleggsprogrammer ennå. Gå over til tilleggsbutikken for å komme i gang!" "no_addons": "Du har ikke installert tilleggsprogrammer ennå. Gå over til tilleggsbutikken for å komme i gang!"
}, },
"dialog": { "dialog": {
"hardware": {
"attributes": "Attributter",
"device_path": "Bane til enhet",
"id": "ID",
"search": "Søk i maskinvare",
"subsystem": "Delsystem",
"title": "Maskinvare"
},
"network": { "network": {
"connected_to": "Koblet til {ssid}", "connected_to": "Koblet til {ssid}",
"dhcp": "", "dhcp": "",
@@ -382,6 +390,7 @@
"snapshot": { "snapshot": {
"addons": "Tillegg", "addons": "Tillegg",
"available_snapshots": "Tilgjengelige sikkerhetskopier", "available_snapshots": "Tilgjengelige sikkerhetskopier",
"confirm_password": "Bekreft passord for øyeblikksbilde",
"could_not_create": "Kunne ikke opprette sikkerhetskopi", "could_not_create": "Kunne ikke opprette sikkerhetskopi",
"create": "Opprett", "create": "Opprett",
"create_blocked_not_running": "Å lage en sikkerhetskopi er ikke mulig akkurat nå fordi systemet er i {state} status", "create_blocked_not_running": "Å lage en sikkerhetskopi er ikke mulig akkurat nå fordi systemet er i {state} status",
@@ -409,6 +418,7 @@
"password": "Passord for øyeblikksbilde", "password": "Passord for øyeblikksbilde",
"password_protected": "Passordbeskyttet", "password_protected": "Passordbeskyttet",
"password_protection": "Passordbeskyttelse", "password_protection": "Passordbeskyttelse",
"passwords_not_matching": "Passordene samsvarer ikke",
"security": "Sikkerhet", "security": "Sikkerhet",
"select_type": "Velg hva du vil gjenopprette", "select_type": "Velg hva du vil gjenopprette",
"selected": "{number} valgt", "selected": "{number} valgt",
@@ -1130,7 +1140,7 @@
"zone": "Soner" "zone": "Soner"
}, },
"reload": { "reload": {
"automation": "Automatisering", "automation": "Automasjoner",
"command_line": "Kommandolinjeenheter", "command_line": "Kommandolinjeenheter",
"core": "Plassering og tilpasninger", "core": "Plassering og tilpasninger",
"filesize": "Enheter for filstørrelse", "filesize": "Enheter for filstørrelse",
@@ -1438,7 +1448,7 @@
"delete_confirm": "Er du sikker på at du vil slette dette?", "delete_confirm": "Er du sikker på at du vil slette dette?",
"duplicate": "Dupliser", "duplicate": "Dupliser",
"header": "Betingelser", "header": "Betingelser",
"introduction": "Betingelsene er valgfrie og vil hindre at automatiseringen kjøres med mindre alle betingelsene er oppfylt.", "introduction": "Betingelsene er valgfrie og vil hindre at automasjonen kjøres med mindre alle betingelsene er oppfylt.",
"learn_more": "Lær mer om betingelser", "learn_more": "Lær mer om betingelser",
"name": "Betingelse", "name": "Betingelse",
"type_select": "Betingelse", "type_select": "Betingelse",
@@ -1519,7 +1529,7 @@
"edit_ui": "Rediger i visuell editor", "edit_ui": "Rediger i visuell editor",
"edit_yaml": "Rediger i YAML", "edit_yaml": "Rediger i YAML",
"enable_disable": "Aktivere/deaktivere automasjon", "enable_disable": "Aktivere/deaktivere automasjon",
"introduction": "Bruk automatisering for å bringe hjemmet ditt til live.", "introduction": "Bruk automasjoner for å bringe hjemmet ditt til live.",
"load_error_not_editable": "Kun automasjoner i automations.yaml kan redigeres", "load_error_not_editable": "Kun automasjoner i automations.yaml kan redigeres",
"load_error_unknown": "Feil ved lasting av automasjon ({err_no}).", "load_error_unknown": "Feil ved lasting av automasjon ({err_no}).",
"max": { "max": {
@@ -1647,8 +1657,8 @@
"add_automation": "Legg til automasjon", "add_automation": "Legg til automasjon",
"delete_automation": "Slett automasjon", "delete_automation": "Slett automasjon",
"delete_confirm": "Er du sikker på at du vil slette denne automasjonen?", "delete_confirm": "Er du sikker på at du vil slette denne automasjonen?",
"dev_automation": "Feilsøk automatisering", "dev_automation": "Feilsøk automasjon",
"dev_only_editable": "Bare automatisering som har en unik ID tildelt, kan feilsøkes.", "dev_only_editable": "Bare automasjoner som har en unik ID tildelt, kan feilsøkes.",
"duplicate": "Dupliser", "duplicate": "Dupliser",
"duplicate_automation": "Dupliser automasjon", "duplicate_automation": "Dupliser automasjon",
"edit_automation": "Rediger automasjon", "edit_automation": "Rediger automasjon",
@@ -1658,7 +1668,7 @@
}, },
"introduction": "Automasjonsredigeringen lar deg lage og redigere automasjoner. Følg lenken under for å forsikre deg om at du har konfigurert Home Assistant riktig.", "introduction": "Automasjonsredigeringen lar deg lage og redigere automasjoner. Følg lenken under for å forsikre deg om at du har konfigurert Home Assistant riktig.",
"learn_more": "Lær mer om automasjoner", "learn_more": "Lær mer om automasjoner",
"no_automations": "Vi fant ingen automatiseringer", "no_automations": "Vi kunne ikke finne noen automasjoner",
"only_editable": "Bare automasjoner definert i automations.yaml kan redigeres.", "only_editable": "Bare automasjoner definert i automations.yaml kan redigeres.",
"pick_automation": "Velg automasjon for å redigere", "pick_automation": "Velg automasjon for å redigere",
"show_info_automation": "Vis informasjon om automasjon" "show_info_automation": "Vis informasjon om automasjon"
@@ -1789,7 +1799,7 @@
"target_browser": "Nettleser" "target_browser": "Nettleser"
}, },
"female": "Kvinne", "female": "Kvinne",
"info": "Ta med personlighet hjem ved å få den til å snakke med deg ved å bruke våre tekst-til-tale-tjenester. Du kan bruke dette i automatiseringer og skript ved hjelp av tjenesten {service}.", "info": "Ta med personlighet hjem ved å få den til å snakke med deg ved å bruke våre tekst-til-tale-tjenester. Du kan bruke dette i automasjoner og skript ved hjelp av tjenesten {service}.",
"male": "Mann", "male": "Mann",
"title": "Tekst til tale", "title": "Tekst til tale",
"try": "Prøve" "try": "Prøve"
@@ -2142,7 +2152,7 @@
"header": "Konfigurer Home Assistant", "header": "Konfigurer Home Assistant",
"helpers": { "helpers": {
"caption": "Hjelpere", "caption": "Hjelpere",
"description": "Elementer som hjelper med å bygge automatiseringer", "description": "Elementer som hjelper med å bygge automasjoner",
"dialog": { "dialog": {
"add_helper": "Legg hjelper", "add_helper": "Legg hjelper",
"add_platform": "Legg til {platform}", "add_platform": "Legg til {platform}",
@@ -2676,7 +2686,7 @@
"description": "Start på nytt og stopp Home Assistant-serveren", "description": "Start på nytt og stopp Home Assistant-serveren",
"section": { "section": {
"reloading": { "reloading": {
"automation": "Automatisering", "automation": "Automasjoner",
"command_line": "Kommandolinjeenheter", "command_line": "Kommandolinjeenheter",
"core": "Plassering og tilpasninger", "core": "Plassering og tilpasninger",
"filesize": "Enheter for filstørrelse", "filesize": "Enheter for filstørrelse",
@@ -3881,10 +3891,19 @@
"intro": "Er du klar til å ta kontroll over hjemmet ditt, gjenvinne ditt privatliv og bli med i et verdensomspennende samfunn av entusiaster?", "intro": "Er du klar til å ta kontroll over hjemmet ditt, gjenvinne ditt privatliv og bli med i et verdensomspennende samfunn av entusiaster?",
"next": "Neste", "next": "Neste",
"restore": { "restore": {
"addons": "Tillegg",
"confirm_password": "Bekreft passord for øyeblikksbilde",
"description": "Alternativt kan du gjenopprette fra et forrige snapshot.", "description": "Alternativt kan du gjenopprette fra et forrige snapshot.",
"folders": "Mapper",
"full_snapshot": "Full sikkerhetskopi",
"hide_log": "Skjul full logg", "hide_log": "Skjul full logg",
"in_progress": "Gjenoppretting pågår", "in_progress": "Gjenoppretting pågår",
"show_log": "Vis full logg" "partial_snapshot": "Delvis sikkerhetskopi",
"password": "Passord for øyeblikksbilde",
"password_protection": "Passordbeskyttelse",
"select_type": "Velg hva du vil gjenopprette",
"show_log": "Vis full logg",
"type": "Type øyeblikksbilde"
}, },
"user": { "user": {
"create_account": "Opprett konto", "create_account": "Opprett konto",

View File

@@ -318,6 +318,14 @@
"no_addons": "Je hebt nog geen add-ons geïnstalleerd. Ga naar de add-on store om te beginnen!" "no_addons": "Je hebt nog geen add-ons geïnstalleerd. Ga naar de add-on store om te beginnen!"
}, },
"dialog": { "dialog": {
"hardware": {
"attributes": "Attributen",
"device_path": "Apparaatpad",
"id": "ID",
"search": "Zoek hardware",
"subsystem": "Subsysteem",
"title": "Hardware"
},
"network": { "network": {
"connected_to": "Verbonden met {ssid}", "connected_to": "Verbonden met {ssid}",
"dhcp": "DHCP", "dhcp": "DHCP",
@@ -382,6 +390,7 @@
"snapshot": { "snapshot": {
"addons": "Invoegtoepassingen", "addons": "Invoegtoepassingen",
"available_snapshots": "Beschikbare snapshots", "available_snapshots": "Beschikbare snapshots",
"confirm_password": "Bevestig Snapshot-wachtwoord",
"could_not_create": "Kon geen snapshot maken", "could_not_create": "Kon geen snapshot maken",
"create": "Maak", "create": "Maak",
"create_blocked_not_running": "Het maken van een snapshot is nu niet mogelijk omdat het systeem in {state} staat.", "create_blocked_not_running": "Het maken van een snapshot is nu niet mogelijk omdat het systeem in {state} staat.",
@@ -409,6 +418,7 @@
"password": "Snapshot wachtwoord", "password": "Snapshot wachtwoord",
"password_protected": "beveiligd met wachtwoord", "password_protected": "beveiligd met wachtwoord",
"password_protection": "Wachtwoord bescherming", "password_protection": "Wachtwoord bescherming",
"passwords_not_matching": "De wachtwoorden komen niet overeen",
"security": "Beveiliging", "security": "Beveiliging",
"select_type": "Selecteer wat u wilt herstellen", "select_type": "Selecteer wat u wilt herstellen",
"selected": "{number} geselecteerd", "selected": "{number} geselecteerd",
@@ -931,7 +941,7 @@
}, },
"dialogs": { "dialogs": {
"config_entry_system_options": { "config_entry_system_options": {
"enable_new_entities_description": "Indien uitgeschakeld, worden nieuwe entiteiten van {integration} niet automatisch aan Home Assistant toegevoegd.", "enable_new_entities_description": "Of nieuw ontdekte apparaten voor {integratie} automatisch moeten worden toegevoegd.",
"enable_new_entities_label": "Voeg nieuwe entiteiten automatisch toe", "enable_new_entities_label": "Voeg nieuwe entiteiten automatisch toe",
"enable_polling_description": "Of Home Assistant automatisch moet pollen voor updates voor {integration} entiteiten", "enable_polling_description": "Of Home Assistant automatisch moet pollen voor updates voor {integration} entiteiten",
"enable_polling_label": "Schakel polling voor updates in.", "enable_polling_label": "Schakel polling voor updates in.",
@@ -972,7 +982,7 @@
}, },
"faq": "documentatie", "faq": "documentatie",
"info_customize": "U kunt sommige attributen overschrijven in de {customize_link} sectie.", "info_customize": "U kunt sommige attributen overschrijven in de {customize_link} sectie.",
"no_unique_id": "Deze entiteit (\" {entity_id} \") heeft geen unieke ID, daarom kunnen de instellingen ervan niet worden beheerd vanuit de gebruikersinterface. Zie de {faq_link} voor meer details.", "no_unique_id": "Deze entiteit (\"{entity_id}\") heeft geen unieke ID, daarom kunnen de instellingen ervan niet worden beheerd vanuit de gebruikersinterface. Zie de {faq_link} voor meer details.",
"related": "Gerelateerd", "related": "Gerelateerd",
"settings": "instellingen" "settings": "instellingen"
}, },
@@ -2212,6 +2222,7 @@
"depends_on_cloud": "Cloud afhankelijk", "depends_on_cloud": "Cloud afhankelijk",
"device_unavailable": "Apparaat niet beschikbaar", "device_unavailable": "Apparaat niet beschikbaar",
"devices": "{count} {count, plural,\n one {apparaat}\n other {apparaten}\n}", "devices": "{count} {count, plural,\n one {apparaat}\n other {apparaten}\n}",
"disable_error": "In- of uitschakelen van de integratie mislukt",
"disable_restart_confirm": "Start Home Assistant opnieuw op om het uitzetten van deze integratie te voltooien", "disable_restart_confirm": "Start Home Assistant opnieuw op om het uitzetten van deze integratie te voltooien",
"disable": { "disable": {
"disable_confirm": "Weet je zeker dat je deze configuratie wilt uitschakelen? Al diens apparaten en entiteiten zullen worden uitgeschakeld.", "disable_confirm": "Weet je zeker dat je deze configuratie wilt uitschakelen? Al diens apparaten en entiteiten zullen worden uitgeschakeld.",
@@ -3880,10 +3891,19 @@
"intro": "Ben je klaar om je huis wakker te maken, je privacy terug te winnen en deel te nemen aan een wereldwijde gemeenschap van knutselaars?", "intro": "Ben je klaar om je huis wakker te maken, je privacy terug te winnen en deel te nemen aan een wereldwijde gemeenschap van knutselaars?",
"next": "Volgende", "next": "Volgende",
"restore": { "restore": {
"addons": "Invoegtoepassingen",
"confirm_password": "Bevestig Snapshot-wachtwoord",
"description": "Je kunt ook herstellen vanaf een eerdere snapshot.", "description": "Je kunt ook herstellen vanaf een eerdere snapshot.",
"folders": "Mappen",
"full_snapshot": "Volledige snapshot",
"hide_log": "Verberg volledig logboek", "hide_log": "Verberg volledig logboek",
"in_progress": "Herstel in uitvoering", "in_progress": "Herstel in uitvoering",
"show_log": "Volledig logboek weergeven" "partial_snapshot": "Gedeeltelijke snapshot",
"password": "Snapshot wachtwoord",
"password_protection": "Wachtwoord bescherming",
"select_type": "Selecteer wat u wilt herstellen",
"show_log": "Volledig logboek weergeven",
"type": "Snapshot type"
}, },
"user": { "user": {
"create_account": "Account aanmaken", "create_account": "Account aanmaken",

View File

@@ -318,6 +318,14 @@
"no_addons": "Nie masz jeszcze zainstalowanych żadnych dodatków. Przejdź do sklepu z dodatkami, aby rozpocząć!" "no_addons": "Nie masz jeszcze zainstalowanych żadnych dodatków. Przejdź do sklepu z dodatkami, aby rozpocząć!"
}, },
"dialog": { "dialog": {
"hardware": {
"attributes": "Atrybuty",
"device_path": "Ścieżka urządzenia",
"id": "Identyfikator",
"search": "Wyszukaj sprzęt",
"subsystem": "Podsystem",
"title": "Sprzęt"
},
"network": { "network": {
"connected_to": "Połączono z {ssid}", "connected_to": "Połączono z {ssid}",
"dhcp": "DHCP", "dhcp": "DHCP",
@@ -382,6 +390,7 @@
"snapshot": { "snapshot": {
"addons": "Dodatki", "addons": "Dodatki",
"available_snapshots": "Dostępne snapshoty", "available_snapshots": "Dostępne snapshoty",
"confirm_password": "Potwierdź hasło snapshota",
"could_not_create": "Nie udało się utworzyć snapshota", "could_not_create": "Nie udało się utworzyć snapshota",
"create": "Utwórz", "create": "Utwórz",
"create_blocked_not_running": "Tworzenie snapshota nie jest teraz możliwe, ponieważ system jest w {state}.", "create_blocked_not_running": "Tworzenie snapshota nie jest teraz możliwe, ponieważ system jest w {state}.",
@@ -409,6 +418,7 @@
"password": "Hasło snapshota", "password": "Hasło snapshota",
"password_protected": "chroniony hasłem", "password_protected": "chroniony hasłem",
"password_protection": "Ochrona hasłem", "password_protection": "Ochrona hasłem",
"passwords_not_matching": "Hasła nie są takie same",
"security": "Bezpieczeństwo", "security": "Bezpieczeństwo",
"select_type": "Wybierz, co przywrócić", "select_type": "Wybierz, co przywrócić",
"selected": "wybrano {number}", "selected": "wybrano {number}",
@@ -3881,10 +3891,19 @@
"intro": "Czy jesteś gotowy, aby ożywić swój dom, odzyskać prywatność i dołączyć do światowej społeczności majsterkowiczów?", "intro": "Czy jesteś gotowy, aby ożywić swój dom, odzyskać prywatność i dołączyć do światowej społeczności majsterkowiczów?",
"next": "Dalej", "next": "Dalej",
"restore": { "restore": {
"addons": "Dodatki",
"confirm_password": "Potwierdź hasło snapshota",
"description": "Alternatywnie możesz przywrócić z poprzedniej migawki.", "description": "Alternatywnie możesz przywrócić z poprzedniej migawki.",
"folders": "Foldery",
"full_snapshot": "Pełny snapshot",
"hide_log": "Ukryj cały log", "hide_log": "Ukryj cały log",
"in_progress": "Trwa przywracanie", "in_progress": "Trwa przywracanie",
"show_log": "Pokaż cały log" "partial_snapshot": "Częściowy snapshot",
"password": "Hasło snapshota",
"password_protection": "Ochrona hasłem",
"select_type": "Wybierz, co przywrócić",
"show_log": "Pokaż cały log",
"type": "Typ snapshota"
}, },
"user": { "user": {
"create_account": "Utwórz konto", "create_account": "Utwórz konto",

View File

@@ -318,6 +318,14 @@
"no_addons": "Нет установленных дополнений. Вы можете установить их из магазина дополнений." "no_addons": "Нет установленных дополнений. Вы можете установить их из магазина дополнений."
}, },
"dialog": { "dialog": {
"hardware": {
"attributes": "Атрибуты",
"device_path": "Путь к устройству",
"id": "ID",
"search": "Поиск оборудования",
"subsystem": "Подсистема",
"title": "Оборудование"
},
"network": { "network": {
"connected_to": "Подключено к {ssid}", "connected_to": "Подключено к {ssid}",
"dhcp": "DHCP", "dhcp": "DHCP",
@@ -382,6 +390,7 @@
"snapshot": { "snapshot": {
"addons": "Дополнения", "addons": "Дополнения",
"available_snapshots": "Доступные снимки", "available_snapshots": "Доступные снимки",
"confirm_password": "Подтверждение пароля",
"could_not_create": "Не удалось создать снимок", "could_not_create": "Не удалось создать снимок",
"create": "Создать", "create": "Создать",
"create_blocked_not_running": "Создание снимка сейчас невозможно, потому что система находится в состоянии {state}.", "create_blocked_not_running": "Создание снимка сейчас невозможно, потому что система находится в состоянии {state}.",
@@ -409,6 +418,7 @@
"password": "Пароль", "password": "Пароль",
"password_protected": "защищено паролем", "password_protected": "защищено паролем",
"password_protection": "Защита паролем", "password_protection": "Защита паролем",
"passwords_not_matching": "Пароли не совпадают",
"security": "Безопасность", "security": "Безопасность",
"select_type": "Выберите что нужно восстановить из этого снимка файловой системы", "select_type": "Выберите что нужно восстановить из этого снимка файловой системы",
"selected": "Выбрано: {number}", "selected": "Выбрано: {number}",
@@ -3881,10 +3891,19 @@
"intro": "Готовы ли Вы разбудить свой дом, вернуть свою конфиденциальность и присоединиться к всемирному сообществу?", "intro": "Готовы ли Вы разбудить свой дом, вернуть свою конфиденциальность и присоединиться к всемирному сообществу?",
"next": "Далее", "next": "Далее",
"restore": { "restore": {
"addons": "Дополнения",
"confirm_password": "Подтверждение пароля",
"description": "Восстановить предыдущее состояние из снимка файловой системы (snapshot)", "description": "Восстановить предыдущее состояние из снимка файловой системы (snapshot)",
"folders": "Папки",
"full_snapshot": "Все файлы",
"hide_log": "Скрыть журнал", "hide_log": "Скрыть журнал",
"in_progress": "Восстановление системы", "in_progress": "Восстановление системы",
"show_log": "Показать журнал" "partial_snapshot": "Выбрать из списка",
"password": "Пароль",
"password_protection": "Защита паролем",
"select_type": "Выберите что нужно восстановить из этого снимка файловой системы",
"show_log": "Показать журнал",
"type": "Выберите что должен включать в себя снимок файловой системы"
}, },
"user": { "user": {
"create_account": "Создать учётную запись", "create_account": "Создать учётную запись",

View File

@@ -285,6 +285,13 @@
"no_addons": "Zatiaľ nemáte nainštalované žiadne doplnky. Choďte do obchodu s doplnkami, aby ste mohli začať!" "no_addons": "Zatiaľ nemáte nainštalované žiadne doplnky. Choďte do obchodu s doplnkami, aby ste mohli začať!"
}, },
"dialog": { "dialog": {
"hardware": {
"attributes": "Atribúty",
"id": "ID",
"search": "Vyhľadať zariadenia",
"subsystem": "Subsystém",
"title": "Hardvér"
},
"network": { "network": {
"connected_to": "Pripojiť na {ssid}", "connected_to": "Pripojiť na {ssid}",
"dhcp": "DHCP", "dhcp": "DHCP",
@@ -346,12 +353,17 @@
"snapshot": { "snapshot": {
"addons": "Doplnky", "addons": "Doplnky",
"available_snapshots": "Dostupné zálohy", "available_snapshots": "Dostupné zálohy",
"confirm_password": "Potvrdiť heslo zálohy",
"could_not_create": "Nepodarilo sa vytvoriť zálohu", "could_not_create": "Nepodarilo sa vytvoriť zálohu",
"create": "Vytvoriť", "create": "Vytvoriť",
"create_blocked_not_running": "Nieje možné vytvoriť zálohu pretože systém je v stave {state}", "create_blocked_not_running": "Nieje možné vytvoriť zálohu pretože systém je v stave {state}",
"create_snapshot": "Vytvoriť zálohu", "create_snapshot": "Vytvoriť zálohu",
"created": "Vytvorené",
"delete_snapshot_confirm": "odstrániť",
"delete_snapshot_title": "Vymazať zálohu",
"description": "Snapshoty ti umožňujú ľahko zálohovať a obnoviť všetky dáta Home Assistant", "description": "Snapshoty ti umožňujú ľahko zálohovať a obnoviť všetky dáta Home Assistant",
"enter_password": "Prosím napíšte heslo.", "enter_password": "Prosím napíšte heslo.",
"failed_to_delete": "Nepodarilo sa odstrániť",
"folder": { "folder": {
"addons/local": "Lokálne doplnky", "addons/local": "Lokálne doplnky",
"homeassistant": "konfigurácia Home Asistenta", "homeassistant": "konfigurácia Home Asistenta",
@@ -367,6 +379,7 @@
"password": "Heslo", "password": "Heslo",
"password_protected": "chránené heslo", "password_protected": "chránené heslo",
"password_protection": "Ochrana Heslom", "password_protection": "Ochrana Heslom",
"passwords_not_matching": "Heslá sa nezhodujú",
"security": "Zabezpečenie", "security": "Zabezpečenie",
"type": "Typ", "type": "Typ",
"upload_snapshot": "Nahrať zálohu" "upload_snapshot": "Nahrať zálohu"
@@ -425,6 +438,7 @@
"leave_beta_description": "Získajte aktualizácie stabilnej verzie pre Home Assistant, supervízora a hostiteľa", "leave_beta_description": "Získajte aktualizácie stabilnej verzie pre Home Assistant, supervízora a hostiteľa",
"ram_usage": "Využitie RAM supervízorom", "ram_usage": "Využitie RAM supervízorom",
"reload_supervisor": "Znova načítať supervízora", "reload_supervisor": "Znova načítať supervízora",
"search": "Hľadať",
"share_diagnostics": "Zdieľajte diagnostiku", "share_diagnostics": "Zdieľajte diagnostiku",
"share_diagnostics_description": "Zdieľajte správy o zlyhaní a diagnostické informácie.", "share_diagnostics_description": "Zdieľajte správy o zlyhaní a diagnostické informácie.",
"share_diagonstics_description": "Chceli by ste automaticky zdieľať správy o zlyhaní a diagnostické informácie, keď sa Supervízor stretne s neočakávanými chybami? {line_break} Umožní nám to vyriešiť problémy, informácie sú prístupné iba tímu Home Assistant Core a nebudú sa zdieľať s ostatnými. {line_break} Údaje neobsahujú žiadne súkromné ani citlivé informácie, ktoré môžete kedykoľvek deaktivovať v nastaveniach.", "share_diagonstics_description": "Chceli by ste automaticky zdieľať správy o zlyhaní a diagnostické informácie, keď sa Supervízor stretne s neočakávanými chybami? {line_break} Umožní nám to vyriešiť problémy, informácie sú prístupné iba tímu Home Assistant Core a nebudú sa zdieľať s ostatnými. {line_break} Údaje neobsahujú žiadne súkromné ani citlivé informácie, ktoré môžete kedykoľvek deaktivovať v nastaveniach.",
@@ -522,6 +536,7 @@
}, },
"light": { "light": {
"brightness": "Jas", "brightness": "Jas",
"color_brightness": "Jas farby",
"color_temperature": "Teplota farby", "color_temperature": "Teplota farby",
"effect": "Efekt", "effect": "Efekt",
"white_value": "Hodnota bielej" "white_value": "Hodnota bielej"
@@ -660,6 +675,9 @@
"no_match": "Nenašli sa žiadne zodpovedajúce oblasti", "no_match": "Nenašli sa žiadne zodpovedajúce oblasti",
"show_areas": "Zobraziť oblasti" "show_areas": "Zobraziť oblasti"
}, },
"attributes": {
"expansion_header": "Atribúty"
},
"blueprint-picker": { "blueprint-picker": {
"add_user": "Pridať používateľa", "add_user": "Pridať používateľa",
"remove_user": "Odstrániť používateľa", "remove_user": "Odstrániť používateľa",
@@ -670,6 +688,9 @@
"today": "Dnes" "today": "Dnes"
}, },
"data-table": { "data-table": {
"clear": "Vyčistiť",
"filtering_by": "Filtrovanie podľa",
"hidden": "{number} skryté",
"no-data": "Žiadne údaje", "no-data": "Žiadne údaje",
"search": "Hľadať" "search": "Hľadať"
}, },
@@ -725,7 +746,8 @@
"was_plugged_in": "bol zapojený", "was_plugged_in": "bol zapojený",
"was_unlocked": "bol odomknutý", "was_unlocked": "bol odomknutý",
"was_unplugged": "bol odpojený" "was_unplugged": "bol odpojený"
} },
"show_trace": "Zobraziť cestu"
}, },
"media-browser": { "media-browser": {
"audio_not_supported": "Váš prehliadač nepodporuje zvukový prvok.", "audio_not_supported": "Váš prehliadač nepodporuje zvukový prvok.",
@@ -1017,6 +1039,11 @@
"smtp": "Znova načítať notifikačné služby SMTP", "smtp": "Znova načítať notifikačné služby SMTP",
"telegram": "Znova načítať notifikačné služby telegram", "telegram": "Znova načítať notifikačné služby telegram",
"zone": "Znova načítať zóny" "zone": "Znova načítať zóny"
},
"types": {
"navigation": "Navigovať",
"reload": "Znova načítať",
"server_control": "Server"
} }
}, },
"filter_placeholder": "Filter entít" "filter_placeholder": "Filter entít"
@@ -1053,6 +1080,9 @@
"zha_device_card": { "zha_device_card": {
"device_name_placeholder": "Zmeniť názov zariadenia" "device_name_placeholder": "Zmeniť názov zariadenia"
} }
},
"zha_reconfigure_device": {
"attribute": "Atribút"
} }
}, },
"duration": { "duration": {
@@ -1101,7 +1131,8 @@
"caption": "Register oblastí", "caption": "Register oblastí",
"data_table": { "data_table": {
"area": "Oblasť", "area": "Oblasť",
"devices": "Zariadenia" "devices": "Zariadenia",
"entities": "Entity"
}, },
"delete": { "delete": {
"confirmation_text": "Všetky zariadenia v tejto oblasti ostanú nepriradené.", "confirmation_text": "Všetky zariadenia v tejto oblasti ostanú nepriradené.",
@@ -1113,6 +1144,7 @@
"create": "VYTVORIŤ", "create": "VYTVORIŤ",
"default_name": "Nová oblasť", "default_name": "Nová oblasť",
"delete": "VYMAZAŤ", "delete": "VYMAZAŤ",
"linked_entities_caption": "Entity",
"name": "Názov", "name": "Názov",
"name_required": "Názov je povinný", "name_required": "Názov je povinný",
"unknown_error": "Neznáma chyba", "unknown_error": "Neznáma chyba",
@@ -1172,10 +1204,14 @@
"device_id": { "device_id": {
"action": "Akcia", "action": "Akcia",
"extra_fields": { "extra_fields": {
"brightness_pct": "Jas",
"code": "Kód", "code": "Kód",
"humidity": "Vlhkosť",
"message": "Správa", "message": "Správa",
"mode": "Režim",
"position": "pozícia", "position": "pozícia",
"title": "Názov" "title": "Názov",
"value": "Hodnota"
}, },
"label": "Zariadenie" "label": "Zariadenie"
}, },
@@ -1232,7 +1268,9 @@
"extra_fields": { "extra_fields": {
"above": "Nad", "above": "Nad",
"below": "Pod", "below": "Pod",
"for": "Trvanie" "for": "Trvanie",
"hvac_mode": "Režim HVAC",
"preset_mode": "Vyber režim"
}, },
"label": "Zariadenie" "label": "Zariadenie"
}, },
@@ -1309,6 +1347,7 @@
"move_down": "Posunúť dole", "move_down": "Posunúť dole",
"move_up": "Posunúť hore", "move_up": "Posunúť hore",
"save": "Uložiť", "save": "Uložiť",
"show_trace": "Zobraziť cestu",
"triggers": { "triggers": {
"add": "Pridať spúšťač", "add": "Pridať spúšťač",
"delete": "Odstrániť", "delete": "Odstrániť",
@@ -1411,6 +1450,8 @@
"add_automation": "Pridať automatizáciu", "add_automation": "Pridať automatizáciu",
"delete_automation": "Odstrániť automatizáciu", "delete_automation": "Odstrániť automatizáciu",
"delete_confirm": "Naozaj chcete odstrániť túto automatizáciu?", "delete_confirm": "Naozaj chcete odstrániť túto automatizáciu?",
"dev_automation": "Automatizácia ladenia",
"dev_only_editable": "Sledovateľné sú iba automatizácie, ktoré majú priradené jedinečné ID.",
"duplicate": "Duplikovať", "duplicate": "Duplikovať",
"duplicate_automation": "Duplikovať automatizáciu", "duplicate_automation": "Duplikovať automatizáciu",
"edit_automation": "Upraviť automatizáciu", "edit_automation": "Upraviť automatizáciu",
@@ -1476,6 +1517,7 @@
"title": "Alexa" "title": "Alexa"
}, },
"connected": "Pripojené", "connected": "Pripojené",
"connecting": "Pripája sa",
"connection_status": "Stav cloudového pripojenia", "connection_status": "Stav cloudového pripojenia",
"fetching_subscription": "Načítava sa predplatné...", "fetching_subscription": "Načítava sa predplatné...",
"google": { "google": {
@@ -1497,10 +1539,15 @@
"remote": { "remote": {
"access_is_being_prepared": "Pripravuje sa vzdialený prístup. Budeme vás informovať, keď bude pripravený.", "access_is_being_prepared": "Pripravuje sa vzdialený prístup. Budeme vás informovať, keď bude pripravený.",
"certificate_info": "Informácie o certifikáte", "certificate_info": "Informácie o certifikáte",
"connected": "Pripojené",
"info": "Home Assistant Cloud poskytuje zabezpečené vzdialené pripojenie k vašej inštancií kým ste mimo domova.", "info": "Home Assistant Cloud poskytuje zabezpečené vzdialené pripojenie k vašej inštancií kým ste mimo domova.",
"instance_is_available": "Vaša inštancia je k dispozícii na stránke", "instance_is_available": "Vaša inštancia je k dispozícii na stránke",
"instance_will_be_available": "Vaša inštancia bude k dispozícii na adrese", "instance_will_be_available": "Vaša inštancia bude k dispozícii na adrese",
"link_learn_how_it_works": "Zistite, ako to funguje", "link_learn_how_it_works": "Zistite, ako to funguje",
"not_connected": "Nepripojené",
"remote_enabled": {
"caption": "Automaticky pripojiť"
},
"title": "Diaľkové ovládanie" "title": "Diaľkové ovládanie"
}, },
"sign_out": "Odhlásiť sa", "sign_out": "Odhlásiť sa",
@@ -1637,6 +1684,17 @@
"description": "Jednotkový systém, umiestnenie, časové pásmo a ďalšie všeobecné parametre", "description": "Jednotkový systém, umiestnenie, časové pásmo a ďalšie všeobecné parametre",
"section": { "section": {
"core": { "core": {
"analytics": {
"header": "Analízy",
"preference": {
"base": {
"title": "Základné analýzy"
},
"diagnostics": {
"title": "Diagnostiky"
}
}
},
"core_config": { "core_config": {
"edit_requires_storage": "Editor je zablokovaný, pretože konfigurácia je uložená v configuration.yaml", "edit_requires_storage": "Editor je zablokovaný, pretože konfigurácia je uložená v configuration.yaml",
"elevation": "Nadmorská výška", "elevation": "Nadmorská výška",
@@ -1842,6 +1900,7 @@
"license": "Publikované pod licenciou Apache 2.0", "license": "Publikované pod licenciou Apache 2.0",
"path_configuration": "Cesta k configuration.yaml: {path}", "path_configuration": "Cesta k configuration.yaml: {path}",
"server": "server", "server": "server",
"setup_time": "Nastaviť čas",
"source": "Zdroj:", "source": "Zdroj:",
"system_health_error": "Súčasť System Health nie je načítaná. Pridajte 'system_health:' do súboru configuration.yaml", "system_health_error": "Súčasť System Health nie je načítaná. Pridajte 'system_health:' do súboru configuration.yaml",
"system_health": { "system_health": {
@@ -1901,6 +1960,7 @@
}, },
"finish": "Dokončiť", "finish": "Dokončiť",
"loading_first_time": "Počkajte, kým sa nainštaluje integrácia", "loading_first_time": "Počkajte, kým sa nainštaluje integrácia",
"next": "Ďalej",
"not_all_required_fields": "Nie sú vyplnené všetky povinné polia.", "not_all_required_fields": "Nie sú vyplnené všetky povinné polia.",
"pick_flow_step": { "pick_flow_step": {
"new_flow": "Nie, nastaviť inú inštanciu {integration}", "new_flow": "Nie, nastaviť inú inštanciu {integration}",
@@ -1916,6 +1976,7 @@
"disable": { "disable": {
"disabled_integrations": "{number} deaktivované", "disabled_integrations": "{number} deaktivované",
"hide_disabled": "Skryť vypnuté integrácie", "hide_disabled": "Skryť vypnuté integrácie",
"show": "Ukázať",
"show_disabled": "Zobraziť vypnuté integrácie" "show_disabled": "Zobraziť vypnuté integrácie"
}, },
"discovered": "Objavené", "discovered": "Objavené",
@@ -2165,6 +2226,7 @@
"add_scene": "Pridať scénu", "add_scene": "Pridať scénu",
"delete_confirm": "Naozaj chcete odstrániť túto scénu?", "delete_confirm": "Naozaj chcete odstrániť túto scénu?",
"delete_scene": "Odstrániť scénu", "delete_scene": "Odstrániť scénu",
"duplicate": "Duplikovať",
"edit_scene": "Upraviť scénu", "edit_scene": "Upraviť scénu",
"header": "Editor scén", "header": "Editor scén",
"headers": { "headers": {
@@ -2658,6 +2720,7 @@
"go_back": "Vrátiť sa späť", "go_back": "Vrátiť sa späť",
"supervisor": { "supervisor": {
"ask": "Požiadať o pomoc", "ask": "Požiadať o pomoc",
"observer": "Skontrolujte pozorovateľa",
"reboot": "Skúsiť reštartovať hostiteľa", "reboot": "Skúsiť reštartovať hostiteľa",
"system_health": "Overiť stav systému", "system_health": "Overiť stav systému",
"title": "Nepodarilo sa načítať panel supervízora!", "title": "Nepodarilo sa načítať panel supervízora!",
@@ -3293,10 +3356,19 @@
}, },
"intro": "Ste pripravení prebudiť váš domov, získať vaše súkromie a pripojiť sa k celosvetovej komunite bastličov?", "intro": "Ste pripravení prebudiť váš domov, získať vaše súkromie a pripojiť sa k celosvetovej komunite bastličov?",
"restore": { "restore": {
"addons": "Doplnky",
"confirm_password": "Potvrďte heslo zálohy",
"description": "Prípadne môžete obnoviť z predchádzajúcej snímky.", "description": "Prípadne môžete obnoviť z predchádzajúcej snímky.",
"folders": "Adresáre",
"full_snapshot": "Úplná záloha",
"hide_log": "Skryť celý denník", "hide_log": "Skryť celý denník",
"in_progress": "Prebieha obnovenie", "in_progress": "Prebieha obnovenie",
"show_log": "Zobraziť celý denník" "partial_snapshot": "Čiastočná záloha",
"password": "Heslo zálohy",
"password_protection": "Ochrana heslom",
"select_type": "Vyberte, čo chcete obnoviť",
"show_log": "Zobraziť celý denník",
"type": "Typ zálohy"
}, },
"user": { "user": {
"create_account": "Vytvoriť účet", "create_account": "Vytvoriť účet",
@@ -3382,6 +3454,18 @@
"enable": "Povoliť", "enable": "Povoliť",
"header": "Multifaktorové autentifikačné moduly" "header": "Multifaktorové autentifikačné moduly"
}, },
"number_format": {
"description": "Vyberte spôsob formátovania čísiel.",
"dropdown_label": "Formát čísla",
"formats": {
"comma_decimal": "1,234,567.89",
"decimal_comma": "1.234.567,89",
"language": "Automaticky (použiť nastavenie jazyka)",
"none": "Žiadny",
"space_comma": "1234567,89"
},
"header": "Formát čísla"
},
"push_notifications": { "push_notifications": {
"description": "Posielať upozornenia na toto zariadenie.", "description": "Posielať upozornenia na toto zariadenie.",
"error_load_platform": "Konfigurujte upozornenia notify.html5.", "error_load_platform": "Konfigurujte upozornenia notify.html5.",
@@ -3418,6 +3502,14 @@
"primary_color": "Primárna farba", "primary_color": "Primárna farba",
"reset": "Reset" "reset": "Reset"
}, },
"time_format": {
"dropdown_label": "Formát času",
"formats": {
"12": "12 hodín (AM / PM)",
"24": "24 hodín"
},
"header": "Formát času"
},
"vibrate": { "vibrate": {
"description": "Pri ovládaní zariadení povoľte alebo zakážte vibrácie tohto zariadenia.", "description": "Pri ovládaní zariadení povoľte alebo zakážte vibrácie tohto zariadenia.",
"header": "Vibrácie" "header": "Vibrácie"

View File

@@ -131,6 +131,12 @@
"uninstall": "Kunde inte avinstallera tillägg" "uninstall": "Kunde inte avinstallera tillägg"
}, },
"capability": { "capability": {
"auth_api": {
"title": "Home Assistant-autentisering"
},
"label": {
"hardware": "hårdvara"
},
"rating": { "rating": {
"title": "Tilläggets säkerhetsrating" "title": "Tilläggets säkerhetsrating"
}, },
@@ -142,6 +148,7 @@
"manager": "föreståndare" "manager": "föreståndare"
} }
}, },
"changelog": "Ändringslogg",
"cpu_usage": "Tillägg CPU-användning", "cpu_usage": "Tillägg CPU-användning",
"hostname": "Värdnamn", "hostname": "Värdnamn",
"install": "installera", "install": "installera",
@@ -174,6 +181,7 @@
"title": "Varning: Skyddsläge är inaktiverat!" "title": "Varning: Skyddsläge är inaktiverat!"
}, },
"ram_usage": "Tillägg RAM-användning", "ram_usage": "Tillägg RAM-användning",
"rebuild": "Bygg om",
"restart": "omstart", "restart": "omstart",
"start": "starta", "start": "starta",
"stop": "stoppa", "stop": "stoppa",
@@ -188,6 +196,11 @@
"documentation": "Dokumentation", "documentation": "Dokumentation",
"info": "Info", "info": "Info",
"log": "Logg" "log": "Logg"
},
"state": {
"installed": "Add-on är installerad",
"not_available": "Add-on är inte tillgänglig på ditt system",
"not_installed": "Add-on är inte installerad"
} }
}, },
"common": { "common": {
@@ -201,7 +214,7 @@
"failed_to_update_name": "Kunde inte uppdatera {name}", "failed_to_update_name": "Kunde inte uppdatera {name}",
"learn_more": "Läs mer", "learn_more": "Läs mer",
"new_version_available": "Ny version tillgänglig", "new_version_available": "Ny version tillgänglig",
"newest_version": "Nyaste version", "newest_version": "Senaste version",
"refresh": "Uppdatera", "refresh": "Uppdatera",
"reload": "Ladda om", "reload": "Ladda om",
"reset_defaults": "Nollställ till standard", "reset_defaults": "Nollställ till standard",
@@ -228,6 +241,11 @@
"title": "Uppdatera {name}" "title": "Uppdatera {name}"
} }
}, },
"dashboard": {
"addon_new_version": "Ny version tillgänglig",
"addon_running": "Add-on körs",
"addons": "Installerade add-ons"
},
"dialog": { "dialog": {
"network": { "network": {
"connected_to": "Ansluten till {ssid}", "connected_to": "Ansluten till {ssid}",
@@ -276,8 +294,11 @@
"my": { "my": {
"error": "Ett okänt fel inträffade", "error": "Ett okänt fel inträffade",
"error_addon_not_found": "Tillägget hittades inte", "error_addon_not_found": "Tillägget hittades inte",
"faq_link": "Min Home Assistant FAQ", "faq_link": "Mitt Home Assistant FAQ",
"not_supported": "Denna omdirigering stöds inte av din Home Assistant-instans. Kontroller {link} för vilka omdirigeringar som stöds och i vilken version de introducerades." "not_supported": "Denna omdirigering stöds inte av din Home Assistant-instans. Kontrollera {link} för vilka omdirigeringar som stöds och i vilken version de introducerades."
},
"panel": {
"system": "System"
}, },
"snapshot": { "snapshot": {
"addons": "Tillägg", "addons": "Tillägg",
@@ -303,6 +324,7 @@
"password_protected": "lösenordskyddad", "password_protected": "lösenordskyddad",
"password_protection": "Lösenordsskydd", "password_protection": "Lösenordsskydd",
"security": "Säkerhet", "security": "Säkerhet",
"select_type": "Välj vad som ska återställas",
"type": "Typ", "type": "Typ",
"upload_snapshot": "Ladda upp avbild" "upload_snapshot": "Ladda upp avbild"
}, },
@@ -351,6 +373,7 @@
"leave_beta_description": "Få stabila uppdateringar för Home Assistant, Supervisor och värd", "leave_beta_description": "Få stabila uppdateringar för Home Assistant, Supervisor och värd",
"ram_usage": "Supervisor RAM-användning", "ram_usage": "Supervisor RAM-användning",
"reload_supervisor": "Ladda om Supervisor", "reload_supervisor": "Ladda om Supervisor",
"search": "Sök",
"share_diagnostics": "Dela diagnostik", "share_diagnostics": "Dela diagnostik",
"share_diagnostics_description": "Dela kraschrapporter och diagnostisk information.", "share_diagnostics_description": "Dela kraschrapporter och diagnostisk information.",
"share_diagonstics_title": "Hjälp till att förbättra Home Assistant", "share_diagonstics_title": "Hjälp till att förbättra Home Assistant",
@@ -564,6 +587,19 @@
"yes": "Ja" "yes": "Ja"
}, },
"components": { "components": {
"addon-picker": {
"addon": "Add-on",
"error": {
"fetch_addons": {
"description": "Hämtning av Add-ons gav ett fel.",
"title": "Fel vid hämtning av Add-ons"
},
"no_supervisor": {
"description": "Ingen Supervisor hittades, Add-on kunde inte laddas.",
"title": "Ingen Supervisor"
}
}
},
"area-picker": { "area-picker": {
"add_dialog": { "add_dialog": {
"add": "Lägg till", "add": "Lägg till",
@@ -738,6 +774,11 @@
"week": "{count} {count, plural,\n one {vecka}\n other {veckor}\n} sedan" "week": "{count} {count, plural,\n one {vecka}\n other {veckor}\n} sedan"
} }
}, },
"service-control": {
"required": "Det här fältet krävs",
"target": "Mål",
"target_description": "Vad ska denna tjänst använda som målområden, enheter eller entiteter?"
},
"service-picker": { "service-picker": {
"service": "Tjänst" "service": "Tjänst"
}, },
@@ -1872,7 +1913,8 @@
}, },
"filtering": { "filtering": {
"clear": "Rensa", "clear": "Rensa",
"filtering_by": "Filtrera efter" "filtering_by": "Filtrera efter",
"show": "Visa"
}, },
"hassio": { "hassio": {
"button": "Konfigurera" "button": "Konfigurera"
@@ -1977,8 +2019,10 @@
"config_flow": { "config_flow": {
"aborted": "Avbruten", "aborted": "Avbruten",
"close": "Stäng", "close": "Stäng",
"could_not_load": "Konfigurationsflödet kunde inte laddas",
"created_config": "Skapad konfiguration för {name}.", "created_config": "Skapad konfiguration för {name}.",
"dismiss": "Avfärda dialogrutan", "dismiss": "Avfärda dialogrutan",
"error": "Fel",
"error_saving_area": "Fel vid sparande av område: {error}", "error_saving_area": "Fel vid sparande av område: {error}",
"external_step": { "external_step": {
"description": "Det här steget kräver att du besöker en extern webbplats för att slutföra.", "description": "Det här steget kräver att du besöker en extern webbplats för att slutföra.",
@@ -1987,15 +2031,21 @@
"finish": "Slutför", "finish": "Slutför",
"loading_first_time": "Vänligen vänta medan integrationen installeras", "loading_first_time": "Vänligen vänta medan integrationen installeras",
"not_all_required_fields": "Alla obligatoriska fält har inte fyllts i.", "not_all_required_fields": "Alla obligatoriska fält har inte fyllts i.",
"pick_flow_step": {
"new_flow": "Nej, skapa en annan instans av {integration}",
"title": "Vi upptäckte dessa, vill du konfigurera dem?"
},
"submit": "Spara" "submit": "Spara"
}, },
"configure": "Konfigurera", "configure": "Konfigurera",
"configured": "Konfigurerad", "configured": "Konfigurerad",
"confirm_new": "Vill du konfigurera {integration}?",
"description": "Hantera integrationer med tjänster, enheter, ...", "description": "Hantera integrationer med tjänster, enheter, ...",
"details": "Integrationsdetaljer", "details": "Integrationsdetaljer",
"disable": { "disable": {
"disabled_integrations": "{number} inaktiverad(e)", "disabled_integrations": "{number} inaktiverad(e)",
"hide_disabled": "Dölj inaktiverade integrationer" "hide_disabled": "Dölj inaktiverade integrationer",
"show_disabled": "Visa inaktiverade integrationer"
}, },
"discovered": "Upptäckt", "discovered": "Upptäckt",
"home_assistant_website": "Home Assistants hemsida", "home_assistant_website": "Home Assistants hemsida",
@@ -2814,13 +2864,17 @@
"type": "Händelsetyp" "type": "Händelsetyp"
}, },
"services": { "services": {
"all_parameters": "Alla tillgängliga parametrar",
"call_service": "Anropa tjänst", "call_service": "Anropa tjänst",
"column_description": "Beskrivning", "column_description": "Beskrivning",
"column_example": "Exempel", "column_example": "Exempel",
"column_parameter": "Parameter", "column_parameter": "Parameter",
"description": "Utvecklarverktyget för tjänster låter dig anropa alla tillgängliga tjänster i Home Assistant.", "description": "Utvecklarverktyget för tjänster låter dig anropa alla tillgängliga tjänster i Home Assistant.",
"fill_example_data": "Fyll på exempeldata", "fill_example_data": "Fyll på exempeldata",
"title": "Tjänster" "title": "Tjänster",
"ui_mode": "Gå till UI-läge",
"yaml_mode": "Gå till YAML-läge",
"yaml_parameters": "Parametrar endast tillgängliga i YAML-läge"
}, },
"states": { "states": {
"alert_entity_field": "Enhet är ett obligatoriskt fält", "alert_entity_field": "Enhet är ett obligatoriskt fält",
@@ -3359,7 +3413,12 @@
"playback_title": "Meddelandeuppspelning" "playback_title": "Meddelandeuppspelning"
}, },
"my": { "my": {
"not_supported": "Denna omdirigering stöds inte av din Home Assistant-instans. Kolla {link} för vilka omdirigeringar som stöds och i vilken version de introducerades." "component_not_loaded": "Denna omdirigering stöds inte av din Home Assistant-instans. Du behöver integrationen {integration} att använda denna omdirigering.",
"documentation": "dokumentation",
"error": "Ett okänt fel inträffade",
"faq_link": "Mitt Home Assistant FAQ",
"no_supervisor": "Denna omdirigering stöds inte av din Home Assistant-installation. Den behöver antingen operativsystemet Home Assistant operativsystem eller Home Assistant Supervised. Mer information finns i {docs_link} .",
"not_supported": "Denna omdirigering stöds inte av din Home Assistant-instans. Kontrollera {link} för vilka omdirigeringar som stöds och i vilken version de introducerades."
}, },
"page-authorize": { "page-authorize": {
"abort_intro": "Inloggning avbruten", "abort_intro": "Inloggning avbruten",
@@ -3457,7 +3516,7 @@
"working": "Vänligen vänta" "working": "Vänligen vänta"
}, },
"initializing": "Initierar", "initializing": "Initierar",
"logging_in_to_with": "Loggar in på ** {locationName} ** med ** {authProviderName} **.", "logging_in_to_with": "Loggar in på **{locationName}** med **{authProviderName}**.",
"logging_in_with": "Loggar in med **{authProviderName}**.", "logging_in_with": "Loggar in med **{authProviderName}**.",
"pick_auth_provider": "Eller logga in med" "pick_auth_provider": "Eller logga in med"
}, },
@@ -3615,6 +3674,15 @@
"enable": "Aktivera", "enable": "Aktivera",
"header": "Tvåfaktorsautentiseringsmoduler" "header": "Tvåfaktorsautentiseringsmoduler"
}, },
"number_format": {
"description": "Välj hur siffror utformas.",
"dropdown_label": "Sifferformat",
"formats": {
"language": "Auto (använd språkinställning)",
"none": "Inget"
},
"header": "Sifferformat"
},
"push_notifications": { "push_notifications": {
"add_device_prompt": { "add_device_prompt": {
"input_label": "Enhetsnamn", "input_label": "Enhetsnamn",
@@ -3656,6 +3724,17 @@
"primary_color": "Primär färg", "primary_color": "Primär färg",
"reset": "Återställ" "reset": "Återställ"
}, },
"time_format": {
"description": "Välj hur tid utformas.",
"dropdown_label": "Tidsformat",
"formats": {
"12": "12-timmars (FM / EM)",
"24": "24-timmarsklocka",
"language": "Auto (använd språkinställning)",
"system": "Använd systemets språk"
},
"header": "Tidsformat"
},
"vibrate": { "vibrate": {
"description": "Aktivera eller inaktivera vibration på den här enheten när du styr enheter.", "description": "Aktivera eller inaktivera vibration på den här enheten när du styr enheter.",
"header": "Vibrera" "header": "Vibrera"

View File

@@ -318,6 +318,14 @@
"no_addons": "您尚未安装任何加载项。去加载项商店看看吧!" "no_addons": "您尚未安装任何加载项。去加载项商店看看吧!"
}, },
"dialog": { "dialog": {
"hardware": {
"attributes": "属性",
"device_path": "设备路径",
"id": "ID",
"search": "搜索硬件",
"subsystem": "子系统",
"title": "硬件"
},
"network": { "network": {
"connected_to": "已连接到 {ssid}", "connected_to": "已连接到 {ssid}",
"dhcp": "DHCP", "dhcp": "DHCP",
@@ -382,6 +390,7 @@
"snapshot": { "snapshot": {
"addons": "加载项", "addons": "加载项",
"available_snapshots": "可用快照", "available_snapshots": "可用快照",
"confirm_password": "确认快照密码",
"could_not_create": "未能创建快照", "could_not_create": "未能创建快照",
"create": "创建", "create": "创建",
"create_blocked_not_running": "现在无法创建快照,因为系统处于{state}状态。", "create_blocked_not_running": "现在无法创建快照,因为系统处于{state}状态。",
@@ -409,6 +418,7 @@
"password": "密码", "password": "密码",
"password_protected": "密码保护", "password_protected": "密码保护",
"password_protection": "密码保护", "password_protection": "密码保护",
"passwords_not_matching": "密码不匹配",
"security": "安全性", "security": "安全性",
"select_type": "请选择要还原的内容", "select_type": "请选择要还原的内容",
"selected": "已选择 {number} 项", "selected": "已选择 {number} 项",
@@ -3881,10 +3891,19 @@
"intro": "准备好唤醒你的家、找回你的隐私,并加入世界级的极客社区了吗?", "intro": "准备好唤醒你的家、找回你的隐私,并加入世界级的极客社区了吗?",
"next": "下一步", "next": "下一步",
"restore": { "restore": {
"addons": "加载项",
"confirm_password": "确认快照密码",
"description": "或者,您也可以从以前的快照还原。", "description": "或者,您也可以从以前的快照还原。",
"folders": "文件夹",
"full_snapshot": "完整快照",
"hide_log": "隐藏完整日志", "hide_log": "隐藏完整日志",
"in_progress": "正在还原", "in_progress": "正在还原",
"show_log": "显示完整日志" "partial_snapshot": "部分快照",
"password": "密码",
"password_protection": "密码保护",
"select_type": "请选择要还原的内容",
"show_log": "显示完整日志",
"type": "类型"
}, },
"user": { "user": {
"create_account": "创建帐户", "create_account": "创建帐户",

View File

@@ -318,6 +318,14 @@
"no_addons": "目前似乎沒有安裝任何附加元件。點選下方附加元件商店以新增!" "no_addons": "目前似乎沒有安裝任何附加元件。點選下方附加元件商店以新增!"
}, },
"dialog": { "dialog": {
"hardware": {
"attributes": "屬性",
"device_path": "裝置路徑",
"id": "ID",
"search": "搜尋硬體",
"subsystem": "子系統",
"title": "硬體"
},
"network": { "network": {
"connected_to": "已連線至 {ssid}", "connected_to": "已連線至 {ssid}",
"dhcp": "DHCP", "dhcp": "DHCP",
@@ -382,6 +390,7 @@
"snapshot": { "snapshot": {
"addons": "附加元件", "addons": "附加元件",
"available_snapshots": "可用系統備份", "available_snapshots": "可用系統備份",
"confirm_password": "確認系統備份密碼",
"could_not_create": "無法製作系統備份", "could_not_create": "無法製作系統備份",
"create": "新增", "create": "新增",
"create_blocked_not_running": "由於系統為 {state} 狀態,無法製作系統備份。", "create_blocked_not_running": "由於系統為 {state} 狀態,無法製作系統備份。",
@@ -409,6 +418,7 @@
"password": "系統備份密碼", "password": "系統備份密碼",
"password_protected": "密碼保護未顯示", "password_protected": "密碼保護未顯示",
"password_protection": "密碼保護", "password_protection": "密碼保護",
"passwords_not_matching": "密碼不相符",
"security": "加密", "security": "加密",
"select_type": "選擇所要回復內容", "select_type": "選擇所要回復內容",
"selected": "已選擇 {number} 個", "selected": "已選擇 {number} 個",
@@ -3881,10 +3891,19 @@
"intro": "準備喚醒您的智慧型家庭、取得隱私自主權,並加入由全球愛好者共同維護的社群了嗎?", "intro": "準備喚醒您的智慧型家庭、取得隱私自主權,並加入由全球愛好者共同維護的社群了嗎?",
"next": "下一步", "next": "下一步",
"restore": { "restore": {
"addons": "附加元件",
"confirm_password": "確認系統備份密碼",
"description": "或者可以自之前的 Snapshot 進行回復。", "description": "或者可以自之前的 Snapshot 進行回復。",
"folders": "資料夾",
"full_snapshot": "全系統備份",
"hide_log": "隱藏完整日誌", "hide_log": "隱藏完整日誌",
"in_progress": "回復進行中", "in_progress": "回復進行中",
"show_log": "顯示完整日誌" "partial_snapshot": "部分系統備份",
"password": "系統備份密碼",
"password_protection": "密碼保護",
"select_type": "選擇所要回復內容",
"show_log": "顯示完整日誌",
"type": "系統備份類型"
}, },
"user": { "user": {
"create_account": "創建帳號", "create_account": "創建帳號",

View File

@@ -2104,13 +2104,6 @@
"@polymer/iron-meta" "^3.0.0-pre.26" "@polymer/iron-meta" "^3.0.0-pre.26"
"@polymer/polymer" "^3.0.0" "@polymer/polymer" "^3.0.0"
"@polymer/iron-image@^3.0.1":
version "3.0.2"
resolved "https://registry.yarnpkg.com/@polymer/iron-image/-/iron-image-3.0.2.tgz#425ee6269634e024dbea726a91a61724ae4402b6"
integrity sha512-VyYtnewGozDb5sUeoLR1OvKzlt5WAL6b8Od7fPpio5oYL+9t061/nTV8+ZMrpMgF2WgB0zqM/3K53o3pbK5v8Q==
dependencies:
"@polymer/polymer" "^3.0.0"
"@polymer/iron-input@^3.0.0-pre.26", "@polymer/iron-input@^3.0.1": "@polymer/iron-input@^3.0.0-pre.26", "@polymer/iron-input@^3.0.1":
version "3.0.1" version "3.0.1"
resolved "https://registry.yarnpkg.com/@polymer/iron-input/-/iron-input-3.0.1.tgz#dc866a25107f9b38d9ca4512dd9a3e51b78b4915" resolved "https://registry.yarnpkg.com/@polymer/iron-input/-/iron-input-3.0.1.tgz#dc866a25107f9b38d9ca4512dd9a3e51b78b4915"
@@ -2120,13 +2113,6 @@
"@polymer/iron-validatable-behavior" "^3.0.0-pre.26" "@polymer/iron-validatable-behavior" "^3.0.0-pre.26"
"@polymer/polymer" "^3.0.0" "@polymer/polymer" "^3.0.0"
"@polymer/iron-label@^3.0.1":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@polymer/iron-label/-/iron-label-3.0.1.tgz#170247dc50d63f4e2ae6c80711dbf5b64fa953d6"
integrity sha512-MkIZ1WfOy10pnIxRwTVPfsoDZYlqMkUp0hmimMj0pGRHmrc9n5phuJUY1pC+S7WoKP1/98iH2qnXQukPGTzoVA==
dependencies:
"@polymer/polymer" "^3.0.0"
"@polymer/iron-list@^3.0.0": "@polymer/iron-list@^3.0.0":
version "3.0.2" version "3.0.2"
resolved "https://registry.yarnpkg.com/@polymer/iron-list/-/iron-list-3.0.2.tgz#9e6b80e503328dc29217dbe26f94faa47adb4124" resolved "https://registry.yarnpkg.com/@polymer/iron-list/-/iron-list-3.0.2.tgz#9e6b80e503328dc29217dbe26f94faa47adb4124"