mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-15 04:09:25 +00:00
Compare commits
1 Commits
blocking-u
...
Add-link-l
Author | SHA1 | Date | |
---|---|---|---|
![]() |
9da2fdced8 |
6
.github/workflows/release.yaml
vendored
6
.github/workflows/release.yaml
vendored
@@ -6,6 +6,7 @@ on:
|
||||
- published
|
||||
|
||||
env:
|
||||
WHEELS_TAG: 3.8-alpine3.12
|
||||
PYTHON_VERSION: 3.8
|
||||
NODE_VERSION: 12.1
|
||||
|
||||
@@ -63,9 +64,6 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
arch: ["aarch64", "armhf", "armv7", "amd64", "i386"]
|
||||
tag:
|
||||
- "3.8-alpine3.12"
|
||||
- "3.9-alpine3.13"
|
||||
steps:
|
||||
- name: Download requirements.txt
|
||||
uses: actions/download-artifact@v2
|
||||
@@ -75,7 +73,7 @@ jobs:
|
||||
- name: Build wheels
|
||||
uses: home-assistant/wheels@master
|
||||
with:
|
||||
tag: ${{ matrix.tag }}
|
||||
tag: ${{ env.WHEELS_TAG }}
|
||||
arch: ${{ matrix.arch }}
|
||||
wheels-host: ${{ secrets.WHEELS_HOST }}
|
||||
wheels-key: ${{ secrets.WHEELS_KEY }}
|
||||
|
@@ -52,7 +52,6 @@ module.exports.terserOptions = (latestBuild) => ({
|
||||
|
||||
module.exports.babelOptions = ({ latestBuild }) => ({
|
||||
babelrc: false,
|
||||
compact: false,
|
||||
presets: [
|
||||
!latestBuild && [
|
||||
"@babel/preset-env",
|
||||
@@ -80,6 +79,12 @@ module.exports.babelOptions = ({ latestBuild }) => ({
|
||||
].filter(Boolean),
|
||||
});
|
||||
|
||||
// Are already ES5, cause warnings when babelified.
|
||||
module.exports.babelExclude = () => [
|
||||
require.resolve("@mdi/js/mdi.js"),
|
||||
require.resolve("hls.js"),
|
||||
];
|
||||
|
||||
const outputPath = (outputRoot, latestBuild) =>
|
||||
path.resolve(outputRoot, latestBuild ? "frontend_latest" : "frontend_es5");
|
||||
|
||||
|
@@ -57,6 +57,7 @@ const createRollupConfig = ({
|
||||
babel({
|
||||
...bundle.babelOptions({ latestBuild }),
|
||||
extensions,
|
||||
exclude: bundle.babelExclude(),
|
||||
babelHelpers: isWDS ? "inline" : "bundled",
|
||||
}),
|
||||
string({
|
||||
|
@@ -47,6 +47,7 @@ const createWebpackConfig = ({
|
||||
rules: [
|
||||
{
|
||||
test: /\.m?js$|\.ts$/,
|
||||
exclude: bundle.babelExclude(),
|
||||
use: {
|
||||
loader: "babel-loader",
|
||||
options: bundle.babelOptions({ latestBuild }),
|
||||
|
@@ -29,6 +29,7 @@ class SupervisorFormfieldLabel extends LitElement {
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
:host {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
@@ -5,7 +5,6 @@ import { customElement, property } from "lit/decorators";
|
||||
import { atLeastVersion } from "../../../src/common/config/version";
|
||||
import { formatDate } from "../../../src/common/datetime/format_date";
|
||||
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-formfield";
|
||||
import "../../../src/components/ha-radio";
|
||||
@@ -68,8 +67,6 @@ const _computeAddons = (addons): AddonCheckboxItem[] =>
|
||||
export class SupervisorSnapshotContent extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public localize?: LocalizeFunc;
|
||||
|
||||
@property({ attribute: false }) public supervisor?: Supervisor;
|
||||
|
||||
@property({ attribute: false }) public snapshot?: HassioSnapshotDetail;
|
||||
@@ -84,14 +81,10 @@ export class SupervisorSnapshotContent extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public snapshotHasPassword = false;
|
||||
|
||||
@property({ type: Boolean }) public onboarding = false;
|
||||
|
||||
@property() public snapshotName = "";
|
||||
|
||||
@property() public snapshotPassword = "";
|
||||
|
||||
@property() public confirmSnapshotPassword = "";
|
||||
|
||||
public willUpdate(changedProps) {
|
||||
super.willUpdate(changedProps);
|
||||
if (!this.hasUpdated) {
|
||||
@@ -111,12 +104,8 @@ 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 {
|
||||
if (!this.onboarding && !this.supervisor) {
|
||||
if (!this.supervisor) {
|
||||
return html``;
|
||||
}
|
||||
const foldersSection =
|
||||
@@ -128,16 +117,14 @@ export class SupervisorSnapshotContent extends LitElement {
|
||||
${this.snapshot
|
||||
? html`<div class="details">
|
||||
${this.snapshot.type === "full"
|
||||
? this._localize("full_snapshot")
|
||||
: this._localize("partial_snapshot")}
|
||||
? this.supervisor.localize("snapshot.full_snapshot")
|
||||
: this.supervisor.localize("snapshot.partial_snapshot")}
|
||||
(${Math.ceil(this.snapshot.size * 10) / 10 + " MB"})<br />
|
||||
${this.hass
|
||||
? formatDateTime(new Date(this.snapshot.date), this.hass.locale)
|
||||
: this.snapshot.date}
|
||||
${formatDateTime(new Date(this.snapshot.date), this.hass.locale)}
|
||||
</div>`
|
||||
: html`<paper-input
|
||||
name="snapshotName"
|
||||
.label=${this.supervisor?.localize("snapshot.name") || "Name"}
|
||||
.label=${this.supervisor.localize("snapshot.name")}
|
||||
.value=${this.snapshotName}
|
||||
@value-changed=${this._handleTextValueChanged}
|
||||
>
|
||||
@@ -145,11 +132,13 @@ export class SupervisorSnapshotContent extends LitElement {
|
||||
${!this.snapshot || this.snapshot.type === "full"
|
||||
? html`<div class="sub-header">
|
||||
${!this.snapshot
|
||||
? this._localize("type")
|
||||
: this._localize("select_type")}
|
||||
? this.supervisor.localize("snapshot.type")
|
||||
: this.supervisor.localize("snapshot.select_type")}
|
||||
</div>
|
||||
<div class="snapshot-types">
|
||||
<ha-formfield .label=${this._localize("full_snapshot")}>
|
||||
<ha-formfield
|
||||
.label=${this.supervisor.localize("snapshot.full_snapshot")}
|
||||
>
|
||||
<ha-radio
|
||||
@change=${this._handleRadioValueChanged}
|
||||
value="full"
|
||||
@@ -158,7 +147,9 @@ export class SupervisorSnapshotContent extends LitElement {
|
||||
>
|
||||
</ha-radio>
|
||||
</ha-formfield>
|
||||
<ha-formfield .label=${this._localize("partial_snapshot")}>
|
||||
<ha-formfield
|
||||
.label=${this.supervisor!.localize("snapshot.partial_snapshot")}
|
||||
>
|
||||
<ha-radio
|
||||
@change=${this._handleRadioValueChanged}
|
||||
value="partial"
|
||||
@@ -169,9 +160,9 @@ export class SupervisorSnapshotContent extends LitElement {
|
||||
</ha-formfield>
|
||||
</div>`
|
||||
: ""}
|
||||
${this.snapshotType === "partial"
|
||||
? html`<div class="partial-picker">
|
||||
${this.snapshot && this.snapshot.homeassistant
|
||||
${this.snapshot && this.snapshotType === "partial"
|
||||
? html`
|
||||
${this.snapshot.homeassistant
|
||||
? html`
|
||||
<ha-formfield
|
||||
.label=${html`<supervisor-formfield-label
|
||||
@@ -191,11 +182,15 @@ export class SupervisorSnapshotContent extends LitElement {
|
||||
</ha-formfield>
|
||||
`
|
||||
: ""}
|
||||
`
|
||||
: ""}
|
||||
${this.snapshotType === "partial"
|
||||
? html`
|
||||
${foldersSection?.templates.length
|
||||
? html`
|
||||
<ha-formfield
|
||||
.label=${html`<supervisor-formfield-label
|
||||
.label=${this._localize("folders")}
|
||||
.label=${this.supervisor.localize("snapshot.folders")}
|
||||
.iconPath=${mdiFolder}
|
||||
>
|
||||
</supervisor-formfield-label>`}
|
||||
@@ -215,7 +210,7 @@ export class SupervisorSnapshotContent extends LitElement {
|
||||
? html`
|
||||
<ha-formfield
|
||||
.label=${html`<supervisor-formfield-label
|
||||
.label=${this._localize("addons")}
|
||||
.label=${this.supervisor.localize("snapshot.addons")}
|
||||
.iconPath=${mdiPuzzle}
|
||||
>
|
||||
</supervisor-formfield-label>`}
|
||||
@@ -231,44 +226,29 @@ export class SupervisorSnapshotContent extends LitElement {
|
||||
<div class="section-content">${addonsSection.templates}</div>
|
||||
`
|
||||
: ""}
|
||||
</div> `
|
||||
: ""}
|
||||
${this.snapshotType === "partial" &&
|
||||
(!this.snapshot || this.snapshotHasPassword)
|
||||
? html`<hr />`
|
||||
`
|
||||
: ""}
|
||||
${!this.snapshot
|
||||
? html`<ha-formfield
|
||||
class="password"
|
||||
.label=${this._localize("password_protection")}
|
||||
.label=${this.supervisor.localize("snapshot.password_protection")}
|
||||
>
|
||||
<ha-checkbox
|
||||
.checked=${this.snapshotHasPassword}
|
||||
@change=${this._toggleHasPassword}
|
||||
>
|
||||
</ha-checkbox>
|
||||
</ha-formfield>`
|
||||
</ha-checkbox
|
||||
></ha-formfield>`
|
||||
: ""}
|
||||
${this.snapshotHasPassword
|
||||
? html`
|
||||
<paper-input
|
||||
.label=${this._localize("password")}
|
||||
.label=${this.supervisor.localize("snapshot.password")}
|
||||
type="password"
|
||||
name="snapshotPassword"
|
||||
.value=${this.snapshotPassword}
|
||||
@value-changed=${this._handleTextValueChanged}
|
||||
>
|
||||
</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>`
|
||||
: ""}
|
||||
`
|
||||
: ""}
|
||||
`;
|
||||
@@ -276,24 +256,21 @@ export class SupervisorSnapshotContent extends LitElement {
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
.partial-picker ha-formfield {
|
||||
ha-checkbox {
|
||||
--mdc-checkbox-touch-target-size: 16px;
|
||||
display: block;
|
||||
margin: 4px 12px 8px 0;
|
||||
}
|
||||
.partial-picker ha-checkbox {
|
||||
--mdc-checkbox-touch-target-size: 32px;
|
||||
}
|
||||
.partial-picker {
|
||||
display: block;
|
||||
margin: 0px -6px;
|
||||
ha-formfield {
|
||||
display: contents;
|
||||
}
|
||||
supervisor-formfield-label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
hr {
|
||||
border-color: var(--divider-color);
|
||||
border-bottom: none;
|
||||
margin: 16px 0;
|
||||
paper-input[type="password"] {
|
||||
display: block;
|
||||
margin: 4px 0 4px 16px;
|
||||
}
|
||||
.details {
|
||||
color: var(--secondary-text-color);
|
||||
@@ -301,15 +278,13 @@ export class SupervisorSnapshotContent extends LitElement {
|
||||
.section-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-left: 30px;
|
||||
margin-left: 16px;
|
||||
}
|
||||
ha-formfield.password {
|
||||
display: block;
|
||||
margin: 0 -14px -16px;
|
||||
.security {
|
||||
margin-top: 16px;
|
||||
}
|
||||
.snapshot-types {
|
||||
display: flex;
|
||||
margin-left: -13px;
|
||||
}
|
||||
.sub-header {
|
||||
margin-top: 8px;
|
||||
@@ -328,9 +303,6 @@ export class SupervisorSnapshotContent extends LitElement {
|
||||
|
||||
if (this.snapshotHasPassword) {
|
||||
data.password = this.snapshotPassword;
|
||||
if (!this.snapshot) {
|
||||
data.confirm_password = this.confirmSnapshotPassword;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.snapshotType === "full") {
|
||||
@@ -362,7 +334,7 @@ export class SupervisorSnapshotContent extends LitElement {
|
||||
const addons =
|
||||
section === "addons"
|
||||
? new Map(
|
||||
this.supervisor?.addon.addons.map((item) => [item.slug, item])
|
||||
this.supervisor!.addon.addons.map((item) => [item.slug, item])
|
||||
)
|
||||
: undefined;
|
||||
let checkedItems = 0;
|
||||
@@ -372,7 +344,6 @@ export class SupervisorSnapshotContent extends LitElement {
|
||||
.label=${item.name}
|
||||
.iconPath=${section === "addons" ? mdiPuzzle : mdiFolder}
|
||||
.imageUrl=${section === "addons" &&
|
||||
!this.onboarding &&
|
||||
atLeastVersion(this.hass.config.version, 0, 105) &&
|
||||
addons?.get(item.slug)?.icon
|
||||
? `/api/hassio/addons/${item.slug}/icon`
|
||||
|
@@ -220,29 +220,10 @@ export class HassioUpdate extends LitElement {
|
||||
}
|
||||
|
||||
private async _updateCore(): Promise<void> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
await updateCore(this.hass);
|
||||
fireEvent(this, "supervisor-collection-refresh", {
|
||||
collection: "core",
|
||||
});
|
||||
fireEvent(this, "supervisor-applying-update", {
|
||||
name: "Home Assistant Core",
|
||||
version: this.supervisor.core.version_latest,
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
|
@@ -1,194 +0,0 @@
|
||||
import { mdiClose } from "@mdi/js";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import "../../../../src/common/search/search-input";
|
||||
import { compare } from "../../../../src/common/string/compare";
|
||||
import "../../../../src/components/ha-dialog";
|
||||
import "../../../../src/components/ha-expansion-panel";
|
||||
import { HassioHardwareInfo } from "../../../../src/data/hassio/hardware";
|
||||
import { dump } from "../../../../src/resources/js-yaml-dump";
|
||||
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import { HassioHardwareDialogParams } from "./show-dialog-hassio-hardware";
|
||||
|
||||
const _filterDevices = memoizeOne(
|
||||
(showAdvanced: boolean, hardware: HassioHardwareInfo, filter: string) =>
|
||||
hardware.devices
|
||||
.filter(
|
||||
(device) =>
|
||||
(showAdvanced ||
|
||||
["tty", "gpio", "input"].includes(device.subsystem)) &&
|
||||
(device.by_id?.toLowerCase().includes(filter) ||
|
||||
device.name.toLowerCase().includes(filter) ||
|
||||
device.dev_path.toLocaleLowerCase().includes(filter) ||
|
||||
JSON.stringify(device.attributes)
|
||||
.toLocaleLowerCase()
|
||||
.includes(filter))
|
||||
)
|
||||
.sort((a, b) => compare(a.name, b.name))
|
||||
);
|
||||
|
||||
@customElement("dialog-hassio-hardware")
|
||||
class HassioHardwareDialog extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _dialogParams?: HassioHardwareDialogParams;
|
||||
|
||||
@state() private _filter?: string;
|
||||
|
||||
public showDialog(params: HassioHardwareDialogParams) {
|
||||
this._dialogParams = params;
|
||||
}
|
||||
|
||||
public closeDialog() {
|
||||
this._dialogParams = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._dialogParams) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
const devices = _filterDevices(
|
||||
this.hass.userData?.showAdvanced || false,
|
||||
this._dialogParams.hardware,
|
||||
(this._filter || "").toLowerCase()
|
||||
);
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
scrimClickAction
|
||||
hideActions
|
||||
@closed=${this.closeDialog}
|
||||
.heading=${true}
|
||||
>
|
||||
<div class="header" slot="heading">
|
||||
<h2>
|
||||
${this._dialogParams.supervisor.localize("dialog.hardware.title")}
|
||||
</h2>
|
||||
<mwc-icon-button dialogAction="close">
|
||||
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<search-input
|
||||
autofocus
|
||||
no-label-float
|
||||
.filter=${this._filter}
|
||||
@value-changed=${this._handleSearchChange}
|
||||
.label=${this._dialogParams.supervisor.localize(
|
||||
"dialog.hardware.search"
|
||||
)}
|
||||
>
|
||||
</search-input>
|
||||
</div>
|
||||
|
||||
${devices.map(
|
||||
(device) =>
|
||||
html`<ha-expansion-panel
|
||||
.header=${device.name}
|
||||
.secondary=${device.by_id || undefined}
|
||||
outlined
|
||||
>
|
||||
<div class="device-property">
|
||||
<span>
|
||||
${this._dialogParams!.supervisor.localize(
|
||||
"dialog.hardware.subsystem"
|
||||
)}:
|
||||
</span>
|
||||
<span>${device.subsystem}</span>
|
||||
</div>
|
||||
<div class="device-property">
|
||||
<span>
|
||||
${this._dialogParams!.supervisor.localize(
|
||||
"dialog.hardware.device_path"
|
||||
)}:
|
||||
</span>
|
||||
<code>${device.dev_path}</code>
|
||||
</div>
|
||||
${device.by_id
|
||||
? html` <div class="device-property">
|
||||
<span>
|
||||
${this._dialogParams!.supervisor.localize(
|
||||
"dialog.hardware.id"
|
||||
)}:
|
||||
</span>
|
||||
<code>${device.by_id}</code>
|
||||
</div>`
|
||||
: ""}
|
||||
<div class="attributes">
|
||||
<span>
|
||||
${this._dialogParams!.supervisor.localize(
|
||||
"dialog.hardware.attributes"
|
||||
)}:
|
||||
</span>
|
||||
<pre>${dump(device.attributes, { indent: 2 })}</pre>
|
||||
</div>
|
||||
</ha-expansion-panel>`
|
||||
)}
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleSearchChange(ev: CustomEvent) {
|
||||
this._filter = ev.detail.value;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
haStyleDialog,
|
||||
css`
|
||||
mwc-icon-button {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 10px;
|
||||
text-decoration: none;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
h2 {
|
||||
margin: 18px 42px 0 18px;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
ha-expansion-panel {
|
||||
margin: 4px 0;
|
||||
}
|
||||
pre,
|
||||
code {
|
||||
background-color: var(--markdown-code-background-color, none);
|
||||
border-radius: 3px;
|
||||
}
|
||||
pre {
|
||||
padding: 16px;
|
||||
overflow: auto;
|
||||
line-height: 1.45;
|
||||
font-family: var(--code-font-family, monospace);
|
||||
}
|
||||
code {
|
||||
font-size: 85%;
|
||||
padding: 0.2em 0.4em;
|
||||
}
|
||||
search-input {
|
||||
margin: 0 16px;
|
||||
display: block;
|
||||
}
|
||||
.device-property {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.attributes {
|
||||
margin-top: 12px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-hassio-hardware": HassioHardwareDialog;
|
||||
}
|
||||
}
|
@@ -1,19 +0,0 @@
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import { HassioHardwareInfo } from "../../../../src/data/hassio/hardware";
|
||||
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
||||
|
||||
export interface HassioHardwareDialogParams {
|
||||
supervisor: Supervisor;
|
||||
hardware: HassioHardwareInfo;
|
||||
}
|
||||
|
||||
export const showHassioHardwareDialog = (
|
||||
element: HTMLElement,
|
||||
dialogParams: HassioHardwareDialogParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-hassio-hardware",
|
||||
dialogImport: () => import("./dialog-hassio-hardware"),
|
||||
dialogParams,
|
||||
});
|
||||
};
|
@@ -339,6 +339,21 @@ export class DialogHassioNetwork
|
||||
>
|
||||
</ha-radio>
|
||||
</ha-formfield>
|
||||
${version === "ipv6"
|
||||
? html` <ha-formfield
|
||||
.label=${this.supervisor.localize("dialog.network.link-local")}
|
||||
class="warning"
|
||||
>
|
||||
<ha-radio
|
||||
@change=${this._handleRadioValueChanged}
|
||||
.version=${version}
|
||||
value="link-local"
|
||||
name="${version}method"
|
||||
.checked=${this._interface![version]?.method === "link-local"}
|
||||
>
|
||||
</ha-radio>
|
||||
</ha-formfield>`
|
||||
: ""}
|
||||
</div>
|
||||
${this._interface![version].method === "static"
|
||||
? html`
|
||||
@@ -558,7 +573,7 @@ export class DialogHassioNetwork
|
||||
|
||||
@media all and (min-width: 451px) and (min-height: 501px) {
|
||||
.container {
|
||||
width: 400px;
|
||||
width: 500px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -95,25 +95,16 @@ class HassioCreateSnapshotDialog extends LitElement {
|
||||
this._creatingSnapshot = true;
|
||||
|
||||
this._error = "";
|
||||
if (snapshotDetails.password && !snapshotDetails.password.length) {
|
||||
if (
|
||||
this._snapshotContent.snapshotHasPassword &&
|
||||
!this._snapshotContent.snapshotPassword.length
|
||||
) {
|
||||
this._error = this._dialogParams!.supervisor.localize(
|
||||
"snapshot.enter_password"
|
||||
);
|
||||
this._creatingSnapshot = false;
|
||||
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 {
|
||||
if (this._snapshotContent.snapshotType === "full") {
|
||||
|
@@ -1,13 +1,13 @@
|
||||
import { ActionDetail } from "@material/mwc-list";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiClose, mdiDotsVertical } from "@mdi/js";
|
||||
import { mdiDotsVertical } from "@mdi/js";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import { slugify } from "../../../../src/common/string/slugify";
|
||||
import "../../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../../src/components/ha-button-menu";
|
||||
import "../../../../src/components/ha-header-bar";
|
||||
import { createCloseHeading } from "../../../../src/components/ha-dialog";
|
||||
import "../../../../src/components/ha-svg-icon";
|
||||
import { getSignedPath } from "../../../../src/data/auth";
|
||||
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
||||
@@ -22,7 +22,6 @@ import {
|
||||
import { HassDialog } from "../../../../src/dialogs/make-dialog-manager";
|
||||
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import { fileDownload } from "../../../../src/util/file_download";
|
||||
import "../../components/supervisor-snapshot-content";
|
||||
import type { SupervisorSnapshotContent } from "../../components/supervisor-snapshot-content";
|
||||
import { HassioSnapshotDialogParams } from "./show-dialog-hassio-snapshot";
|
||||
@@ -67,24 +66,14 @@ class HassioSnapshotDialog
|
||||
open
|
||||
scrimClickAction
|
||||
@closed=${this.closeDialog}
|
||||
.heading=${true}
|
||||
.heading=${createCloseHeading(this.hass, this._computeName)}
|
||||
>
|
||||
<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
|
||||
? html` <ha-circular-progress active></ha-circular-progress>`
|
||||
: html`<supervisor-snapshot-content
|
||||
.hass=${this.hass}
|
||||
.supervisor=${this._dialogParams.supervisor}
|
||||
.snapshot=${this._snapshot}
|
||||
.onboarding=${this._dialogParams.onboarding || false}
|
||||
.localize=${this._dialogParams.localize}
|
||||
>
|
||||
</supervisor-snapshot-content>`}
|
||||
${this._error ? html`<p class="error">Error: ${this._error}</p>` : ""}
|
||||
@@ -97,20 +86,18 @@ class HassioSnapshotDialog
|
||||
Restore
|
||||
</mwc-button>
|
||||
|
||||
${!this._dialogParams.onboarding
|
||||
? html`<ha-button-menu
|
||||
fixed
|
||||
slot="primaryAction"
|
||||
@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>
|
||||
<mwc-list-item>Download Snapshot</mwc-list-item>
|
||||
<mwc-list-item class="error">Delete Snapshot</mwc-list-item>
|
||||
</ha-button-menu>`
|
||||
: ""}
|
||||
<ha-button-menu
|
||||
fixed
|
||||
slot="primaryAction"
|
||||
@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>
|
||||
<mwc-list-item>Download Snapshot</mwc-list-item>
|
||||
<mwc-list-item class="error">Delete Snapshot</mwc-list-item>
|
||||
</ha-button-menu>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
@@ -127,12 +114,6 @@ class HassioSnapshotDialog
|
||||
display: block;
|
||||
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;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
@@ -307,11 +288,12 @@ class HassioSnapshotDialog
|
||||
}
|
||||
}
|
||||
|
||||
fileDownload(
|
||||
this,
|
||||
signedPath.path,
|
||||
`home_assistant_snapshot_${slugify(this._computeName)}.tar`
|
||||
);
|
||||
const a = document.createElement("a");
|
||||
a.href = signedPath.path;
|
||||
a.download = `home_assistant_snapshot_${slugify(this._computeName)}.tar`;
|
||||
this.shadowRoot!.appendChild(a);
|
||||
a.click();
|
||||
this.shadowRoot!.removeChild(a);
|
||||
}
|
||||
|
||||
private get _computeName() {
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import { LocalizeFunc } from "../../../../src/common/translations/localize";
|
||||
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
||||
|
||||
export interface HassioSnapshotDialogParams {
|
||||
@@ -7,7 +6,6 @@ export interface HassioSnapshotDialogParams {
|
||||
onDelete?: () => void;
|
||||
onboarding?: boolean;
|
||||
supervisor?: Supervisor;
|
||||
localize?: LocalizeFunc;
|
||||
}
|
||||
|
||||
export const showHassioSnapshotDialog = (
|
||||
|
@@ -7,7 +7,10 @@ import "../../../../src/components/ha-dialog";
|
||||
import "../../../../src/components/ha-settings-row";
|
||||
import "../../../../src/components/ha-svg-icon";
|
||||
import "../../../../src/components/ha-switch";
|
||||
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
||||
import {
|
||||
extractApiErrorMessage,
|
||||
ignoreSupervisorError,
|
||||
} from "../../../../src/data/hassio/common";
|
||||
import { createHassioPartialSnapshot } from "../../../../src/data/hassio/snapshot";
|
||||
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
|
||||
import type { HomeAssistant } from "../../../../src/types";
|
||||
@@ -150,7 +153,16 @@ class DialogSupervisorUpdate extends LitElement {
|
||||
}
|
||||
|
||||
this._action = "update";
|
||||
await this._dialogParams!.updateHandler!();
|
||||
try {
|
||||
await this._dialogParams!.updateHandler!();
|
||||
} catch (err) {
|
||||
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
|
||||
this._error = extractApiErrorMessage(err);
|
||||
}
|
||||
this._action = null;
|
||||
return;
|
||||
}
|
||||
|
||||
this.closeDialog();
|
||||
}
|
||||
|
||||
|
@@ -1,18 +1,17 @@
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import { HassioPartialSnapshotCreateParams } from "../../../../src/data/hassio/snapshot";
|
||||
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
||||
|
||||
export interface SupervisorDialogSupervisorUpdateParams {
|
||||
supervisor: Supervisor;
|
||||
name: string;
|
||||
version: string;
|
||||
snapshotParams: HassioPartialSnapshotCreateParams;
|
||||
snapshotParams: any;
|
||||
updateHandler: () => Promise<void>;
|
||||
}
|
||||
|
||||
export const showDialogSupervisorUpdate = (
|
||||
element: HTMLElement,
|
||||
dialogParams: Partial<SupervisorDialogSupervisorUpdateParams>
|
||||
dialogParams: SupervisorDialogSupervisorUpdateParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-supervisor-update",
|
||||
|
@@ -1,26 +1,12 @@
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import {
|
||||
Supervisor,
|
||||
supervisorApplyUpdateDetails,
|
||||
supervisorCollection,
|
||||
} from "../../src/data/supervisor/supervisor";
|
||||
import { HomeAssistant, Route } from "../../src/types";
|
||||
import "./hassio-panel-router";
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
"supervisor-applying-update": supervisorApplyUpdateDetails;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement("hassio-panel")
|
||||
class HassioPanel extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -31,16 +17,6 @@ class HassioPanel extends LitElement {
|
||||
|
||||
@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 {
|
||||
if (!this.hass) {
|
||||
return html`<hass-loading-screen></hass-loading-screen>`;
|
||||
@@ -53,16 +29,6 @@ class HassioPanel extends LitElement {
|
||||
) {
|
||||
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`
|
||||
<hassio-panel-router
|
||||
.hass=${this.hass}
|
||||
|
@@ -97,23 +97,16 @@ class HassioIngressView extends LitElement {
|
||||
title: requestedAddon,
|
||||
});
|
||||
await nextRender();
|
||||
navigate("/hassio/store", { replace: true });
|
||||
history.back();
|
||||
return;
|
||||
}
|
||||
if (!addonInfo.version) {
|
||||
await showAlertDialog(this, {
|
||||
text: this.supervisor.localize("my.error_addon_not_installed"),
|
||||
title: addonInfo.name,
|
||||
});
|
||||
await nextRender();
|
||||
navigate(`/hassio/addon/${addonInfo.slug}/info`, { replace: true });
|
||||
} else if (!addonInfo.ingress) {
|
||||
if (!addonInfo.ingress) {
|
||||
await showAlertDialog(this, {
|
||||
text: this.supervisor.localize("my.error_addon_no_ingress"),
|
||||
title: addonInfo.name,
|
||||
});
|
||||
await nextRender();
|
||||
navigate(`/hassio/addon/${addonInfo.slug}/info`, { replace: true });
|
||||
history.back();
|
||||
} else {
|
||||
navigate(`/hassio/ingress/${addonInfo.slug}`, { replace: true });
|
||||
}
|
||||
|
@@ -11,7 +11,6 @@ import {
|
||||
extractApiErrorMessage,
|
||||
fetchHassioStats,
|
||||
HassioStats,
|
||||
ignoreSupervisorError,
|
||||
} from "../../../src/data/hassio/common";
|
||||
import { restartCore, updateCore } from "../../../src/data/supervisor/core";
|
||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||
@@ -151,7 +150,7 @@ class HassioCoreInfo extends LitElement {
|
||||
title: this.supervisor.localize(
|
||||
"common.failed_to_restart_name",
|
||||
"name",
|
||||
"Home Assistant Core"
|
||||
"Home AssistantCore"
|
||||
),
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
@@ -176,29 +175,10 @@ class HassioCoreInfo extends LitElement {
|
||||
}
|
||||
|
||||
private async _updateCore(): Promise<void> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
await updateCore(this.hass);
|
||||
fireEvent(this, "supervisor-collection-refresh", {
|
||||
collection: "core",
|
||||
});
|
||||
fireEvent(this, "supervisor-applying-update", {
|
||||
name: "Home Assistant Core",
|
||||
version: this.supervisor.core.version_latest,
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
|
@@ -2,6 +2,7 @@ import "@material/mwc-button";
|
||||
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiDotsVertical } from "@mdi/js";
|
||||
import { dump } from "js-yaml";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
@@ -40,8 +41,8 @@ import {
|
||||
roundWithOneDecimal,
|
||||
} from "../../../src/util/calculate";
|
||||
import "../components/supervisor-metric";
|
||||
import { showHassioMarkdownDialog } from "../dialogs/markdown/show-dialog-hassio-markdown";
|
||||
import { showNetworkDialog } from "../dialogs/network/show-dialog-network";
|
||||
import { showHassioHardwareDialog } from "../dialogs/hardware/show-dialog-hassio-hardware";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
|
||||
@customElement("hassio-host-info")
|
||||
@@ -228,19 +229,20 @@ class HassioHostInfo extends LitElement {
|
||||
}
|
||||
|
||||
private async _showHardware(): Promise<void> {
|
||||
let hardware;
|
||||
try {
|
||||
hardware = await fetchHassioHardwareInfo(this.hass);
|
||||
const content = await fetchHassioHardwareInfo(this.hass);
|
||||
showHassioMarkdownDialog(this, {
|
||||
title: this.supervisor.localize("system.host.hardware"),
|
||||
content: `<pre>${dump(content, { indent: 2 })}</pre>`,
|
||||
});
|
||||
} catch (err) {
|
||||
await showAlertDialog(this, {
|
||||
showAlertDialog(this, {
|
||||
title: this.supervisor.localize(
|
||||
"system.host.failed_to_get_hardware_list"
|
||||
),
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
return;
|
||||
}
|
||||
showHassioHardwareDialog(this, { supervisor: this.supervisor, hardware });
|
||||
}
|
||||
|
||||
private async _hostReboot(ev: CustomEvent): Promise<void> {
|
||||
|
@@ -1,4 +1,5 @@
|
||||
module.exports = {
|
||||
"*.ts": () => "tsc -p tsconfig.json",
|
||||
"*.{js,ts}": "eslint --fix",
|
||||
"!(/translations)*.{js,ts,json,css,md,html}": "prettier --write",
|
||||
};
|
||||
|
@@ -66,7 +66,9 @@
|
||||
"@polymer/iron-autogrow-textarea": "^3.0.1",
|
||||
"@polymer/iron-flex-layout": "^3.0.1",
|
||||
"@polymer/iron-icon": "^3.0.1",
|
||||
"@polymer/iron-image": "^3.0.1",
|
||||
"@polymer/iron-input": "^3.0.1",
|
||||
"@polymer/iron-label": "^3.0.1",
|
||||
"@polymer/iron-overlay-behavior": "^3.0.2",
|
||||
"@polymer/iron-resizable-behavior": "^3.0.1",
|
||||
"@polymer/paper-checkbox": "^3.1.0",
|
||||
|
2
setup.py
2
setup.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="home-assistant-frontend",
|
||||
version="20210603.0",
|
||||
version="20210601.1",
|
||||
description="The Home Assistant frontend",
|
||||
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||
author="The Home Assistant Authors",
|
||||
|
@@ -6,7 +6,8 @@ export type LeafletDrawModuleType = typeof import("leaflet-draw");
|
||||
|
||||
export const setupLeafletMap = async (
|
||||
mapElement: HTMLElement,
|
||||
darkMode?: boolean
|
||||
darkMode?: boolean,
|
||||
draw = false
|
||||
): Promise<[Map, LeafletModuleType, TileLayer]> => {
|
||||
if (!mapElement.parentNode) {
|
||||
throw new Error("Cannot setup Leaflet map on disconnected element");
|
||||
@@ -16,6 +17,10 @@ export const setupLeafletMap = async (
|
||||
.default as LeafletModuleType;
|
||||
Leaflet.Icon.Default.imagePath = "/static/images/leaflet/images/";
|
||||
|
||||
if (draw) {
|
||||
await import("leaflet-draw");
|
||||
}
|
||||
|
||||
const map = Leaflet.map(mapElement);
|
||||
const style = document.createElement("link");
|
||||
style.setAttribute("href", "/static/images/leaflet/leaflet.css");
|
||||
|
@@ -14,17 +14,12 @@ class HaExpansionPanel extends LitElement {
|
||||
|
||||
@property() header?: string;
|
||||
|
||||
@property() secondary?: string;
|
||||
|
||||
@query(".container") private _container!: HTMLDivElement;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<div class="summary" @click=${this._toggleContainer}>
|
||||
<slot class="header" name="header">
|
||||
${this.header}
|
||||
<slot class="secondary" name="secondary">${this.secondary}</slot>
|
||||
</slot>
|
||||
<slot name="header">${this.header}</slot>
|
||||
<ha-svg-icon
|
||||
.path=${mdiChevronDown}
|
||||
class="summary-icon ${classMap({ expanded: this.expanded })}"
|
||||
@@ -111,16 +106,6 @@ class HaExpansionPanel extends LitElement {
|
||||
.container.expanded {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.secondary {
|
||||
display: block;
|
||||
color: var(--secondary-text-color);
|
||||
font-size: 12px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@@ -7,14 +7,14 @@ import { afterNextRender } from "../common/util/render-status";
|
||||
import { FrontendLocaleData } from "../data/translation";
|
||||
import { getValueInPercentage, normalize } from "../util/calculate";
|
||||
|
||||
// Workaround for https://github.com/home-assistant/frontend/issues/6467
|
||||
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
||||
|
||||
const getAngle = (value: number, min: number, max: number) => {
|
||||
const percentage = getValueInPercentage(normalize(value, min, max), min, max);
|
||||
return (percentage * 180) / 100;
|
||||
};
|
||||
|
||||
// Workaround for https://github.com/home-assistant/frontend/issues/6467
|
||||
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
||||
|
||||
@customElement("ha-gauge")
|
||||
export class Gauge extends LitElement {
|
||||
@property({ type: Number }) public min = 0;
|
||||
|
@@ -1,69 +0,0 @@
|
||||
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);
|
299
src/components/map/ha-location-editor.ts
Normal file
299
src/components/map/ha-location-editor.ts
Normal file
@@ -0,0 +1,299 @@
|
||||
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;
|
||||
}
|
||||
}
|
@@ -3,8 +3,10 @@ import {
|
||||
DivIcon,
|
||||
DragEndEvent,
|
||||
LatLng,
|
||||
Map,
|
||||
Marker,
|
||||
MarkerOptions,
|
||||
TileLayer,
|
||||
} from "leaflet";
|
||||
import {
|
||||
css,
|
||||
@@ -14,13 +16,15 @@ import {
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import type { LeafletModuleType } from "../../common/dom/setup-leaflet-map";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "./ha-map";
|
||||
import type { HaMap } from "./ha-map";
|
||||
import {
|
||||
LeafletModuleType,
|
||||
replaceTileLayer,
|
||||
setupLeafletMap,
|
||||
} from "../../common/dom/setup-leaflet-map";
|
||||
import { defaultRadiusColor } from "../../data/zone";
|
||||
import { HomeAssistant } from "../../types";
|
||||
|
||||
declare global {
|
||||
// for fire event
|
||||
@@ -47,40 +51,38 @@ export interface MarkerLocation {
|
||||
export class HaLocationsEditor extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public locations?: MarkerLocation[];
|
||||
@property() public locations?: MarkerLocation[];
|
||||
|
||||
@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;
|
||||
public fitZoom = 16;
|
||||
|
||||
// eslint-disable-next-line
|
||||
private Leaflet?: LeafletModuleType;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
// eslint-disable-next-line
|
||||
private _leafletMap?: Map;
|
||||
|
||||
import("leaflet").then((module) => {
|
||||
import("leaflet-draw").then(() => {
|
||||
this.Leaflet = module.default as LeafletModuleType;
|
||||
this._updateMarkers();
|
||||
this.updateComplete.then(() => this.fitMap());
|
||||
});
|
||||
});
|
||||
}
|
||||
private _tileLayer?: TileLayer;
|
||||
|
||||
private _locationMarkers?: { [key: string]: Marker | Circle };
|
||||
|
||||
private _circles: Record<string, Circle> = {};
|
||||
|
||||
public fitMap(): void {
|
||||
this.map.fitMap();
|
||||
if (
|
||||
!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 {
|
||||
if (!this.map.leafletMap || !this._locationMarkers) {
|
||||
if (!this._leafletMap || !this._locationMarkers) {
|
||||
return;
|
||||
}
|
||||
const marker = this._locationMarkers[id];
|
||||
@@ -88,44 +90,29 @@ export class HaLocationsEditor extends LitElement {
|
||||
return;
|
||||
}
|
||||
if ("getBounds" in marker) {
|
||||
this.map.leafletMap.fitBounds(marker.getBounds());
|
||||
this._leafletMap.fitBounds(marker.getBounds());
|
||||
(marker as Circle).bringToFront();
|
||||
} else {
|
||||
const circle = this._circles[id];
|
||||
if (circle) {
|
||||
this.map.leafletMap.fitBounds(circle.getBounds());
|
||||
this._leafletMap.fitBounds(circle.getBounds());
|
||||
} else {
|
||||
this.map.leafletMap.setView(marker.getLatLng(), this.zoom);
|
||||
this._leafletMap.setView(marker.getLatLng(), this.fitZoom);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
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>`;
|
||||
return html` <div id="map"></div> `;
|
||||
}
|
||||
|
||||
private _getLayers = memoizeOne(
|
||||
(
|
||||
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 firstUpdated(changedProps: PropertyValues): void {
|
||||
super.firstUpdated(changedProps);
|
||||
this._initMap();
|
||||
}
|
||||
|
||||
public willUpdate(changedProps: PropertyValues): void {
|
||||
super.willUpdate(changedProps);
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
super.updated(changedProps);
|
||||
|
||||
// Still loading.
|
||||
if (!this.Leaflet) {
|
||||
@@ -135,6 +122,37 @@ export class HaLocationsEditor extends LitElement {
|
||||
if (changedProps.has("locations")) {
|
||||
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) {
|
||||
@@ -171,18 +189,21 @@ export class HaLocationsEditor extends LitElement {
|
||||
}
|
||||
|
||||
private _updateMarkers(): void {
|
||||
if (!this.locations || !this.locations.length) {
|
||||
this._circles = {};
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
|
||||
const locationMarkers = {};
|
||||
const circles = {};
|
||||
|
||||
const defaultZoneRadiusColor = getComputedStyle(this).getPropertyValue(
|
||||
"--accent-color"
|
||||
);
|
||||
this._locationMarkers = {};
|
||||
|
||||
this.locations.forEach((location: MarkerLocation) => {
|
||||
let icon: DivIcon | undefined;
|
||||
@@ -207,46 +228,45 @@ export class HaLocationsEditor extends LitElement {
|
||||
const circle = this.Leaflet!.circle(
|
||||
[location.latitude, location.longitude],
|
||||
{
|
||||
color: location.radius_color || defaultZoneRadiusColor,
|
||||
color: location.radius_color || defaultRadiusColor,
|
||||
radius: location.radius,
|
||||
}
|
||||
);
|
||||
circle.addTo(this._leafletMap!);
|
||||
if (location.radius_editable || location.location_editable) {
|
||||
// @ts-ignore
|
||||
circle.editing.enable();
|
||||
circle.addEventListener("add", () => {
|
||||
// @ts-ignore
|
||||
const moveMarker = circle.editing._moveMarker;
|
||||
// @ts-ignore
|
||||
const resizeMarker = circle.editing._resizeMarkers[0];
|
||||
if (icon) {
|
||||
moveMarker.setIcon(icon);
|
||||
}
|
||||
resizeMarker.id = moveMarker.id = location.id;
|
||||
moveMarker
|
||||
.addEventListener(
|
||||
"dragend",
|
||||
// @ts-ignore
|
||||
(ev: DragEndEvent) => this._updateLocation(ev)
|
||||
)
|
||||
.addEventListener(
|
||||
"click",
|
||||
// @ts-ignore
|
||||
(ev: MouseEvent) => this._markerClicked(ev)
|
||||
);
|
||||
if (location.radius_editable) {
|
||||
resizeMarker.addEventListener(
|
||||
"dragend",
|
||||
// @ts-ignore
|
||||
(ev: DragEndEvent) => this._updateRadius(ev)
|
||||
);
|
||||
} else {
|
||||
resizeMarker.remove();
|
||||
}
|
||||
});
|
||||
locationMarkers[location.id] = circle;
|
||||
// @ts-ignore
|
||||
const moveMarker = circle.editing._moveMarker;
|
||||
// @ts-ignore
|
||||
const resizeMarker = circle.editing._resizeMarkers[0];
|
||||
if (icon) {
|
||||
moveMarker.setIcon(icon);
|
||||
}
|
||||
resizeMarker.id = moveMarker.id = location.id;
|
||||
moveMarker
|
||||
.addEventListener(
|
||||
"dragend",
|
||||
// @ts-ignore
|
||||
(ev: DragEndEvent) => this._updateLocation(ev)
|
||||
)
|
||||
.addEventListener(
|
||||
"click",
|
||||
// @ts-ignore
|
||||
(ev: MouseEvent) => this._markerClicked(ev)
|
||||
);
|
||||
if (location.radius_editable) {
|
||||
resizeMarker.addEventListener(
|
||||
"dragend",
|
||||
// @ts-ignore
|
||||
(ev: DragEndEvent) => this._updateRadius(ev)
|
||||
);
|
||||
} else {
|
||||
resizeMarker.remove();
|
||||
}
|
||||
this._locationMarkers![location.id] = circle;
|
||||
} else {
|
||||
circles[location.id] = circle;
|
||||
this._circles[location.id] = circle;
|
||||
}
|
||||
}
|
||||
if (
|
||||
@@ -255,7 +275,6 @@ export class HaLocationsEditor extends LitElement {
|
||||
) {
|
||||
const options: MarkerOptions = {
|
||||
title: location.name,
|
||||
draggable: location.location_editable,
|
||||
};
|
||||
|
||||
if (icon) {
|
||||
@@ -274,14 +293,13 @@ export class HaLocationsEditor extends LitElement {
|
||||
"click",
|
||||
// @ts-ignore
|
||||
(ev: MouseEvent) => this._markerClicked(ev)
|
||||
);
|
||||
)
|
||||
.addTo(this._leafletMap!);
|
||||
(marker as any).id = location.id;
|
||||
|
||||
locationMarkers[location.id] = marker;
|
||||
this._locationMarkers![location.id] = marker;
|
||||
}
|
||||
});
|
||||
this._circles = circles;
|
||||
this._locationMarkers = locationMarkers;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
@@ -290,9 +308,23 @@ export class HaLocationsEditor extends LitElement {
|
||||
display: block;
|
||||
height: 300px;
|
||||
}
|
||||
ha-map {
|
||||
#map {
|
||||
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;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@@ -1,15 +1,13 @@
|
||||
import { Circle, Layer, Map, Marker, TileLayer } from "leaflet";
|
||||
import {
|
||||
Circle,
|
||||
CircleMarker,
|
||||
LatLngTuple,
|
||||
Layer,
|
||||
Map,
|
||||
Marker,
|
||||
Polyline,
|
||||
TileLayer,
|
||||
} from "leaflet";
|
||||
import { css, CSSResultGroup, PropertyValues, ReactiveElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import {
|
||||
LeafletModuleType,
|
||||
replaceTileLayer,
|
||||
@@ -17,324 +15,194 @@ import {
|
||||
} from "../../common/dom/setup-leaflet-map";
|
||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import "./ha-entity-marker";
|
||||
import { debounce } from "../../common/util/debounce";
|
||||
import "../../panels/map/ha-entity-marker";
|
||||
import { HomeAssistant } from "../../types";
|
||||
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")
|
||||
export class HaMap extends ReactiveElement {
|
||||
class HaMap extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public entities?: string[] | HaMapEntity[];
|
||||
@property() public entities?: string[];
|
||||
|
||||
@property({ attribute: false }) public paths?: HaMapPaths[];
|
||||
@property() public darkMode?: boolean;
|
||||
|
||||
@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;
|
||||
@property() public zoom?: number;
|
||||
|
||||
// eslint-disable-next-line
|
||||
private Leaflet?: LeafletModuleType;
|
||||
|
||||
private _leafletMap?: Map;
|
||||
|
||||
private _tileLayer?: TileLayer;
|
||||
|
||||
// @ts-ignore
|
||||
private _resizeObserver?: ResizeObserver;
|
||||
|
||||
private _debouncedResizeListener = debounce(
|
||||
() => {
|
||||
if (!this._leafletMap) {
|
||||
return;
|
||||
}
|
||||
this._leafletMap.invalidateSize();
|
||||
},
|
||||
100,
|
||||
false
|
||||
);
|
||||
|
||||
private _mapItems: Array<Marker | Circle> = [];
|
||||
|
||||
private _mapZones: Array<Marker | Circle> = [];
|
||||
|
||||
private _mapPaths: Array<Polyline | CircleMarker> = [];
|
||||
private _connected = false;
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this._loadMap();
|
||||
this._attachObserver();
|
||||
this._connected = true;
|
||||
if (this.hasUpdated) {
|
||||
this.loadMap();
|
||||
this._attachObserver();
|
||||
}
|
||||
}
|
||||
|
||||
public disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
if (this.leafletMap) {
|
||||
this.leafletMap.remove();
|
||||
this.leafletMap = undefined;
|
||||
this._connected = false;
|
||||
|
||||
if (this._leafletMap) {
|
||||
this._leafletMap.remove();
|
||||
this._leafletMap = undefined;
|
||||
this.Leaflet = undefined;
|
||||
}
|
||||
|
||||
this._loaded = false;
|
||||
|
||||
if (this._resizeObserver) {
|
||||
this._resizeObserver.unobserve(this);
|
||||
this._resizeObserver.unobserve(this._mapEl);
|
||||
} else {
|
||||
window.removeEventListener("resize", this._debouncedResizeListener);
|
||||
}
|
||||
}
|
||||
|
||||
protected update(changedProps: PropertyValues) {
|
||||
super.update(changedProps);
|
||||
|
||||
if (!this._loaded) {
|
||||
return;
|
||||
protected render(): TemplateResult {
|
||||
if (!this.entities) {
|
||||
return html``;
|
||||
}
|
||||
return html` <div id="map"></div> `;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues): void {
|
||||
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 (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 (!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;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
return false;
|
||||
}
|
||||
|
||||
private async _loadMap(): Promise<void> {
|
||||
let map = this.shadowRoot!.getElementById("map");
|
||||
if (!map) {
|
||||
map = document.createElement("div");
|
||||
map.id = "map";
|
||||
this.shadowRoot!.append(map);
|
||||
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
|
||||
);
|
||||
}
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
|
||||
if (!this._mapItems.length && !this.layers?.length) {
|
||||
this.leafletMap.setView(
|
||||
if (this._mapItems.length === 0) {
|
||||
this._leafletMap.setView(
|
||||
new this.Leaflet.LatLng(
|
||||
this.hass.config.latitude,
|
||||
this.hass.config.longitude
|
||||
),
|
||||
this.zoom
|
||||
this.zoom || 14
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let bounds = this.Leaflet.latLngBounds(
|
||||
const bounds = this.Leaflet.latLngBounds(
|
||||
this._mapItems ? this._mapItems.map((item) => item.getLatLng()) : []
|
||||
);
|
||||
this._leafletMap.fitBounds(bounds.pad(0.5));
|
||||
|
||||
if (this.fitZones) {
|
||||
this._mapZones?.forEach((zone) => {
|
||||
bounds.extend(
|
||||
"getBounds" in zone ? zone.getBounds() : zone.getLatLng()
|
||||
);
|
||||
});
|
||||
if (this.zoom && this._leafletMap.getZoom() > this.zoom) {
|
||||
this._leafletMap.setZoom(this.zoom);
|
||||
}
|
||||
|
||||
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 {
|
||||
const hass = this.hass;
|
||||
const map = this.leafletMap;
|
||||
const map = this._leafletMap;
|
||||
const Leaflet = this.Leaflet;
|
||||
|
||||
if (!hass || !map || !Leaflet) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._mapItems.length) {
|
||||
if (this._mapItems) {
|
||||
this._mapItems.forEach((marker) => marker.remove());
|
||||
this._mapItems = [];
|
||||
}
|
||||
const mapItems: Layer[] = (this._mapItems = []);
|
||||
|
||||
if (this._mapZones.length) {
|
||||
if (this._mapZones) {
|
||||
this._mapZones.forEach((marker) => marker.remove());
|
||||
this._mapZones = [];
|
||||
}
|
||||
const mapZones: Layer[] = (this._mapZones = []);
|
||||
|
||||
if (!this.entities) {
|
||||
return;
|
||||
}
|
||||
const allEntities = this.entities!.concat();
|
||||
|
||||
const computedStyles = getComputedStyle(this);
|
||||
const zoneColor = computedStyles.getPropertyValue("--accent-color");
|
||||
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)];
|
||||
for (const entity of allEntities) {
|
||||
const entityId = entity;
|
||||
const stateObj = hass.states[entityId];
|
||||
if (!stateObj) {
|
||||
continue;
|
||||
}
|
||||
@@ -372,12 +240,13 @@ export class HaMap extends ReactiveElement {
|
||||
}
|
||||
|
||||
// create marker with the icon
|
||||
this._mapZones.push(
|
||||
mapZones.push(
|
||||
Leaflet.marker([latitude, longitude], {
|
||||
icon: Leaflet.divIcon({
|
||||
html: iconHTML,
|
||||
iconSize: [24, 24],
|
||||
className,
|
||||
className:
|
||||
this.darkMode ?? this.hass.themes.darkMode ? "dark" : "light",
|
||||
}),
|
||||
interactive: false,
|
||||
title,
|
||||
@@ -385,10 +254,10 @@ export class HaMap extends ReactiveElement {
|
||||
);
|
||||
|
||||
// create circle around it
|
||||
this._mapZones.push(
|
||||
mapZones.push(
|
||||
Leaflet.circle([latitude, longitude], {
|
||||
interactive: false,
|
||||
color: zoneColor,
|
||||
color: "#FF9800",
|
||||
radius,
|
||||
})
|
||||
);
|
||||
@@ -404,20 +273,17 @@ export class HaMap extends ReactiveElement {
|
||||
.join("")
|
||||
.substr(0, 3);
|
||||
|
||||
// create marker with the icon
|
||||
this._mapItems.push(
|
||||
// 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="${getEntityId(entity)}"
|
||||
entity-id="${entityId}"
|
||||
entity-name="${entityName}"
|
||||
entity-picture="${entityPicture || ""}"
|
||||
${
|
||||
typeof entity !== "string"
|
||||
? `entity-color="${entity.color}"`
|
||||
: ""
|
||||
}
|
||||
></ha-entity-marker>
|
||||
`,
|
||||
iconSize: [48, 48],
|
||||
@@ -429,10 +295,10 @@ export class HaMap extends ReactiveElement {
|
||||
|
||||
// create circle around if entity has accuracy
|
||||
if (gpsAccuracy) {
|
||||
this._mapItems.push(
|
||||
mapItems.push(
|
||||
Leaflet.circle([latitude, longitude], {
|
||||
interactive: false,
|
||||
color: darkPrimaryColor,
|
||||
color: "#0288D1",
|
||||
radius: gpsAccuracy,
|
||||
})
|
||||
);
|
||||
@@ -443,14 +309,20 @@ export class HaMap extends ReactiveElement {
|
||||
this._mapZones.forEach((marker) => map.addLayer(marker));
|
||||
}
|
||||
|
||||
private async _attachObserver(): Promise<void> {
|
||||
if (!this._resizeObserver) {
|
||||
await installResizeObserver();
|
||||
this._resizeObserver = new ResizeObserver(() => {
|
||||
this.leafletMap?.invalidateSize({ debounceMoveend: true });
|
||||
});
|
||||
private _attachObserver(): void {
|
||||
// Observe changes to map size and invalidate to prevent broken rendering
|
||||
// Uses ResizeObserver in Chrome, otherwise window resize event
|
||||
|
||||
// @ts-ignore
|
||||
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 {
|
||||
@@ -465,25 +337,13 @@ export class HaMap extends ReactiveElement {
|
||||
#map.dark {
|
||||
background: #090909;
|
||||
}
|
||||
.light {
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.dark {
|
||||
color: #ffffff;
|
||||
}
|
||||
.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;
|
||||
|
||||
.light {
|
||||
color: #000000;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
@@ -14,17 +14,12 @@ interface HassioHardwareAudioList {
|
||||
};
|
||||
}
|
||||
|
||||
interface HardwareDevice {
|
||||
attributes: Record<string, string>;
|
||||
by_id: null | string;
|
||||
dev_path: string;
|
||||
name: string;
|
||||
subsystem: string;
|
||||
sysfs: string;
|
||||
}
|
||||
|
||||
export interface HassioHardwareInfo {
|
||||
devices: HardwareDevice[];
|
||||
serial: string[];
|
||||
input: string[];
|
||||
disk: string[];
|
||||
gpio: string[];
|
||||
audio: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export const fetchHassioHardwareAudio = async (
|
||||
|
@@ -5,7 +5,7 @@ import { hassioApiResultExtractor, HassioResponse } from "./common";
|
||||
interface IpConfiguration {
|
||||
address: string[];
|
||||
gateway: string;
|
||||
method: "disabled" | "static" | "auto";
|
||||
method: "disabled" | "static" | "auto" | "link-local";
|
||||
nameservers: string[];
|
||||
}
|
||||
|
||||
|
@@ -41,7 +41,6 @@ export interface HassioSnapshotDetail extends HassioSnapshot {
|
||||
export interface HassioFullSnapshotCreateParams {
|
||||
name: string;
|
||||
password?: string;
|
||||
confirm_password?: string;
|
||||
}
|
||||
export interface HassioPartialSnapshotCreateParams
|
||||
extends HassioFullSnapshotCreateParams {
|
||||
|
@@ -13,11 +13,6 @@ import {
|
||||
} from "../hassio/supervisor";
|
||||
import { SupervisorStore } from "./store";
|
||||
|
||||
export interface supervisorApplyUpdateDetails {
|
||||
name: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
export const supervisorWSbaseCommand = {
|
||||
type: "supervisor/api",
|
||||
method: "GET",
|
||||
|
@@ -1,6 +1,14 @@
|
||||
import { navigate } from "../common/navigate";
|
||||
import {
|
||||
DEFAULT_ACCENT_COLOR,
|
||||
DEFAULT_PRIMARY_COLOR,
|
||||
} from "../resources/ha-style";
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export const defaultRadiusColor = DEFAULT_ACCENT_COLOR;
|
||||
export const homeRadiusColor = DEFAULT_PRIMARY_COLOR;
|
||||
export const passiveRadiusColor = "#9b9b9b";
|
||||
|
||||
export interface Zone {
|
||||
id: string;
|
||||
name: string;
|
||||
|
@@ -23,12 +23,16 @@ class MoreInfoPerson extends LitElement {
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-attributes
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this.stateObj}
|
||||
extra-filters="id,user_id,editable"
|
||||
></ha-attributes>
|
||||
${this.stateObj.attributes.latitude && this.stateObj.attributes.longitude
|
||||
? html`
|
||||
<ha-map
|
||||
.hass=${this.hass}
|
||||
.entities=${this._entityArray(this.stateObj.entity_id)}
|
||||
autoFit
|
||||
></ha-map>
|
||||
`
|
||||
: ""}
|
||||
@@ -47,11 +51,6 @@ class MoreInfoPerson extends LitElement {
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<ha-attributes
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this.stateObj}
|
||||
extra-filters="id,user_id,editable"
|
||||
></ha-attributes>
|
||||
`;
|
||||
}
|
||||
|
||||
|
@@ -17,6 +17,11 @@ class MoreInfoTimer extends LitElement {
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-attributes
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this.stateObj}
|
||||
extra-filters="remaining"
|
||||
></ha-attributes>
|
||||
<div class="actions">
|
||||
${this.stateObj.state === "idle" || this.stateObj.state === "paused"
|
||||
? html`
|
||||
@@ -52,11 +57,6 @@ class MoreInfoTimer extends LitElement {
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
<ha-attributes
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this.stateObj}
|
||||
extra-filters="remaining"
|
||||
></ha-attributes>
|
||||
`;
|
||||
}
|
||||
|
||||
|
@@ -5,7 +5,6 @@ import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||
import { throttle } from "../../common/util/throttle";
|
||||
import "../../components/ha-circular-progress";
|
||||
import "../../components/state-history-charts";
|
||||
import { fetchUsers } from "../../data/user";
|
||||
import { getLogbookData, LogbookEntry } from "../../data/logbook";
|
||||
import { loadTraceContexts, TraceContexts } from "../../data/trace";
|
||||
import "../../panels/logbook/ha-logbook";
|
||||
@@ -23,12 +22,10 @@ export class MoreInfoLogbook extends LitElement {
|
||||
|
||||
@state() private _traceContexts?: TraceContexts;
|
||||
|
||||
@state() private _userIdToName = {};
|
||||
@state() private _persons = {};
|
||||
|
||||
private _lastLogbookDate?: Date;
|
||||
|
||||
private _fetchUserPromise?: Promise<void>;
|
||||
|
||||
private _throttleGetLogbookEntries = throttle(() => {
|
||||
this._getLogBookData();
|
||||
}, 10000);
|
||||
@@ -62,7 +59,7 @@ export class MoreInfoLogbook extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.entries=${this._logbookEntries}
|
||||
.traceContexts=${this._traceContexts}
|
||||
.userIdToName=${this._userIdToName}
|
||||
.userIdToName=${this._persons}
|
||||
></ha-logbook>
|
||||
`
|
||||
: html`<div class="no-entries">
|
||||
@@ -73,7 +70,7 @@ export class MoreInfoLogbook extends LitElement {
|
||||
}
|
||||
|
||||
protected firstUpdated(): void {
|
||||
this._fetchUserPromise = this._fetchUserNames();
|
||||
this._fetchPersonNames();
|
||||
this.addEventListener("click", (ev) => {
|
||||
if ((ev.composedPath()[0] as HTMLElement).tagName === "A") {
|
||||
setTimeout(() => closeDialog("ha-more-info-dialog"), 500);
|
||||
@@ -128,7 +125,6 @@ export class MoreInfoLogbook extends LitElement {
|
||||
true
|
||||
),
|
||||
loadTraceContexts(this.hass),
|
||||
this._fetchUserPromise,
|
||||
]);
|
||||
this._logbookEntries = this._logbookEntries
|
||||
? [...newEntries, ...this._logbookEntries]
|
||||
@@ -137,34 +133,16 @@ export class MoreInfoLogbook extends LitElement {
|
||||
this._traceContexts = traceContexts;
|
||||
}
|
||||
|
||||
private async _fetchUserNames() {
|
||||
const userIdToName = {};
|
||||
|
||||
// Start loading users
|
||||
const userProm = this.hass.user?.is_admin && fetchUsers(this.hass);
|
||||
|
||||
// Process persons
|
||||
private _fetchPersonNames() {
|
||||
Object.values(this.hass.states).forEach((entity) => {
|
||||
if (
|
||||
entity.attributes.user_id &&
|
||||
computeStateDomain(entity) === "person"
|
||||
) {
|
||||
this._userIdToName[entity.attributes.user_id] =
|
||||
this._persons[entity.attributes.user_id] =
|
||||
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() {
|
||||
|
@@ -48,9 +48,6 @@
|
||||
window.providersPromise = fetch("/auth/providers", {
|
||||
credentials: "same-origin",
|
||||
});
|
||||
if (!window.globalThis) {
|
||||
window.globalThis = window;
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
|
@@ -71,9 +71,6 @@
|
||||
import("<%= latestAppJS %>");
|
||||
window.customPanelJS = "<%= latestCustomPanelJS %>";
|
||||
window.latestJS = true;
|
||||
if (!window.globalThis) {
|
||||
window.globalThis = window;
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
{% for extra_module in extra_modules -%}
|
||||
|
@@ -80,9 +80,6 @@
|
||||
window.stepsPromise = fetch("/api/onboarding", {
|
||||
credentials: "same-origin",
|
||||
});
|
||||
if (!window.globalThis) {
|
||||
window.globalThis = window;
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
|
@@ -39,7 +39,6 @@ class HassLoadingScreen extends LitElement {
|
||||
</div>`}
|
||||
<div class="content">
|
||||
<ha-circular-progress active></ha-circular-progress>
|
||||
<slot></slot>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -77,7 +76,6 @@ class HassLoadingScreen extends LitElement {
|
||||
.content {
|
||||
height: calc(100% - var(--header-height));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
@@ -12,10 +12,7 @@ import { HASSDomEvent } from "../common/dom/fire_event";
|
||||
import { extractSearchParamsObject } from "../common/url/search-params";
|
||||
import { subscribeOne } from "../common/util/subscribe-one";
|
||||
import { AuthUrlSearchParams, hassUrl } from "../data/auth";
|
||||
import {
|
||||
DiscoveryInformation,
|
||||
fetchDiscoveryInformation,
|
||||
} from "../data/discovery";
|
||||
import { fetchDiscoveryInformation } from "../data/discovery";
|
||||
import {
|
||||
fetchOnboardingOverview,
|
||||
OnboardingResponses,
|
||||
@@ -71,8 +68,6 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
||||
|
||||
@state() private _steps?: OnboardingStep[];
|
||||
|
||||
@state() private _discoveryInformation?: DiscoveryInformation;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const step = this._curStep()!;
|
||||
|
||||
@@ -92,7 +87,6 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
||||
? html`<onboarding-restore-snapshot
|
||||
.localize=${this.localize}
|
||||
.restoring=${this._restoring}
|
||||
.discoveryInformation=${this._discoveryInformation}
|
||||
@restoring=${this._restoringSnapshot}
|
||||
>
|
||||
</onboarding-restore-snapshot>`
|
||||
|
@@ -5,11 +5,9 @@ import "@polymer/paper-radio-button/paper-radio-button";
|
||||
import "@polymer/paper-radio-group/paper-radio-group";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import type { LocalizeFunc } from "../common/translations/localize";
|
||||
import "../components/map/ha-locations-editor";
|
||||
import type { MarkerLocation } from "../components/map/ha-locations-editor";
|
||||
import "../components/map/ha-location-editor";
|
||||
import { createTimezoneListEl } from "../components/timezone-datalist";
|
||||
import {
|
||||
ConfigUpdateValues,
|
||||
@@ -83,14 +81,14 @@ class OnboardingCoreConfig extends LitElement {
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<ha-locations-editor
|
||||
<ha-location-editor
|
||||
class="flex"
|
||||
.hass=${this.hass}
|
||||
.locations=${this._markerLocation(this._locationValue)}
|
||||
zoom="14"
|
||||
.location=${this._locationValue}
|
||||
.fitZoom=${14}
|
||||
.darkMode=${mql.matches}
|
||||
@location-updated=${this._locationChanged}
|
||||
></ha-locations-editor>
|
||||
@change=${this._locationChanged}
|
||||
></ha-location-editor>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
@@ -210,24 +208,13 @@ class OnboardingCoreConfig extends LitElement {
|
||||
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>) {
|
||||
const target = ev.currentTarget as PaperInputElement;
|
||||
this[`_${target.name}`] = target.value;
|
||||
}
|
||||
|
||||
private _locationChanged(ev) {
|
||||
this._location = ev.detail.location;
|
||||
this._location = ev.currentTarget.location;
|
||||
}
|
||||
|
||||
private _unitSystemChanged(
|
||||
|
@@ -4,12 +4,9 @@ import { customElement, property } from "lit/decorators";
|
||||
import "../../hassio/src/components/hassio-ansi-to-html";
|
||||
import { showHassioSnapshotDialog } from "../../hassio/src/dialogs/snapshot/show-dialog-hassio-snapshot";
|
||||
import { showSnapshotUploadDialog } from "../../hassio/src/dialogs/snapshot/show-dialog-snapshot-upload";
|
||||
import { navigate } from "../common/navigate";
|
||||
import type { LocalizeFunc } from "../common/translations/localize";
|
||||
import "../components/ha-card";
|
||||
import {
|
||||
DiscoveryInformation,
|
||||
fetchDiscoveryInformation,
|
||||
} from "../data/discovery";
|
||||
import { makeDialogManager } from "../dialogs/make-dialog-manager";
|
||||
import { ProvideHassLitMixin } from "../mixins/provide-hass-lit-mixin";
|
||||
import { haStyle } from "../resources/styles";
|
||||
@@ -29,9 +26,6 @@ class OnboardingRestoreSnapshot extends ProvideHassLitMixin(LitElement) {
|
||||
|
||||
@property({ type: Boolean }) public restoring = false;
|
||||
|
||||
@property({ attribute: false })
|
||||
public discoveryInformation?: DiscoveryInformation;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return this.restoring
|
||||
? html`<ha-card
|
||||
@@ -64,14 +58,13 @@ class OnboardingRestoreSnapshot extends ProvideHassLitMixin(LitElement) {
|
||||
private async _checkRestoreStatus(): Promise<void> {
|
||||
if (this.restoring) {
|
||||
try {
|
||||
const response = await fetchDiscoveryInformation();
|
||||
|
||||
if (
|
||||
!this.discoveryInformation ||
|
||||
this.discoveryInformation.uuid !== response.uuid
|
||||
) {
|
||||
// When the UUID changes, the restore is complete
|
||||
window.location.replace("/");
|
||||
const response = await fetch("/api/hassio/supervisor/info", {
|
||||
method: "GET",
|
||||
});
|
||||
if (response.status === 401) {
|
||||
// If we get a unauthorized response, the restore is done
|
||||
navigate("/", { replace: true });
|
||||
location.reload();
|
||||
}
|
||||
} catch (err) {
|
||||
// We fully expected issues with fetching info untill restore is complete.
|
||||
@@ -83,7 +76,6 @@ class OnboardingRestoreSnapshot extends ProvideHassLitMixin(LitElement) {
|
||||
showHassioSnapshotDialog(this, {
|
||||
slug,
|
||||
onboarding: true,
|
||||
localize: this.localize,
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -8,8 +8,7 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { UNIT_C } from "../../../common/const";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/map/ha-locations-editor";
|
||||
import type { MarkerLocation } from "../../../components/map/ha-locations-editor";
|
||||
import "../../../components/map/ha-location-editor";
|
||||
import { createTimezoneListEl } from "../../../components/timezone-datalist";
|
||||
import { ConfigUpdateValues, saveCoreConfig } from "../../../data/core";
|
||||
import type { PolymerChangedEvent } from "../../../polymer-types";
|
||||
@@ -21,13 +20,13 @@ class ConfigCoreForm extends LitElement {
|
||||
|
||||
@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 {
|
||||
const canEdit = ["storage", "default"].includes(
|
||||
@@ -53,16 +52,16 @@ class ConfigCoreForm extends LitElement {
|
||||
: ""}
|
||||
|
||||
<div class="row">
|
||||
<ha-locations-editor
|
||||
<ha-location-editor
|
||||
class="flex"
|
||||
.hass=${this.hass}
|
||||
.locations=${this._markerLocation(
|
||||
.location=${this._locationValue(
|
||||
this._location,
|
||||
this.hass.config.latitude,
|
||||
this.hass.config.longitude,
|
||||
this._location
|
||||
this.hass.config.longitude
|
||||
)}
|
||||
@location-updated=${this._locationChanged}
|
||||
></ha-locations-editor>
|
||||
@change=${this._locationChanged}
|
||||
></ha-location-editor>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
@@ -163,19 +162,8 @@ class ConfigCoreForm extends LitElement {
|
||||
input.inputElement.appendChild(createTimezoneListEl());
|
||||
}
|
||||
|
||||
private _markerLocation = memoizeOne(
|
||||
(
|
||||
lat: number,
|
||||
lng: number,
|
||||
location?: [number, number]
|
||||
): MarkerLocation[] => [
|
||||
{
|
||||
id: "location",
|
||||
latitude: location ? location[0] : lat,
|
||||
longitude: location ? location[1] : lng,
|
||||
location_editable: true,
|
||||
},
|
||||
]
|
||||
private _locationValue = memoizeOne(
|
||||
(location, lat, lng) => location || [Number(lat), Number(lng)]
|
||||
);
|
||||
|
||||
private get _elevationValue() {
|
||||
@@ -204,7 +192,7 @@ class ConfigCoreForm extends LitElement {
|
||||
}
|
||||
|
||||
private _locationChanged(ev) {
|
||||
this._location = ev.detail.location;
|
||||
this._location = ev.currentTarget.location;
|
||||
}
|
||||
|
||||
private _unitSystemChanged(
|
||||
@@ -216,10 +204,11 @@ class ConfigCoreForm extends LitElement {
|
||||
private async _save() {
|
||||
this._working = true;
|
||||
try {
|
||||
const location = this._location || [
|
||||
const location = this._locationValue(
|
||||
this._location,
|
||||
this.hass.config.latitude,
|
||||
this.hass.config.longitude,
|
||||
];
|
||||
this.hass.config.longitude
|
||||
);
|
||||
await saveCoreConfig(this.hass, {
|
||||
latitude: location[0],
|
||||
longitude: location[1],
|
||||
|
@@ -24,7 +24,6 @@ import {
|
||||
import "../../../../../layouts/hass-tabs-subpage";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import type { HomeAssistant, Route } from "../../../../../types";
|
||||
import { fileDownload } from "../../../../../util/file_download";
|
||||
import "../../../ha-config-section";
|
||||
import { showZWaveJSAddNodeDialog } from "./show-dialog-zwave_js-add-node";
|
||||
import { showZWaveJSRemoveNodeDialog } from "./show-dialog-zwave_js-remove-node";
|
||||
@@ -313,7 +312,12 @@ class ZWaveJSConfigDashboard extends LitElement {
|
||||
return;
|
||||
}
|
||||
|
||||
fileDownload(this, signedPath.path, `zwave_js_dump.jsonl`);
|
||||
const a = document.createElement("a");
|
||||
a.href = signedPath.path;
|
||||
a.download = `zwave_js_dump.jsonl`;
|
||||
this.shadowRoot!.appendChild(a);
|
||||
a.click();
|
||||
this.shadowRoot!.removeChild(a);
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
|
@@ -9,9 +9,13 @@ import { computeRTLDirection } from "../../../common/util/compute_rtl";
|
||||
import { createCloseHeading } from "../../../components/ha-dialog";
|
||||
import "../../../components/ha-formfield";
|
||||
import "../../../components/ha-switch";
|
||||
import "../../../components/map/ha-locations-editor";
|
||||
import type { MarkerLocation } from "../../../components/map/ha-locations-editor";
|
||||
import { getZoneEditorInitData, ZoneMutableParams } from "../../../data/zone";
|
||||
import "../../../components/map/ha-location-editor";
|
||||
import {
|
||||
defaultRadiusColor,
|
||||
getZoneEditorInitData,
|
||||
passiveRadiusColor,
|
||||
ZoneMutableParams,
|
||||
} from "../../../data/zone";
|
||||
import { haStyleDialog } from "../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { ZoneDetailDialogParams } from "./show-dialog-zone-detail";
|
||||
@@ -128,19 +132,17 @@ class DialogZoneDetail extends LitElement {
|
||||
)}"
|
||||
.invalid=${iconValid}
|
||||
></paper-input>
|
||||
<ha-locations-editor
|
||||
<ha-location-editor
|
||||
class="flex"
|
||||
.hass=${this.hass}
|
||||
.locations=${this._location(
|
||||
this._latitude,
|
||||
this._longitude,
|
||||
this._radius,
|
||||
this._passive,
|
||||
this._icon
|
||||
)}
|
||||
@location-updated=${this._locationChanged}
|
||||
@radius-updated=${this._radiusChanged}
|
||||
></ha-locations-editor>
|
||||
.location=${this._locationValue(this._latitude, this._longitude)}
|
||||
.radius=${this._radius}
|
||||
.radiusColor=${this._passive
|
||||
? passiveRadiusColor
|
||||
: defaultRadiusColor}
|
||||
.icon=${this._icon}
|
||||
@change=${this._locationChanged}
|
||||
></ha-location-editor>
|
||||
<div class="location">
|
||||
<paper-input
|
||||
.value=${this._latitude}
|
||||
@@ -220,40 +222,11 @@ class DialogZoneDetail extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
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 _locationValue = memoizeOne((lat, lng) => [Number(lat), Number(lng)]);
|
||||
|
||||
private _locationChanged(ev: CustomEvent) {
|
||||
[this._latitude, this._longitude] = ev.detail.location;
|
||||
}
|
||||
|
||||
private _radiusChanged(ev: CustomEvent) {
|
||||
this._radius = ev.detail.radius;
|
||||
private _locationChanged(ev) {
|
||||
[this._latitude, this._longitude] = ev.currentTarget.location;
|
||||
this._radius = ev.currentTarget.radius;
|
||||
}
|
||||
|
||||
private _passiveChanged(ev) {
|
||||
@@ -319,7 +292,7 @@ class DialogZoneDetail extends LitElement {
|
||||
.location > *:last-child {
|
||||
margin-left: 4px;
|
||||
}
|
||||
ha-locations-editor {
|
||||
ha-location-editor {
|
||||
margin-top: 16px;
|
||||
}
|
||||
a {
|
||||
|
@@ -31,8 +31,11 @@ import { saveCoreConfig } from "../../../data/core";
|
||||
import { subscribeEntityRegistry } from "../../../data/entity_registry";
|
||||
import {
|
||||
createZone,
|
||||
defaultRadiusColor,
|
||||
deleteZone,
|
||||
fetchZones,
|
||||
homeRadiusColor,
|
||||
passiveRadiusColor,
|
||||
updateZone,
|
||||
Zone,
|
||||
ZoneMutableParams,
|
||||
@@ -70,15 +73,6 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
|
||||
|
||||
private _getZones = memoizeOne(
|
||||
(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(
|
||||
(entityState) => ({
|
||||
id: entityState.entity_id,
|
||||
@@ -92,7 +86,7 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
|
||||
? homeRadiusColor
|
||||
: entityState.attributes.passive
|
||||
? passiveRadiusColor
|
||||
: zoneRadiusColor,
|
||||
: defaultRadiusColor,
|
||||
location_editable:
|
||||
entityState.entity_id === "zone.home" && this._canEditCore,
|
||||
radius_editable: false,
|
||||
@@ -100,7 +94,7 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
|
||||
);
|
||||
const storageLocations: MarkerLocation[] = storageItems.map((zone) => ({
|
||||
...zone,
|
||||
radius_color: zone.passive ? passiveRadiusColor : zoneRadiusColor,
|
||||
radius_color: zone.passive ? passiveRadiusColor : defaultRadiusColor,
|
||||
location_editable: true,
|
||||
radius_editable: true,
|
||||
}));
|
||||
@@ -280,7 +274,7 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
}
|
||||
|
||||
public willUpdate(changedProps: PropertyValues) {
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||
if (oldHass && this._stateItems) {
|
||||
@@ -416,9 +410,8 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
|
||||
if (this.narrow) {
|
||||
return;
|
||||
}
|
||||
this._activeEntry = created.id;
|
||||
await this.updateComplete;
|
||||
await this._map?.updateComplete;
|
||||
this._activeEntry = created.id;
|
||||
this._map?.fitMarker(created.id);
|
||||
}
|
||||
|
||||
@@ -434,9 +427,8 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
|
||||
if (this.narrow || !fitMap) {
|
||||
return;
|
||||
}
|
||||
this._activeEntry = entry.id;
|
||||
await this.updateComplete;
|
||||
await this._map?.updateComplete;
|
||||
this._activeEntry = entry.id;
|
||||
this._map?.fitMarker(entry.id);
|
||||
}
|
||||
|
||||
|
@@ -1,22 +1,22 @@
|
||||
import { mdiRefresh } from "@mdi/js";
|
||||
import "@material/mwc-icon-button";
|
||||
import "@polymer/app-layout/app-header/app-header";
|
||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||
import { css, html, LitElement, PropertyValues } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||
import { computeRTL } from "../../common/util/compute_rtl";
|
||||
import "../../components/entity/ha-entity-picker";
|
||||
import "../../components/ha-circular-progress";
|
||||
import "../../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 {
|
||||
clearLogbookCache,
|
||||
getLogbookData,
|
||||
LogbookEntry,
|
||||
} from "../../data/logbook";
|
||||
import { fetchPersons } from "../../data/person";
|
||||
import { loadTraceContexts, TraceContexts } from "../../data/trace";
|
||||
import { fetchUsers } from "../../data/user";
|
||||
import "../../layouts/ha-app-layout";
|
||||
@@ -44,7 +44,7 @@ export class HaPanelLogbook extends LitElement {
|
||||
|
||||
@state() private _ranges?: DateRangePickerRanges;
|
||||
|
||||
private _fetchUserPromise?: Promise<void>;
|
||||
private _fetchUserDone?: Promise<unknown>;
|
||||
|
||||
@state() private _userIdToName = {};
|
||||
|
||||
@@ -136,7 +136,7 @@ export class HaPanelLogbook extends LitElement {
|
||||
super.firstUpdated(changedProps);
|
||||
this.hass.loadBackendTranslation("title");
|
||||
|
||||
this._fetchUserPromise = this._fetchUserNames();
|
||||
this._fetchUserDone = this._fetchUserNames();
|
||||
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
@@ -198,19 +198,23 @@ export class HaPanelLogbook extends LitElement {
|
||||
private async _fetchUserNames() {
|
||||
const userIdToName = {};
|
||||
|
||||
// Start loading users
|
||||
const userProm = this.hass.user?.is_admin && fetchUsers(this.hass);
|
||||
// Start loading all the data
|
||||
const personProm = fetchPersons(this.hass);
|
||||
const userProm = this.hass.user!.is_admin && fetchUsers(this.hass);
|
||||
|
||||
// Process persons
|
||||
Object.values(this.hass.states).forEach((entity) => {
|
||||
if (
|
||||
entity.attributes.user_id &&
|
||||
computeStateDomain(entity) === "person"
|
||||
) {
|
||||
this._userIdToName[entity.attributes.user_id] =
|
||||
entity.attributes.friendly_name;
|
||||
const persons = await personProm;
|
||||
|
||||
for (const person of persons.storage) {
|
||||
if (person.user_id) {
|
||||
userIdToName[person.user_id] = person.name;
|
||||
}
|
||||
});
|
||||
}
|
||||
for (const person of persons.config) {
|
||||
if (person.user_id) {
|
||||
userIdToName[person.user_id] = person.name;
|
||||
}
|
||||
}
|
||||
|
||||
// Process users
|
||||
if (userProm) {
|
||||
@@ -258,7 +262,7 @@ export class HaPanelLogbook extends LitElement {
|
||||
this._entityId
|
||||
),
|
||||
isComponentLoaded(this.hass, "trace") ? loadTraceContexts(this.hass) : {},
|
||||
this._fetchUserPromise,
|
||||
this._fetchUserDone,
|
||||
]);
|
||||
|
||||
this._entries = entries;
|
||||
|
@@ -9,12 +9,11 @@ import {
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
||||
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
|
||||
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
||||
import { throttle } from "../../../common/util/throttle";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-circular-progress";
|
||||
import { fetchUsers } from "../../../data/user";
|
||||
import { getLogbookData, LogbookEntry } from "../../../data/logbook";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import "../../logbook/ha-logbook";
|
||||
@@ -52,20 +51,18 @@ 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 _logbookEntries?: LogbookEntry[];
|
||||
|
||||
@state() private _persons = {};
|
||||
|
||||
@state() private _configEntities?: EntityConfig[];
|
||||
|
||||
@state() private _userIdToName = {};
|
||||
|
||||
private _lastLogbookDate?: Date;
|
||||
|
||||
private _fetchUserPromise?: Promise<void>;
|
||||
|
||||
private _throttleGetLogbookEntries = throttle(() => {
|
||||
this._getLogBookData();
|
||||
}, 10000);
|
||||
@@ -117,7 +114,7 @@ export class HuiLogbookCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
|
||||
protected firstUpdated(): void {
|
||||
this._fetchUserPromise = this._fetchUserNames();
|
||||
this._fetchPersonNames();
|
||||
}
|
||||
|
||||
protected updated(changedProperties: PropertyValues) {
|
||||
@@ -202,7 +199,7 @@ export class HuiLogbookCard extends LitElement implements LovelaceCard {
|
||||
virtualize
|
||||
.hass=${this.hass}
|
||||
.entries=${this._logbookEntries}
|
||||
.userIdToName=${this._userIdToName}
|
||||
.userIdToName=${this._persons}
|
||||
></ha-logbook>
|
||||
`
|
||||
: html`
|
||||
@@ -232,16 +229,13 @@ export class HuiLogbookCard extends LitElement implements LovelaceCard {
|
||||
const lastDate = this._lastLogbookDate || hoursToShowDate;
|
||||
const now = new Date();
|
||||
|
||||
const [newEntries] = await Promise.all([
|
||||
getLogbookData(
|
||||
this.hass,
|
||||
lastDate.toISOString(),
|
||||
now.toISOString(),
|
||||
this._configEntities!.map((entity) => entity.entity).toString(),
|
||||
true
|
||||
),
|
||||
this._fetchUserPromise,
|
||||
]);
|
||||
const newEntries = await getLogbookData(
|
||||
this.hass,
|
||||
lastDate.toISOString(),
|
||||
now.toISOString(),
|
||||
this._configEntities!.map((entity) => entity.entity).toString(),
|
||||
true
|
||||
);
|
||||
|
||||
const logbookEntries = this._logbookEntries
|
||||
? [...newEntries, ...this._logbookEntries]
|
||||
@@ -254,34 +248,20 @@ export class HuiLogbookCard extends LitElement implements LovelaceCard {
|
||||
this._lastLogbookDate = now;
|
||||
}
|
||||
|
||||
private async _fetchUserNames() {
|
||||
const userIdToName = {};
|
||||
private _fetchPersonNames() {
|
||||
if (!this.hass) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Start loading users
|
||||
const userProm = this.hass.user?.is_admin && fetchUsers(this.hass);
|
||||
|
||||
// Process persons
|
||||
Object.values(this.hass!.states).forEach((entity) => {
|
||||
if (
|
||||
entity.attributes.user_id &&
|
||||
computeStateDomain(entity) === "person"
|
||||
) {
|
||||
this._userIdToName[entity.attributes.user_id] =
|
||||
this._persons[entity.attributes.user_id] =
|
||||
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 {
|
||||
|
@@ -1,5 +1,14 @@
|
||||
import { HassEntities, HassEntity } from "home-assistant-js-websocket";
|
||||
import { LatLngTuple } from "leaflet";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import {
|
||||
Circle,
|
||||
CircleMarker,
|
||||
LatLngTuple,
|
||||
Layer,
|
||||
Map,
|
||||
Marker,
|
||||
Polyline,
|
||||
TileLayer,
|
||||
} from "leaflet";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
@@ -8,106 +17,32 @@ import {
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { customElement, property } 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 { 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 "../../../components/ha-card";
|
||||
import "../../../components/ha-icon-button";
|
||||
import { fetchRecent } from "../../../data/history";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import "../../../components/map/ha-entity-marker";
|
||||
import "../../map/ha-entity-marker";
|
||||
import { findEntities } from "../common/find-entities";
|
||||
import { installResizeObserver } from "../common/install-resize-observer";
|
||||
import { processConfigEntities } from "../common/process-config-entities";
|
||||
import { EntityConfig } from "../entity-rows/types";
|
||||
import { LovelaceCard } 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")
|
||||
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() {
|
||||
await import("../editor/config-elements/hui-map-card-editor");
|
||||
return document.createElement("hui-map-card-editor");
|
||||
@@ -131,6 +66,129 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
||||
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 {
|
||||
if (!this._config) {
|
||||
return html``;
|
||||
@@ -138,29 +196,22 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
||||
return html`
|
||||
<ha-card id="card" .header=${this._config.title}>
|
||||
<div id="root">
|
||||
<ha-map
|
||||
.hass=${this.hass}
|
||||
.entities=${this._getEntities(
|
||||
this.hass.states,
|
||||
this._config,
|
||||
this._configEntities
|
||||
)}
|
||||
.paths=${this._getHistoryPaths(this._config, this._history)}
|
||||
.darkMode=${this._config.dark_mode}
|
||||
></ha-map>
|
||||
<mwc-icon-button
|
||||
<div
|
||||
id="map"
|
||||
class=${classMap({ dark: this._config.dark_mode === true })}
|
||||
></div>
|
||||
<ha-icon-button
|
||||
@click=${this._fitMap}
|
||||
tabindex="0"
|
||||
icon="hass:image-filter-center-focus"
|
||||
title="Reset focus"
|
||||
>
|
||||
<ha-svg-icon .path=${mdiImageFilterCenterFocus}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues) {
|
||||
protected shouldUpdate(changedProps) {
|
||||
if (!changedProps.has("hass") || changedProps.size > 1) {
|
||||
return true;
|
||||
}
|
||||
@@ -177,7 +228,7 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
||||
|
||||
// Check if any state has changed
|
||||
for (const entity of this._configEntities) {
|
||||
if (oldHass.states[entity] !== this.hass!.states[entity]) {
|
||||
if (oldHass.states[entity.entity] !== this.hass!.states[entity.entity]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -187,12 +238,17 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues): void {
|
||||
super.firstUpdated(changedProps);
|
||||
if (this.isConnected) {
|
||||
this.loadMap();
|
||||
}
|
||||
const root = this.shadowRoot!.getElementById("root");
|
||||
|
||||
if (!this._config || this.isPanel || !root) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._attachObserver();
|
||||
|
||||
if (!this._config.aspect_ratio) {
|
||||
root.style.paddingBottom = "100%";
|
||||
return;
|
||||
@@ -207,86 +263,172 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
|
||||
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) {
|
||||
const minute = 60000;
|
||||
if (changedProps.has("_config")) {
|
||||
this._getHistory();
|
||||
} else if (Date.now() - this._date!.getTime() >= MINUTE) {
|
||||
} else if (Date.now() - this._date!.getTime() >= minute) {
|
||||
this._getHistory();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _fitMap() {
|
||||
this._map?.fitMap();
|
||||
private get _mapEl(): HTMLDivElement {
|
||||
return this.shadowRoot!.getElementById("map") as HTMLDivElement;
|
||||
}
|
||||
|
||||
private _getColor(entityId: string): string {
|
||||
let color = this._colorDict[entityId];
|
||||
if (color) {
|
||||
return color;
|
||||
private async loadMap(): Promise<void> {
|
||||
[this._leafletMap, this.Leaflet, this._tileLayer] = await setupLeafletMap(
|
||||
this._mapEl,
|
||||
this._config!.dark_mode ?? this.hass.themes.darkMode
|
||||
);
|
||||
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;
|
||||
}
|
||||
|
||||
private _getEntities = memoizeOne(
|
||||
(
|
||||
states: HassEntities,
|
||||
config: MapCardConfig,
|
||||
configEntities?: string[]
|
||||
) => {
|
||||
if (!states || !config) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let entities = configEntities || [];
|
||||
|
||||
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 _drawEntities(): void {
|
||||
const hass = this.hass;
|
||||
const map = this._leafletMap;
|
||||
const config = this._config;
|
||||
const Leaflet = this.Leaflet;
|
||||
if (!hass || !map || !config || !Leaflet) {
|
||||
return;
|
||||
}
|
||||
);
|
||||
|
||||
private _getHistoryPaths = memoizeOne(
|
||||
(
|
||||
config: MapCardConfig,
|
||||
history?: HassEntity[][]
|
||||
): HaMapPaths[] | undefined => {
|
||||
if (!config.hours_to_show || !history) {
|
||||
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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const paths: HaMapPaths[] = [];
|
||||
|
||||
for (const entityStates of history) {
|
||||
// DRAW history
|
||||
if (this._config!.hours_to_show && this._history) {
|
||||
for (const entityStates of this._history) {
|
||||
if (entityStates?.length <= 1) {
|
||||
continue;
|
||||
}
|
||||
const entityId = entityStates[0].entity_id;
|
||||
|
||||
// filter location data from states and remove all invalid locations
|
||||
const points = entityStates.reduce(
|
||||
(accumulator: LatLngTuple[], entityState) => {
|
||||
const latitude = entityState.attributes.latitude;
|
||||
const longitude = entityState.attributes.longitude;
|
||||
const path = entityStates.reduce(
|
||||
(accumulator: LatLngTuple[], state) => {
|
||||
const latitude = state.attributes.latitude;
|
||||
const longitude = state.attributes.longitude;
|
||||
if (latitude && longitude) {
|
||||
accumulator.push([latitude, longitude] as LatLngTuple);
|
||||
}
|
||||
@@ -295,15 +437,162 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
||||
[]
|
||||
) as LatLngTuple[];
|
||||
|
||||
paths.push({
|
||||
points,
|
||||
color: this._getColor(entityStates[0].entity_id),
|
||||
gradualOpacity: 0.8,
|
||||
});
|
||||
// DRAW HISTORY
|
||||
for (
|
||||
let markerIndex = 0;
|
||||
markerIndex < path.length - 1;
|
||||
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> {
|
||||
this._date = new Date();
|
||||
@@ -312,7 +601,9 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
||||
return;
|
||||
}
|
||||
|
||||
const entityIds = this._configEntities!.join(",");
|
||||
const entityIds = this._configEntities!.map((entity) => entity.entity).join(
|
||||
","
|
||||
);
|
||||
const endTime = new Date();
|
||||
const startTime = new Date();
|
||||
startTime.setHours(endTime.getHours() - this._config!.hours_to_show!);
|
||||
@@ -333,6 +624,7 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
||||
if (stateHistory.length < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._history = stateHistory;
|
||||
}
|
||||
|
||||
@@ -344,10 +636,13 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
||||
this._history = undefined;
|
||||
} else {
|
||||
// remove unused entities
|
||||
const configEntityIds = this._configEntities?.map(
|
||||
(configEntity) => configEntity.entity
|
||||
);
|
||||
this._history = this._history!.reduce(
|
||||
(accumulator: HassEntity[][], entityStates) => {
|
||||
const entityId = entityStates[0].entity_id;
|
||||
if (this._configEntities?.includes(entityId)) {
|
||||
if (configEntityIds?.includes(entityId)) {
|
||||
accumulator.push(entityStates);
|
||||
}
|
||||
return accumulator;
|
||||
@@ -365,7 +660,7 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
ha-map {
|
||||
#map {
|
||||
z-index: 0;
|
||||
border: none;
|
||||
position: absolute;
|
||||
@@ -376,7 +671,7 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
||||
background: inherit;
|
||||
}
|
||||
|
||||
mwc-icon-button {
|
||||
ha-icon-button {
|
||||
position: absolute;
|
||||
top: 75px;
|
||||
left: 3px;
|
||||
@@ -390,6 +685,14 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
||||
:host([ispanel]) #root {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.dark {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.light {
|
||||
color: #000000;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@@ -33,18 +33,18 @@ export class HuiActionEditor extends LitElement {
|
||||
@property() protected hass?: HomeAssistant;
|
||||
|
||||
get _navigation_path(): string {
|
||||
const config = this.config as NavigateActionConfig | undefined;
|
||||
return config?.navigation_path || "";
|
||||
const config = this.config as NavigateActionConfig;
|
||||
return config.navigation_path || "";
|
||||
}
|
||||
|
||||
get _url_path(): string {
|
||||
const config = this.config as UrlActionConfig | undefined;
|
||||
return config?.url_path || "";
|
||||
const config = this.config as UrlActionConfig;
|
||||
return config.url_path || "";
|
||||
}
|
||||
|
||||
get _service(): string {
|
||||
const config = this.config as CallServiceActionConfig;
|
||||
return config?.service || "";
|
||||
return config.service || "";
|
||||
}
|
||||
|
||||
private _serviceAction = memoizeOne(
|
||||
|
@@ -29,7 +29,6 @@ export class HuiInputListEditor extends LitElement {
|
||||
.index=${index}
|
||||
@value-changed=${this._valueChanged}
|
||||
@blur=${this._consolidateEntries}
|
||||
@keydown=${this._handleKeyDown}
|
||||
><ha-icon-button
|
||||
slot="suffix"
|
||||
class="clear-button"
|
||||
@@ -71,13 +70,6 @@ export class HuiInputListEditor extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _handleKeyDown(ev: KeyboardEvent) {
|
||||
if (ev.key === "Enter") {
|
||||
ev.stopPropagation();
|
||||
this._consolidateEntries(ev);
|
||||
}
|
||||
}
|
||||
|
||||
private _consolidateEntries(ev: Event): void {
|
||||
const target = ev.target! as EditorTarget;
|
||||
if (target.value === "") {
|
||||
|
@@ -34,7 +34,7 @@ const cardConfigStruct = object({
|
||||
dark_mode: optional(boolean()),
|
||||
entities: array(entitiesConfigStruct),
|
||||
hours_to_show: optional(number()),
|
||||
geo_location_sources: optional(array(string())),
|
||||
geo_location_sources: optional(array()),
|
||||
});
|
||||
|
||||
@customElement("hui-map-card-editor")
|
||||
|
88
src/panels/map/ha-entity-marker.js
Normal file
88
src/panels/map/ha-entity-marker.js
Normal file
@@ -0,0 +1,88 @@
|
||||
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);
|
263
src/panels/map/ha-panel-map.js
Normal file
263
src/panels/map/ha-panel-map.js
Normal file
@@ -0,0 +1,263 @@
|
||||
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);
|
@@ -1,103 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
@@ -36,9 +36,7 @@ export class HaPickThemeRow extends LitElement {
|
||||
const hasThemes =
|
||||
this.hass.themes.themes && Object.keys(this.hass.themes.themes).length;
|
||||
const curTheme =
|
||||
this.hass.selectedTheme?.theme || this.hass.themes.darkMode
|
||||
? this.hass.themes.default_dark_theme || this.hass.themes.default_theme
|
||||
: this.hass.themes.default_theme;
|
||||
this.hass.selectedTheme?.theme || this.hass.themes.default_theme;
|
||||
|
||||
const themeSettings = this.hass.selectedTheme;
|
||||
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
||||
import "@polymer/iron-label/iron-label";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
@@ -1,10 +1,10 @@
|
||||
// For localize
|
||||
import "core-js";
|
||||
import "regenerator-runtime/runtime";
|
||||
import "lit/polyfill-support";
|
||||
import "@formatjs/intl-getcanonicallocales/polyfill";
|
||||
import "lit/polyfill-support";
|
||||
import "core-js";
|
||||
// To use comlink under ES5
|
||||
import "proxy-polyfill";
|
||||
import "regenerator-runtime/runtime";
|
||||
import "unfetch/polyfill";
|
||||
|
||||
// Source: https://github.com/jserz/js_piece/blob/master/DOM/ParentNode/append()/append().md
|
||||
@@ -32,16 +32,3 @@ import "unfetch/polyfill";
|
||||
});
|
||||
});
|
||||
})([Element.prototype, Document.prototype, DocumentFragment.prototype]);
|
||||
|
||||
// Source: https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttributeNames
|
||||
if (Element.prototype.getAttributeNames === undefined) {
|
||||
Element.prototype.getAttributeNames = function () {
|
||||
const attributes = this.attributes;
|
||||
const length = attributes.length;
|
||||
const result = new Array(length);
|
||||
for (let i = 0; i < length; i++) {
|
||||
result[i] = attributes[i].name;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
@@ -29,10 +29,10 @@ documentContainer.innerHTML = `<custom-style>
|
||||
--disabled-text-color: #bdbdbd;
|
||||
|
||||
/* main interface colors */
|
||||
--primary-color: ${DEFAULT_PRIMARY_COLOR};
|
||||
--primary-color: #03a9f4;
|
||||
--dark-primary-color: #0288d1;
|
||||
--light-primary-color: #b3e5fC;
|
||||
--accent-color: ${DEFAULT_ACCENT_COLOR};
|
||||
--accent-color: #ff9800;
|
||||
--divider-color: rgba(0, 0, 0, .12);
|
||||
|
||||
--scrollbar-thumb-color: rgb(194, 194, 194);
|
||||
|
@@ -2,7 +2,6 @@
|
||||
const isSafari14 = /^((?!chrome|android).)*version\/14\.0\s.*safari/i.test(
|
||||
navigator.userAgent
|
||||
);
|
||||
|
||||
if (isSafari14) {
|
||||
const origAttachShadow = window.Element.prototype.attachShadow;
|
||||
window.Element.prototype.attachShadow = function (init) {
|
||||
|
@@ -1155,7 +1155,7 @@
|
||||
"section": {
|
||||
"validation": {
|
||||
"heading": "Configuration validation",
|
||||
"introduction": "Validate your configuration if you recently made some changes to your configuration and want to make sure that it is all valid.",
|
||||
"introduction": "Validate your configuration if you recently made some changes to your configuration and want to make sure that it is all valid",
|
||||
"check_config": "Check configuration",
|
||||
"valid": "Configuration valid!",
|
||||
"invalid": "Configuration invalid"
|
||||
@@ -3569,16 +3569,7 @@
|
||||
"description": "Alternatively you can restore from a previous snapshot.",
|
||||
"in_progress": "Restore in progress",
|
||||
"show_log": "Show 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%]"
|
||||
"hide_log": "Hide full log"
|
||||
}
|
||||
},
|
||||
"custom": {
|
||||
@@ -3822,8 +3813,6 @@
|
||||
"faq_link": "[%key:ui::panel::my::faq_link%]",
|
||||
"error": "[%key:ui::panel::my::error%]",
|
||||
"error_addon_not_found": "Add-on not found",
|
||||
"error_addon_not_started": "The requested add-on are not running. Please start it first",
|
||||
"error_addon_not_installed": "The requested add-on is not installed. Please install it first",
|
||||
"error_addon_no_ingress": "The requested add-on does not support ingress"
|
||||
},
|
||||
"system": {
|
||||
@@ -3933,11 +3922,9 @@
|
||||
"addons": "Add-ons",
|
||||
"folders": "Folders",
|
||||
"password": "Snapshot password",
|
||||
"confirm_password": "Confirm Snapshot password",
|
||||
"password_protection": "Password protection",
|
||||
"password_protected": "password protected",
|
||||
"enter_password": "Please enter a password.",
|
||||
"passwords_not_matching": "The passwords does not match",
|
||||
"folder": {
|
||||
"homeassistant": "Home Assistant configuration",
|
||||
"ssl": "SSL",
|
||||
@@ -3958,6 +3945,7 @@
|
||||
"static": "Static",
|
||||
"dhcp": "DHCP",
|
||||
"disabled": "Disabled",
|
||||
"link-local": "link-local",
|
||||
"ip_netmask": "IP address/Netmask",
|
||||
"gateway": "Gateway address",
|
||||
"dns_servers": "DNS Servers",
|
||||
@@ -3991,14 +3979,6 @@
|
||||
"create_snapshot": "Create a snapshot of {name} before updating",
|
||||
"updating": "Updating {name} to version {version}",
|
||||
"snapshotting": "Creating snapshot of {name}"
|
||||
},
|
||||
"hardware": {
|
||||
"title": "Hardware",
|
||||
"search": "Search hardware",
|
||||
"subsystem": "Subsystem",
|
||||
"id": "ID",
|
||||
"attributes": "Attributes",
|
||||
"device_path": "Device path"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,14 +0,0 @@
|
||||
export const fileDownload = (
|
||||
element: HTMLElement,
|
||||
href: string,
|
||||
filename: string
|
||||
): void => {
|
||||
const a = document.createElement("a");
|
||||
a.target = "_blank";
|
||||
a.href = href;
|
||||
a.download = filename;
|
||||
|
||||
element.shadowRoot!.appendChild(a);
|
||||
a.dispatchEvent(new MouseEvent("click"));
|
||||
element.shadowRoot!.removeChild(a);
|
||||
};
|
@@ -130,12 +130,12 @@
|
||||
"action_error": {
|
||||
"get_changelog": "Неуспешно получаване на списък с промени на добавката",
|
||||
"go_to_config": "Грешка при стартиране на добавката - неуспешна проверка на конфигурацията!",
|
||||
"install": "Инсталирането на добавката не е успешно",
|
||||
"restart": "Рестартирането на добавката не е успешно",
|
||||
"start": "Стартирането на добавката не е успешно",
|
||||
"install": "Инсталирането на добавката не бе успешно",
|
||||
"restart": "Рестартирането на добавката не бе успешно",
|
||||
"start": "Стартирането на добавката не бе успешно",
|
||||
"start_invalid_config": "Към конфигурацията",
|
||||
"stop": "Спирането на добавката не е успешно",
|
||||
"uninstall": "Деинсталирането на добавката не е успешно",
|
||||
"stop": "Спирането на добавката не бе успешно",
|
||||
"uninstall": "Деинсталирането на добавката не бе успешно",
|
||||
"validate_config": "Грешка при проверка на конфигурацията на добавката"
|
||||
},
|
||||
"capability": {
|
||||
@@ -218,8 +218,8 @@
|
||||
"documentation": {
|
||||
"get_documentation": "Неуспешно получаване на документация за добавката, {error}"
|
||||
},
|
||||
"failed_to_reset": "Нулирането на конфигурацията на добавката не е успешно, {error}",
|
||||
"failed_to_save": "Запазването на конфигурацията на добавката не е успешно, {error}",
|
||||
"failed_to_reset": "Нулирането на конфигурацията на добавката не бе успешно, {error}",
|
||||
"failed_to_save": "Запазването на конфигурацията на добавката не бе успешно, {error}",
|
||||
"logs": {
|
||||
"get_logs": "Неуспешно получаване журнали на добавка, {грешка}"
|
||||
},
|
||||
@@ -241,10 +241,10 @@
|
||||
"description": "Описание",
|
||||
"error": {
|
||||
"unknown": "Неизвестна грешка",
|
||||
"update_failed": "Актуализацията не е успешна"
|
||||
"update_failed": "Актуализацията не бе успешна"
|
||||
},
|
||||
"failed_to_restart_name": "Рестартирането на {name} не е успешно",
|
||||
"failed_to_update_name": "Актуализирането на {name} не е успешно",
|
||||
"failed_to_restart_name": "Рестартирането на {name} не бе успешно",
|
||||
"failed_to_update_name": "Актуализирането на {name} не бе успешно",
|
||||
"learn_more": "Научете повече",
|
||||
"new_version_available": "Налична е нова версия",
|
||||
"newest_version": "Последна версия",
|
||||
@@ -260,7 +260,6 @@
|
||||
"save": "Запис",
|
||||
"show_more": "Показване на повече информация за това",
|
||||
"update": "Актуализиране",
|
||||
"update_available": "{count}{count, plural,\n one {обновление изчаква}\n other {{count} обновления изчакват}\n}",
|
||||
"version": "Версия",
|
||||
"yes": "Да"
|
||||
},
|
||||
@@ -286,20 +285,12 @@
|
||||
"no_addons": "Все още нямате инсталирани добавки. Насочете се към хранилището за добавки, за да започнете!"
|
||||
},
|
||||
"dialog": {
|
||||
"hardware": {
|
||||
"attributes": "Атрибути",
|
||||
"device_path": "Път до устройството",
|
||||
"id": "ID",
|
||||
"search": "Търсене на хардуер",
|
||||
"subsystem": "Подсистема",
|
||||
"title": "Хардуер"
|
||||
},
|
||||
"network": {
|
||||
"connected_to": "Свързан с {ssid}",
|
||||
"dhcp": "DHCP",
|
||||
"disabled": "Деактивиран",
|
||||
"dns_servers": "DNS Сървъри",
|
||||
"failed_to_change": "Промяната на мрежовите настройки не е успешна",
|
||||
"failed_to_change": "Промяната на мрежовите настройки не бе успешна",
|
||||
"gateway": "Адрес на шлюза",
|
||||
"ip_netmask": "IP адрес/Мрежова маска",
|
||||
"open": "Отворена",
|
||||
@@ -314,8 +305,8 @@
|
||||
"registries": {
|
||||
"add_new_registry": "Добавяне на нов регистър",
|
||||
"add_registry": "Добавяне на регистър",
|
||||
"failed_to_add": "Добавянето на регистър не е успешно",
|
||||
"failed_to_remove": "Премахването на регистър не е успешно",
|
||||
"failed_to_add": "Добавянето на регистър не бе успешно",
|
||||
"failed_to_remove": "Премахването на регистър не бе успешно",
|
||||
"no_registries": "Няма конфигурирани регистри",
|
||||
"password": "Парола",
|
||||
"registry": "Регистър",
|
||||
@@ -343,8 +334,6 @@
|
||||
"my": {
|
||||
"error": "Възникна неизвестна грешка",
|
||||
"error_addon_not_found": "Не е намерена добавка",
|
||||
"error_addon_not_installed": "Исканата добавка не е инсталирана. Моля, първо я инсталирайте",
|
||||
"error_addon_not_started": "Заявените добавки не е стартирана. Моля, първо я стартирайте",
|
||||
"faq_link": "My Home Assistant ЧЗВ",
|
||||
"not_supported": "Тази препратка не се поддържа от вашата Home Assistant инсталация. Последвайте {link} за поддържани препратки както и версиите при тяхното пускане."
|
||||
},
|
||||
@@ -357,19 +346,13 @@
|
||||
"snapshot": {
|
||||
"addons": "Добавки",
|
||||
"available_snapshots": "Налични снапшоти",
|
||||
"confirm_password": "Потвърдете паролата за снапшота",
|
||||
"could_not_create": "Не можа да се създаде снапшот",
|
||||
"create": "Създаване",
|
||||
"create_blocked_not_running": "Създаването на снапшот в момента не е възможно, тъй като системата е в състояние {state}.",
|
||||
"create_snapshot": "Създаване на снапшот",
|
||||
"created": "Създаден",
|
||||
"delete_selected": "Изтриване на избраните снапшоти",
|
||||
"delete_snapshot_confirm": "Изтрий",
|
||||
"delete_snapshot_text": "Искате ли да изтриете {number} {number, plural,\n one {резервно копие}\n other {резервни копия}\n}?",
|
||||
"delete_snapshot_title": "Изтриване на снапшота",
|
||||
"description": "Снапшотите ви позволяват лесно да архивирате и възстановявате всички данни от вашия екземпляр на Home Assistant.",
|
||||
"enter_password": "Моля, въведете парола.",
|
||||
"failed_to_delete": "Изтриването не е успешно",
|
||||
"folder": {
|
||||
"addons/local": "Локални добавки",
|
||||
"homeassistant": "Конфигурация на Home Assistant",
|
||||
@@ -385,10 +368,7 @@
|
||||
"password": "Парола",
|
||||
"password_protected": "защитен с парола",
|
||||
"password_protection": "Защита с парола",
|
||||
"passwords_not_matching": "Паролите не съвпадат",
|
||||
"security": "Сигурност",
|
||||
"select_type": "Изберете какво да възстановите",
|
||||
"selected": "{number} избрани",
|
||||
"type": "Тип",
|
||||
"upload_snapshot": "Качване на снапшот"
|
||||
},
|
||||
@@ -455,7 +435,7 @@
|
||||
"unhealthy_reason": {
|
||||
"docker": "Средата на Docker не работи правилно",
|
||||
"privileged": "Supervisor не е привилегирован",
|
||||
"setup": "Настройката на Supervisor не е успешна",
|
||||
"setup": "Настройката на Supervisor не бе успешна",
|
||||
"supervisor": "Supervisor не успя да се актуализира",
|
||||
"untrusted": "Открито e ненадежден съдържание"
|
||||
},
|
||||
@@ -514,7 +494,7 @@
|
||||
"heating": "{name} отопление",
|
||||
"high": "високо",
|
||||
"low": "ниско",
|
||||
"on_off": "Вкл. / Изкл.",
|
||||
"on_off": "Вкл. / Изкл",
|
||||
"operation": "Режим",
|
||||
"preset_mode": "Предварително зададени настройки",
|
||||
"swing_mode": "Режим на люлеене",
|
||||
@@ -894,9 +874,6 @@
|
||||
"config_entry_system_options": {
|
||||
"enable_new_entities_description": "Ако е изключено, новооткритите обекти за {integration} няма да бъдат автоматично добавяни в Home Assistant",
|
||||
"enable_new_entities_label": "Активирай новодобавените обекти.",
|
||||
"enable_polling_description": "Дали Home Assistant трябва да обновява автоматично обектите от {integration}.",
|
||||
"enable_polling_label": "Включи автоматично опресняване",
|
||||
"restart_home_assistant": "Трябва да рестартирате Home Assistant, за да влязат в сила промените.",
|
||||
"title": "Системни опции за {integration}",
|
||||
"update": "Актуализация"
|
||||
},
|
||||
@@ -1144,7 +1121,6 @@
|
||||
"cluster_header": "Клъстер",
|
||||
"configuration_complete": "Преконфигурирането на устройството завърши.",
|
||||
"configuration_failed": "Преконфигурирането на устройството не бе успешно. Допълнителна информация може да бъде налична в дневниците.",
|
||||
"configuring_alt": "Конфигуриране",
|
||||
"heading": "Преконфигуриране на устройство",
|
||||
"in_progress": "Устройството се преконфигурира. Това може да отнеме известно време.",
|
||||
"min_max_change": "мин/макс/промяна",
|
||||
@@ -1661,8 +1637,7 @@
|
||||
"link_learn_how_it_works": "Научете как работи",
|
||||
"not_connected": "Не е свързан",
|
||||
"remote_enabled": {
|
||||
"caption": "Автоматично свързване",
|
||||
"description": "Активирайте тази опция, за да сте сигурни, че вашият Home Assistant е винаги достъпен от разстояние."
|
||||
"caption": "Автоматично свързване"
|
||||
},
|
||||
"title": "Дистанционен контрол"
|
||||
},
|
||||
@@ -1941,8 +1916,7 @@
|
||||
"scripts": "Скриптове",
|
||||
"unknown_error": "Неизвестна грешка",
|
||||
"unnamed_device": "Устройство без име",
|
||||
"update": "Актуализация",
|
||||
"update_device_error": "Актуализирането на устройството не е успешно"
|
||||
"update": "Актуализация"
|
||||
},
|
||||
"entities": {
|
||||
"caption": "Обекти",
|
||||
@@ -2054,7 +2028,6 @@
|
||||
"depends_on_cloud": "Зависи от облака",
|
||||
"device_unavailable": "Устройството е недостъпно",
|
||||
"devices": "{count} {count, plural,\n one {устройство}\n other {устройства}\n}",
|
||||
"disable_error": "Активирането или деактивирането на интеграцията не бе успешно",
|
||||
"disable_restart_confirm": "Рестартирайте Home Assistant за да завършите деактивирането на тази интеграция",
|
||||
"disable": {
|
||||
"disable_confirm": "Наистина ли искате да забраните този конфигурационен запис? Устройствата и обекти му ще бъдат деактивирани.",
|
||||
@@ -2066,7 +2039,6 @@
|
||||
},
|
||||
"disabled_cause": "Деактивирано от {cause}"
|
||||
},
|
||||
"disabled_polling": "Автоматичното обновяване за данни е забранено",
|
||||
"documentation": "Документация",
|
||||
"enable_restart_confirm": "Рестартирайте Home Assistant за да завършите активирането на тази интеграция",
|
||||
"entities": "{count} {count, plural,\n one {обект}\n other {обекта}\n}",
|
||||
@@ -2121,7 +2093,7 @@
|
||||
"confirm_delete_ignore_title": "Да се спре ли игнорирането на {name}?",
|
||||
"confirm_ignore": "Наистина ли не искате да настроите тази интеграция? Можете да отмените това, като кликнете върху „Показване на игнорирани интеграции“ в менюто горе вдясно.",
|
||||
"hide_ignored": "Скриване на игнорираните интеграции",
|
||||
"ignore": "Игнориране",
|
||||
"ignore": "Игнорирайте",
|
||||
"ignored": "Игнорирана",
|
||||
"show_ignored": "Показване на игнорираните интеграции",
|
||||
"stop_ignore": "Спрете да игнорирате"
|
||||
@@ -2268,8 +2240,7 @@
|
||||
"product_manual": "Ръководство за продукта"
|
||||
},
|
||||
"node_query_stages": {
|
||||
"associations": "Обновяване на свързаните групи и членства",
|
||||
"complete": "Разпита приключи"
|
||||
"associations": "Обновяване на свързаните групи и членства"
|
||||
},
|
||||
"node": {
|
||||
"button": "Детайли за възела",
|
||||
@@ -2285,7 +2256,6 @@
|
||||
},
|
||||
"refresh_node": {
|
||||
"button": "Обнови възела",
|
||||
"description": "Това ще накара OpenZWave да разпита дадено устройство и да актуализира командните му класове, възможностите и стойностите му.",
|
||||
"node_status": "Състояние на възела",
|
||||
"refreshing_description": "Опресняване на информацията за възела...",
|
||||
"step": "Стъпка"
|
||||
@@ -2355,8 +2325,6 @@
|
||||
"add_scene": "Добавяне на сцена",
|
||||
"delete_confirm": "Сигурни ли сте, че искате да изтриете тази сцена?",
|
||||
"delete_scene": "Изтриване на сцената",
|
||||
"duplicate": "Дублиране",
|
||||
"duplicate_scene": "Дублиране на сцената",
|
||||
"edit_scene": "Редактиране на сцената",
|
||||
"header": "Редактор на сцени",
|
||||
"headers": {
|
||||
@@ -2560,10 +2528,8 @@
|
||||
"CONFIGURED_status_text": "Инициализация",
|
||||
"INITIALIZED": "Инициализацията завърши",
|
||||
"INITIALIZED_status_text": "Устройството е готово за употреба",
|
||||
"INTERVIEW_COMPLETE": "Разпита приключи",
|
||||
"INTERVIEW_COMPLETE_status_text": "Конфигуриране",
|
||||
"PAIRED": "Намерено устройство",
|
||||
"PAIRED_status_text": "Започване на разпит"
|
||||
"PAIRED": "Намерено устройство"
|
||||
},
|
||||
"groups": {
|
||||
"add_group": "Добавяне на група",
|
||||
@@ -2619,8 +2585,6 @@
|
||||
"zwave_js": {
|
||||
"add_node": {
|
||||
"inclusion_failed": "Възелът не може да бъде добавен. Моля, проверете журналите за повече информация.",
|
||||
"interview_failed": "Разпита на устройството не бе успешен. Допълнителна информация може да е налична в логовете.",
|
||||
"interview_started": "Устройството се разпитва. Това може да отнеме известно време.",
|
||||
"title": "Добавяне на Z-Wave възел"
|
||||
},
|
||||
"button": "Конфигуриране",
|
||||
@@ -2664,16 +2628,6 @@
|
||||
"node_status": {
|
||||
"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": {
|
||||
"exclusion_failed": "Възелът не може да бъде премахнат. Моля, проверете журналите за повече информация.",
|
||||
"title": "Премахване на Z-Wave възел"
|
||||
@@ -2724,7 +2678,6 @@
|
||||
"node_protection": "Защита на възела",
|
||||
"nodes": "Възли",
|
||||
"nodes_in_group": "Други възли в тази група:",
|
||||
"pooling_intensity": "Честота на опресняване",
|
||||
"protection": "Защита",
|
||||
"remove_from_group": "Премахване от групата"
|
||||
},
|
||||
@@ -3393,19 +3346,10 @@
|
||||
"intro": "Готови ли сте да събудите дома си, да отвоювате независимостта си и да се присъедините към световна общност от хора автоматизиращи домовете си?",
|
||||
"next": "Следващ",
|
||||
"restore": {
|
||||
"addons": "Добавки",
|
||||
"confirm_password": "Потвърдете паролата за снапшота",
|
||||
"description": "Като алтернатива можете да възстановите от предишен снапшот.",
|
||||
"folders": "Папки",
|
||||
"full_snapshot": "Пълен снапшот",
|
||||
"hide_log": "Скриване на пълния дневник",
|
||||
"in_progress": "Възстановяването е в ход",
|
||||
"partial_snapshot": "Частичен снапшот",
|
||||
"password": "Парола",
|
||||
"password_protection": "Защита с парола",
|
||||
"select_type": "Изберете какво да възстановите",
|
||||
"show_log": "Показване на пълния дневник",
|
||||
"type": "Тип"
|
||||
"show_log": "Показване на пълния дневник"
|
||||
},
|
||||
"user": {
|
||||
"create_account": "Създай акаунт",
|
||||
|
@@ -284,7 +284,7 @@
|
||||
"refresh": "Actualitza",
|
||||
"release_notes": "Notes de la versió",
|
||||
"reload": "Torna a carregar",
|
||||
"reset_defaults": "Restableix els valors per defecte",
|
||||
"reset_defaults": "Restableix als valors per defecte",
|
||||
"reset_options": "Opcions de reinici",
|
||||
"restart": "Reinicia",
|
||||
"restart_name": "Reinicia {name}",
|
||||
@@ -318,14 +318,6 @@
|
||||
"no_addons": "Encara no tens cap complement instal·lat. Vés al directori de complements per començar!"
|
||||
},
|
||||
"dialog": {
|
||||
"hardware": {
|
||||
"attributes": "Atributs",
|
||||
"device_path": "Ruta del dispositiu",
|
||||
"id": "ID",
|
||||
"search": "Cerca maquinari",
|
||||
"subsystem": "Subsistema",
|
||||
"title": "Maquinari"
|
||||
},
|
||||
"network": {
|
||||
"connected_to": "Connectat a {ssid}",
|
||||
"dhcp": "DHCP",
|
||||
@@ -376,8 +368,6 @@
|
||||
"error": "S'ha produït un error desconegut",
|
||||
"error_addon_no_ingress": "El complement sol·licitat no admet ingress",
|
||||
"error_addon_not_found": "No s'ha trobat el complement",
|
||||
"error_addon_not_installed": "El complement sol·licitat no està instal·lat. Instal·la'l primer",
|
||||
"error_addon_not_started": "El complement sol·licitat no s'està executant. Inicia'l primer",
|
||||
"faq_link": "Preguntes freqüents de My Home Assistant",
|
||||
"not_supported": "La instància de Home Assistant no admet aquesta redirecció. Consulta {link} per veure les redireccions compatibles i en quina versió es van introduir."
|
||||
},
|
||||
@@ -390,7 +380,6 @@
|
||||
"snapshot": {
|
||||
"addons": "Complements",
|
||||
"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",
|
||||
"create": "Crea",
|
||||
"create_blocked_not_running": "Ara mateix no és possible crear una instantània perquè el sistema es troba en estat {state}.",
|
||||
@@ -418,7 +407,6 @@
|
||||
"password": "Contrasenya de la instantània",
|
||||
"password_protected": "protegit amb contrasenya",
|
||||
"password_protection": "Protecció amb contrasenya",
|
||||
"passwords_not_matching": "Les contrasenyes no coincideixen",
|
||||
"security": "Seguretat",
|
||||
"select_type": "Selecciona què vols restaurar",
|
||||
"selected": "{number} seleccionada/es",
|
||||
@@ -2731,7 +2719,7 @@
|
||||
"validation": {
|
||||
"check_config": "Comprova la configuració",
|
||||
"heading": "Validació de la configuració",
|
||||
"introduction": "Valida la configuració si recentment n'has fet algun canvi i vols assegurar-te de que sigui vàlida.",
|
||||
"introduction": "Valida la configuració si recentment has fet algun canvi a la configuració i vols assegurar-te de que sigui vàlida.",
|
||||
"invalid": "Configuració invàlida",
|
||||
"valid": "Configuració vàlida!"
|
||||
}
|
||||
@@ -3726,7 +3714,7 @@
|
||||
},
|
||||
"page-authorize": {
|
||||
"abort_intro": "S'ha avortat l'inici de sessió",
|
||||
"authorizing_client": "Estàs a punt de concedir al client {clientId} l'accés a la teva instància de Home Assistant.",
|
||||
"authorizing_client": "Esteu a punt de permetre l'accés a la vostra instància de Home Assistant al client {clientId}.",
|
||||
"form": {
|
||||
"error": "Error: {error}",
|
||||
"next": "Següent",
|
||||
@@ -3820,7 +3808,7 @@
|
||||
"working": "Si us plau, espereu"
|
||||
},
|
||||
"initializing": "S'està inicialitzant",
|
||||
"logging_in_to_with": "Iniciant sessió a **{locationName}** amb **{authProviderName}**.",
|
||||
"logging_in_to_with": "S'escriuen els registes amb **{authProviderName}** a **{locationName}**.",
|
||||
"logging_in_with": "Iniciant sessió amb **{authProviderName}**.",
|
||||
"pick_auth_provider": "O bé inicieu sessió amb"
|
||||
},
|
||||
@@ -3891,19 +3879,10 @@
|
||||
"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",
|
||||
"restore": {
|
||||
"addons": "Complements",
|
||||
"confirm_password": "Confirma la contrasenya de la instantània",
|
||||
"description": "També pots restaurar des d'una instantània anterior.",
|
||||
"folders": "Carpetes",
|
||||
"full_snapshot": "Instantània completa",
|
||||
"hide_log": "Amaga el registre complet",
|
||||
"in_progress": "Restauració en curs",
|
||||
"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"
|
||||
"show_log": "Mostra el registre complet"
|
||||
},
|
||||
"user": {
|
||||
"create_account": "Crear compte",
|
||||
|
@@ -318,14 +318,6 @@
|
||||
"no_addons": "Zatím nemáte nainstalované žádné doplňky. Chcete-li začít, přejděte do obchodu s doplňky."
|
||||
},
|
||||
"dialog": {
|
||||
"hardware": {
|
||||
"attributes": "Atributy",
|
||||
"device_path": "Cesta k zařízení",
|
||||
"id": "ID",
|
||||
"search": "Hledat hardware",
|
||||
"subsystem": "Subsystém",
|
||||
"title": "Hardware"
|
||||
},
|
||||
"network": {
|
||||
"connected_to": "Připojeno k {ssid}",
|
||||
"dhcp": "DHCP",
|
||||
@@ -376,8 +368,6 @@
|
||||
"error": "Nastala neznámá chyba",
|
||||
"error_addon_no_ingress": "Požadovaný doplněk nepodporuje ingress",
|
||||
"error_addon_not_found": "Doplněk nebyl nalezen",
|
||||
"error_addon_not_installed": "Požadovaný doplněk není nainstalován. Nejprve jej prosím nainstalujte",
|
||||
"error_addon_not_started": "Požadovaný doplněk není spuštěn. Nejprve jej prosím spusťte",
|
||||
"faq_link": "Časté dotazy týkající se My Home Assistant",
|
||||
"not_supported": "Toto přesměrování není vaší instancí Home Assistant podporováno. Zkontrolujte {link} pro podporovaná přesměrování a verzi, ve které byla zavedena."
|
||||
},
|
||||
@@ -390,7 +380,6 @@
|
||||
"snapshot": {
|
||||
"addons": "Doplňky",
|
||||
"available_snapshots": "Dostupné zálohy",
|
||||
"confirm_password": "Potvrďte heslo zálohy",
|
||||
"could_not_create": "Nelze vytvořit zálohu",
|
||||
"create": "Vytvořit",
|
||||
"create_blocked_not_running": "Vytvoření zálohy není momentálně možné, protože systém je ve \"{state}\".",
|
||||
@@ -418,7 +407,6 @@
|
||||
"password": "Heslo zálohy",
|
||||
"password_protected": "chráněno heslem",
|
||||
"password_protection": "Ochrana heslem",
|
||||
"passwords_not_matching": "Hesla se neshodují",
|
||||
"security": "Zabezpečení",
|
||||
"select_type": "Vyberte, co chcete obnovit",
|
||||
"selected": "{number} vybraných",
|
||||
@@ -941,11 +929,8 @@
|
||||
},
|
||||
"dialogs": {
|
||||
"config_entry_system_options": {
|
||||
"enable_new_entities_description": "Pokud se mají automaticky přidat nově objevená zařízení integrace {integration}.",
|
||||
"enable_new_entities_description": "Pokud je zakázáno, nově objevené entity pro {integration} nebudou automaticky přidány do Home Assistant.",
|
||||
"enable_new_entities_label": "Povolit nově přidané entity.",
|
||||
"enable_polling_description": "Má-li Home Assistant automaticky zjišťovat aktualizace entit integrace {integration}.",
|
||||
"enable_polling_label": "Povolit dotazování na aktualizace.",
|
||||
"restart_home_assistant": "Aby se změny projevily, je třeba restartovat Home Assistant.",
|
||||
"title": "Upravit nastavení pro {integration}",
|
||||
"update": "Aktualizovat"
|
||||
},
|
||||
@@ -1179,7 +1164,7 @@
|
||||
},
|
||||
"types": {
|
||||
"navigation": "Navigovat",
|
||||
"reload": "Nově načíst",
|
||||
"reload": "Znovu načíst",
|
||||
"server_control": "Server"
|
||||
}
|
||||
},
|
||||
@@ -2088,8 +2073,7 @@
|
||||
"scripts": "Skripty",
|
||||
"unknown_error": "Neznámá chyba",
|
||||
"unnamed_device": "Nepojmenované zařízení",
|
||||
"update": "Aktualizovat",
|
||||
"update_device_error": "Aktualizace zařízení se nezdařila"
|
||||
"update": "Aktualizovat"
|
||||
},
|
||||
"entities": {
|
||||
"caption": "Entity",
|
||||
@@ -2222,7 +2206,6 @@
|
||||
"depends_on_cloud": "Závisí na cloudu",
|
||||
"device_unavailable": "Zařízení není dostupné",
|
||||
"devices": "{count} {count, plural,\n one {zařízení}\n other {zařízení}\n}",
|
||||
"disable_error": "Povolení nebo zakázání integrace se nezdařilo",
|
||||
"disable_restart_confirm": "Restartujte Home Assistant pro dokončení odstranění této integrace",
|
||||
"disable": {
|
||||
"disable_confirm": "Opravdu chcete zakázat tuto položku konfigurace? Její zařízení a entity budou zakázány.",
|
||||
@@ -2234,7 +2217,6 @@
|
||||
},
|
||||
"disabled_cause": "Zakázáno {cause}"
|
||||
},
|
||||
"disabled_polling": "Automatické dotazování na aktualizovaná data je zakázáno",
|
||||
"documentation": "Dokumentace",
|
||||
"enable_restart_confirm": "Restartujte Home Assistant pro dokončení přidání této integrace",
|
||||
"entities": "{count} {count, plural,\n one {entita}\n other {entit}\n}",
|
||||
@@ -2731,7 +2713,7 @@
|
||||
"validation": {
|
||||
"check_config": "Zkontrolujte konfiguraci",
|
||||
"heading": "Ověření konfigurace",
|
||||
"introduction": "Pokud jste nedávno provedli nějaké změny v konfiguraci a chcete se ujistit, že jsou všechny platné, proveďte ověření konfigurace.",
|
||||
"introduction": "Pokud jste nedávno provedli změny konfigurace a chcete se ujistit, že je vše v pořádku, můžete zde konfiguraci ověřit",
|
||||
"invalid": "Konfigurace není v pořádku!",
|
||||
"valid": "Konfigurace je v pořádku!"
|
||||
}
|
||||
@@ -2946,7 +2928,6 @@
|
||||
"follow_device_instructions": "Podle pokynů dodaných se zařízením aktivujte párování na zařízení.",
|
||||
"inclusion_failed": "Uzel nelze přidat. Další informace najdete v protokolech.",
|
||||
"inclusion_finished": "Uzel byl přidán.",
|
||||
"interview_failed": "Komunikace se zařízením se nezdařila. V logách mohou být k dispozici další informace.",
|
||||
"introduction": "Tento průvodce vás provede přidáním uzlu do vaší sítě Z-Wave.",
|
||||
"secure_inclusion_warning": "Zabezpečená zařízení vyžadují větší šířku pásma; příliš mnoho zabezpečených zařízení může zpomalit vaši síť Z-Wave. Bezpečné začlenění doporučujeme používat pouze u zařízení, která to vyžadují, jako jsou zámky nebo otvírače garážových vrat.",
|
||||
"start_inclusion": "Zahájit začlenění",
|
||||
@@ -3111,7 +3092,7 @@
|
||||
"heal_node": "Uzdravit uzel",
|
||||
"node_info": "Informace o uzlu",
|
||||
"print_node": "Otisk uzlu",
|
||||
"refresh_entity": "Nově načíst Entitu",
|
||||
"refresh_entity": "Znovu načíst Entitu",
|
||||
"refresh_node": "Obnovit uzel",
|
||||
"remove_failed_node": "Odebrat selhaný uzel",
|
||||
"remove_node": "Odebrat uzel",
|
||||
@@ -3176,7 +3157,6 @@
|
||||
"copy_id": "Zkopírovat ID do schránky",
|
||||
"current_entities": "Současné entity",
|
||||
"description1": "Nastavte stav zařízení v Home Assistant.",
|
||||
"description2": "Pokud entita patří k zařízení, neprobíhá s tímto zařízením žádná skutečná komunikace.",
|
||||
"entity": "Entita",
|
||||
"filter_attributes": "Filtrovat atributy",
|
||||
"filter_entities": "Filtrovat entity",
|
||||
@@ -3554,7 +3534,7 @@
|
||||
"options": "Více možností",
|
||||
"pick_card": "Kterou kartu chcete přidat?",
|
||||
"pick_card_view_title": "Kterou kartu byste chtěli přidat do svého {name} pohledu?",
|
||||
"search_cards": "Hledat karty",
|
||||
"search_cards": "Vyhledat karty",
|
||||
"show_code_editor": "Zobrazit editor kódu",
|
||||
"show_visual_editor": "Zobrazit vizuální editor",
|
||||
"toggle_editor": "Přepnout Editor",
|
||||
@@ -3886,19 +3866,10 @@
|
||||
"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ší",
|
||||
"restore": {
|
||||
"addons": "Doplňky",
|
||||
"confirm_password": "Potvrďte heslo 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",
|
||||
"in_progress": "Probíhá obnovení",
|
||||
"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"
|
||||
"show_log": "Zobrazit celý log"
|
||||
},
|
||||
"user": {
|
||||
"create_account": "Vytvořit účet",
|
||||
|
@@ -318,14 +318,6 @@
|
||||
"no_addons": "Du har endnu ikke installeret nogen tilføjelsesprogrammer. Gå over til butikken for tilføjelsesprogrammer for at komme i gang!"
|
||||
},
|
||||
"dialog": {
|
||||
"hardware": {
|
||||
"attributes": "Attributter",
|
||||
"device_path": "Enhedssti",
|
||||
"id": "Identifikationsnummer",
|
||||
"search": "Søg efter hardware",
|
||||
"subsystem": "Delsystem",
|
||||
"title": "Hardware"
|
||||
},
|
||||
"network": {
|
||||
"connected_to": "Forbundet til {ssid}",
|
||||
"dhcp": "DHCP",
|
||||
@@ -376,8 +368,6 @@
|
||||
"error": "Der opstod en ukendt fejl",
|
||||
"error_addon_no_ingress": "Tilføjelsesprogrammet understøtter ikke ingress",
|
||||
"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",
|
||||
"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."
|
||||
},
|
||||
@@ -390,7 +380,6 @@
|
||||
"snapshot": {
|
||||
"addons": "Tilføjelsesprogrammer",
|
||||
"available_snapshots": "Tilgængelige snapshots",
|
||||
"confirm_password": "Bekræft snapshot kodeord",
|
||||
"could_not_create": "Kunne ikke oprette snapshot",
|
||||
"create": "Opret",
|
||||
"create_blocked_not_running": "Det er ikke muligt at oprette et snapshot lige nu, fordi systemet er i tilstanden {state}.",
|
||||
@@ -418,7 +407,6 @@
|
||||
"password": "Kodeord",
|
||||
"password_protected": "beskyttet med kodeord",
|
||||
"password_protection": "Kodeordsbeskyttelse",
|
||||
"passwords_not_matching": "Kodeordene er forskellige",
|
||||
"security": "Sikkerhed",
|
||||
"select_type": "Vælg, hvad der skal gendannes",
|
||||
"selected": "{number} valgt",
|
||||
@@ -943,9 +931,6 @@
|
||||
"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_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}",
|
||||
"update": "Opdater"
|
||||
},
|
||||
@@ -2088,8 +2073,7 @@
|
||||
"scripts": "Scripts",
|
||||
"unknown_error": "Ukendt fejl",
|
||||
"unnamed_device": "Enhed uden navn",
|
||||
"update": "Opdater",
|
||||
"update_device_error": "Enhedsopdateringen fejlede"
|
||||
"update": "Opdater"
|
||||
},
|
||||
"entities": {
|
||||
"caption": "Entiteter",
|
||||
@@ -2222,7 +2206,6 @@
|
||||
"depends_on_cloud": "Afhængig af Cloud tjenester",
|
||||
"device_unavailable": "Enheden er utilgængelig",
|
||||
"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": {
|
||||
"disable_confirm": "Er du sikker på, at du vil deaktivere denne integration? Integrationens enheder og entiteter vil blive deaktiveret.",
|
||||
@@ -2234,7 +2217,6 @@
|
||||
},
|
||||
"disabled_cause": "Deaktiveret af {cause}"
|
||||
},
|
||||
"disabled_polling": "Den automatiske hentning af opdateret data er frakoblet",
|
||||
"documentation": "Dokumentation",
|
||||
"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}",
|
||||
@@ -3891,19 +3873,10 @@
|
||||
"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",
|
||||
"restore": {
|
||||
"addons": "Tilføjelse",
|
||||
"confirm_password": "Bekræft Snapshot kodeord",
|
||||
"description": "Alternativt kan du gendanne fra et tidligere snapshot.",
|
||||
"folders": "Mappe",
|
||||
"full_snapshot": "Fuld snapshot",
|
||||
"hide_log": "Skjul fuld log",
|
||||
"in_progress": "Gendannelse er i gang",
|
||||
"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"
|
||||
"show_log": "Vis den fulde log"
|
||||
},
|
||||
"user": {
|
||||
"create_account": "Opret konto",
|
||||
|
@@ -314,18 +314,10 @@
|
||||
"addon_new_version": "Neue Version verfügbar",
|
||||
"addon_running": "Add-on wird ausgeführt",
|
||||
"addon_stopped": "Add-on ist gestoppt",
|
||||
"addons": "Installierte Add-ons",
|
||||
"addons": "Add-ons",
|
||||
"no_addons": "Du hast noch keine Add-ons installiert. Gehe zum Add-on Store, um loszulegen!"
|
||||
},
|
||||
"dialog": {
|
||||
"hardware": {
|
||||
"attributes": "Attribute",
|
||||
"device_path": "Gerätepfad",
|
||||
"id": "ID",
|
||||
"search": "Hardware suchen",
|
||||
"subsystem": "Subsystem",
|
||||
"title": "Hardware"
|
||||
},
|
||||
"network": {
|
||||
"connected_to": "Verbunden mit {ssid}",
|
||||
"dhcp": "DHCP",
|
||||
@@ -376,8 +368,6 @@
|
||||
"error": "Ein unbekannter Fehler ist aufgetreten.",
|
||||
"error_addon_no_ingress": "Das angeforderte Add-on unterstützt keinen Ingress",
|
||||
"error_addon_not_found": "Add-on nicht gefunden",
|
||||
"error_addon_not_installed": "Das angeforderte Add-on ist nicht installiert. Bitte installiere es zuerst",
|
||||
"error_addon_not_started": "Das angeforderte Add-on läuft nicht. Bitte starte es zuerst",
|
||||
"faq_link": "Häufig gestellten Fragen zu Home Assistant",
|
||||
"not_supported": "Diese Weiterleitung wird von deiner Home Assistant-Instanz nicht unterstützt. Überprüfe den {link} auf die unterstützten Weiterleitungen und die Version, in der sie eingeführt wurden."
|
||||
},
|
||||
@@ -390,19 +380,13 @@
|
||||
"snapshot": {
|
||||
"addons": "Add-ons",
|
||||
"available_snapshots": "Verfügbare Datensicherungen",
|
||||
"confirm_password": "Snapshot-Passwort bestätigen",
|
||||
"could_not_create": "Datensicherung konnte nicht erstellt werden",
|
||||
"create": "Erstellen",
|
||||
"create_blocked_not_running": "Das Erstellen eines Snapshots ist derzeit nicht möglich, da sich das System im Zustand {state} befindet.",
|
||||
"create_snapshot": "Datensicherung erstellen",
|
||||
"created": "Erstellt",
|
||||
"delete_selected": "Ausgewählten Snapshot löschen",
|
||||
"delete_snapshot_confirm": "löschen",
|
||||
"delete_snapshot_text": "Möchtest du {number} {number, plural,\n one {den Snapshot}\n other {die Snapshots}\n} löschen?",
|
||||
"delete_snapshot_title": "Snapshot löschen",
|
||||
"description": "Datensicherungen ermöglichen dir das leichte Speichern und Wiederherstellen von allen Daten aus Home Assistant.",
|
||||
"enter_password": "Bitte Passwort eingeben.",
|
||||
"failed_to_delete": "Löschen fehlgeschlagen",
|
||||
"folder": {
|
||||
"addons/local": "Lokale Add-ons",
|
||||
"homeassistant": "Home Assistant-Konfiguration",
|
||||
@@ -412,17 +396,14 @@
|
||||
},
|
||||
"folders": "Ordner",
|
||||
"full_snapshot": "Vollständige Datensicherung",
|
||||
"name": "Snapshot-Name",
|
||||
"name": "Name",
|
||||
"no_snapshots": "Du hast bisher keine Datensicherungen erstellt.",
|
||||
"partial_snapshot": "Selektive Datensicherung",
|
||||
"password": "Snapshot-Passwort",
|
||||
"password": "Passwort",
|
||||
"password_protected": "Passwort geschützt",
|
||||
"password_protection": "Passwortschutz",
|
||||
"passwords_not_matching": "Passwörter stimmen nicht überein",
|
||||
"security": "Sicherheit",
|
||||
"select_type": "Wähle aus, was wiederhergestellt werden soll",
|
||||
"selected": "{number} ausgewählt",
|
||||
"type": "Snapshot-Typ",
|
||||
"type": "Typ",
|
||||
"upload_snapshot": "Datensicherung hochladen"
|
||||
},
|
||||
"store": {
|
||||
@@ -739,9 +720,6 @@
|
||||
"no_match": "Keine übereinstimmende Bereiche gefunden",
|
||||
"show_areas": "Bereiche anzeigen"
|
||||
},
|
||||
"attributes": {
|
||||
"expansion_header": "Attribute"
|
||||
},
|
||||
"blueprint-picker": {
|
||||
"add_user": "Benutzer hinzufügen",
|
||||
"remove_user": "Benutzer entfernen",
|
||||
@@ -943,9 +921,6 @@
|
||||
"config_entry_system_options": {
|
||||
"enable_new_entities_description": "Wenn deaktiviert werden neu erkannte Entitäten für {integration} nicht automatisch zu Home Assistant hinzugefügt.",
|
||||
"enable_new_entities_label": "Neu hinzugefügte Entitäten aktivieren.",
|
||||
"enable_polling_description": "Ob Home Assistant automatisch {integration} Entitäten nach Updates abfragen soll.",
|
||||
"enable_polling_label": "Aktiviere Polling für Updates.",
|
||||
"restart_home_assistant": "Du musst Home Assistant neu starten, damit deine Änderungen wirksam werden.",
|
||||
"title": "Einstellungen für {integration}",
|
||||
"update": "Aktualisieren"
|
||||
},
|
||||
@@ -1744,7 +1719,6 @@
|
||||
"title": "Alexa"
|
||||
},
|
||||
"connected": "Verbunden",
|
||||
"connecting": "Verbinde...",
|
||||
"connection_status": "Cloud-Verbindungsstatus",
|
||||
"fetching_subscription": "Abo wird abgerufen ...",
|
||||
"google": {
|
||||
@@ -2088,8 +2062,7 @@
|
||||
"scripts": "Skripte",
|
||||
"unknown_error": "Unbekannter Fehler",
|
||||
"unnamed_device": "Unbenanntes Gerät",
|
||||
"update": "Aktualisieren",
|
||||
"update_device_error": "Aktualisieren des Geräts fehlgeschlagen"
|
||||
"update": "Aktualisieren"
|
||||
},
|
||||
"entities": {
|
||||
"caption": "Entitäten",
|
||||
@@ -2222,7 +2195,6 @@
|
||||
"depends_on_cloud": "Abhängig von der Cloud",
|
||||
"device_unavailable": "Gerät nicht verfügbar",
|
||||
"devices": "{count} {count, plural,\n one {Gerät}\n other {Geräte}\n}",
|
||||
"disable_error": "Aktivieren oder Deaktivieren der Integration fehlgeschlagen",
|
||||
"disable_restart_confirm": "Home Assistant neu starten, um das Deaktivieren dieser Integration abzuschließen",
|
||||
"disable": {
|
||||
"disable_confirm": "Möchtest du diesen Konfigurationseintrag wirklich deaktivieren? Die Geräte und Entitäten werden deaktiviert.",
|
||||
@@ -2234,7 +2206,6 @@
|
||||
},
|
||||
"disabled_cause": "Deaktiviert durch {cause}."
|
||||
},
|
||||
"disabled_polling": "Automatisches Abfragen nach aktualisierten Daten deaktiviert",
|
||||
"documentation": "Dokumentation",
|
||||
"enable_restart_confirm": "Home Assistant neu starten, um das Aktivieren dieser Integration abzuschließen",
|
||||
"entities": "{count} {count, plural,\none {Entität}\nother {Entitäten}\n}",
|
||||
@@ -2988,7 +2959,6 @@
|
||||
},
|
||||
"logs": {
|
||||
"log_level": "Protokollstufe",
|
||||
"log_level_changed": "Log Level geändert auf: {level}",
|
||||
"subscribed_to_logs": "Abonniert Z-Wave JS-Protokollnachrichten ...",
|
||||
"title": "Z-Wave JS Protokolle"
|
||||
},
|
||||
@@ -3891,19 +3861,10 @@
|
||||
"intro": "Sind Sie bereit, dein Zuhause zu wecken, Ihre Privatsphäre zurückzugewinnen und einer weltweiten Gemeinschaft von Tüftlern beizutreten?",
|
||||
"next": "Weiter",
|
||||
"restore": {
|
||||
"addons": "Add-ons",
|
||||
"confirm_password": "Snapshot-Passwort bestätigen",
|
||||
"description": "Alternativ kannst du von einem vorherigen Snapshot wiederherstellen.",
|
||||
"folders": "Ordner",
|
||||
"full_snapshot": "Vollständige Datensicherung",
|
||||
"hide_log": "Vollständiges Protokoll ausblenden",
|
||||
"in_progress": "Wiederherstellung im Gange",
|
||||
"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"
|
||||
"show_log": "Vollständiges Protokoll anzeigen"
|
||||
},
|
||||
"user": {
|
||||
"create_account": "Benutzerkonto anlegen",
|
||||
|
@@ -318,14 +318,6 @@
|
||||
"no_addons": "You don't have any add-ons installed yet. Head over to the add-on store to get started!"
|
||||
},
|
||||
"dialog": {
|
||||
"hardware": {
|
||||
"attributes": "Attributes",
|
||||
"device_path": "Device path",
|
||||
"id": "ID",
|
||||
"search": "Search hardware",
|
||||
"subsystem": "Subsystem",
|
||||
"title": "Hardware"
|
||||
},
|
||||
"network": {
|
||||
"connected_to": "Connected to {ssid}",
|
||||
"dhcp": "DHCP",
|
||||
@@ -376,8 +368,6 @@
|
||||
"error": "An unknown error occurred",
|
||||
"error_addon_no_ingress": "The requested add-on does not support ingress",
|
||||
"error_addon_not_found": "Add-on not found",
|
||||
"error_addon_not_installed": "The requested add-on is not installed. Please install it first",
|
||||
"error_addon_not_started": "The requested add-on are not running. Please start it first",
|
||||
"faq_link": "My Home Assistant FAQ",
|
||||
"not_supported": "This redirect is not supported by your Home Assistant instance. Check the {link} for the supported redirects and the version they where introduced."
|
||||
},
|
||||
@@ -390,7 +380,6 @@
|
||||
"snapshot": {
|
||||
"addons": "Add-ons",
|
||||
"available_snapshots": "Available Snapshots",
|
||||
"confirm_password": "Confirm Snapshot password",
|
||||
"could_not_create": "Could not create snapshot",
|
||||
"create": "Create",
|
||||
"create_blocked_not_running": "Creating a snapshot is not possible right now because the system is in {state} state.",
|
||||
@@ -418,7 +407,6 @@
|
||||
"password": "Snapshot password",
|
||||
"password_protected": "password protected",
|
||||
"password_protection": "Password protection",
|
||||
"passwords_not_matching": "The passwords does not match",
|
||||
"security": "Security",
|
||||
"select_type": "Select what to restore",
|
||||
"selected": "{number} selected",
|
||||
@@ -2731,7 +2719,7 @@
|
||||
"validation": {
|
||||
"check_config": "Check configuration",
|
||||
"heading": "Configuration validation",
|
||||
"introduction": "Validate your configuration if you recently made some changes to your configuration and want to make sure that it is all valid.",
|
||||
"introduction": "Validate your configuration if you recently made some changes to your configuration and want to make sure that it is all valid",
|
||||
"invalid": "Configuration invalid",
|
||||
"valid": "Configuration valid!"
|
||||
}
|
||||
@@ -3891,19 +3879,10 @@
|
||||
"intro": "Are you ready to awaken your home, reclaim your privacy and join a worldwide community of tinkerers?",
|
||||
"next": "Next",
|
||||
"restore": {
|
||||
"addons": "Add-ons",
|
||||
"confirm_password": "Confirm Snapshot password",
|
||||
"description": "Alternatively you can restore from a previous snapshot.",
|
||||
"folders": "Folders",
|
||||
"full_snapshot": "Full snapshot",
|
||||
"hide_log": "Hide full log",
|
||||
"in_progress": "Restore in progress",
|
||||
"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"
|
||||
"show_log": "Show full log"
|
||||
},
|
||||
"user": {
|
||||
"create_account": "Create Account",
|
||||
|
@@ -367,8 +367,6 @@
|
||||
"my": {
|
||||
"error": "Ha ocurrido un error desconocido",
|
||||
"error_addon_not_found": "Complemento no encontrado",
|
||||
"error_addon_not_installed": "El complemento solicitado no está instalado. Por favor instálelo primero",
|
||||
"error_addon_not_started": "El complemento solicitado no se están ejecutando. Por favor inícielo antes.",
|
||||
"faq_link": "Mis preguntas frecuentes de Home Assistant",
|
||||
"not_supported": "Esta redirección no está soportada por su instancia de Home Assistant. Consulte las redirecciones soportadas y la versión en la que fueron introducidas en {link}."
|
||||
},
|
||||
@@ -2033,8 +2031,7 @@
|
||||
"scripts": "Scripts",
|
||||
"unknown_error": "Error desconocido",
|
||||
"unnamed_device": "Dispositivo sin nombre",
|
||||
"update": "Actualizar",
|
||||
"update_device_error": "Error al actualizar el dispositivo"
|
||||
"update": "Actualizar"
|
||||
},
|
||||
"entities": {
|
||||
"caption": "Entidades",
|
||||
@@ -2166,7 +2163,6 @@
|
||||
"depends_on_cloud": "Depende de la nube",
|
||||
"device_unavailable": "Dispositivo no disponible",
|
||||
"devices": "{count} {count, plural,\n one {dispositivo}\n other {dispositivos}\n}",
|
||||
"disable_error": "Ha fallado la habilitación o deshabilitación de la integración",
|
||||
"disable_restart_confirm": "Reinicie Home Assistant para terminar de deshabilitar esta integración",
|
||||
"disable": {
|
||||
"disable_confirm": "¿Está seguro de que desea deshabilitar esta entrada de configuración? Sus dispositivos y entidades serán deshabilitados.",
|
||||
|
@@ -318,14 +318,6 @@
|
||||
"no_addons": "Aún no tienes ningún complemento instalado. ¡Dirígete a la tienda de complementos para comenzar!"
|
||||
},
|
||||
"dialog": {
|
||||
"hardware": {
|
||||
"attributes": "Atributos",
|
||||
"device_path": "Ruta del dispositivo",
|
||||
"id": "ID",
|
||||
"search": "Buscar hardware",
|
||||
"subsystem": "Subsistema",
|
||||
"title": "Hardware"
|
||||
},
|
||||
"network": {
|
||||
"connected_to": "Conectado a {ssid}",
|
||||
"dhcp": "DHCP",
|
||||
@@ -376,8 +368,6 @@
|
||||
"error": "Se ha producido un error desconocido",
|
||||
"error_addon_no_ingress": "El complemento solicitado no admite la entrada",
|
||||
"error_addon_not_found": "Complemento no encontrado",
|
||||
"error_addon_not_installed": "El complemento solicitado no está instalado. Por favor instálalo primero",
|
||||
"error_addon_not_started": "El complemento solicitado no se está ejecutando. Por favor, inícialo primero",
|
||||
"faq_link": "Preguntas frecuentes sobre mi Home Assistant",
|
||||
"not_supported": "Esta redirección no es compatible con tu instancia de Home Assistant. Consulta el {link} para conocer las redirecciones admitidas y la versión en la que se introdujeron."
|
||||
},
|
||||
@@ -390,7 +380,6 @@
|
||||
"snapshot": {
|
||||
"addons": "Complementos",
|
||||
"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",
|
||||
"create": "Crear",
|
||||
"create_blocked_not_running": "No es posible crear una instantánea en este momento porque el sistema está en el estado {state}.",
|
||||
@@ -418,7 +407,6 @@
|
||||
"password": "Contraseña de la instantánea",
|
||||
"password_protected": "protegida por contraseña",
|
||||
"password_protection": "Protección con contraseña",
|
||||
"passwords_not_matching": "Las contraseñas no coinciden",
|
||||
"security": "Seguridad",
|
||||
"select_type": "Selecciona qué restaurar",
|
||||
"selected": "{number} {number, plural,\n one {seleccionada}\n other {seleccionadas}\n}",
|
||||
@@ -2731,7 +2719,7 @@
|
||||
"validation": {
|
||||
"check_config": "Verificar configuración",
|
||||
"heading": "Validación de la configuración",
|
||||
"introduction": "Valida tu configuración si has realizado cambios recientemente en ella y quieres asegurarte de que son correctos",
|
||||
"introduction": "Valida tu configuración si has realizado cambios recientemente y quieres asegurarte de que son correctos",
|
||||
"invalid": "Configuración no válida",
|
||||
"valid": "¡Configuración valida!"
|
||||
}
|
||||
@@ -3891,19 +3879,10 @@
|
||||
"intro": "¿Estás listo para despertar tu casa, reclamar tu privacidad y unirte a una comunidad mundial de pensadores?",
|
||||
"next": "Siguiente",
|
||||
"restore": {
|
||||
"addons": "Complementos",
|
||||
"confirm_password": "Confirma la contraseña de la instantánea",
|
||||
"description": "Alternativamente, puedes restaurar desde una copia de seguridad anterior.",
|
||||
"folders": "Carpetas",
|
||||
"full_snapshot": "Instantánea completa",
|
||||
"hide_log": "Ocultar registro completo",
|
||||
"in_progress": "Restauración en curso",
|
||||
"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"
|
||||
"show_log": "Mostrar registro completo"
|
||||
},
|
||||
"user": {
|
||||
"create_account": "Crear una cuenta",
|
||||
|
@@ -318,14 +318,6 @@
|
||||
"no_addons": "Ühtegi lisandmoodulit pole veel paigaldatud. Alustamiseks mine lisandmoodulite hoidlasse!"
|
||||
},
|
||||
"dialog": {
|
||||
"hardware": {
|
||||
"attributes": "Atribuudid",
|
||||
"device_path": "Seadme asukoht",
|
||||
"id": "ID",
|
||||
"search": "Otsi riistvara",
|
||||
"subsystem": "Alamsüsteem",
|
||||
"title": "Riistvara"
|
||||
},
|
||||
"network": {
|
||||
"connected_to": "Ühendatud pääsupunktiga {ssid}",
|
||||
"dhcp": "DHCP",
|
||||
@@ -366,9 +358,9 @@
|
||||
"text": "Kas soovid lisandmooduli taaskäivitada rakendades muudatused?"
|
||||
},
|
||||
"update": {
|
||||
"create_snapshot": "Enne {name} värskendamist loo varukoopia",
|
||||
"snapshot": "Varukoopia",
|
||||
"snapshotting": "Üksuse {name} varukoopia loomine",
|
||||
"create_snapshot": "Enne {name} värskendamist loo hetktõmmis",
|
||||
"snapshot": "Hetktõmmis",
|
||||
"snapshotting": "Üksuse {name} hetktõmmise loomine",
|
||||
"updating": "Üksuse {name} värskendamine versioonile {version}"
|
||||
}
|
||||
},
|
||||
@@ -376,8 +368,6 @@
|
||||
"error": "Viga",
|
||||
"error_addon_no_ingress": "Valitud lisandmoodul ei toeta ingressi",
|
||||
"error_addon_not_found": "Lisandmoodulit ei leitud",
|
||||
"error_addon_not_installed": "Soovitud lisandmoodul pole pigaldatud. Alustamiseks paigalda see",
|
||||
"error_addon_not_started": "Soovitud lisandmoodul ei tööta. Alustamiseks käivita see",
|
||||
"faq_link": "KKK viide",
|
||||
"not_supported": "pole toetatud"
|
||||
},
|
||||
@@ -389,18 +379,17 @@
|
||||
},
|
||||
"snapshot": {
|
||||
"addons": "Lisandmoodulid",
|
||||
"available_snapshots": "Saadaolevad varukoopiad",
|
||||
"confirm_password": "Varukoopia salasõna kinnitamine",
|
||||
"could_not_create": "Varukoopia loomine nurjus",
|
||||
"available_snapshots": "Saadaolevad hetktõmmised",
|
||||
"could_not_create": "Hetktõmmise loomine nurjus",
|
||||
"create": "Loo",
|
||||
"create_blocked_not_running": "Varukoopia loomine pole praegu võimalik kuna süsteem on olekus {state}.",
|
||||
"create_snapshot": "Loo varukoopia",
|
||||
"create_blocked_not_running": "Hetktõmmise loomine pole praegu võimalik kuna süsteem on olekus {state}.",
|
||||
"create_snapshot": "Loo hetktõmmis",
|
||||
"created": "Loodud",
|
||||
"delete_selected": "Kustuta valitud varukoopiad",
|
||||
"delete_selected": "Kustuta valitud hetktõmmised",
|
||||
"delete_snapshot_confirm": "kustuta",
|
||||
"delete_snapshot_text": "Kas kustutada {number} {number, plural,\n one {varukoopia}\n other {varukoopiat}\n}?",
|
||||
"delete_snapshot_title": "Kustuta varukoopia",
|
||||
"description": "Varukoopiad võimaldavad hõlpsalt varundada ja taastada kõik Home Assistanti andmed.",
|
||||
"delete_snapshot_text": "Kas kustutada {number} {number, plural,\n one {hetktõmmis}\n other {hetktõmmist}\n}?",
|
||||
"delete_snapshot_title": "Kustuta hetktõmmis",
|
||||
"description": "Hetktõmmised võimaldavad hõlpsalt varundada ja taastada kõik Home Assistanti andmed.",
|
||||
"enter_password": "Sisesta salasõna.",
|
||||
"failed_to_delete": "Kustutamine nurjus",
|
||||
"folder": {
|
||||
@@ -411,19 +400,18 @@
|
||||
"ssl": "SSL"
|
||||
},
|
||||
"folders": "Kaustad",
|
||||
"full_snapshot": "Täielik varukoopia",
|
||||
"name": "Varukoopia nimi",
|
||||
"no_snapshots": "Sul pole veel ühtegi varukoopiat.",
|
||||
"partial_snapshot": "Osaline varukoopia",
|
||||
"password": "Varukoopia salasõna",
|
||||
"full_snapshot": "Täielik hetktõmmis",
|
||||
"name": "Hetktõmmise nimi",
|
||||
"no_snapshots": "Sul pole veel ühtegi hetktõmmist.",
|
||||
"partial_snapshot": "Osaline hetktõmmis",
|
||||
"password": "Hetktõmmise salasõna",
|
||||
"password_protected": "salasõnaha kaitstud",
|
||||
"password_protection": "Salasõnaga kaitstud",
|
||||
"passwords_not_matching": "Salasõnad ei ühti",
|
||||
"security": "Turvalisus",
|
||||
"select_type": "Vali mida taastada",
|
||||
"selected": "{number} valitud",
|
||||
"type": "Varukoopia tüüp",
|
||||
"upload_snapshot": "Varukoopia üleslaadimine"
|
||||
"type": "Hetktõmmise tüüp",
|
||||
"upload_snapshot": "Hetktõmmise üleslaadimine"
|
||||
},
|
||||
"store": {
|
||||
"missing_addons": "Ei näe lisandmooduleid? Luba täpsem režiim oma kasutajaprofiili lehel",
|
||||
@@ -941,7 +929,7 @@
|
||||
},
|
||||
"dialogs": {
|
||||
"config_entry_system_options": {
|
||||
"enable_new_entities_description": "Kas lisada äsja avastatud sidumise {integration} olemeid automaatselt Home Assistant'i.",
|
||||
"enable_new_entities_description": "Kas lisada äsja avastatud sidumise {integration} olemed automaatselt Home Assistant'i.",
|
||||
"enable_new_entities_label": "Luba äsja lisatud olemid.",
|
||||
"enable_polling_description": "Kas Home Assistant peaks automaatselt küsitlema {integration} üksusi uuenduste saamiseks.",
|
||||
"enable_polling_label": "Luba värskenduste jaoks küsitlus.",
|
||||
@@ -2731,7 +2719,7 @@
|
||||
"validation": {
|
||||
"check_config": "Kontrolli seadeid",
|
||||
"heading": "Seadete kontrollimine",
|
||||
"introduction": "Kontrolli oma seadeid kui oled neis hiljuti muutusi teinud ja tahad veenduda, et kõik on korrektne.",
|
||||
"introduction": "Kontrolli oma seadeid kui oled neis hiljuti muutusi teinud ja tahad veenduda, et kõik on korrektne",
|
||||
"invalid": "Konfiguratsioon on vigane",
|
||||
"valid": "Konfiguratsioon on korrektne!"
|
||||
}
|
||||
@@ -3891,19 +3879,10 @@
|
||||
"intro": "Kas oled valmis oma kodu ellu äratama, oma privaatsust tagasi võitma ja ühinema ülemaailmse nokitsejate kogukonnaga?",
|
||||
"next": "Järgmine",
|
||||
"restore": {
|
||||
"addons": "Lisandmoodulid",
|
||||
"confirm_password": "Kinnita salasõna",
|
||||
"description": "Teise võimalusena saad taastada eelmise varukoopia.",
|
||||
"folders": "Kaustad",
|
||||
"full_snapshot": "Täielik varukoopia",
|
||||
"description": "Teise võimalusena saad taastada eelmise hetktõmmise.",
|
||||
"hide_log": "Peida täielik logi",
|
||||
"in_progress": "Toimub taastamine",
|
||||
"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"
|
||||
"show_log": "Kuva täielik logi"
|
||||
},
|
||||
"user": {
|
||||
"create_account": "Loo konto",
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -318,14 +318,6 @@
|
||||
"no_addons": "Non hai ancora installato alcun componente aggiuntivo. Vai al negozio di componenti aggiuntivi per iniziare!"
|
||||
},
|
||||
"dialog": {
|
||||
"hardware": {
|
||||
"attributes": "Attributi",
|
||||
"device_path": "Percorso del dispositivo",
|
||||
"id": "ID",
|
||||
"search": "Cerca hardware",
|
||||
"subsystem": "Sottosistema",
|
||||
"title": "Hardware"
|
||||
},
|
||||
"network": {
|
||||
"connected_to": "Connesso a {ssid}",
|
||||
"dhcp": "DHCP",
|
||||
@@ -376,8 +368,6 @@
|
||||
"error": "Si è verificato un errore sconosciuto",
|
||||
"error_addon_no_ingress": "Il componente aggiuntivo richiesto non supporta l'ingresso",
|
||||
"error_addon_not_found": "Componente aggiuntivo non trovato",
|
||||
"error_addon_not_installed": "Il componente aggiuntivo richiesto non è installato. Si prega di installarlo prima di proseguire",
|
||||
"error_addon_not_started": "Il componente aggiuntivo non è in esecuzione. Si prega di avviarlo prima di proseguire",
|
||||
"faq_link": "My Home Assistant FAQ",
|
||||
"not_supported": "Questo reindirizzamento non è supportato dall'istanza di Home Assistant. Controlla il {link} per i reindirizzamenti supportati e la versione in cui sono stati introdotti."
|
||||
},
|
||||
@@ -390,7 +380,6 @@
|
||||
"snapshot": {
|
||||
"addons": "Componenti aggiuntivi",
|
||||
"available_snapshots": "Istantanee disponibili",
|
||||
"confirm_password": "Conferma la password dell'istantanea",
|
||||
"could_not_create": "Impossibile creare l'istantanea",
|
||||
"create": "Crea",
|
||||
"create_blocked_not_running": "La creazione di un'istantanea non è al momento possibile perché il sistema è nello stato {state}.",
|
||||
@@ -418,12 +407,11 @@
|
||||
"password": "Password dell'istantanea",
|
||||
"password_protected": "protetto da password",
|
||||
"password_protection": "Protezione con password",
|
||||
"passwords_not_matching": "Le password non corrispondono",
|
||||
"security": "Sicurezza",
|
||||
"select_type": "Seleziona cosa ripristinare",
|
||||
"selected": "{number} selezionato/i",
|
||||
"type": "Tipo di istantanea",
|
||||
"upload_snapshot": "Aggiungi istantanea"
|
||||
"upload_snapshot": "Invia istantanea"
|
||||
},
|
||||
"store": {
|
||||
"missing_addons": "Componenti aggiuntivi mancanti? Abilita la modalità avanzata nella pagina del tuo profilo utente",
|
||||
@@ -2088,8 +2076,7 @@
|
||||
"scripts": "Script",
|
||||
"unknown_error": "Errore sconosciuto",
|
||||
"unnamed_device": "Dispositivo senza nome",
|
||||
"update": "Aggiorna",
|
||||
"update_device_error": "Aggiornamento del dispositivo non riuscito"
|
||||
"update": "Aggiorna"
|
||||
},
|
||||
"entities": {
|
||||
"caption": "Entità",
|
||||
@@ -2222,7 +2209,6 @@
|
||||
"depends_on_cloud": "Dipende dal cloud",
|
||||
"device_unavailable": "Dispositivo non disponibile",
|
||||
"devices": "{count} {count, plural, \none {dispositivo}\nother {dispositivi}\n}",
|
||||
"disable_error": "Abilitazione o disabilitazione dell'integrazione non riuscita",
|
||||
"disable_restart_confirm": "Riavvia Home Assistant per terminare la disabilitazione di questa integrazione",
|
||||
"disable": {
|
||||
"disable_confirm": "Sei sicuro di voler disabilitare questa voce di configurazione? I suoi dispositivi ed entità saranno disabilitati.",
|
||||
@@ -3891,19 +3877,10 @@
|
||||
"intro": "Sei pronto per risvegliare la tua casa, reclamare la tua privacy e far parte di una comunità mondiale di smanettoni?",
|
||||
"next": "Avanti",
|
||||
"restore": {
|
||||
"addons": "Componenti aggiuntivi",
|
||||
"confirm_password": "Conferma la password dell'istantanea",
|
||||
"description": "In alternativa è possibile ripristinare da un'istantanea precedente.",
|
||||
"folders": "Cartelle",
|
||||
"full_snapshot": "Istantanea completa",
|
||||
"hide_log": "Nascondi il registro completo",
|
||||
"in_progress": "Ripristino in corso",
|
||||
"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"
|
||||
"show_log": "Mostra il registro completo"
|
||||
},
|
||||
"user": {
|
||||
"create_account": "Crea un Account",
|
||||
|
@@ -318,14 +318,6 @@
|
||||
"no_addons": "아직 설치된 애드온이 없습니다. 시작하려면 애드온 스토어로 이동해보세요!"
|
||||
},
|
||||
"dialog": {
|
||||
"hardware": {
|
||||
"attributes": "속성",
|
||||
"device_path": "장치 경로",
|
||||
"id": "ID",
|
||||
"search": "하드웨어 검색",
|
||||
"subsystem": "서브 시스템",
|
||||
"title": "하드웨어"
|
||||
},
|
||||
"network": {
|
||||
"connected_to": "{ssid}에 연결되었습니다",
|
||||
"dhcp": "자동 구성",
|
||||
@@ -376,8 +368,6 @@
|
||||
"error": "알 수 없는 오류가 발생했습니다",
|
||||
"error_addon_no_ingress": "해당 애드온은 인그레스를 지원하지 않습니다.",
|
||||
"error_addon_not_found": "애드온을 찾을 수 없습니다",
|
||||
"error_addon_not_installed": "해당 애드온은 설치되어있지 않습니다. 애드온을 설치해주세요.",
|
||||
"error_addon_not_started": "해당 애드온은 실행중이지 않습니다. 애드온을 실행해주세요.",
|
||||
"faq_link": "내 Home Assistant 자주 묻는 질문",
|
||||
"not_supported": "이 리디렉션은 Home Assistant 인스턴스에서 지원되지 않습니다. {link}에서 지원되는 리디렉션과 리디렉션이 도입된 버전을 확인해주세요."
|
||||
},
|
||||
@@ -390,7 +380,6 @@
|
||||
"snapshot": {
|
||||
"addons": "애드온",
|
||||
"available_snapshots": "사용 가능한 스냅숏",
|
||||
"confirm_password": "스냅샷 비밀번호 확인",
|
||||
"could_not_create": "스냅숏을 만들 수 없습니다",
|
||||
"create": "생성하기",
|
||||
"create_blocked_not_running": "시스템이 {state} 상태이기 때문에 지금은 스냅숏을 생성할 수 없습니다.",
|
||||
@@ -398,7 +387,7 @@
|
||||
"created": "생성됨",
|
||||
"delete_selected": "선택한 스냅샷 삭제",
|
||||
"delete_snapshot_confirm": "삭제",
|
||||
"delete_snapshot_text": "{number} {number, plural,\n one{개의 스냅샷}\n other{개의 스냅샷}\n} 삭제하시겠습니까?",
|
||||
"delete_snapshot_text": "{number} {number, plural,\n one{개의 스냅샷}\n other{개의 스냅샷}\n}를 삭제하시겠습니까?",
|
||||
"delete_snapshot_title": "스냅샷 삭제",
|
||||
"description": "스냅숏을 사용하면 Home Assistant 인스턴스의 모든 데이터를 쉽게 백업하고 복원할 수 있습니다.",
|
||||
"enter_password": "비밀번호를 입력해주세요.",
|
||||
@@ -418,7 +407,6 @@
|
||||
"password": "비밀번호",
|
||||
"password_protected": "비밀번호로 보호됨",
|
||||
"password_protection": "비밀번호 보호",
|
||||
"passwords_not_matching": "비밀번호가 일치하지 않습니다",
|
||||
"security": "보안",
|
||||
"select_type": "복원 할 항목 선택",
|
||||
"selected": "{number}개 선택됨",
|
||||
@@ -2088,8 +2076,7 @@
|
||||
"scripts": "스크립트",
|
||||
"unknown_error": "알 수 없는 오류",
|
||||
"unnamed_device": "이름이 없는 기기",
|
||||
"update": "업데이트",
|
||||
"update_device_error": "장치 업데이트 실패"
|
||||
"update": "업데이트"
|
||||
},
|
||||
"entities": {
|
||||
"caption": "구성요소",
|
||||
@@ -2222,7 +2209,6 @@
|
||||
"depends_on_cloud": "클라우드 서비스",
|
||||
"device_unavailable": "기기 사용불가",
|
||||
"devices": "{count} {count, plural,\none{개의 기기}\nother{개의 기기}\n}",
|
||||
"disable_error": "통합구성요소 활성화 혹은 비활성화 실패",
|
||||
"disable_restart_confirm": "이 통합 구성요소를 비활성화하려면 Home Assistant를 다시 시작해주세요",
|
||||
"disable": {
|
||||
"disable_confirm": "이 구성 항목을 비활성화하시겠습니까? 해당 기기 및 구성요소가 비활성화됩니다.",
|
||||
@@ -3891,19 +3877,10 @@
|
||||
"intro": "잠들어 있는 집을 깨우고 개인정보를 보호하며 전세계의 공돌이 커뮤니티에 가입 할 준비가 되셨나요?",
|
||||
"next": "다음",
|
||||
"restore": {
|
||||
"addons": "애드온",
|
||||
"confirm_password": "스냅샷 비밀번호 확인",
|
||||
"description": "이전 스냅숏에서 복원할 수 있습니다.",
|
||||
"folders": "폴더",
|
||||
"full_snapshot": "전체 스냅샷",
|
||||
"hide_log": "전체 로그 숨기기",
|
||||
"in_progress": "복원 중",
|
||||
"partial_snapshot": "부분 스냅샷",
|
||||
"password": "스냅샷 비밀번호",
|
||||
"password_protection": "비밀번호 보호",
|
||||
"select_type": "무엇을 복원할지 선택하세요",
|
||||
"show_log": "전체 로그 표시하기",
|
||||
"type": "스냅샷 종류"
|
||||
"show_log": "전체 로그 표시하기"
|
||||
},
|
||||
"user": {
|
||||
"create_account": "계정 만들기",
|
||||
|
@@ -318,14 +318,6 @@
|
||||
"no_addons": "Du har ikke installert tilleggsprogrammer ennå. Gå over til tilleggsbutikken for å komme i gang!"
|
||||
},
|
||||
"dialog": {
|
||||
"hardware": {
|
||||
"attributes": "Attributter",
|
||||
"device_path": "Bane til enhet",
|
||||
"id": "ID",
|
||||
"search": "Søk i maskinvare",
|
||||
"subsystem": "Delsystem",
|
||||
"title": "Maskinvare"
|
||||
},
|
||||
"network": {
|
||||
"connected_to": "Koblet til {ssid}",
|
||||
"dhcp": "",
|
||||
@@ -376,8 +368,6 @@
|
||||
"error": "En ukjent feil har oppstått",
|
||||
"error_addon_no_ingress": "Det etterspurte tillegget støtter ikke inngang",
|
||||
"error_addon_not_found": "Tillegget ble ikke funnet",
|
||||
"error_addon_not_installed": "Det forespurte tillegget er ikke installert. Vennligst installer den først",
|
||||
"error_addon_not_started": "Det valgte tillegget kjører ikke. Vennligst start den først",
|
||||
"faq_link": "Vanlige spørsmål om Min Home Assistant",
|
||||
"not_supported": "Denne viderekoblingen støttes ikke av Home Assistant-forekomsten. Se på {link} for viderekoblinger som støttes, og hvilken versjon de ble introdusert."
|
||||
},
|
||||
@@ -390,7 +380,6 @@
|
||||
"snapshot": {
|
||||
"addons": "Tillegg",
|
||||
"available_snapshots": "Tilgjengelige sikkerhetskopier",
|
||||
"confirm_password": "Bekreft passord for øyeblikksbilde",
|
||||
"could_not_create": "Kunne ikke opprette sikkerhetskopi",
|
||||
"create": "Opprett",
|
||||
"create_blocked_not_running": "Å lage en sikkerhetskopi er ikke mulig akkurat nå fordi systemet er i {state} status",
|
||||
@@ -418,7 +407,6 @@
|
||||
"password": "Passord for øyeblikksbilde",
|
||||
"password_protected": "Passordbeskyttet",
|
||||
"password_protection": "Passordbeskyttelse",
|
||||
"passwords_not_matching": "Passordene samsvarer ikke",
|
||||
"security": "Sikkerhet",
|
||||
"select_type": "Velg hva du vil gjenopprette",
|
||||
"selected": "{number} valgt",
|
||||
@@ -1140,7 +1128,7 @@
|
||||
"zone": "Soner"
|
||||
},
|
||||
"reload": {
|
||||
"automation": "Automasjoner",
|
||||
"automation": "Automatisering",
|
||||
"command_line": "Kommandolinjeenheter",
|
||||
"core": "Plassering og tilpasninger",
|
||||
"filesize": "Enheter for filstørrelse",
|
||||
@@ -1448,7 +1436,7 @@
|
||||
"delete_confirm": "Er du sikker på at du vil slette dette?",
|
||||
"duplicate": "Dupliser",
|
||||
"header": "Betingelser",
|
||||
"introduction": "Betingelsene er valgfrie og vil hindre at automasjonen kjøres med mindre alle betingelsene er oppfylt.",
|
||||
"introduction": "Betingelsene er valgfrie og vil hindre at automatiseringen kjøres med mindre alle betingelsene er oppfylt.",
|
||||
"learn_more": "Lær mer om betingelser",
|
||||
"name": "Betingelse",
|
||||
"type_select": "Betingelse",
|
||||
@@ -1529,7 +1517,7 @@
|
||||
"edit_ui": "Rediger i visuell editor",
|
||||
"edit_yaml": "Rediger i YAML",
|
||||
"enable_disable": "Aktivere/deaktivere automasjon",
|
||||
"introduction": "Bruk automasjoner for å bringe hjemmet ditt til live.",
|
||||
"introduction": "Bruk automatisering for å bringe hjemmet ditt til live.",
|
||||
"load_error_not_editable": "Kun automasjoner i automations.yaml kan redigeres",
|
||||
"load_error_unknown": "Feil ved lasting av automasjon ({err_no}).",
|
||||
"max": {
|
||||
@@ -1657,8 +1645,8 @@
|
||||
"add_automation": "Legg til automasjon",
|
||||
"delete_automation": "Slett automasjon",
|
||||
"delete_confirm": "Er du sikker på at du vil slette denne automasjonen?",
|
||||
"dev_automation": "Feilsøk automasjon",
|
||||
"dev_only_editable": "Bare automasjoner som har en unik ID tildelt, kan feilsøkes.",
|
||||
"dev_automation": "Feilsøk automatisering",
|
||||
"dev_only_editable": "Bare automatisering som har en unik ID tildelt, kan feilsøkes.",
|
||||
"duplicate": "Dupliser",
|
||||
"duplicate_automation": "Dupliser automasjon",
|
||||
"edit_automation": "Rediger automasjon",
|
||||
@@ -1668,7 +1656,7 @@
|
||||
},
|
||||
"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",
|
||||
"no_automations": "Vi kunne ikke finne noen automasjoner",
|
||||
"no_automations": "Vi fant ingen automatiseringer",
|
||||
"only_editable": "Bare automasjoner definert i automations.yaml kan redigeres.",
|
||||
"pick_automation": "Velg automasjon for å redigere",
|
||||
"show_info_automation": "Vis informasjon om automasjon"
|
||||
@@ -1799,7 +1787,7 @@
|
||||
"target_browser": "Nettleser"
|
||||
},
|
||||
"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 automasjoner 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 automatiseringer og skript ved hjelp av tjenesten {service}.",
|
||||
"male": "Mann",
|
||||
"title": "Tekst til tale",
|
||||
"try": "Prøve"
|
||||
@@ -2152,7 +2140,7 @@
|
||||
"header": "Konfigurer Home Assistant",
|
||||
"helpers": {
|
||||
"caption": "Hjelpere",
|
||||
"description": "Elementer som hjelper med å bygge automasjoner",
|
||||
"description": "Elementer som hjelper med å bygge automatiseringer",
|
||||
"dialog": {
|
||||
"add_helper": "Legg hjelper",
|
||||
"add_platform": "Legg til {platform}",
|
||||
@@ -2686,7 +2674,7 @@
|
||||
"description": "Start på nytt og stopp Home Assistant-serveren",
|
||||
"section": {
|
||||
"reloading": {
|
||||
"automation": "Automasjoner",
|
||||
"automation": "Automatisering",
|
||||
"command_line": "Kommandolinjeenheter",
|
||||
"core": "Plassering og tilpasninger",
|
||||
"filesize": "Enheter for filstørrelse",
|
||||
@@ -2731,7 +2719,7 @@
|
||||
"validation": {
|
||||
"check_config": "Sjekk konfigurasjonen",
|
||||
"heading": "Validering av konfigurasjon",
|
||||
"introduction": "Bekreft konfigurasjonen hvis du nylig har gjort noen endringer i konfigurasjonen og vil være sikker på at den er gyldig.",
|
||||
"introduction": "Valider konfigurasjonen hvis du nylig har gjort endringer i konfigurasjonen og vil forsikre deg om at den er gyldig",
|
||||
"invalid": "Ugyldig konfigurasjon",
|
||||
"valid": "Gyldig konfigurasjon"
|
||||
}
|
||||
@@ -3891,19 +3879,10 @@
|
||||
"intro": "Er du klar til å ta kontroll over hjemmet ditt, gjenvinne ditt privatliv og bli med i et verdensomspennende samfunn av entusiaster?",
|
||||
"next": "Neste",
|
||||
"restore": {
|
||||
"addons": "Tillegg",
|
||||
"confirm_password": "Bekreft passord for øyeblikksbilde",
|
||||
"description": "Alternativt kan du gjenopprette fra et forrige snapshot.",
|
||||
"folders": "Mapper",
|
||||
"full_snapshot": "Full sikkerhetskopi",
|
||||
"hide_log": "Skjul full logg",
|
||||
"in_progress": "Gjenoppretting pågår",
|
||||
"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"
|
||||
"show_log": "Vis full logg"
|
||||
},
|
||||
"user": {
|
||||
"create_account": "Opprett konto",
|
||||
|
@@ -318,14 +318,6 @@
|
||||
"no_addons": "Je hebt nog geen add-ons geïnstalleerd. Ga naar de add-on store om te beginnen!"
|
||||
},
|
||||
"dialog": {
|
||||
"hardware": {
|
||||
"attributes": "Attributen",
|
||||
"device_path": "Apparaatpad",
|
||||
"id": "ID",
|
||||
"search": "Zoek hardware",
|
||||
"subsystem": "Subsysteem",
|
||||
"title": "Hardware"
|
||||
},
|
||||
"network": {
|
||||
"connected_to": "Verbonden met {ssid}",
|
||||
"dhcp": "DHCP",
|
||||
@@ -376,8 +368,6 @@
|
||||
"error": "Er is een onbekende fout opgetreden",
|
||||
"error_addon_no_ingress": "De gevraagde add-on ondersteunt geen ingress",
|
||||
"error_addon_not_found": "Invoegtoepassing niet gevonden",
|
||||
"error_addon_not_installed": "De gevraagde add-on is niet geïnstalleerd. Gelieve deze eerst te installeren",
|
||||
"error_addon_not_started": "De gevraagde add-on is niet actief. Start deze a.u.b. eerst",
|
||||
"faq_link": "My Home Assistant FAQ",
|
||||
"not_supported": "Deze redirect wordt niet ondersteund door uw Home Assistant instantie. Controleer de {link} voor de ondersteunde redirects en de versie waarin ze zijn geïntroduceerd."
|
||||
},
|
||||
@@ -390,7 +380,6 @@
|
||||
"snapshot": {
|
||||
"addons": "Invoegtoepassingen",
|
||||
"available_snapshots": "Beschikbare snapshots",
|
||||
"confirm_password": "Bevestig Snapshot-wachtwoord",
|
||||
"could_not_create": "Kon geen snapshot maken",
|
||||
"create": "Maak",
|
||||
"create_blocked_not_running": "Het maken van een snapshot is nu niet mogelijk omdat het systeem in {state} staat.",
|
||||
@@ -418,7 +407,6 @@
|
||||
"password": "Snapshot wachtwoord",
|
||||
"password_protected": "beveiligd met wachtwoord",
|
||||
"password_protection": "Wachtwoord bescherming",
|
||||
"passwords_not_matching": "De wachtwoorden komen niet overeen",
|
||||
"security": "Beveiliging",
|
||||
"select_type": "Selecteer wat u wilt herstellen",
|
||||
"selected": "{number} geselecteerd",
|
||||
@@ -941,7 +929,7 @@
|
||||
},
|
||||
"dialogs": {
|
||||
"config_entry_system_options": {
|
||||
"enable_new_entities_description": "Of nieuw ontdekte apparaten voor {integratie} automatisch moeten worden toegevoegd.",
|
||||
"enable_new_entities_description": "Indien uitgeschakeld, worden nieuwe entiteiten van {integration} niet automatisch aan Home Assistant toegevoegd.",
|
||||
"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_label": "Schakel polling voor updates in.",
|
||||
@@ -982,7 +970,7 @@
|
||||
},
|
||||
"faq": "documentatie",
|
||||
"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",
|
||||
"settings": "instellingen"
|
||||
},
|
||||
@@ -2088,8 +2076,7 @@
|
||||
"scripts": "Scripts",
|
||||
"unknown_error": "Onbekende fout",
|
||||
"unnamed_device": "Naamloos apparaat",
|
||||
"update": "Bijwerken",
|
||||
"update_device_error": "Updaten van het apparaat mislukt"
|
||||
"update": "Bijwerken"
|
||||
},
|
||||
"entities": {
|
||||
"caption": "Entiteiten",
|
||||
@@ -2222,7 +2209,6 @@
|
||||
"depends_on_cloud": "Cloud afhankelijk",
|
||||
"device_unavailable": "Apparaat niet beschikbaar",
|
||||
"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": {
|
||||
"disable_confirm": "Weet je zeker dat je deze configuratie wilt uitschakelen? Al diens apparaten en entiteiten zullen worden uitgeschakeld.",
|
||||
@@ -3891,19 +3877,10 @@
|
||||
"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",
|
||||
"restore": {
|
||||
"addons": "Invoegtoepassingen",
|
||||
"confirm_password": "Bevestig Snapshot-wachtwoord",
|
||||
"description": "Je kunt ook herstellen vanaf een eerdere snapshot.",
|
||||
"folders": "Mappen",
|
||||
"full_snapshot": "Volledige snapshot",
|
||||
"hide_log": "Verberg volledig logboek",
|
||||
"in_progress": "Herstel in uitvoering",
|
||||
"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"
|
||||
"show_log": "Volledig logboek weergeven"
|
||||
},
|
||||
"user": {
|
||||
"create_account": "Account aanmaken",
|
||||
|
@@ -318,14 +318,6 @@
|
||||
"no_addons": "Nie masz jeszcze zainstalowanych żadnych dodatków. Przejdź do sklepu z dodatkami, aby rozpocząć!"
|
||||
},
|
||||
"dialog": {
|
||||
"hardware": {
|
||||
"attributes": "Atrybuty",
|
||||
"device_path": "Ścieżka urządzenia",
|
||||
"id": "Identyfikator",
|
||||
"search": "Wyszukaj sprzęt",
|
||||
"subsystem": "Podsystem",
|
||||
"title": "Sprzęt"
|
||||
},
|
||||
"network": {
|
||||
"connected_to": "Połączono z {ssid}",
|
||||
"dhcp": "DHCP",
|
||||
@@ -376,8 +368,6 @@
|
||||
"error": "Wystąpił nieznany błąd",
|
||||
"error_addon_no_ingress": "Żądany dodatek nie obsługuje osadzania",
|
||||
"error_addon_not_found": "Nie znaleziono dodatku",
|
||||
"error_addon_not_installed": "Żądany dodatek nie jest zainstalowany. Najpierw go zainstaluj.",
|
||||
"error_addon_not_started": "Żądany dodatek nie jest uruchomiony. Proszę najpierw go uruchomić.",
|
||||
"faq_link": "Mój Home Assistant - często zadawane pytania",
|
||||
"not_supported": "To przekierowanie nie jest obsługiwane przez Twoją instancję Home Assistanta. Sprawdź {link} aby znaleźć obsługiwane przekierowania i wersję, w której zostały wprowadzone."
|
||||
},
|
||||
@@ -390,7 +380,6 @@
|
||||
"snapshot": {
|
||||
"addons": "Dodatki",
|
||||
"available_snapshots": "Dostępne snapshoty",
|
||||
"confirm_password": "Potwierdź hasło snapshota",
|
||||
"could_not_create": "Nie udało się utworzyć snapshota",
|
||||
"create": "Utwórz",
|
||||
"create_blocked_not_running": "Tworzenie snapshota nie jest teraz możliwe, ponieważ system jest w {state}.",
|
||||
@@ -418,7 +407,6 @@
|
||||
"password": "Hasło snapshota",
|
||||
"password_protected": "chroniony hasłem",
|
||||
"password_protection": "Ochrona hasłem",
|
||||
"passwords_not_matching": "Hasła nie są takie same",
|
||||
"security": "Bezpieczeństwo",
|
||||
"select_type": "Wybierz, co przywrócić",
|
||||
"selected": "wybrano {number}",
|
||||
@@ -941,11 +929,8 @@
|
||||
},
|
||||
"dialogs": {
|
||||
"config_entry_system_options": {
|
||||
"enable_new_entities_description": "Jeśli nowo odkryte urządzenia dla integracji {integration} powinny zostać dodane automatycznie.",
|
||||
"enable_new_entities_description": "Jeśli wyłączone, nowo wykryte encje integracji {integration} nie będą automatycznie dodawane do Home Assistanta.",
|
||||
"enable_new_entities_label": "Włącz dodawanie nowych encji.",
|
||||
"enable_polling_description": "Czy Home Assistant powinien automatycznie odpytywać encje {integration} w poszukiwaniu aktualizacji.",
|
||||
"enable_polling_label": "Włącz odpytywanie o aktualizacje.",
|
||||
"restart_home_assistant": "Aby zmiany zostały wprowadzone, musisz ponownie uruchomić Home Assistanta.",
|
||||
"title": "Opcje systemowe dla {integration}",
|
||||
"update": "Aktualizuj"
|
||||
},
|
||||
@@ -2088,8 +2073,7 @@
|
||||
"scripts": "Skrypty",
|
||||
"unknown_error": "Nieznany błąd",
|
||||
"unnamed_device": "Nienazwane urządzenie",
|
||||
"update": "Aktualizuj",
|
||||
"update_device_error": "Aktualizacja urządzenia nie powiodła się"
|
||||
"update": "Aktualizuj"
|
||||
},
|
||||
"entities": {
|
||||
"caption": "Rejestr encji",
|
||||
@@ -2222,7 +2206,6 @@
|
||||
"depends_on_cloud": "Zależny od chmury",
|
||||
"device_unavailable": "Urządzenie niedostępne",
|
||||
"devices": "{count} {count, plural,\n one {urządzenie}\n few {urządzenia}\n many {urządzeń}\n other {urządzeń}\n}",
|
||||
"disable_error": "Nie udało się włączyć lub wyłączyć integracji",
|
||||
"disable_restart_confirm": "Zrestartuj Home Assistanta, aby zakończyć wyłączanie tej integracji",
|
||||
"disable": {
|
||||
"disable_confirm": "Czy na pewno chcesz wyłączyć ten wpis w konfiguracji? Jego urządzenia i instancje zostaną wyłączone.",
|
||||
@@ -2234,7 +2217,6 @@
|
||||
},
|
||||
"disabled_cause": "Wyłączone przez {cause}."
|
||||
},
|
||||
"disabled_polling": "Automatyczne odpytywanie o zaktualizowane dane jest wyłączone",
|
||||
"documentation": "Dokumentacja",
|
||||
"enable_restart_confirm": "Uruchom ponownie Home Assistanta, aby dokończyć uruchamianie tej integracji",
|
||||
"entities": "{count} {count, plural,\n one {encja}\n few {encje}\n many {encji}\n other {encji}\n}",
|
||||
@@ -3891,19 +3873,10 @@
|
||||
"intro": "Czy jesteś gotowy, aby ożywić swój dom, odzyskać prywatność i dołączyć do światowej społeczności majsterkowiczów?",
|
||||
"next": "Dalej",
|
||||
"restore": {
|
||||
"addons": "Dodatki",
|
||||
"confirm_password": "Potwierdź hasło snapshota",
|
||||
"description": "Alternatywnie możesz przywrócić z poprzedniej migawki.",
|
||||
"folders": "Foldery",
|
||||
"full_snapshot": "Pełny snapshot",
|
||||
"hide_log": "Ukryj cały log",
|
||||
"in_progress": "Trwa przywracanie",
|
||||
"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"
|
||||
"show_log": "Pokaż cały log"
|
||||
},
|
||||
"user": {
|
||||
"create_account": "Utwórz konto",
|
||||
|
@@ -318,14 +318,6 @@
|
||||
"no_addons": "Нет установленных дополнений. Вы можете установить их из магазина дополнений."
|
||||
},
|
||||
"dialog": {
|
||||
"hardware": {
|
||||
"attributes": "Атрибуты",
|
||||
"device_path": "Путь к устройству",
|
||||
"id": "ID",
|
||||
"search": "Поиск оборудования",
|
||||
"subsystem": "Подсистема",
|
||||
"title": "Оборудование"
|
||||
},
|
||||
"network": {
|
||||
"connected_to": "Подключено к {ssid}",
|
||||
"dhcp": "DHCP",
|
||||
@@ -376,8 +368,6 @@
|
||||
"error": "Произошла неизвестная ошибка",
|
||||
"error_addon_no_ingress": "Дополнение не поддерживает ingress.",
|
||||
"error_addon_not_found": "Дополнение не найдено.",
|
||||
"error_addon_not_installed": "Запрошенное дополнение не установлено. Сначала нужно установить его.",
|
||||
"error_addon_not_started": "Запрашиваемое дополнение не запущено. Сначала нужно запустить его.",
|
||||
"faq_link": "часто задаваемыми вопросами по My Home Assistant",
|
||||
"not_supported": "Это перенаправление не поддерживается Вашим Home Assistant. Ознакомьтесь с {link}, чтобы узнать поддерживаемые перенаправления и версии, в которых они были добавлены."
|
||||
},
|
||||
@@ -390,7 +380,6 @@
|
||||
"snapshot": {
|
||||
"addons": "Дополнения",
|
||||
"available_snapshots": "Доступные снимки",
|
||||
"confirm_password": "Подтверждение пароля",
|
||||
"could_not_create": "Не удалось создать снимок",
|
||||
"create": "Создать",
|
||||
"create_blocked_not_running": "Создание снимка сейчас невозможно, потому что система находится в состоянии {state}.",
|
||||
@@ -411,18 +400,17 @@
|
||||
"ssl": "SSL"
|
||||
},
|
||||
"folders": "Папки",
|
||||
"full_snapshot": "Все файлы",
|
||||
"full_snapshot": "Полный",
|
||||
"name": "Название",
|
||||
"no_snapshots": "Не найдено ни одного снимка",
|
||||
"partial_snapshot": "Выбрать из списка",
|
||||
"partial_snapshot": "Выборочный",
|
||||
"password": "Пароль",
|
||||
"password_protected": "защищено паролем",
|
||||
"password_protection": "Защита паролем",
|
||||
"passwords_not_matching": "Пароли не совпадают",
|
||||
"security": "Безопасность",
|
||||
"select_type": "Выберите что нужно восстановить из этого снимка файловой системы",
|
||||
"select_type": "Выберите что нужно восстановить",
|
||||
"selected": "Выбрано: {number}",
|
||||
"type": "Выберите что должен включать в себя снимок файловой системы",
|
||||
"type": "Тип снимка",
|
||||
"upload_snapshot": "Загрузить снимок на сервер"
|
||||
},
|
||||
"store": {
|
||||
@@ -2731,7 +2719,7 @@
|
||||
"validation": {
|
||||
"check_config": "Начать проверку",
|
||||
"heading": "Проверка конфигурации",
|
||||
"introduction": "Выполните проверку конфигурации, если в неё были внесены изменения и Вы хотите убедиться в её работоспособности.",
|
||||
"introduction": "Проверьте файлы конфигурации, если Вы внесли в них изменения.",
|
||||
"invalid": "Ошибка в конфигурации",
|
||||
"valid": "Конфигурация выполнена верно"
|
||||
}
|
||||
@@ -3891,19 +3879,10 @@
|
||||
"intro": "Готовы ли Вы разбудить свой дом, вернуть свою конфиденциальность и присоединиться к всемирному сообществу?",
|
||||
"next": "Далее",
|
||||
"restore": {
|
||||
"addons": "Дополнения",
|
||||
"confirm_password": "Подтверждение пароля",
|
||||
"description": "Восстановить предыдущее состояние из снимка файловой системы (snapshot)",
|
||||
"folders": "Папки",
|
||||
"full_snapshot": "Все файлы",
|
||||
"hide_log": "Скрыть журнал",
|
||||
"in_progress": "Восстановление системы",
|
||||
"partial_snapshot": "Выбрать из списка",
|
||||
"password": "Пароль",
|
||||
"password_protection": "Защита паролем",
|
||||
"select_type": "Выберите что нужно восстановить из этого снимка файловой системы",
|
||||
"show_log": "Показать журнал",
|
||||
"type": "Выберите что должен включать в себя снимок файловой системы"
|
||||
"show_log": "Показать журнал"
|
||||
},
|
||||
"user": {
|
||||
"create_account": "Создать учётную запись",
|
||||
|
@@ -285,13 +285,6 @@
|
||||
"no_addons": "Zatiaľ nemáte nainštalované žiadne doplnky. Choďte do obchodu s doplnkami, aby ste mohli začať!"
|
||||
},
|
||||
"dialog": {
|
||||
"hardware": {
|
||||
"attributes": "Atribúty",
|
||||
"id": "ID",
|
||||
"search": "Vyhľadať zariadenia",
|
||||
"subsystem": "Subsystém",
|
||||
"title": "Hardvér"
|
||||
},
|
||||
"network": {
|
||||
"connected_to": "Pripojiť na {ssid}",
|
||||
"dhcp": "DHCP",
|
||||
@@ -353,17 +346,12 @@
|
||||
"snapshot": {
|
||||
"addons": "Doplnky",
|
||||
"available_snapshots": "Dostupné zálohy",
|
||||
"confirm_password": "Potvrdiť heslo zálohy",
|
||||
"could_not_create": "Nepodarilo sa vytvoriť zálohu",
|
||||
"create": "Vytvoriť",
|
||||
"create_blocked_not_running": "Nieje možné vytvoriť zálohu pretože systém je v stave {state}",
|
||||
"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",
|
||||
"enter_password": "Prosím napíšte heslo.",
|
||||
"failed_to_delete": "Nepodarilo sa odstrániť",
|
||||
"folder": {
|
||||
"addons/local": "Lokálne doplnky",
|
||||
"homeassistant": "konfigurácia Home Asistenta",
|
||||
@@ -379,7 +367,6 @@
|
||||
"password": "Heslo",
|
||||
"password_protected": "chránené heslo",
|
||||
"password_protection": "Ochrana Heslom",
|
||||
"passwords_not_matching": "Heslá sa nezhodujú",
|
||||
"security": "Zabezpečenie",
|
||||
"type": "Typ",
|
||||
"upload_snapshot": "Nahrať zálohu"
|
||||
@@ -438,7 +425,6 @@
|
||||
"leave_beta_description": "Získajte aktualizácie stabilnej verzie pre Home Assistant, supervízora a hostiteľa",
|
||||
"ram_usage": "Využitie RAM supervízorom",
|
||||
"reload_supervisor": "Znova načítať supervízora",
|
||||
"search": "Hľadať",
|
||||
"share_diagnostics": "Zdieľajte diagnostiku",
|
||||
"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.",
|
||||
@@ -536,7 +522,6 @@
|
||||
},
|
||||
"light": {
|
||||
"brightness": "Jas",
|
||||
"color_brightness": "Jas farby",
|
||||
"color_temperature": "Teplota farby",
|
||||
"effect": "Efekt",
|
||||
"white_value": "Hodnota bielej"
|
||||
@@ -675,9 +660,6 @@
|
||||
"no_match": "Nenašli sa žiadne zodpovedajúce oblasti",
|
||||
"show_areas": "Zobraziť oblasti"
|
||||
},
|
||||
"attributes": {
|
||||
"expansion_header": "Atribúty"
|
||||
},
|
||||
"blueprint-picker": {
|
||||
"add_user": "Pridať používateľa",
|
||||
"remove_user": "Odstrániť používateľa",
|
||||
@@ -688,9 +670,6 @@
|
||||
"today": "Dnes"
|
||||
},
|
||||
"data-table": {
|
||||
"clear": "Vyčistiť",
|
||||
"filtering_by": "Filtrovanie podľa",
|
||||
"hidden": "{number} skryté",
|
||||
"no-data": "Žiadne údaje",
|
||||
"search": "Hľadať"
|
||||
},
|
||||
@@ -746,8 +725,7 @@
|
||||
"was_plugged_in": "bol zapojený",
|
||||
"was_unlocked": "bol odomknutý",
|
||||
"was_unplugged": "bol odpojený"
|
||||
},
|
||||
"show_trace": "Zobraziť cestu"
|
||||
}
|
||||
},
|
||||
"media-browser": {
|
||||
"audio_not_supported": "Váš prehliadač nepodporuje zvukový prvok.",
|
||||
@@ -1039,11 +1017,6 @@
|
||||
"smtp": "Znova načítať notifikačné služby SMTP",
|
||||
"telegram": "Znova načítať notifikačné služby telegram",
|
||||
"zone": "Znova načítať zóny"
|
||||
},
|
||||
"types": {
|
||||
"navigation": "Navigovať",
|
||||
"reload": "Znova načítať",
|
||||
"server_control": "Server"
|
||||
}
|
||||
},
|
||||
"filter_placeholder": "Filter entít"
|
||||
@@ -1080,9 +1053,6 @@
|
||||
"zha_device_card": {
|
||||
"device_name_placeholder": "Zmeniť názov zariadenia"
|
||||
}
|
||||
},
|
||||
"zha_reconfigure_device": {
|
||||
"attribute": "Atribút"
|
||||
}
|
||||
},
|
||||
"duration": {
|
||||
@@ -1131,8 +1101,7 @@
|
||||
"caption": "Register oblastí",
|
||||
"data_table": {
|
||||
"area": "Oblasť",
|
||||
"devices": "Zariadenia",
|
||||
"entities": "Entity"
|
||||
"devices": "Zariadenia"
|
||||
},
|
||||
"delete": {
|
||||
"confirmation_text": "Všetky zariadenia v tejto oblasti ostanú nepriradené.",
|
||||
@@ -1144,7 +1113,6 @@
|
||||
"create": "VYTVORIŤ",
|
||||
"default_name": "Nová oblasť",
|
||||
"delete": "VYMAZAŤ",
|
||||
"linked_entities_caption": "Entity",
|
||||
"name": "Názov",
|
||||
"name_required": "Názov je povinný",
|
||||
"unknown_error": "Neznáma chyba",
|
||||
@@ -1204,14 +1172,10 @@
|
||||
"device_id": {
|
||||
"action": "Akcia",
|
||||
"extra_fields": {
|
||||
"brightness_pct": "Jas",
|
||||
"code": "Kód",
|
||||
"humidity": "Vlhkosť",
|
||||
"message": "Správa",
|
||||
"mode": "Režim",
|
||||
"position": "pozícia",
|
||||
"title": "Názov",
|
||||
"value": "Hodnota"
|
||||
"title": "Názov"
|
||||
},
|
||||
"label": "Zariadenie"
|
||||
},
|
||||
@@ -1268,9 +1232,7 @@
|
||||
"extra_fields": {
|
||||
"above": "Nad",
|
||||
"below": "Pod",
|
||||
"for": "Trvanie",
|
||||
"hvac_mode": "Režim HVAC",
|
||||
"preset_mode": "Vyber režim"
|
||||
"for": "Trvanie"
|
||||
},
|
||||
"label": "Zariadenie"
|
||||
},
|
||||
@@ -1347,7 +1309,6 @@
|
||||
"move_down": "Posunúť dole",
|
||||
"move_up": "Posunúť hore",
|
||||
"save": "Uložiť",
|
||||
"show_trace": "Zobraziť cestu",
|
||||
"triggers": {
|
||||
"add": "Pridať spúšťač",
|
||||
"delete": "Odstrániť",
|
||||
@@ -1450,8 +1411,6 @@
|
||||
"add_automation": "Pridať automatizáciu",
|
||||
"delete_automation": "Odstrániť 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_automation": "Duplikovať automatizáciu",
|
||||
"edit_automation": "Upraviť automatizáciu",
|
||||
@@ -1517,7 +1476,6 @@
|
||||
"title": "Alexa"
|
||||
},
|
||||
"connected": "Pripojené",
|
||||
"connecting": "Pripája sa",
|
||||
"connection_status": "Stav cloudového pripojenia",
|
||||
"fetching_subscription": "Načítava sa predplatné...",
|
||||
"google": {
|
||||
@@ -1539,15 +1497,10 @@
|
||||
"remote": {
|
||||
"access_is_being_prepared": "Pripravuje sa vzdialený prístup. Budeme vás informovať, keď bude pripravený.",
|
||||
"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.",
|
||||
"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",
|
||||
"link_learn_how_it_works": "Zistite, ako to funguje",
|
||||
"not_connected": "Nepripojené",
|
||||
"remote_enabled": {
|
||||
"caption": "Automaticky pripojiť"
|
||||
},
|
||||
"title": "Diaľkové ovládanie"
|
||||
},
|
||||
"sign_out": "Odhlásiť sa",
|
||||
@@ -1684,17 +1637,6 @@
|
||||
"description": "Jednotkový systém, umiestnenie, časové pásmo a ďalšie všeobecné parametre",
|
||||
"section": {
|
||||
"core": {
|
||||
"analytics": {
|
||||
"header": "Analízy",
|
||||
"preference": {
|
||||
"base": {
|
||||
"title": "Základné analýzy"
|
||||
},
|
||||
"diagnostics": {
|
||||
"title": "Diagnostiky"
|
||||
}
|
||||
}
|
||||
},
|
||||
"core_config": {
|
||||
"edit_requires_storage": "Editor je zablokovaný, pretože konfigurácia je uložená v configuration.yaml",
|
||||
"elevation": "Nadmorská výška",
|
||||
@@ -1900,7 +1842,6 @@
|
||||
"license": "Publikované pod licenciou Apache 2.0",
|
||||
"path_configuration": "Cesta k configuration.yaml: {path}",
|
||||
"server": "server",
|
||||
"setup_time": "Nastaviť čas",
|
||||
"source": "Zdroj:",
|
||||
"system_health_error": "Súčasť System Health nie je načítaná. Pridajte 'system_health:' do súboru configuration.yaml",
|
||||
"system_health": {
|
||||
@@ -1960,7 +1901,6 @@
|
||||
},
|
||||
"finish": "Dokončiť",
|
||||
"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.",
|
||||
"pick_flow_step": {
|
||||
"new_flow": "Nie, nastaviť inú inštanciu {integration}",
|
||||
@@ -1976,7 +1916,6 @@
|
||||
"disable": {
|
||||
"disabled_integrations": "{number} deaktivované",
|
||||
"hide_disabled": "Skryť vypnuté integrácie",
|
||||
"show": "Ukázať",
|
||||
"show_disabled": "Zobraziť vypnuté integrácie"
|
||||
},
|
||||
"discovered": "Objavené",
|
||||
@@ -2226,7 +2165,6 @@
|
||||
"add_scene": "Pridať scénu",
|
||||
"delete_confirm": "Naozaj chcete odstrániť túto scénu?",
|
||||
"delete_scene": "Odstrániť scénu",
|
||||
"duplicate": "Duplikovať",
|
||||
"edit_scene": "Upraviť scénu",
|
||||
"header": "Editor scén",
|
||||
"headers": {
|
||||
@@ -2720,7 +2658,6 @@
|
||||
"go_back": "Vrátiť sa späť",
|
||||
"supervisor": {
|
||||
"ask": "Požiadať o pomoc",
|
||||
"observer": "Skontrolujte pozorovateľa",
|
||||
"reboot": "Skúsiť reštartovať hostiteľa",
|
||||
"system_health": "Overiť stav systému",
|
||||
"title": "Nepodarilo sa načítať panel supervízora!",
|
||||
@@ -3356,19 +3293,10 @@
|
||||
},
|
||||
"intro": "Ste pripravení prebudiť váš domov, získať vaše súkromie a pripojiť sa k celosvetovej komunite bastličov?",
|
||||
"restore": {
|
||||
"addons": "Doplnky",
|
||||
"confirm_password": "Potvrďte heslo zálohy",
|
||||
"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",
|
||||
"in_progress": "Prebieha obnovenie",
|
||||
"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"
|
||||
"show_log": "Zobraziť celý denník"
|
||||
},
|
||||
"user": {
|
||||
"create_account": "Vytvoriť účet",
|
||||
@@ -3454,18 +3382,6 @@
|
||||
"enable": "Povoliť",
|
||||
"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": "1 234 567,89"
|
||||
},
|
||||
"header": "Formát čísla"
|
||||
},
|
||||
"push_notifications": {
|
||||
"description": "Posielať upozornenia na toto zariadenie.",
|
||||
"error_load_platform": "Konfigurujte upozornenia notify.html5.",
|
||||
@@ -3502,14 +3418,6 @@
|
||||
"primary_color": "Primárna farba",
|
||||
"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": {
|
||||
"description": "Pri ovládaní zariadení povoľte alebo zakážte vibrácie tohto zariadenia.",
|
||||
"header": "Vibrácie"
|
||||
|
@@ -131,12 +131,6 @@
|
||||
"uninstall": "Kunde inte avinstallera tillägg"
|
||||
},
|
||||
"capability": {
|
||||
"auth_api": {
|
||||
"title": "Home Assistant-autentisering"
|
||||
},
|
||||
"label": {
|
||||
"hardware": "hårdvara"
|
||||
},
|
||||
"rating": {
|
||||
"title": "Tilläggets säkerhetsrating"
|
||||
},
|
||||
@@ -148,7 +142,6 @@
|
||||
"manager": "föreståndare"
|
||||
}
|
||||
},
|
||||
"changelog": "Ändringslogg",
|
||||
"cpu_usage": "Tillägg CPU-användning",
|
||||
"hostname": "Värdnamn",
|
||||
"install": "installera",
|
||||
@@ -181,7 +174,6 @@
|
||||
"title": "Varning: Skyddsläge är inaktiverat!"
|
||||
},
|
||||
"ram_usage": "Tillägg RAM-användning",
|
||||
"rebuild": "Bygg om",
|
||||
"restart": "omstart",
|
||||
"start": "starta",
|
||||
"stop": "stoppa",
|
||||
@@ -196,11 +188,6 @@
|
||||
"documentation": "Dokumentation",
|
||||
"info": "Info",
|
||||
"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": {
|
||||
@@ -214,7 +201,7 @@
|
||||
"failed_to_update_name": "Kunde inte uppdatera {name}",
|
||||
"learn_more": "Läs mer",
|
||||
"new_version_available": "Ny version tillgänglig",
|
||||
"newest_version": "Senaste version",
|
||||
"newest_version": "Nyaste version",
|
||||
"refresh": "Uppdatera",
|
||||
"reload": "Ladda om",
|
||||
"reset_defaults": "Nollställ till standard",
|
||||
@@ -241,11 +228,6 @@
|
||||
"title": "Uppdatera {name}"
|
||||
}
|
||||
},
|
||||
"dashboard": {
|
||||
"addon_new_version": "Ny version tillgänglig",
|
||||
"addon_running": "Add-on körs",
|
||||
"addons": "Installerade add-ons"
|
||||
},
|
||||
"dialog": {
|
||||
"network": {
|
||||
"connected_to": "Ansluten till {ssid}",
|
||||
@@ -294,11 +276,8 @@
|
||||
"my": {
|
||||
"error": "Ett okänt fel inträffade",
|
||||
"error_addon_not_found": "Tillägget hittades inte",
|
||||
"faq_link": "Mitt Home Assistant FAQ",
|
||||
"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"
|
||||
"faq_link": "Min 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."
|
||||
},
|
||||
"snapshot": {
|
||||
"addons": "Tillägg",
|
||||
@@ -324,7 +303,6 @@
|
||||
"password_protected": "lösenordskyddad",
|
||||
"password_protection": "Lösenordsskydd",
|
||||
"security": "Säkerhet",
|
||||
"select_type": "Välj vad som ska återställas",
|
||||
"type": "Typ",
|
||||
"upload_snapshot": "Ladda upp avbild"
|
||||
},
|
||||
@@ -373,7 +351,6 @@
|
||||
"leave_beta_description": "Få stabila uppdateringar för Home Assistant, Supervisor och värd",
|
||||
"ram_usage": "Supervisor RAM-användning",
|
||||
"reload_supervisor": "Ladda om Supervisor",
|
||||
"search": "Sök",
|
||||
"share_diagnostics": "Dela diagnostik",
|
||||
"share_diagnostics_description": "Dela kraschrapporter och diagnostisk information.",
|
||||
"share_diagonstics_title": "Hjälp till att förbättra Home Assistant",
|
||||
@@ -587,19 +564,6 @@
|
||||
"yes": "Ja"
|
||||
},
|
||||
"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": {
|
||||
"add_dialog": {
|
||||
"add": "Lägg till",
|
||||
@@ -774,11 +738,6 @@
|
||||
"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": "Tjänst"
|
||||
},
|
||||
@@ -1913,8 +1872,7 @@
|
||||
},
|
||||
"filtering": {
|
||||
"clear": "Rensa",
|
||||
"filtering_by": "Filtrera efter",
|
||||
"show": "Visa"
|
||||
"filtering_by": "Filtrera efter"
|
||||
},
|
||||
"hassio": {
|
||||
"button": "Konfigurera"
|
||||
@@ -2019,10 +1977,8 @@
|
||||
"config_flow": {
|
||||
"aborted": "Avbruten",
|
||||
"close": "Stäng",
|
||||
"could_not_load": "Konfigurationsflödet kunde inte laddas",
|
||||
"created_config": "Skapad konfiguration för {name}.",
|
||||
"dismiss": "Avfärda dialogrutan",
|
||||
"error": "Fel",
|
||||
"error_saving_area": "Fel vid sparande av område: {error}",
|
||||
"external_step": {
|
||||
"description": "Det här steget kräver att du besöker en extern webbplats för att slutföra.",
|
||||
@@ -2031,21 +1987,15 @@
|
||||
"finish": "Slutför",
|
||||
"loading_first_time": "Vänligen vänta medan integrationen installeras",
|
||||
"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"
|
||||
},
|
||||
"configure": "Konfigurera",
|
||||
"configured": "Konfigurerad",
|
||||
"confirm_new": "Vill du konfigurera {integration}?",
|
||||
"description": "Hantera integrationer med tjänster, enheter, ...",
|
||||
"details": "Integrationsdetaljer",
|
||||
"disable": {
|
||||
"disabled_integrations": "{number} inaktiverad(e)",
|
||||
"hide_disabled": "Dölj inaktiverade integrationer",
|
||||
"show_disabled": "Visa inaktiverade integrationer"
|
||||
"hide_disabled": "Dölj inaktiverade integrationer"
|
||||
},
|
||||
"discovered": "Upptäckt",
|
||||
"home_assistant_website": "Home Assistants hemsida",
|
||||
@@ -2864,17 +2814,13 @@
|
||||
"type": "Händelsetyp"
|
||||
},
|
||||
"services": {
|
||||
"all_parameters": "Alla tillgängliga parametrar",
|
||||
"call_service": "Anropa tjänst",
|
||||
"column_description": "Beskrivning",
|
||||
"column_example": "Exempel",
|
||||
"column_parameter": "Parameter",
|
||||
"description": "Utvecklarverktyget för tjänster låter dig anropa alla tillgängliga tjänster i Home Assistant.",
|
||||
"fill_example_data": "Fyll på exempeldata",
|
||||
"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"
|
||||
"title": "Tjänster"
|
||||
},
|
||||
"states": {
|
||||
"alert_entity_field": "Enhet är ett obligatoriskt fält",
|
||||
@@ -3413,12 +3359,7 @@
|
||||
"playback_title": "Meddelandeuppspelning"
|
||||
},
|
||||
"my": {
|
||||
"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."
|
||||
"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."
|
||||
},
|
||||
"page-authorize": {
|
||||
"abort_intro": "Inloggning avbruten",
|
||||
@@ -3516,7 +3457,7 @@
|
||||
"working": "Vänligen vänta"
|
||||
},
|
||||
"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}**.",
|
||||
"pick_auth_provider": "Eller logga in med"
|
||||
},
|
||||
@@ -3674,15 +3615,6 @@
|
||||
"enable": "Aktivera",
|
||||
"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": {
|
||||
"add_device_prompt": {
|
||||
"input_label": "Enhetsnamn",
|
||||
@@ -3724,17 +3656,6 @@
|
||||
"primary_color": "Primär färg",
|
||||
"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": {
|
||||
"description": "Aktivera eller inaktivera vibration på den här enheten när du styr enheter.",
|
||||
"header": "Vibrera"
|
||||
|
@@ -318,14 +318,6 @@
|
||||
"no_addons": "您尚未安装任何加载项。去加载项商店看看吧!"
|
||||
},
|
||||
"dialog": {
|
||||
"hardware": {
|
||||
"attributes": "属性",
|
||||
"device_path": "设备路径",
|
||||
"id": "ID",
|
||||
"search": "搜索硬件",
|
||||
"subsystem": "子系统",
|
||||
"title": "硬件"
|
||||
},
|
||||
"network": {
|
||||
"connected_to": "已连接到 {ssid}",
|
||||
"dhcp": "DHCP",
|
||||
@@ -376,8 +368,6 @@
|
||||
"error": "发生未知错误",
|
||||
"error_addon_no_ingress": "请求的加载项不支持 ingress",
|
||||
"error_addon_not_found": "未找到加载项",
|
||||
"error_addon_not_installed": "请求的加载项尚未安装,请先安装",
|
||||
"error_addon_not_started": "请求的加载项未运行,请先运行",
|
||||
"faq_link": "我的 Home Assistant 常见问题",
|
||||
"not_supported": "您的 Home Assistant 不支持此重定向。请查阅{link}以获取受支持的重定向及其引入的版本。"
|
||||
},
|
||||
@@ -390,7 +380,6 @@
|
||||
"snapshot": {
|
||||
"addons": "加载项",
|
||||
"available_snapshots": "可用快照",
|
||||
"confirm_password": "确认快照密码",
|
||||
"could_not_create": "未能创建快照",
|
||||
"create": "创建",
|
||||
"create_blocked_not_running": "现在无法创建快照,因为系统处于{state}状态。",
|
||||
@@ -418,7 +407,6 @@
|
||||
"password": "密码",
|
||||
"password_protected": "密码保护",
|
||||
"password_protection": "密码保护",
|
||||
"passwords_not_matching": "密码不匹配",
|
||||
"security": "安全性",
|
||||
"select_type": "请选择要还原的内容",
|
||||
"selected": "已选择 {number} 项",
|
||||
@@ -1818,7 +1806,7 @@
|
||||
}
|
||||
},
|
||||
"alexa": {
|
||||
"banner": "无法通过此 UI 修改要开放的实体,因为您在 configuration.yaml 中配置了实体过滤器。",
|
||||
"banner": "由于您在 configuration.yaml 中配置了实体过滤器,无法通过此 UI 修改要开放的实体。",
|
||||
"dont_expose_entity": "使实体不可发现",
|
||||
"expose": "向Alexa发送你的位置",
|
||||
"expose_entity": "使实体可发现",
|
||||
@@ -2731,7 +2719,7 @@
|
||||
"validation": {
|
||||
"check_config": "检查配置",
|
||||
"heading": "配置检查",
|
||||
"introduction": "此处可以帮助您检验最新修改的配置文件的有效性。",
|
||||
"introduction": "此处可以帮助您检验最新修改的配置文件有效性",
|
||||
"invalid": "配置无效",
|
||||
"valid": "配置有效!"
|
||||
}
|
||||
@@ -3891,19 +3879,10 @@
|
||||
"intro": "准备好唤醒你的家、找回你的隐私,并加入世界级的极客社区了吗?",
|
||||
"next": "下一步",
|
||||
"restore": {
|
||||
"addons": "加载项",
|
||||
"confirm_password": "确认快照密码",
|
||||
"description": "或者,您也可以从以前的快照还原。",
|
||||
"folders": "文件夹",
|
||||
"full_snapshot": "完整快照",
|
||||
"hide_log": "隐藏完整日志",
|
||||
"in_progress": "正在还原",
|
||||
"partial_snapshot": "部分快照",
|
||||
"password": "密码",
|
||||
"password_protection": "密码保护",
|
||||
"select_type": "请选择要还原的内容",
|
||||
"show_log": "显示完整日志",
|
||||
"type": "类型"
|
||||
"show_log": "显示完整日志"
|
||||
},
|
||||
"user": {
|
||||
"create_account": "创建帐户",
|
||||
|
@@ -318,14 +318,6 @@
|
||||
"no_addons": "目前似乎沒有安裝任何附加元件。點選下方附加元件商店以新增!"
|
||||
},
|
||||
"dialog": {
|
||||
"hardware": {
|
||||
"attributes": "屬性",
|
||||
"device_path": "裝置路徑",
|
||||
"id": "ID",
|
||||
"search": "搜尋硬體",
|
||||
"subsystem": "子系統",
|
||||
"title": "硬體"
|
||||
},
|
||||
"network": {
|
||||
"connected_to": "已連線至 {ssid}",
|
||||
"dhcp": "DHCP",
|
||||
@@ -376,8 +368,6 @@
|
||||
"error": "發生未知錯誤",
|
||||
"error_addon_no_ingress": "所要求的附加元件不支援 ingress",
|
||||
"error_addon_not_found": "找不到附加元件",
|
||||
"error_addon_not_installed": "所要求的附加元件未安裝,請先進行安裝。",
|
||||
"error_addon_not_started": "所要求的附加元件未執行,請先進行啟動。",
|
||||
"faq_link": "Home Assistant 常見問答集",
|
||||
"not_supported": "Home Assistant 不支援此重新導向。點選 {link} 獲取支援之重新導向與版本。"
|
||||
},
|
||||
@@ -390,7 +380,6 @@
|
||||
"snapshot": {
|
||||
"addons": "附加元件",
|
||||
"available_snapshots": "可用系統備份",
|
||||
"confirm_password": "確認系統備份密碼",
|
||||
"could_not_create": "無法製作系統備份",
|
||||
"create": "新增",
|
||||
"create_blocked_not_running": "由於系統為 {state} 狀態,無法製作系統備份。",
|
||||
@@ -418,7 +407,6 @@
|
||||
"password": "系統備份密碼",
|
||||
"password_protected": "密碼保護未顯示",
|
||||
"password_protection": "密碼保護",
|
||||
"passwords_not_matching": "密碼不相符",
|
||||
"security": "加密",
|
||||
"select_type": "選擇所要回復內容",
|
||||
"selected": "已選擇 {number} 個",
|
||||
@@ -2731,7 +2719,7 @@
|
||||
"validation": {
|
||||
"check_config": "檢查設定內容",
|
||||
"heading": "設定驗證",
|
||||
"introduction": "如果您對設定進行了部分更改、並且想確保設定有無錯誤,可以選擇驗證設定內容。",
|
||||
"introduction": "如果您對設定進行了一些更改、並且想確保設定有無錯誤,可以選擇驗證設定內容。",
|
||||
"invalid": "設定無效",
|
||||
"valid": "設定檔內容檢查正確"
|
||||
}
|
||||
@@ -3891,19 +3879,10 @@
|
||||
"intro": "準備喚醒您的智慧型家庭、取得隱私自主權,並加入由全球愛好者共同維護的社群了嗎?",
|
||||
"next": "下一步",
|
||||
"restore": {
|
||||
"addons": "附加元件",
|
||||
"confirm_password": "確認系統備份密碼",
|
||||
"description": "或者可以自之前的 Snapshot 進行回復。",
|
||||
"folders": "資料夾",
|
||||
"full_snapshot": "全系統備份",
|
||||
"hide_log": "隱藏完整日誌",
|
||||
"in_progress": "回復進行中",
|
||||
"partial_snapshot": "部分系統備份",
|
||||
"password": "系統備份密碼",
|
||||
"password_protection": "密碼保護",
|
||||
"select_type": "選擇所要回復內容",
|
||||
"show_log": "顯示完整日誌",
|
||||
"type": "系統備份類型"
|
||||
"show_log": "顯示完整日誌"
|
||||
},
|
||||
"user": {
|
||||
"create_account": "創建帳號",
|
||||
|
26
yarn.lock
26
yarn.lock
@@ -2104,6 +2104,13 @@
|
||||
"@polymer/iron-meta" "^3.0.0-pre.26"
|
||||
"@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":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@polymer/iron-input/-/iron-input-3.0.1.tgz#dc866a25107f9b38d9ca4512dd9a3e51b78b4915"
|
||||
@@ -2113,6 +2120,13 @@
|
||||
"@polymer/iron-validatable-behavior" "^3.0.0-pre.26"
|
||||
"@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":
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@polymer/iron-list/-/iron-list-3.0.2.tgz#9e6b80e503328dc29217dbe26f94faa47adb4124"
|
||||
@@ -4027,9 +4041,9 @@ async-each@^1.0.0, async-each@^1.0.1:
|
||||
integrity sha512-6xrbvN0MOBKSJDdonmSSz2OwFSgxRaVtBDes26mj9KIGtDo+g9xosFRSC+i1gQh2oAN/tQ62AI/pGZGQjVOiRg==
|
||||
|
||||
async-limiter@~1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd"
|
||||
integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8"
|
||||
integrity sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==
|
||||
|
||||
async-settle@^1.0.0:
|
||||
version "1.0.0"
|
||||
@@ -13489,9 +13503,9 @@ write@1.0.3:
|
||||
mkdirp "^0.5.1"
|
||||
|
||||
ws@^6.2.1:
|
||||
version "6.2.2"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.2.tgz#dd5cdbd57a9979916097652d78f1cc5faea0c32e"
|
||||
integrity sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==
|
||||
version "6.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb"
|
||||
integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==
|
||||
dependencies:
|
||||
async-limiter "~1.0.0"
|
||||
|
||||
|
Reference in New Issue
Block a user