mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-01 13:37:47 +00:00
Merge pull request #6765 from home-assistant/dev
This commit is contained in:
commit
ba3cc7df0f
@ -9,21 +9,19 @@ import {
|
||||
mdiExclamationThick,
|
||||
mdiFlask,
|
||||
mdiHomeAssistant,
|
||||
mdiInformation,
|
||||
mdiKey,
|
||||
mdiNetwork,
|
||||
mdiPound,
|
||||
mdiShield,
|
||||
} from "@mdi/js";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
@ -35,6 +33,7 @@ import "../../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/components/ha-label-badge";
|
||||
import "../../../../src/components/ha-markdown";
|
||||
import "../../../../src/components/ha-settings-row";
|
||||
import "../../../../src/components/ha-svg-icon";
|
||||
import "../../../../src/components/ha-switch";
|
||||
import {
|
||||
@ -386,67 +385,94 @@ class HassioAddonInfo extends LitElement {
|
||||
|
||||
${this.addon.version
|
||||
? html`
|
||||
<div class="state">
|
||||
<div>Start on boot</div>
|
||||
<ha-switch
|
||||
@change=${this._startOnBootToggled}
|
||||
.checked=${this.addon.boot === "auto"}
|
||||
haptic
|
||||
></ha-switch>
|
||||
</div>
|
||||
${this.addon.auto_update || this.hass.userData?.showAdvanced
|
||||
? html`
|
||||
<div class="state">
|
||||
<div>Auto update</div>
|
||||
<ha-switch
|
||||
@change=${this._autoUpdateToggled}
|
||||
.checked=${this.addon.auto_update}
|
||||
haptic
|
||||
></ha-switch>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
${this.addon.ingress
|
||||
? html`
|
||||
<div class="state">
|
||||
<div>Show in sidebar</div>
|
||||
<ha-switch
|
||||
@change=${this._panelToggled}
|
||||
.checked=${this.addon.ingress_panel}
|
||||
.disabled=${this._computeCannotIngressSidebar}
|
||||
haptic
|
||||
></ha-switch>
|
||||
${this._computeCannotIngressSidebar
|
||||
? html`
|
||||
<span>
|
||||
This option requires Home Assistant 0.92 or
|
||||
later.
|
||||
</span>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
${this._computeUsesProtectedOptions
|
||||
? html`
|
||||
<div class="state">
|
||||
<div>
|
||||
Protection mode
|
||||
<span>
|
||||
<ha-svg-icon path=${mdiInformation}></ha-svg-icon>
|
||||
<paper-tooltip>
|
||||
Grant the add-on elevated system access.
|
||||
</paper-tooltip>
|
||||
<div class="addon-options">
|
||||
<ha-settings-row ?three-line=${this.narrow}>
|
||||
<span slot="heading">
|
||||
Start on boot
|
||||
</span>
|
||||
<span slot="description">
|
||||
Make the add-on start during a system boot
|
||||
</span>
|
||||
<ha-switch
|
||||
@change=${this._startOnBootToggled}
|
||||
.checked=${this.addon.boot === "auto"}
|
||||
haptic
|
||||
></ha-switch>
|
||||
</ha-settings-row>
|
||||
|
||||
${this.hass.userData?.showAdvanced
|
||||
? html`
|
||||
<ha-settings-row ?three-line=${this.narrow}>
|
||||
<span slot="heading">
|
||||
Watchdog
|
||||
</span>
|
||||
</div>
|
||||
<ha-switch
|
||||
@change=${this._protectionToggled}
|
||||
.checked=${this.addon.protected}
|
||||
haptic
|
||||
></ha-switch>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<span slot="description">
|
||||
This will start the add-on if it crashes
|
||||
</span>
|
||||
<ha-switch
|
||||
@change=${this._watchdogToggled}
|
||||
.checked=${this.addon.watchdog}
|
||||
haptic
|
||||
></ha-switch>
|
||||
</ha-settings-row>
|
||||
`
|
||||
: ""}
|
||||
${this.addon.auto_update || this.hass.userData?.showAdvanced
|
||||
? html`
|
||||
<ha-settings-row ?three-line=${this.narrow}>
|
||||
<span slot="heading">
|
||||
Auto update
|
||||
</span>
|
||||
<span slot="description">
|
||||
Auto update the add-on when there is a new version
|
||||
available
|
||||
</span>
|
||||
<ha-switch
|
||||
@change=${this._autoUpdateToggled}
|
||||
.checked=${this.addon.auto_update}
|
||||
haptic
|
||||
></ha-switch>
|
||||
</ha-settings-row>
|
||||
`
|
||||
: ""}
|
||||
${this.addon.ingress
|
||||
? html`
|
||||
<ha-settings-row ?three-line=${this.narrow}>
|
||||
<span slot="heading">
|
||||
Show in sidebar
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this._computeCannotIngressSidebar
|
||||
? "This option requires Home Assistant 0.92 or later."
|
||||
: "Add this add-on to your sidebar"}
|
||||
</span>
|
||||
<ha-switch
|
||||
@change=${this._panelToggled}
|
||||
.checked=${this.addon.ingress_panel}
|
||||
.disabled=${this._computeCannotIngressSidebar}
|
||||
haptic
|
||||
></ha-switch>
|
||||
</ha-settings-row>
|
||||
`
|
||||
: ""}
|
||||
${this._computeUsesProtectedOptions
|
||||
? html`
|
||||
<ha-settings-row ?three-line=${this.narrow}>
|
||||
<span slot="heading">
|
||||
Protection mode
|
||||
</span>
|
||||
<span slot="description">
|
||||
Blocks elevated system access from the add-on
|
||||
</span>
|
||||
<ha-switch
|
||||
@change=${this._protectionToggled}
|
||||
.checked=${this.addon.protected}
|
||||
haptic
|
||||
></ha-switch>
|
||||
</ha-settings-row>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
|
||||
@ -552,137 +578,6 @@ class HassioAddonInfo extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
hassioStyle,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
ha-card {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
ha-card.warning {
|
||||
background-color: var(--error-color);
|
||||
color: white;
|
||||
}
|
||||
ha-card.warning .card-header {
|
||||
color: white;
|
||||
}
|
||||
ha-card.warning .card-content {
|
||||
color: white;
|
||||
}
|
||||
ha-card.warning mwc-button {
|
||||
--mdc-theme-primary: white !important;
|
||||
}
|
||||
.warning {
|
||||
color: var(--error-color);
|
||||
--mdc-theme-primary: var(--error-color);
|
||||
}
|
||||
.light-color {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.addon-header {
|
||||
padding-left: 8px;
|
||||
font-size: 24px;
|
||||
color: var(--ha-card-header-color, --primary-text-color);
|
||||
}
|
||||
.addon-version {
|
||||
float: right;
|
||||
font-size: 15px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.errors {
|
||||
color: var(--error-color);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.description {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
img.logo {
|
||||
max-height: 60px;
|
||||
margin: 16px 0;
|
||||
display: block;
|
||||
}
|
||||
.state {
|
||||
display: flex;
|
||||
margin: 33px 0;
|
||||
}
|
||||
.state div {
|
||||
width: 180px;
|
||||
display: inline-block;
|
||||
}
|
||||
.state ha-svg-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
ha-switch {
|
||||
display: flex;
|
||||
}
|
||||
ha-svg-icon.running {
|
||||
color: var(--paper-green-400);
|
||||
}
|
||||
ha-svg-icon.stopped {
|
||||
color: var(--google-red-300);
|
||||
}
|
||||
ha-call-api-button {
|
||||
font-weight: 500;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
.right {
|
||||
float: right;
|
||||
}
|
||||
protection-enable mwc-button {
|
||||
--mdc-theme-primary: white;
|
||||
}
|
||||
.description a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
.red {
|
||||
--ha-label-badge-color: var(--label-badge-red, #df4c1e);
|
||||
}
|
||||
.blue {
|
||||
--ha-label-badge-color: var(--label-badge-blue, #039be5);
|
||||
}
|
||||
.green {
|
||||
--ha-label-badge-color: var(--label-badge-green, #0da035);
|
||||
}
|
||||
.yellow {
|
||||
--ha-label-badge-color: var(--label-badge-yellow, #f4b400);
|
||||
}
|
||||
.security {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.card-actions {
|
||||
display: flow-root;
|
||||
}
|
||||
.security h3 {
|
||||
margin-bottom: 8px;
|
||||
font-weight: normal;
|
||||
}
|
||||
.security ha-label-badge {
|
||||
cursor: pointer;
|
||||
margin-right: 4px;
|
||||
--ha-label-badge-padding: 8px 0 0 0;
|
||||
}
|
||||
.changelog {
|
||||
display: contents;
|
||||
}
|
||||
.changelog-link {
|
||||
color: var(--primary-color);
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
ha-markdown {
|
||||
padding: 16px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
private get _computeHassioApi(): boolean {
|
||||
return (
|
||||
this.addon.hassio_api &&
|
||||
@ -771,6 +666,24 @@ class HassioAddonInfo extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private async _watchdogToggled(): Promise<void> {
|
||||
this._error = undefined;
|
||||
const data: HassioAddonSetOptionParams = {
|
||||
watchdog: !this.addon.watchdog,
|
||||
};
|
||||
try {
|
||||
await setHassioAddonOption(this.hass, this.addon.slug, data);
|
||||
const eventdata = {
|
||||
success: true,
|
||||
response: undefined,
|
||||
path: "option",
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err) {
|
||||
this._error = `Failed to set addon option, ${err.body?.message || err}`;
|
||||
}
|
||||
}
|
||||
|
||||
private async _autoUpdateToggled(): Promise<void> {
|
||||
this._error = undefined;
|
||||
const data: HassioAddonSetOptionParams = {
|
||||
@ -887,6 +800,146 @@ class HassioAddonInfo extends LitElement {
|
||||
this._error = `Failed to uninstall addon, ${err.body?.message || err}`;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
hassioStyle,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
ha-card {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
ha-card.warning {
|
||||
background-color: var(--error-color);
|
||||
color: white;
|
||||
}
|
||||
ha-card.warning .card-header {
|
||||
color: white;
|
||||
}
|
||||
ha-card.warning .card-content {
|
||||
color: white;
|
||||
}
|
||||
ha-card.warning mwc-button {
|
||||
--mdc-theme-primary: white !important;
|
||||
}
|
||||
.warning {
|
||||
color: var(--error-color);
|
||||
--mdc-theme-primary: var(--error-color);
|
||||
}
|
||||
.light-color {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.addon-header {
|
||||
padding-left: 8px;
|
||||
font-size: 24px;
|
||||
color: var(--ha-card-header-color, --primary-text-color);
|
||||
}
|
||||
.addon-version {
|
||||
float: right;
|
||||
font-size: 15px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.errors {
|
||||
color: var(--error-color);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.description {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
img.logo {
|
||||
max-height: 60px;
|
||||
margin: 16px 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
ha-switch {
|
||||
display: flex;
|
||||
}
|
||||
ha-svg-icon.running {
|
||||
color: var(--paper-green-400);
|
||||
}
|
||||
ha-svg-icon.stopped {
|
||||
color: var(--google-red-300);
|
||||
}
|
||||
ha-call-api-button {
|
||||
font-weight: 500;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
.right {
|
||||
float: right;
|
||||
}
|
||||
protection-enable mwc-button {
|
||||
--mdc-theme-primary: white;
|
||||
}
|
||||
.description a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
.red {
|
||||
--ha-label-badge-color: var(--label-badge-red, #df4c1e);
|
||||
}
|
||||
.blue {
|
||||
--ha-label-badge-color: var(--label-badge-blue, #039be5);
|
||||
}
|
||||
.green {
|
||||
--ha-label-badge-color: var(--label-badge-green, #0da035);
|
||||
}
|
||||
.yellow {
|
||||
--ha-label-badge-color: var(--label-badge-yellow, #f4b400);
|
||||
}
|
||||
.security {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.card-actions {
|
||||
display: flow-root;
|
||||
}
|
||||
.security h3 {
|
||||
margin-bottom: 8px;
|
||||
font-weight: normal;
|
||||
}
|
||||
.security ha-label-badge {
|
||||
cursor: pointer;
|
||||
margin-right: 4px;
|
||||
--ha-label-badge-padding: 8px 0 0 0;
|
||||
}
|
||||
.changelog {
|
||||
display: contents;
|
||||
}
|
||||
.changelog-link {
|
||||
color: var(--primary-color);
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
ha-markdown {
|
||||
padding: 16px;
|
||||
}
|
||||
ha-settings-row {
|
||||
padding: 0;
|
||||
height: 54px;
|
||||
width: 100%;
|
||||
}
|
||||
ha-settings-row > span[slot="description"] {
|
||||
white-space: normal;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
ha-settings-row[three-line] {
|
||||
height: 74px;
|
||||
}
|
||||
|
||||
.addon-options {
|
||||
max-width: 50%;
|
||||
}
|
||||
@media (max-width: 720px) {
|
||||
.addon-options {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
|
@ -21,7 +21,7 @@ interface State {
|
||||
class HassioAnsiToHtml extends LitElement {
|
||||
@property() public content!: string;
|
||||
|
||||
public render(): TemplateResult | void {
|
||||
protected render(): TemplateResult | void {
|
||||
return html`${this._parseTextToColoredPre(this.content)}`;
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "../../../src/components/buttons/ha-call-api-button";
|
||||
import "../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../src/components/ha-card";
|
||||
import "../../../src/components/ha-svg-icon";
|
||||
import { HassioHassOSInfo } from "../../../src/data/hassio/host";
|
||||
@ -21,6 +21,11 @@ import {
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
import {
|
||||
showConfirmationDialog,
|
||||
showAlertDialog,
|
||||
} from "../../../src/dialogs/generic/show-dialog-box";
|
||||
import { HassioResponse } from "../../../src/data/hassio/common";
|
||||
|
||||
@customElement("hassio-update")
|
||||
export class HassioUpdate extends LitElement {
|
||||
@ -126,31 +131,50 @@ export class HassioUpdate extends LitElement {
|
||||
<a href="${releaseNotesUrl}" target="_blank" rel="noreferrer">
|
||||
<mwc-button>Release notes</mwc-button>
|
||||
</a>
|
||||
<ha-call-api-button
|
||||
.hass=${this.hass}
|
||||
.path=${apiPath}
|
||||
@hass-api-called=${this._apiCalled}
|
||||
<ha-progress-button
|
||||
.apiPath=${apiPath}
|
||||
.name=${name}
|
||||
.version=${lastVersion}
|
||||
@click=${this._confirmUpdate}
|
||||
>
|
||||
Update
|
||||
</ha-call-api-button>
|
||||
</ha-progress-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
private _apiCalled(ev): void {
|
||||
if (ev.detail.success) {
|
||||
this._error = "";
|
||||
private async _confirmUpdate(ev): Promise<void> {
|
||||
const item = ev.target;
|
||||
item.progress = true;
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: `Update ${item.name}`,
|
||||
text: `Are you sure you want to upgrade ${item.name} to version ${item.version}?`,
|
||||
confirmText: "update",
|
||||
dismissText: "cancel",
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
item.progress = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const response = ev.detail.response;
|
||||
|
||||
if (typeof response.body === "object") {
|
||||
this._error = response.body.message || "Unknown error";
|
||||
} else {
|
||||
this._error = response.body;
|
||||
try {
|
||||
await this.hass.callApi<HassioResponse<void>>("POST", item.apiPath);
|
||||
} catch (err) {
|
||||
// Only show an error if the status code was not 504 (timeout reported by proxies)
|
||||
if (err.status_code !== 504) {
|
||||
showAlertDialog(this, {
|
||||
title: "Update failed",
|
||||
text:
|
||||
typeof err === "object"
|
||||
? typeof err.body === "object"
|
||||
? err.body.message
|
||||
: err.body || "Unkown error"
|
||||
: err,
|
||||
});
|
||||
}
|
||||
}
|
||||
item.progress = false;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
|
335
hassio/src/dialogs/network/dialog-hassio-network.ts
Normal file
335
hassio/src/dialogs/network/dialog-hassio-network.ts
Normal file
@ -0,0 +1,335 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@material/mwc-icon-button";
|
||||
import "@material/mwc-tab-bar";
|
||||
import "@material/mwc-tab";
|
||||
import { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||
import { mdiClose } from "@mdi/js";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { cache } from "lit-html/directives/cache";
|
||||
|
||||
import {
|
||||
updateNetworkInterface,
|
||||
NetworkInterface,
|
||||
} from "../../../../src/data/hassio/network";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import { HassioNetworkDialogParams } from "./show-dialog-network";
|
||||
import { haStyleDialog } from "../../../../src/resources/styles";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
} from "../../../../src/dialogs/generic/show-dialog-box";
|
||||
import type { HomeAssistant } from "../../../../src/types";
|
||||
import type { HaRadio } from "../../../../src/components/ha-radio";
|
||||
import { HassDialog } from "../../../../src/dialogs/make-dialog-manager";
|
||||
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import "../../../../src/components/ha-dialog";
|
||||
import "../../../../src/components/ha-formfield";
|
||||
import "../../../../src/components/ha-header-bar";
|
||||
import "../../../../src/components/ha-radio";
|
||||
import "../../../../src/components/ha-related-items";
|
||||
import "../../../../src/components/ha-svg-icon";
|
||||
|
||||
@customElement("dialog-hassio-network")
|
||||
export class DialogHassioNetwork extends LitElement implements HassDialog {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@internalProperty() private _prosessing = false;
|
||||
|
||||
@internalProperty() private _params?: HassioNetworkDialogParams;
|
||||
|
||||
@internalProperty() private _network!: {
|
||||
interface: string;
|
||||
data: NetworkInterface;
|
||||
}[];
|
||||
|
||||
@internalProperty() private _curTabIndex = 0;
|
||||
|
||||
@internalProperty() private _device?: {
|
||||
interface: string;
|
||||
data: NetworkInterface;
|
||||
};
|
||||
|
||||
@internalProperty() private _dirty = false;
|
||||
|
||||
public async showDialog(params: HassioNetworkDialogParams): Promise<void> {
|
||||
this._params = params;
|
||||
this._dirty = false;
|
||||
this._curTabIndex = 0;
|
||||
this._network = Object.keys(params.network?.interfaces)
|
||||
.map((device) => ({
|
||||
interface: device,
|
||||
data: params.network.interfaces[device],
|
||||
}))
|
||||
.sort((a, b) => {
|
||||
return a.data.primary > b.data.primary ? -1 : 1;
|
||||
});
|
||||
this._device = this._network[this._curTabIndex];
|
||||
this._device.data.nameservers = String(this._device.data.nameservers);
|
||||
await this.updateComplete;
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this._params = undefined;
|
||||
this._prosessing = false;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._params || !this._network) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
scrimClickAction
|
||||
escapeKeyAction
|
||||
.heading=${true}
|
||||
hideActions
|
||||
@closed=${this.closeDialog}
|
||||
>
|
||||
<div slot="heading">
|
||||
<ha-header-bar>
|
||||
<span slot="title">
|
||||
Network settings
|
||||
</span>
|
||||
<mwc-icon-button slot="actionItems" dialogAction="cancel">
|
||||
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
</ha-header-bar>
|
||||
${this._network.length > 1
|
||||
? html` <mwc-tab-bar
|
||||
.activeIndex=${this._curTabIndex}
|
||||
@MDCTabBar:activated=${this._handleTabActivated}
|
||||
>${this._network.map(
|
||||
(device) =>
|
||||
html`<mwc-tab
|
||||
.id=${device.interface}
|
||||
.label=${device.interface}
|
||||
>
|
||||
</mwc-tab>`
|
||||
)}
|
||||
</mwc-tab-bar>`
|
||||
: ""}
|
||||
</div>
|
||||
${cache(this._renderTab())}
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderTab() {
|
||||
return html` <div class="form container">
|
||||
<ha-formfield label="DHCP">
|
||||
<ha-radio
|
||||
@change=${this._handleRadioValueChanged}
|
||||
value="dhcp"
|
||||
name="method"
|
||||
?checked=${this._device!.data.method === "dhcp"}
|
||||
>
|
||||
</ha-radio>
|
||||
</ha-formfield>
|
||||
<ha-formfield label="Static">
|
||||
<ha-radio
|
||||
@change=${this._handleRadioValueChanged}
|
||||
value="static"
|
||||
name="method"
|
||||
?checked=${this._device!.data.method === "static"}
|
||||
>
|
||||
</ha-radio>
|
||||
</ha-formfield>
|
||||
${this._device!.data.method !== "dhcp"
|
||||
? html` <paper-input
|
||||
class="flex-auto"
|
||||
id="ip_address"
|
||||
label="IP address/Netmask"
|
||||
.value="${this._device!.data.ip_address}"
|
||||
@value-changed=${this._handleInputValueChanged}
|
||||
></paper-input>
|
||||
<paper-input
|
||||
class="flex-auto"
|
||||
id="gateway"
|
||||
label="Gateway address"
|
||||
.value="${this._device!.data.gateway}"
|
||||
@value-changed=${this._handleInputValueChanged}
|
||||
></paper-input>
|
||||
<paper-input
|
||||
class="flex-auto"
|
||||
id="nameservers"
|
||||
label="DNS servers"
|
||||
.value="${this._device!.data.nameservers as string}"
|
||||
@value-changed=${this._handleInputValueChanged}
|
||||
></paper-input>
|
||||
NB!: If you are changing IP or gateway addresses, you might lose
|
||||
the connection.`
|
||||
: ""}
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<mwc-button label="close" @click=${this.closeDialog}> </mwc-button>
|
||||
<mwc-button @click=${this._updateNetwork} ?disabled=${!this._dirty}>
|
||||
${this._prosessing
|
||||
? html`<ha-circular-progress active></ha-circular-progress>`
|
||||
: "Update"}
|
||||
</mwc-button>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
private async _updateNetwork() {
|
||||
this._prosessing = true;
|
||||
let options: Partial<NetworkInterface> = {
|
||||
method: this._device!.data.method,
|
||||
};
|
||||
if (options.method !== "dhcp") {
|
||||
options = {
|
||||
...options,
|
||||
address: this._device!.data.ip_address,
|
||||
gateway: this._device!.data.gateway,
|
||||
dns: String(this._device!.data.nameservers).split(","),
|
||||
};
|
||||
}
|
||||
try {
|
||||
await updateNetworkInterface(this.hass, this._device!.interface, options);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to change network settings",
|
||||
text:
|
||||
typeof err === "object" ? err.body.message || "Unkown error" : err,
|
||||
});
|
||||
this._prosessing = false;
|
||||
return;
|
||||
}
|
||||
this._params?.loadData();
|
||||
this.closeDialog();
|
||||
}
|
||||
|
||||
private async _handleTabActivated(ev: CustomEvent): Promise<void> {
|
||||
if (this._dirty) {
|
||||
const confirm = await showConfirmationDialog(this, {
|
||||
text:
|
||||
"You have unsaved changes, these will get lost if you change tabs, do you want to continue?",
|
||||
confirmText: "yes",
|
||||
dismissText: "no",
|
||||
});
|
||||
if (!confirm) {
|
||||
this.requestUpdate("_device");
|
||||
return;
|
||||
}
|
||||
}
|
||||
this._curTabIndex = ev.detail.index;
|
||||
this._device = this._network[ev.detail.index];
|
||||
this._device.data.nameservers = String(this._device.data.nameservers);
|
||||
}
|
||||
|
||||
private _handleRadioValueChanged(ev: CustomEvent): void {
|
||||
const value = (ev.target as HaRadio).value as "dhcp" | "static";
|
||||
|
||||
if (!value || !this._device || this._device!.data.method === value) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._dirty = true;
|
||||
|
||||
this._device!.data.method = value;
|
||||
this.requestUpdate("_device");
|
||||
}
|
||||
|
||||
private _handleInputValueChanged(ev: CustomEvent): void {
|
||||
const value: string | null | undefined = (ev.target as PaperInputElement)
|
||||
.value;
|
||||
const id = (ev.target as PaperInputElement).id;
|
||||
|
||||
if (!value || !this._device || this._device.data[id] === value) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._dirty = true;
|
||||
|
||||
this._device.data[id] = value;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-header-bar {
|
||||
--mdc-theme-on-primary: var(--primary-text-color);
|
||||
--mdc-theme-primary: var(--mdc-theme-surface);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
mwc-tab-bar {
|
||||
border-bottom: 1px solid
|
||||
var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12));
|
||||
}
|
||||
|
||||
ha-dialog {
|
||||
--dialog-content-position: static;
|
||||
--dialog-content-padding: 0;
|
||||
--dialog-z-index: 6;
|
||||
}
|
||||
|
||||
@media all and (min-width: 451px) and (min-height: 501px) {
|
||||
.container {
|
||||
width: 400px;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
display: block;
|
||||
padding: 20px 24px;
|
||||
}
|
||||
|
||||
/* overrule the ha-style-dialog max-height on small screens */
|
||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||
ha-header-bar {
|
||||
--mdc-theme-primary: var(--app-header-background-color);
|
||||
--mdc-theme-on-primary: var(--app-header-text-color, white);
|
||||
}
|
||||
}
|
||||
|
||||
mwc-button.warning {
|
||||
--mdc-theme-primary: var(--error-color);
|
||||
}
|
||||
|
||||
:host([rtl]) app-toolbar {
|
||||
direction: rtl;
|
||||
text-align: right;
|
||||
}
|
||||
.container {
|
||||
padding: 20px 24px;
|
||||
}
|
||||
.form {
|
||||
margin-bottom: 53px;
|
||||
}
|
||||
.buttons {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
border-top: 1px solid
|
||||
var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12));
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 8px;
|
||||
padding-bottom: max(env(safe-area-inset-bottom), 8px);
|
||||
background-color: var(--mdc-theme-surface, #fff);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-hassio-network": DialogHassioNetwork;
|
||||
}
|
||||
}
|
22
hassio/src/dialogs/network/show-dialog-network.ts
Normal file
22
hassio/src/dialogs/network/show-dialog-network.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import { NetworkInfo } from "../../../../src/data/hassio/network";
|
||||
import "./dialog-hassio-network";
|
||||
|
||||
export interface HassioNetworkDialogParams {
|
||||
network: NetworkInfo;
|
||||
loadData: () => Promise<void>;
|
||||
}
|
||||
|
||||
export const showNetworkDialog = (
|
||||
element: HTMLElement,
|
||||
dialogParams: HassioNetworkDialogParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-hassio-network",
|
||||
dialogImport: () =>
|
||||
import(
|
||||
/* webpackChunkName: "dialog-hassio-network" */ "./dialog-hassio-network"
|
||||
),
|
||||
dialogParams,
|
||||
});
|
||||
};
|
@ -1,18 +1,26 @@
|
||||
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 { safeDump } from "js-yaml";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "../../../src/components/buttons/ha-call-api-button";
|
||||
import memoizeOne from "memoize-one";
|
||||
import "../../../src/components/ha-button-menu";
|
||||
import "../../../src/components/ha-card";
|
||||
import "../../../src/components/ha-settings-row";
|
||||
import { fetchHassioHardwareInfo } from "../../../src/data/hassio/hardware";
|
||||
import {
|
||||
changeHostOptions,
|
||||
configSyncOS,
|
||||
fetchHassioHostInfo,
|
||||
HassioHassOSInfo,
|
||||
HassioHostInfo as HassioHostInfoType,
|
||||
@ -20,6 +28,10 @@ import {
|
||||
shutdownHost,
|
||||
updateOS,
|
||||
} from "../../../src/data/hassio/host";
|
||||
import {
|
||||
fetchNetworkInfo,
|
||||
NetworkInfo,
|
||||
} from "../../../src/data/hassio/network";
|
||||
import { HassioInfo } from "../../../src/data/hassio/supervisor";
|
||||
import {
|
||||
showAlertDialog,
|
||||
@ -29,6 +41,7 @@ import {
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import { showHassioMarkdownDialog } from "../dialogs/markdown/show-dialog-hassio-markdown";
|
||||
import { showNetworkDialog } from "../dialogs/network/show-dialog-network";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
|
||||
@customElement("hassio-host-info")
|
||||
@ -41,86 +54,130 @@ class HassioHostInfo extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public hassOsInfo!: HassioHassOSInfo;
|
||||
|
||||
@internalProperty() private _errors?: string;
|
||||
@internalProperty() public _networkInfo?: NetworkInfo;
|
||||
|
||||
public render(): TemplateResult | void {
|
||||
protected render(): TemplateResult | void {
|
||||
const primaryIpAddress = this.hostInfo.features.includes("network")
|
||||
? this._primaryIpAddress(this._networkInfo!)
|
||||
: "";
|
||||
return html`
|
||||
<ha-card>
|
||||
<ha-card header="Host System">
|
||||
<div class="card-content">
|
||||
<h2>Host system</h2>
|
||||
<table class="info">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Hostname</td>
|
||||
<td>${this.hostInfo.hostname}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>System</td>
|
||||
<td>${this.hostInfo.operating_system}</td>
|
||||
</tr>
|
||||
${!this.hostInfo.features.includes("hassos")
|
||||
? html`<tr>
|
||||
<td>Docker version</td>
|
||||
<td>${this.hassioInfo.docker}</td>
|
||||
</tr>`
|
||||
: ""}
|
||||
${this.hostInfo.deployment
|
||||
? html`
|
||||
<tr>
|
||||
<td>Deployment</td>
|
||||
<td>${this.hostInfo.deployment}</td>
|
||||
</tr>
|
||||
`
|
||||
: ""}
|
||||
</tbody>
|
||||
</table>
|
||||
<mwc-button raised @click=${this._showHardware} class="info">
|
||||
Hardware
|
||||
</mwc-button>
|
||||
${this.hostInfo.features.includes("hostname")
|
||||
? html`
|
||||
? html`<ha-settings-row>
|
||||
<span slot="heading">
|
||||
Hostname
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this.hostInfo.hostname}
|
||||
</span>
|
||||
<mwc-button
|
||||
raised
|
||||
title="Change the hostname"
|
||||
label="Change"
|
||||
@click=${this._changeHostnameClicked}
|
||||
class="info"
|
||||
>
|
||||
Change hostname
|
||||
</mwc-button>
|
||||
`
|
||||
</ha-settings-row>`
|
||||
: ""}
|
||||
${this._errors
|
||||
? html` <div class="errors">Error: ${this._errors}</div> `
|
||||
${this.hostInfo.features.includes("network")
|
||||
? html` <ha-settings-row>
|
||||
<span slot="heading">
|
||||
IP address
|
||||
</span>
|
||||
<span slot="description">
|
||||
${primaryIpAddress}
|
||||
</span>
|
||||
<mwc-button
|
||||
title="Change the network"
|
||||
label="Change"
|
||||
@click=${this._changeNetworkClicked}
|
||||
>
|
||||
</mwc-button>
|
||||
</ha-settings-row>`
|
||||
: ""}
|
||||
|
||||
<ha-settings-row>
|
||||
<span slot="heading">
|
||||
Operating system
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this.hostInfo.operating_system}
|
||||
</span>
|
||||
${this.hostInfo.version !== this.hostInfo.version_latest &&
|
||||
this.hostInfo.features.includes("hassos")
|
||||
? html`
|
||||
<mwc-button
|
||||
title="Update the host OS"
|
||||
label="Update"
|
||||
@click=${this._osUpdate}
|
||||
>
|
||||
</mwc-button>
|
||||
`
|
||||
: ""}
|
||||
</ha-settings-row>
|
||||
${!this.hostInfo.features.includes("hassos")
|
||||
? html`<ha-settings-row>
|
||||
<span slot="heading">
|
||||
Docker version
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this.hassioInfo.docker}
|
||||
</span>
|
||||
</ha-settings-row>`
|
||||
: ""}
|
||||
${this.hostInfo.deployment
|
||||
? html`<ha-settings-row>
|
||||
<span slot="heading">
|
||||
Deployment
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this.hostInfo.deployment}
|
||||
</span>
|
||||
</ha-settings-row>`
|
||||
: ""}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
${this.hostInfo.features.includes("reboot")
|
||||
? html`
|
||||
<mwc-button class="warning" @click=${this._rebootHost}
|
||||
>Reboot</mwc-button
|
||||
<mwc-button
|
||||
title="Reboot the host OS"
|
||||
label="Reboot"
|
||||
class="warning"
|
||||
@click=${this._hostReboot}
|
||||
>
|
||||
</mwc-button>
|
||||
`
|
||||
: ""}
|
||||
${this.hostInfo.features.includes("shutdown")
|
||||
? html`
|
||||
<mwc-button class="warning" @click=${this._shutdownHost}
|
||||
>Shutdown</mwc-button
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
${this.hostInfo.features.includes("hassos")
|
||||
? html`
|
||||
<ha-call-api-button
|
||||
<mwc-button
|
||||
title="Shutdown the host OS"
|
||||
label="Shutdown"
|
||||
class="warning"
|
||||
.hass=${this.hass}
|
||||
path="hassio/os/config/sync"
|
||||
title="Load HassOS configs or updates from USB"
|
||||
>Import from USB</ha-call-api-button
|
||||
@click=${this._hostShutdown}
|
||||
>
|
||||
</mwc-button>
|
||||
`
|
||||
: ""}
|
||||
${this.hostInfo.version !== this.hostInfo.version_latest
|
||||
? html` <mwc-button @click=${this._updateOS}>Update</mwc-button> `
|
||||
: ""}
|
||||
|
||||
<ha-button-menu
|
||||
corner="BOTTOM_START"
|
||||
@action=${this._handleMenuAction}
|
||||
>
|
||||
<mwc-icon-button slot="trigger">
|
||||
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<mwc-list-item title="Show a list of hardware">
|
||||
Hardware
|
||||
</mwc-list-item>
|
||||
${this.hostInfo.features.includes("hassos")
|
||||
? html`<mwc-list-item
|
||||
title="Load HassOS configs or updates from USB"
|
||||
>
|
||||
Import from USB
|
||||
</mwc-list-item>`
|
||||
: ""}
|
||||
</ha-button-menu>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
@ -133,72 +190,96 @@ class HassioHostInfo extends LitElement {
|
||||
css`
|
||||
ha-card {
|
||||
height: 100%;
|
||||
justify-content: space-between;
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
}
|
||||
.card-actions {
|
||||
height: 48px;
|
||||
border-top: none;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
ha-settings-row {
|
||||
padding: 0;
|
||||
height: 54px;
|
||||
width: 100%;
|
||||
}
|
||||
.card-content {
|
||||
color: var(--primary-text-color);
|
||||
box-sizing: border-box;
|
||||
height: calc(100% - 47px);
|
||||
ha-settings-row[three-line] {
|
||||
height: 74px;
|
||||
}
|
||||
.info {
|
||||
width: 100%;
|
||||
}
|
||||
.info td:nth-child(2) {
|
||||
text-align: right;
|
||||
}
|
||||
.errors {
|
||||
color: var(--error-color);
|
||||
margin-top: 16px;
|
||||
}
|
||||
mwc-button.info {
|
||||
max-width: calc(50% - 12px);
|
||||
}
|
||||
table.info {
|
||||
margin-bottom: 10px;
|
||||
ha-settings-row > span[slot="description"] {
|
||||
white-space: normal;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
.warning {
|
||||
--mdc-theme-primary: var(--error-color);
|
||||
}
|
||||
|
||||
ha-button-menu {
|
||||
color: var(--secondary-text-color);
|
||||
--mdc-menu-min-width: 200px;
|
||||
}
|
||||
@media (min-width: 563px) {
|
||||
paper-listbox {
|
||||
max-height: 150px;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
paper-item {
|
||||
cursor: pointer;
|
||||
min-height: 35px;
|
||||
}
|
||||
mwc-list-item ha-svg-icon {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
protected firstUpdated(): void {
|
||||
this.addEventListener("hass-api-called", (ev) => this._apiCalled(ev));
|
||||
this._loadData();
|
||||
}
|
||||
|
||||
private _apiCalled(ev): void {
|
||||
if (ev.detail.success) {
|
||||
this._errors = undefined;
|
||||
return;
|
||||
private _primaryIpAddress = memoizeOne((network_info: NetworkInfo) => {
|
||||
if (!network_info) {
|
||||
return "";
|
||||
}
|
||||
return Object.keys(network_info?.interfaces)
|
||||
.map((device) => network_info.interfaces[device])
|
||||
.find((device) => device.primary)?.ip_address;
|
||||
});
|
||||
|
||||
const response = ev.detail.response;
|
||||
|
||||
this._errors =
|
||||
typeof response.body === "object"
|
||||
? response.body.message || "Unknown error"
|
||||
: response.body;
|
||||
private async _handleMenuAction(ev: CustomEvent<ActionDetail>) {
|
||||
switch (ev.detail.index) {
|
||||
case 0:
|
||||
await this._showHardware();
|
||||
break;
|
||||
case 1:
|
||||
await this._importFromUSB();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async _showHardware(): Promise<void> {
|
||||
try {
|
||||
const content = this._objectToMarkdown(
|
||||
await fetchHassioHardwareInfo(this.hass)
|
||||
);
|
||||
const content = await fetchHassioHardwareInfo(this.hass);
|
||||
showHassioMarkdownDialog(this, {
|
||||
title: "Hardware",
|
||||
content,
|
||||
content: `<pre>${safeDump(content, { indent: 2 })}</pre>`,
|
||||
});
|
||||
} catch (err) {
|
||||
showHassioMarkdownDialog(this, {
|
||||
title: "Hardware",
|
||||
content: "Error getting hardware info",
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to get Hardware list",
|
||||
text:
|
||||
typeof err === "object" ? err.body?.message || "Unkown error" : err,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async _rebootHost(): Promise<void> {
|
||||
private async _hostReboot(): Promise<void> {
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: "Reboot",
|
||||
text: "Are you sure you want to reboot the host?",
|
||||
@ -215,12 +296,13 @@ class HassioHostInfo extends LitElement {
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to reboot",
|
||||
text: err.body.message,
|
||||
text:
|
||||
typeof err === "object" ? err.body?.message || "Unkown error" : err,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async _shutdownHost(): Promise<void> {
|
||||
private async _hostShutdown(): Promise<void> {
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: "Shutdown",
|
||||
text: "Are you sure you want to shutdown the host?",
|
||||
@ -237,12 +319,13 @@ class HassioHostInfo extends LitElement {
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to shutdown",
|
||||
text: err.body.message,
|
||||
text:
|
||||
typeof err === "object" ? err.body?.message || "Unkown error" : err,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async _updateOS(): Promise<void> {
|
||||
private async _osUpdate(): Promise<void> {
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: "Update",
|
||||
text: "Are you sure you want to update the OS?",
|
||||
@ -259,30 +342,17 @@ class HassioHostInfo extends LitElement {
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to update",
|
||||
text: err.body.message,
|
||||
text:
|
||||
typeof err === "object" ? err.body?.message || "Unkown error" : err,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _objectToMarkdown(obj, indent = ""): string {
|
||||
let data = "";
|
||||
Object.keys(obj).forEach((key) => {
|
||||
if (typeof obj[key] !== "object") {
|
||||
data += `${indent}- ${key}: ${obj[key]}\n`;
|
||||
} else {
|
||||
data += `${indent}- ${key}:\n`;
|
||||
if (Array.isArray(obj[key])) {
|
||||
if (obj[key].length) {
|
||||
data +=
|
||||
`${indent} - ` + obj[key].join(`\n${indent} - `) + "\n";
|
||||
}
|
||||
} else {
|
||||
data += this._objectToMarkdown(obj[key], ` ${indent}`);
|
||||
}
|
||||
}
|
||||
private async _changeNetworkClicked(): Promise<void> {
|
||||
showNetworkDialog(this, {
|
||||
network: this._networkInfo!,
|
||||
loadData: () => this._loadData(),
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private async _changeHostnameClicked(): Promise<void> {
|
||||
@ -301,11 +371,29 @@ class HassioHostInfo extends LitElement {
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Setting hostname failed",
|
||||
text: err.body.message,
|
||||
text:
|
||||
typeof err === "object" ? err.body?.message || "Unkown error" : err,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async _importFromUSB(): Promise<void> {
|
||||
try {
|
||||
await configSyncOS(this.hass);
|
||||
this.hostInfo = await fetchHassioHostInfo(this.hass);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to import from USB",
|
||||
text:
|
||||
typeof err === "object" ? err.body?.message || "Unkown error" : err,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async _loadData(): Promise<void> {
|
||||
this._networkInfo = await fetchNetworkInfo(this.hass);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@ -6,24 +6,23 @@ import {
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
import "../../../src/components/buttons/ha-call-api-button";
|
||||
import "../../../src/components/ha-card";
|
||||
import "../../../src/components/ha-settings-row";
|
||||
import "../../../src/components/ha-switch";
|
||||
import { HassioHostInfo as HassioHostInfoType } from "../../../src/data/hassio/host";
|
||||
import {
|
||||
HassioSupervisorInfo as HassioSupervisorInfoType,
|
||||
reloadSupervisor,
|
||||
setSupervisorOption,
|
||||
SupervisorOptions,
|
||||
updateSupervisor,
|
||||
} from "../../../src/data/hassio/supervisor";
|
||||
import "../../../src/components/ha-switch";
|
||||
import {
|
||||
showConfirmationDialog,
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
} from "../../../src/dialogs/generic/show-dialog-box";
|
||||
import "../../../src/components/ha-settings-row";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
@ -36,104 +35,108 @@ class HassioSupervisorInfo extends LitElement {
|
||||
|
||||
@property() public hostInfo!: HassioHostInfoType;
|
||||
|
||||
@internalProperty() private _errors?: string;
|
||||
|
||||
public render(): TemplateResult | void {
|
||||
protected render(): TemplateResult | void {
|
||||
return html`
|
||||
<ha-card>
|
||||
<ha-card header="Supervisor">
|
||||
<div class="card-content">
|
||||
<h2>Supervisor</h2>
|
||||
<table class="info">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Version</td>
|
||||
<td>${this.supervisorInfo.version}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Latest version</td>
|
||||
<td>${this.supervisorInfo.version_latest}</td>
|
||||
</tr>
|
||||
${this.supervisorInfo.channel !== "stable"
|
||||
? html`
|
||||
<tr>
|
||||
<td>Channel</td>
|
||||
<td>${this.supervisorInfo.channel}</td>
|
||||
</tr>
|
||||
`
|
||||
: ""}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="options">
|
||||
${this.supervisorInfo?.supported
|
||||
? html` <ha-settings-row>
|
||||
<span slot="heading">
|
||||
Share Diagnostics
|
||||
</span>
|
||||
<div slot="description" class="diagnostics-description">
|
||||
Share crash reports and diagnostic information.
|
||||
<button
|
||||
class="link"
|
||||
@click=${this._diagnosticsInformationDialog}
|
||||
>
|
||||
Learn more
|
||||
</button>
|
||||
</div>
|
||||
<ha-switch
|
||||
.checked=${this.supervisorInfo.diagnostics}
|
||||
@change=${this._toggleDiagnostics}
|
||||
></ha-switch>
|
||||
</ha-settings-row>`
|
||||
: html`<div class="error">
|
||||
You are running an unsupported installation.
|
||||
<a
|
||||
href="https://github.com/home-assistant/architecture/blob/master/adr/${this.hostInfo.features.includes(
|
||||
"hassos"
|
||||
)
|
||||
? "0015-home-assistant-os.md"
|
||||
: "0014-home-assistant-supervised.md"}"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>Learn More</a
|
||||
<ha-settings-row>
|
||||
<span slot="heading">
|
||||
Version
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this.supervisorInfo.version}
|
||||
</span>
|
||||
</ha-settings-row>
|
||||
<ha-settings-row>
|
||||
<span slot="heading">
|
||||
Newest version
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this.supervisorInfo.version_latest}
|
||||
</span>
|
||||
${this.supervisorInfo.version !== this.supervisorInfo.version_latest
|
||||
? html`
|
||||
<mwc-button
|
||||
title="Update the supervisor"
|
||||
label="Update"
|
||||
@click=${this._supervisorUpdate}
|
||||
>
|
||||
</div>`}
|
||||
</div>
|
||||
${this._errors
|
||||
? html` <div class="error">Error: ${this._errors}</div> `
|
||||
: ""}
|
||||
</mwc-button>
|
||||
`
|
||||
: ""}
|
||||
</ha-settings-row>
|
||||
<ha-settings-row>
|
||||
<span slot="heading">
|
||||
Channel
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this.supervisorInfo.channel}
|
||||
</span>
|
||||
${this.supervisorInfo.channel === "beta"
|
||||
? html`
|
||||
<mwc-button
|
||||
@click=${this._toggleBeta}
|
||||
label="Leave beta channel"
|
||||
title="Get stable updates for Home Assistant, supervisor and host"
|
||||
>
|
||||
</mwc-button>
|
||||
`
|
||||
: this.supervisorInfo.channel === "stable"
|
||||
? html`
|
||||
<mwc-button
|
||||
@click=${this._toggleBeta}
|
||||
label="Join beta channel"
|
||||
title="Get beta updates for Home Assistant (RCs), supervisor and host"
|
||||
>
|
||||
</mwc-button>
|
||||
`
|
||||
: ""}
|
||||
</ha-settings-row>
|
||||
|
||||
${this.supervisorInfo?.supported
|
||||
? html` <ha-settings-row three-line>
|
||||
<span slot="heading">
|
||||
Share diagnostics
|
||||
</span>
|
||||
<div slot="description" class="diagnostics-description">
|
||||
Share crash reports and diagnostic information.
|
||||
<button
|
||||
class="link"
|
||||
title="Show more information about this"
|
||||
@click=${this._diagnosticsInformationDialog}
|
||||
>
|
||||
Learn more
|
||||
</button>
|
||||
</div>
|
||||
<ha-switch
|
||||
haptic
|
||||
.checked=${this.supervisorInfo.diagnostics}
|
||||
@change=${this._toggleDiagnostics}
|
||||
></ha-switch>
|
||||
</ha-settings-row>`
|
||||
: html`<div class="error">
|
||||
You are running an unsupported installation.
|
||||
<a
|
||||
href="https://github.com/home-assistant/architecture/blob/master/adr/${this.hostInfo.features.includes(
|
||||
"hassos"
|
||||
)
|
||||
? "0015-home-assistant-os.md"
|
||||
: "0014-home-assistant-supervised.md"}"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
title="Learn more about how you can make your system compliant"
|
||||
>
|
||||
Learn More
|
||||
</a>
|
||||
</div>`}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-call-api-button .hass=${this.hass} path="hassio/supervisor/reload"
|
||||
>Reload</ha-call-api-button
|
||||
<mwc-button
|
||||
@click=${this._supervisorReload}
|
||||
title="Reload parts of the supervisor."
|
||||
label="Reload"
|
||||
>
|
||||
${this.supervisorInfo.version !== this.supervisorInfo.version_latest
|
||||
? html`
|
||||
<ha-call-api-button
|
||||
.hass=${this.hass}
|
||||
path="hassio/supervisor/update"
|
||||
>Update</ha-call-api-button
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
${this.supervisorInfo.channel === "beta"
|
||||
? html`
|
||||
<ha-call-api-button
|
||||
.hass=${this.hass}
|
||||
path="hassio/supervisor/options"
|
||||
.data=${{ channel: "stable" }}
|
||||
>Leave beta channel</ha-call-api-button
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
${this.supervisorInfo.channel === "stable"
|
||||
? html`
|
||||
<mwc-button
|
||||
@click=${this._joinBeta}
|
||||
class="warning"
|
||||
title="Get beta updates for Home Assistant (RCs), supervisor and host"
|
||||
>Join beta channel</mwc-button
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
</mwc-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
@ -146,93 +149,103 @@ class HassioSupervisorInfo extends LitElement {
|
||||
css`
|
||||
ha-card {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
}
|
||||
.card-content {
|
||||
color: var(--primary-text-color);
|
||||
box-sizing: border-box;
|
||||
height: calc(100% - 47px);
|
||||
}
|
||||
.info,
|
||||
.options {
|
||||
width: 100%;
|
||||
}
|
||||
.info td:nth-child(2) {
|
||||
text-align: right;
|
||||
}
|
||||
ha-settings-row {
|
||||
padding: 0;
|
||||
.card-actions {
|
||||
height: 48px;
|
||||
border-top: none;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
button.link {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
.diagnostics-description {
|
||||
white-space: normal;
|
||||
ha-settings-row {
|
||||
padding: 0;
|
||||
height: 54px;
|
||||
width: 100%;
|
||||
}
|
||||
ha-settings-row[three-line] {
|
||||
height: 74px;
|
||||
}
|
||||
ha-settings-row > span[slot="description"] {
|
||||
white-space: normal;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
protected firstUpdated(): void {
|
||||
this.addEventListener("hass-api-called", (ev) => this._apiCalled(ev));
|
||||
}
|
||||
private async _toggleBeta(): Promise<void> {
|
||||
if (this.supervisorInfo.channel === "stable") {
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: "WARNING",
|
||||
text: html` Beta releases are for testers and early adopters and can
|
||||
contain unstable code changes.
|
||||
<br />
|
||||
<b>
|
||||
Make sure you have backups of your data before you activate this
|
||||
feature.
|
||||
</b>
|
||||
<br /><br />
|
||||
This includes beta releases for:
|
||||
<li>Home Assistant Core</li>
|
||||
<li>Home Assistant Supervisor</li>
|
||||
<li>Home Assistant Operating System</li>
|
||||
<br />
|
||||
Do you want to join the beta channel?`,
|
||||
confirmText: "join beta",
|
||||
dismissText: "no",
|
||||
});
|
||||
|
||||
private _apiCalled(ev): void {
|
||||
if (ev.detail.success) {
|
||||
this._errors = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
const response = ev.detail.response;
|
||||
|
||||
this._errors =
|
||||
typeof response.body === "object"
|
||||
? response.body.message || "Unknown error"
|
||||
: response.body;
|
||||
}
|
||||
|
||||
private async _joinBeta() {
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: "WARNING",
|
||||
text: html` Beta releases are for testers and early adopters and can
|
||||
contain unstable code changes.
|
||||
<br />
|
||||
<b>
|
||||
Make sure you have backups of your data before you activate this
|
||||
feature.
|
||||
</b>
|
||||
<br /><br />
|
||||
This includes beta releases for:
|
||||
<li>Home Assistant Core</li>
|
||||
<li>Home Assistant Supervisor</li>
|
||||
<li>Home Assistant Operating System</li>
|
||||
<br />
|
||||
Do you want to join the beta channel?`,
|
||||
confirmText: "join beta",
|
||||
dismissText: "no",
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const data: SupervisorOptions = { channel: "beta" };
|
||||
await setSupervisorOption(this.hass, data);
|
||||
const eventdata = {
|
||||
success: true,
|
||||
response: undefined,
|
||||
path: "option",
|
||||
const data: Partial<SupervisorOptions> = {
|
||||
channel: this.supervisorInfo.channel !== "stable" ? "beta" : "stable",
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
await setSupervisorOption(this.hass, data);
|
||||
await reloadSupervisor(this.hass);
|
||||
} catch (err) {
|
||||
this._errors = `Error joining beta channel, ${err.body?.message || err}`;
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to set supervisor option",
|
||||
text:
|
||||
typeof err === "object" ? err.body?.message || "Unkown error" : err,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async _diagnosticsInformationDialog() {
|
||||
private async _supervisorReload(): Promise<void> {
|
||||
try {
|
||||
await reloadSupervisor(this.hass);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to reload the supervisor",
|
||||
text:
|
||||
typeof err === "object" ? err.body?.message || "Unkown error" : err,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async _supervisorUpdate(): Promise<void> {
|
||||
try {
|
||||
await updateSupervisor(this.hass);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to update the supervisor",
|
||||
text:
|
||||
typeof err === "object" ? err.body.message || "Unkown error" : err,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async _diagnosticsInformationDialog(): Promise<void> {
|
||||
await showAlertDialog(this, {
|
||||
title: "Help Improve Home Assistant",
|
||||
text: html`Would you want to automatically share crash reports and
|
||||
@ -247,22 +260,18 @@ class HassioSupervisorInfo extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private async _toggleDiagnostics() {
|
||||
private async _toggleDiagnostics(): Promise<void> {
|
||||
try {
|
||||
const data: SupervisorOptions = {
|
||||
diagnostics: !this.supervisorInfo?.diagnostics,
|
||||
};
|
||||
await setSupervisorOption(this.hass, data);
|
||||
const eventdata = {
|
||||
success: true,
|
||||
response: undefined,
|
||||
path: "option",
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err) {
|
||||
this._errors = `Error changing supervisor setting, ${
|
||||
err.body?.message || err
|
||||
}`;
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to set supervisor option",
|
||||
text:
|
||||
typeof err === "object" ? err.body.message || "Unkown error" : err,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,18 +7,20 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "../../../src/components/ha-card";
|
||||
|
||||
import { fetchHassioLogs } from "../../../src/data/hassio/supervisor";
|
||||
import "../../../src/layouts/hass-loading-screen";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
|
||||
import "../../../src/components/ha-card";
|
||||
import "../../../src/layouts/hass-loading-screen";
|
||||
import "../components/hassio-ansi-to-html";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
|
||||
interface LogProvider {
|
||||
key: string;
|
||||
@ -67,7 +69,7 @@ class HassioSupervisorLog extends LitElement {
|
||||
await this._loadData();
|
||||
}
|
||||
|
||||
public render(): TemplateResult | void {
|
||||
protected render(): TemplateResult | void {
|
||||
return html`
|
||||
<ha-card>
|
||||
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
|
||||
@ -102,7 +104,7 @@ class HassioSupervisorLog extends LitElement {
|
||||
: html`<hass-loading-screen no-toolbar></hass-loading-screen>`}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<mwc-button @click=${this._refresh}>Refresh</mwc-button>
|
||||
<mwc-button @click=${this._loadData}>Refresh</mwc-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
@ -114,6 +116,7 @@ class HassioSupervisorLog extends LitElement {
|
||||
hassioStyle,
|
||||
css`
|
||||
ha-card {
|
||||
margin-top: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
pre {
|
||||
@ -127,9 +130,6 @@ class HassioSupervisorLog extends LitElement {
|
||||
color: var(--error-color);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.card-content {
|
||||
padding-top: 0px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
@ -142,7 +142,6 @@ class HassioSupervisorLog extends LitElement {
|
||||
|
||||
private async _loadData(): Promise<void> {
|
||||
this._error = undefined;
|
||||
this._content = undefined;
|
||||
|
||||
try {
|
||||
this._content = await fetchHassioLogs(
|
||||
@ -151,14 +150,10 @@ class HassioSupervisorLog extends LitElement {
|
||||
);
|
||||
} catch (err) {
|
||||
this._error = `Failed to get supervisor logs, ${
|
||||
err.body?.message || err
|
||||
typeof err === "object" ? err.body?.message || "Unkown error" : err
|
||||
}`;
|
||||
}
|
||||
}
|
||||
|
||||
private async _refresh(): Promise<void> {
|
||||
await this._loadData();
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@ -12,8 +12,8 @@ import {
|
||||
HassioHostInfo,
|
||||
} from "../../../src/data/hassio/host";
|
||||
import {
|
||||
HassioSupervisorInfo,
|
||||
HassioInfo,
|
||||
HassioSupervisorInfo,
|
||||
} from "../../../src/data/hassio/supervisor";
|
||||
import "../../../src/layouts/hass-tabs-subpage";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
@ -40,7 +40,7 @@ class HassioSystem extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public hassOsInfo!: HassioHassOSInfo;
|
||||
|
||||
public render(): TemplateResult | void {
|
||||
protected render(): TemplateResult | void {
|
||||
return html`
|
||||
<hass-tabs-subpage
|
||||
.hass=${this.hass}
|
||||
@ -52,7 +52,6 @@ class HassioSystem extends LitElement {
|
||||
>
|
||||
<span slot="header">System</span>
|
||||
<div class="content">
|
||||
<h1>Information</h1>
|
||||
<div class="card-group">
|
||||
<hassio-supervisor-info
|
||||
.hass=${this.hass}
|
||||
@ -66,7 +65,6 @@ class HassioSystem extends LitElement {
|
||||
.hassOsInfo=${this.hassOsInfo}
|
||||
></hassio-host-info>
|
||||
</div>
|
||||
<h1>System log</h1>
|
||||
<hassio-supervisor-log .hass=${this.hass}></hassio-supervisor-log>
|
||||
</div>
|
||||
</hass-tabs-subpage>
|
||||
|
@ -114,6 +114,7 @@
|
||||
"regenerator-runtime": "^0.13.2",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"roboto-fontface": "^0.10.0",
|
||||
"sortablejs": "^1.10.2",
|
||||
"superstruct": "^0.10.12",
|
||||
"unfetch": "^4.1.0",
|
||||
"vue": "^2.6.11",
|
||||
|
2
setup.py
2
setup.py
@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="home-assistant-frontend",
|
||||
version="20200824.0",
|
||||
version="20200901.0",
|
||||
description="The Home Assistant frontend",
|
||||
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||
author="The Home Assistant Authors",
|
||||
|
68
src/common/decorators/local-storage.ts
Normal file
68
src/common/decorators/local-storage.ts
Normal file
@ -0,0 +1,68 @@
|
||||
import type { ClassElement } from "../../types";
|
||||
|
||||
class Storage {
|
||||
private _storage: any = {};
|
||||
|
||||
public addFromStorage(storageKey: any): void {
|
||||
if (!this._storage[storageKey]) {
|
||||
const data = window.localStorage.getItem(storageKey);
|
||||
if (data) {
|
||||
this._storage[storageKey] = JSON.parse(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public hasKey(storageKey: string): any {
|
||||
return storageKey in this._storage;
|
||||
}
|
||||
|
||||
public getValue(storageKey: string): any {
|
||||
return this._storage[storageKey];
|
||||
}
|
||||
|
||||
public setValue(storageKey: string, value: any): any {
|
||||
this._storage[storageKey] = value;
|
||||
try {
|
||||
window.localStorage.setItem(storageKey, JSON.stringify(value));
|
||||
} catch (err) {
|
||||
// Safari in private mode doesn't allow localstorage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const storage = new Storage();
|
||||
|
||||
export const LocalStorage = (key?: string) => {
|
||||
return (element: ClassElement, propName: string) => {
|
||||
const storageKey = key || propName;
|
||||
const initVal = element.initializer ? element.initializer() : undefined;
|
||||
|
||||
storage.addFromStorage(storageKey);
|
||||
|
||||
const getValue = (): any => {
|
||||
return storage.hasKey(storageKey)
|
||||
? storage.getValue(storageKey)
|
||||
: initVal;
|
||||
};
|
||||
|
||||
const setValue = (val: any) => {
|
||||
storage.setValue(storageKey, val);
|
||||
};
|
||||
|
||||
return {
|
||||
kind: "method",
|
||||
placement: "own",
|
||||
key: element.key,
|
||||
descriptor: {
|
||||
set(value) {
|
||||
setValue(value);
|
||||
},
|
||||
get() {
|
||||
return getValue();
|
||||
},
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
},
|
||||
};
|
||||
};
|
||||
};
|
@ -22,9 +22,6 @@ const _load = (
|
||||
(element as HTMLScriptElement).async = true;
|
||||
if (type) {
|
||||
(element as HTMLScriptElement).type = type;
|
||||
// https://github.com/home-assistant/frontend/pull/6328
|
||||
(element as HTMLScriptElement).crossOrigin =
|
||||
url.substr(0, 1) === "/" ? "use-credentials" : "anonymous";
|
||||
}
|
||||
break;
|
||||
case "link":
|
||||
|
@ -1,110 +0,0 @@
|
||||
import "@material/mwc-button";
|
||||
import "../ha-circular-progress";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
class HaProgressButton extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<style>
|
||||
:host {
|
||||
outline: none;
|
||||
}
|
||||
.container {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
mwc-button {
|
||||
transition: all 1s;
|
||||
}
|
||||
|
||||
.success mwc-button {
|
||||
--mdc-theme-primary: white;
|
||||
background-color: var(--success-color);
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.error mwc-button {
|
||||
--mdc-theme-primary: white;
|
||||
background-color: var(--error-color);
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.progress {
|
||||
@apply --layout;
|
||||
@apply --layout-center-center;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
</style>
|
||||
<div class="container" id="container">
|
||||
<mwc-button
|
||||
id="button"
|
||||
disabled="[[computeDisabled(disabled, progress)]]"
|
||||
on-click="buttonTapped"
|
||||
>
|
||||
<slot></slot>
|
||||
</mwc-button>
|
||||
<template is="dom-if" if="[[progress]]">
|
||||
<div class="progress">
|
||||
<ha-circular-progress active size="small"></ha-circular-progress>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: {
|
||||
type: Object,
|
||||
},
|
||||
|
||||
progress: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
tempClass(className) {
|
||||
const classList = this.$.container.classList;
|
||||
classList.add(className);
|
||||
setTimeout(() => {
|
||||
classList.remove(className);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
ready() {
|
||||
super.ready();
|
||||
this.addEventListener("click", (ev) => this.buttonTapped(ev));
|
||||
}
|
||||
|
||||
buttonTapped(ev) {
|
||||
if (this.progress) ev.stopPropagation();
|
||||
}
|
||||
|
||||
actionSuccess() {
|
||||
this.tempClass("success");
|
||||
}
|
||||
|
||||
actionError() {
|
||||
this.tempClass("error");
|
||||
}
|
||||
|
||||
computeDisabled(disabled, progress) {
|
||||
return disabled || progress;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-progress-button", HaProgressButton);
|
114
src/components/buttons/ha-progress-button.ts
Normal file
114
src/components/buttons/ha-progress-button.ts
Normal file
@ -0,0 +1,114 @@
|
||||
import "@material/mwc-button";
|
||||
import type { Button } from "@material/mwc-button";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
query,
|
||||
} from "lit-element";
|
||||
|
||||
import "../ha-circular-progress";
|
||||
|
||||
@customElement("ha-progress-button")
|
||||
class HaProgressButton extends LitElement {
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public progress = false;
|
||||
|
||||
@property({ type: Boolean }) public raised = false;
|
||||
|
||||
@query("mwc-button") private _button?: Button;
|
||||
|
||||
public render(): TemplateResult {
|
||||
return html`
|
||||
<mwc-button
|
||||
?raised=${this.raised}
|
||||
.disabled=${this.disabled || this.progress}
|
||||
@click=${this._buttonTapped}
|
||||
>
|
||||
<slot></slot>
|
||||
</mwc-button>
|
||||
${this.progress
|
||||
? html`<div class="progress">
|
||||
<ha-circular-progress size="small" active></ha-circular-progress>
|
||||
</div>`
|
||||
: ""}
|
||||
`;
|
||||
}
|
||||
|
||||
public actionSuccess(): void {
|
||||
this._tempClass("success");
|
||||
}
|
||||
|
||||
public actionError(): void {
|
||||
this._tempClass("error");
|
||||
}
|
||||
|
||||
private _tempClass(className: string): void {
|
||||
this._button!.classList.add(className);
|
||||
setTimeout(() => {
|
||||
this._button!.classList.remove(className);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
private _buttonTapped(ev: Event): void {
|
||||
if (this.progress) {
|
||||
ev.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
outline: none;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
mwc-button {
|
||||
transition: all 1s;
|
||||
}
|
||||
|
||||
mwc-button.success {
|
||||
--mdc-theme-primary: white;
|
||||
background-color: var(--success-color);
|
||||
transition: none;
|
||||
}
|
||||
|
||||
mwc-button[raised].success {
|
||||
--mdc-theme-primary: var(--success-color);
|
||||
--mdc-theme-on-primary: white;
|
||||
}
|
||||
|
||||
mwc-button.error {
|
||||
--mdc-theme-primary: white;
|
||||
background-color: var(--error-color);
|
||||
transition: none;
|
||||
}
|
||||
|
||||
mwc-button[raised].error {
|
||||
--mdc-theme-primary: var(--error-color);
|
||||
--mdc-theme-on-primary: white;
|
||||
}
|
||||
|
||||
.progress {
|
||||
bottom: 0;
|
||||
margin-top: 4px;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-progress-button": HaProgressButton;
|
||||
}
|
||||
}
|
@ -70,6 +70,7 @@ export interface DataTableColumnData extends DataTableSortColumnData {
|
||||
maxWidth?: string;
|
||||
grows?: boolean;
|
||||
forceLTR?: boolean;
|
||||
hidden?: boolean;
|
||||
}
|
||||
|
||||
export interface DataTableRowData {
|
||||
@ -214,13 +215,15 @@ export class HaDataTable extends LitElement {
|
||||
class="mdc-data-table__table ${classMap({
|
||||
"auto-height": this.autoHeight,
|
||||
})}"
|
||||
role="table"
|
||||
aria-rowcount=${this._filteredData.length}
|
||||
style=${styleMap({
|
||||
height: this.autoHeight
|
||||
? `${(this._filteredData.length || 1) * 53 + 57}px`
|
||||
: `calc(100% - ${this._header?.clientHeight}px)`,
|
||||
})}
|
||||
>
|
||||
<div class="mdc-data-table__header-row">
|
||||
<div class="mdc-data-table__header-row" role="row">
|
||||
${this.selectable
|
||||
? html`
|
||||
<div
|
||||
@ -240,8 +243,10 @@ export class HaDataTable extends LitElement {
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
${Object.entries(this.columns).map((columnEntry) => {
|
||||
const [key, column] = columnEntry;
|
||||
${Object.entries(this.columns).map(([key, column]) => {
|
||||
if (column.hidden) {
|
||||
return "";
|
||||
}
|
||||
const sorted = key === this._sortColumn;
|
||||
const classes = {
|
||||
"mdc-data-table__header-cell--numeric": Boolean(
|
||||
@ -288,8 +293,8 @@ export class HaDataTable extends LitElement {
|
||||
${!this._filteredData.length
|
||||
? html`
|
||||
<div class="mdc-data-table__content">
|
||||
<div class="mdc-data-table__row">
|
||||
<div class="mdc-data-table__cell grows center">
|
||||
<div class="mdc-data-table__row" role="row">
|
||||
<div class="mdc-data-table__cell grows center" role="cell">
|
||||
${this.noDataText || "No data"}
|
||||
</div>
|
||||
</div>
|
||||
@ -304,12 +309,14 @@ export class HaDataTable extends LitElement {
|
||||
items: !this.hasFab
|
||||
? this._filteredData
|
||||
: [...this._filteredData, ...[{ empty: true }]],
|
||||
renderItem: (row: DataTableRowData) => {
|
||||
renderItem: (row: DataTableRowData, index) => {
|
||||
if (row.empty) {
|
||||
return html` <div class="mdc-data-table__row"></div> `;
|
||||
}
|
||||
return html`
|
||||
<div
|
||||
aria-rowindex=${index}
|
||||
role="row"
|
||||
.rowId="${row[this.id]}"
|
||||
@click=${this._handleRowClick}
|
||||
class="mdc-data-table__row ${classMap({
|
||||
@ -328,6 +335,7 @@ export class HaDataTable extends LitElement {
|
||||
? html`
|
||||
<div
|
||||
class="mdc-data-table__cell mdc-data-table__cell--checkbox"
|
||||
role="cell"
|
||||
>
|
||||
<ha-checkbox
|
||||
class="mdc-data-table__row-checkbox"
|
||||
@ -341,40 +349,45 @@ export class HaDataTable extends LitElement {
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
${Object.entries(this.columns).map((columnEntry) => {
|
||||
const [key, column] = columnEntry;
|
||||
return html`
|
||||
<div
|
||||
class="mdc-data-table__cell ${classMap({
|
||||
"mdc-data-table__cell--numeric": Boolean(
|
||||
column.type === "numeric"
|
||||
),
|
||||
"mdc-data-table__cell--icon": Boolean(
|
||||
column.type === "icon"
|
||||
),
|
||||
"mdc-data-table__cell--icon-button": Boolean(
|
||||
column.type === "icon-button"
|
||||
),
|
||||
grows: Boolean(column.grows),
|
||||
forceLTR: Boolean(column.forceLTR),
|
||||
})}"
|
||||
style=${column.width
|
||||
? styleMap({
|
||||
[column.grows
|
||||
? "minWidth"
|
||||
: "width"]: column.width,
|
||||
maxWidth: column.maxWidth
|
||||
? column.maxWidth
|
||||
: "",
|
||||
})
|
||||
: ""}
|
||||
>
|
||||
${column.template
|
||||
? column.template(row[key], row)
|
||||
: row[key]}
|
||||
</div>
|
||||
`;
|
||||
})}
|
||||
${Object.entries(this.columns).map(
|
||||
([key, column]) => {
|
||||
if (column.hidden) {
|
||||
return "";
|
||||
}
|
||||
return html`
|
||||
<div
|
||||
role="cell"
|
||||
class="mdc-data-table__cell ${classMap({
|
||||
"mdc-data-table__cell--numeric": Boolean(
|
||||
column.type === "numeric"
|
||||
),
|
||||
"mdc-data-table__cell--icon": Boolean(
|
||||
column.type === "icon"
|
||||
),
|
||||
"mdc-data-table__cell--icon-button": Boolean(
|
||||
column.type === "icon-button"
|
||||
),
|
||||
grows: Boolean(column.grows),
|
||||
forceLTR: Boolean(column.forceLTR),
|
||||
})}"
|
||||
style=${column.width
|
||||
? styleMap({
|
||||
[column.grows
|
||||
? "minWidth"
|
||||
: "width"]: column.width,
|
||||
maxWidth: column.maxWidth
|
||||
? column.maxWidth
|
||||
: "",
|
||||
})
|
||||
: ""}
|
||||
>
|
||||
${column.template
|
||||
? column.template(row[key], row)
|
||||
: row[key]}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
|
@ -1,11 +1,11 @@
|
||||
// To use comlink under ES5
|
||||
import "proxy-polyfill";
|
||||
import { expose } from "comlink";
|
||||
import "proxy-polyfill";
|
||||
import type {
|
||||
DataTableSortColumnData,
|
||||
DataTableRowData,
|
||||
SortingDirection,
|
||||
DataTableSortColumnData,
|
||||
SortableColumnContainer,
|
||||
SortingDirection,
|
||||
} from "./ha-data-table";
|
||||
|
||||
const filterData = (
|
||||
@ -19,7 +19,7 @@ const filterData = (
|
||||
const [key, column] = columnEntry;
|
||||
if (column.filterable) {
|
||||
if (
|
||||
(column.filterKey ? row[key][column.filterKey] : row[key])
|
||||
String(column.filterKey ? row[key][column.filterKey] : row[key])
|
||||
.toUpperCase()
|
||||
.includes(filter)
|
||||
) {
|
||||
|
67
src/components/ha-bar.ts
Normal file
67
src/components/ha-bar.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
LitElement,
|
||||
property,
|
||||
svg,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
|
||||
import {
|
||||
getValueInPercentage,
|
||||
normalize,
|
||||
roundWithOneDecimal,
|
||||
} from "../util/calculate";
|
||||
|
||||
@customElement("ha-bar")
|
||||
export class HaBar extends LitElement {
|
||||
@property({ type: Number }) public min = 0;
|
||||
|
||||
@property({ type: Number }) public max = 100;
|
||||
|
||||
@property({ type: Number }) public value!: number;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const valuePrecentage = roundWithOneDecimal(
|
||||
getValueInPercentage(
|
||||
normalize(this.value, this.min, this.max),
|
||||
this.min,
|
||||
this.max
|
||||
)
|
||||
);
|
||||
|
||||
return svg`
|
||||
<svg>
|
||||
<g>
|
||||
<rect></rect>
|
||||
<rect width="${valuePrecentage}%"></rect>
|
||||
</g>
|
||||
</svg>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
rect:first-child {
|
||||
width: 100%;
|
||||
fill: var(--ha-bar-background-color, var(--secondary-background-color));
|
||||
}
|
||||
rect:last-child {
|
||||
fill: var(--ha-bar-primary-color, var(--primary-color));
|
||||
rx: var(--ha-bar-border-radius, 4px);
|
||||
}
|
||||
svg {
|
||||
border-radius: var(--ha-bar-border-radius, 4px);
|
||||
height: 12px;
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-bar": HaBar;
|
||||
}
|
||||
}
|
@ -1,16 +1,16 @@
|
||||
import {
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
property,
|
||||
svg,
|
||||
html,
|
||||
customElement,
|
||||
unsafeCSS,
|
||||
SVGTemplateResult,
|
||||
css,
|
||||
} from "lit-element";
|
||||
// @ts-ignore
|
||||
import progressStyles from "@material/circular-progress/dist/mdc.circular-progress.min.css";
|
||||
import {
|
||||
css,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
svg,
|
||||
SVGTemplateResult,
|
||||
TemplateResult,
|
||||
unsafeCSS,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
|
||||
@customElement("ha-circular-progress")
|
||||
@ -24,7 +24,7 @@ export class HaCircularProgress extends LitElement {
|
||||
@property()
|
||||
public size: "small" | "medium" | "large" = "medium";
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
protected render(): TemplateResult {
|
||||
let indeterminatePart: SVGTemplateResult;
|
||||
|
||||
if (this.size === "small") {
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Editor } from "codemirror";
|
||||
import {
|
||||
customElement,
|
||||
property,
|
||||
internalProperty,
|
||||
property,
|
||||
PropertyValues,
|
||||
UpdatingElement,
|
||||
} from "lit-element";
|
||||
@ -123,7 +123,7 @@ export class HaCodeEditor extends UpdatingElement {
|
||||
transition: 0.2s ease border-right;
|
||||
}
|
||||
.cm-s-default.CodeMirror {
|
||||
background-color: var(--card-background-color);
|
||||
background-color: var(--code-editor-background-color, var(--card-background-color));
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.cm-s-default .CodeMirror-cursor {
|
||||
|
@ -1,11 +1,11 @@
|
||||
import "@material/mwc-dialog";
|
||||
import type { Dialog } from "@material/mwc-dialog";
|
||||
import { style } from "@material/mwc-dialog/mwc-dialog-css";
|
||||
import "./ha-icon-button";
|
||||
import { css, CSSResult, customElement, html } from "lit-element";
|
||||
import type { Constructor, HomeAssistant } from "../types";
|
||||
import { mdiClose } from "@mdi/js";
|
||||
import { css, CSSResult, customElement, html } from "lit-element";
|
||||
import { computeRTLDirection } from "../common/util/compute_rtl";
|
||||
import type { Constructor, HomeAssistant } from "../types";
|
||||
import "./ha-icon-button";
|
||||
|
||||
const MwcDialog = customElements.get("mwc-dialog") as Constructor<Dialog>;
|
||||
|
||||
@ -23,6 +23,10 @@ export const createCloseHeading = (hass: HomeAssistant, title: string) => html`
|
||||
|
||||
@customElement("ha-dialog")
|
||||
export class HaDialog extends MwcDialog {
|
||||
public scrollToPos(x: number, y: number) {
|
||||
this.contentElement.scrollTo(x, y);
|
||||
}
|
||||
|
||||
protected renderHeading() {
|
||||
return html`<slot name="heading">
|
||||
${super.renderHeading()}
|
||||
@ -62,6 +66,10 @@ export class HaDialog extends MwcDialog {
|
||||
position: var(--dialog-surface-position, relative);
|
||||
min-height: var(--mdc-dialog-min-height, auto);
|
||||
}
|
||||
:host([flexContent]) .mdc-dialog .mdc-dialog__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.header_button {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
|
@ -11,23 +11,13 @@ import { styleMap } from "lit-html/directives/style-map";
|
||||
import { afterNextRender } from "../common/util/render-status";
|
||||
import { ifDefined } from "lit-html/directives/if-defined";
|
||||
|
||||
import { getValueInPercentage, normalize } from "../util/calculate";
|
||||
|
||||
const getAngle = (value: number, min: number, max: number) => {
|
||||
const percentage = getValueInPercentage(normalize(value, min, max), min, max);
|
||||
return (percentage * 180) / 100;
|
||||
};
|
||||
|
||||
const normalize = (value: number, min: number, max: number) => {
|
||||
if (value > max) return max;
|
||||
if (value < min) return min;
|
||||
return value;
|
||||
};
|
||||
|
||||
const getValueInPercentage = (value: number, min: number, max: number) => {
|
||||
const newMax = max - min;
|
||||
const newVal = value - min;
|
||||
return (100 * newVal) / newMax;
|
||||
};
|
||||
|
||||
// Workaround for https://github.com/home-assistant/frontend/issues/6467
|
||||
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
||||
|
||||
|
@ -25,7 +25,7 @@ export class HaSettingsRow extends LitElement {
|
||||
</style>
|
||||
<paper-item-body
|
||||
?two-line=${!this.threeLine}
|
||||
?three-line=${!this.threeLine}
|
||||
?three-line=${this.threeLine}
|
||||
>
|
||||
<slot name="heading"></slot>
|
||||
<div secondary><slot name="description"></slot></div>
|
||||
|
77
src/components/ha-sidebar-sort-styles.ts
Normal file
77
src/components/ha-sidebar-sort-styles.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import { html } from "lit-element";
|
||||
|
||||
export const sortStyles = html`
|
||||
<style>
|
||||
#sortable a:nth-of-type(2n) paper-icon-item {
|
||||
animation-name: keyframes1;
|
||||
animation-iteration-count: infinite;
|
||||
transform-origin: 50% 10%;
|
||||
animation-delay: -0.75s;
|
||||
animation-duration: 0.25s;
|
||||
}
|
||||
|
||||
#sortable a:nth-of-type(2n-1) paper-icon-item {
|
||||
animation-name: keyframes2;
|
||||
animation-iteration-count: infinite;
|
||||
animation-direction: alternate;
|
||||
transform-origin: 30% 5%;
|
||||
animation-delay: -0.5s;
|
||||
animation-duration: 0.33s;
|
||||
}
|
||||
|
||||
#sortable {
|
||||
outline: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sortable-ghost {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.sortable-fallback {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
@keyframes keyframes1 {
|
||||
0% {
|
||||
transform: rotate(-1deg);
|
||||
animation-timing-function: ease-in;
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: rotate(1.5deg);
|
||||
animation-timing-function: ease-out;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes keyframes2 {
|
||||
0% {
|
||||
transform: rotate(1deg);
|
||||
animation-timing-function: ease-in;
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: rotate(-1.5deg);
|
||||
animation-timing-function: ease-out;
|
||||
}
|
||||
}
|
||||
|
||||
.hide-panel {
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
}
|
||||
|
||||
:host([expanded]) .hide-panel {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
paper-icon-item.hidden-panel,
|
||||
paper-icon-item.hidden-panel span,
|
||||
paper-icon-item.hidden-panel ha-icon[slot="item-icon"] {
|
||||
color: var(--secondary-text-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
`;
|
@ -1,9 +1,12 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@material/mwc-icon-button";
|
||||
import {
|
||||
mdiBell,
|
||||
mdiCellphoneCog,
|
||||
mdiMenuOpen,
|
||||
mdiClose,
|
||||
mdiMenu,
|
||||
mdiMenuOpen,
|
||||
mdiPlus,
|
||||
mdiViewDashboard,
|
||||
} from "@mdi/js";
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
@ -13,20 +16,24 @@ import "@polymer/paper-listbox/paper-listbox";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
eventOptions,
|
||||
html,
|
||||
customElement,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import { guard } from "lit-html/directives/guard";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { LocalStorage } from "../common/decorators/local-storage";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { computeDomain } from "../common/entity/compute_domain";
|
||||
import { compare } from "../common/string/compare";
|
||||
import { computeRTL } from "../common/util/compute_rtl";
|
||||
import { getDefaultPanel } from "../data/panel";
|
||||
import { ActionHandlerDetail } from "../data/lovelace";
|
||||
import {
|
||||
PersistentNotification,
|
||||
subscribeNotifications,
|
||||
@ -35,6 +42,7 @@ import {
|
||||
ExternalConfig,
|
||||
getExternalConfig,
|
||||
} from "../external_app/external_config";
|
||||
import { actionHandler } from "../panels/lovelace/common/directives/action-handler-directive";
|
||||
import type { HomeAssistant, PanelInfo } from "../types";
|
||||
import "./ha-icon";
|
||||
import "./ha-menu-button";
|
||||
@ -54,11 +62,39 @@ const SORT_VALUE_URL_PATHS = {
|
||||
config: 11,
|
||||
};
|
||||
|
||||
const panelSorter = (a: PanelInfo, b: PanelInfo) => {
|
||||
const panelSorter = (
|
||||
reverseSort: string[],
|
||||
defaultPanel: string,
|
||||
a: PanelInfo,
|
||||
b: PanelInfo
|
||||
) => {
|
||||
const indexA = reverseSort.indexOf(a.url_path);
|
||||
const indexB = reverseSort.indexOf(b.url_path);
|
||||
if (indexA !== indexB) {
|
||||
if (indexA < indexB) {
|
||||
return 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
return defaultPanelSorter(defaultPanel, a, b);
|
||||
};
|
||||
|
||||
const defaultPanelSorter = (
|
||||
defaultPanel: string,
|
||||
a: PanelInfo,
|
||||
b: PanelInfo
|
||||
) => {
|
||||
// Put all the Lovelace at the top.
|
||||
const aLovelace = a.component_name === "lovelace";
|
||||
const bLovelace = b.component_name === "lovelace";
|
||||
|
||||
if (a.url_path === defaultPanel) {
|
||||
return -1;
|
||||
}
|
||||
if (b.url_path === defaultPanel) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (aLovelace && bLovelace) {
|
||||
return compare(a.title!, b.title!);
|
||||
}
|
||||
@ -85,30 +121,45 @@ const panelSorter = (a: PanelInfo, b: PanelInfo) => {
|
||||
return compare(a.title!, b.title!);
|
||||
};
|
||||
|
||||
const computePanels = (hass: HomeAssistant): [PanelInfo[], PanelInfo[]] => {
|
||||
const panels = hass.panels;
|
||||
if (!panels) {
|
||||
return [[], []];
|
||||
}
|
||||
|
||||
const beforeSpacer: PanelInfo[] = [];
|
||||
const afterSpacer: PanelInfo[] = [];
|
||||
|
||||
Object.values(panels).forEach((panel) => {
|
||||
if (!panel.title || panel.url_path === hass.defaultPanel) {
|
||||
return;
|
||||
const computePanels = memoizeOne(
|
||||
(
|
||||
panels: HomeAssistant["panels"],
|
||||
defaultPanel: HomeAssistant["defaultPanel"],
|
||||
panelsOrder: string[],
|
||||
hiddenPanels: string[]
|
||||
): [PanelInfo[], PanelInfo[]] => {
|
||||
if (!panels) {
|
||||
return [[], []];
|
||||
}
|
||||
(SHOW_AFTER_SPACER.includes(panel.url_path)
|
||||
? afterSpacer
|
||||
: beforeSpacer
|
||||
).push(panel);
|
||||
});
|
||||
|
||||
beforeSpacer.sort(panelSorter);
|
||||
afterSpacer.sort(panelSorter);
|
||||
const beforeSpacer: PanelInfo[] = [];
|
||||
const afterSpacer: PanelInfo[] = [];
|
||||
|
||||
return [beforeSpacer, afterSpacer];
|
||||
};
|
||||
Object.values(panels).forEach((panel) => {
|
||||
if (
|
||||
hiddenPanels.includes(panel.url_path) ||
|
||||
(!panel.title && panel.url_path !== defaultPanel)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
(SHOW_AFTER_SPACER.includes(panel.url_path)
|
||||
? afterSpacer
|
||||
: beforeSpacer
|
||||
).push(panel);
|
||||
});
|
||||
|
||||
const reverseSort = [...panelsOrder].reverse();
|
||||
|
||||
beforeSpacer.sort((a, b) => panelSorter(reverseSort, defaultPanel, a, b));
|
||||
afterSpacer.sort((a, b) => panelSorter(reverseSort, defaultPanel, a, b));
|
||||
|
||||
return [beforeSpacer, afterSpacer];
|
||||
}
|
||||
);
|
||||
|
||||
let Sortable;
|
||||
|
||||
let sortStyles: TemplateResult;
|
||||
|
||||
@customElement("ha-sidebar")
|
||||
class HaSidebar extends LitElement {
|
||||
@ -124,16 +175,30 @@ class HaSidebar extends LitElement {
|
||||
|
||||
@internalProperty() private _notifications?: PersistentNotification[];
|
||||
|
||||
@internalProperty() private _editMode = false;
|
||||
|
||||
// property used only in css
|
||||
// @ts-ignore
|
||||
@property({ type: Boolean, reflect: true }) public rtl = false;
|
||||
|
||||
@internalProperty() private _renderEmptySortable = false;
|
||||
|
||||
private _mouseLeaveTimeout?: number;
|
||||
|
||||
private _tooltipHideTimeout?: number;
|
||||
|
||||
private _recentKeydownActiveUntil = 0;
|
||||
|
||||
// @ts-ignore
|
||||
@LocalStorage("sidebarPanelOrder")
|
||||
private _panelOrder: string[] = [];
|
||||
|
||||
// @ts-ignore
|
||||
@LocalStorage("sidebarHiddenPanels")
|
||||
private _hiddenPanels: string[] = [];
|
||||
|
||||
private _sortable?;
|
||||
|
||||
protected render() {
|
||||
const hass = this.hass;
|
||||
|
||||
@ -141,7 +206,12 @@ class HaSidebar extends LitElement {
|
||||
return html``;
|
||||
}
|
||||
|
||||
const [beforeSpacer, afterSpacer] = computePanels(hass);
|
||||
const [beforeSpacer, afterSpacer] = computePanels(
|
||||
hass.panels,
|
||||
hass.defaultPanel,
|
||||
this._panelOrder,
|
||||
this._hiddenPanels
|
||||
);
|
||||
|
||||
let notificationCount = this._notifications
|
||||
? this._notifications.length
|
||||
@ -152,9 +222,8 @@ class HaSidebar extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
const defaultPanel = getDefaultPanel(hass);
|
||||
|
||||
return html`
|
||||
${this._editMode ? sortStyles : ""}
|
||||
<div class="menu">
|
||||
${!this.narrow
|
||||
? html`
|
||||
@ -170,7 +239,13 @@ class HaSidebar extends LitElement {
|
||||
</mwc-icon-button>
|
||||
`
|
||||
: ""}
|
||||
<span class="title">Home Assistant</span>
|
||||
<div class="title">
|
||||
${this._editMode
|
||||
? html`<mwc-button outlined @click=${this._closeEditMode}>
|
||||
DONE
|
||||
</mwc-button>`
|
||||
: "Home Assistant"}
|
||||
</div>
|
||||
</div>
|
||||
<paper-listbox
|
||||
attr-for-selected="data-panel"
|
||||
@ -179,31 +254,53 @@ class HaSidebar extends LitElement {
|
||||
@focusout=${this._listboxFocusOut}
|
||||
@scroll=${this._listboxScroll}
|
||||
@keydown=${this._listboxKeydown}
|
||||
@action=${this._handleAction}
|
||||
.actionHandler=${actionHandler({
|
||||
hasHold: !this._editMode,
|
||||
disabled: this._editMode,
|
||||
})}
|
||||
>
|
||||
${this._renderPanel(
|
||||
defaultPanel.url_path,
|
||||
defaultPanel.title || hass.localize("panel.states"),
|
||||
defaultPanel.icon,
|
||||
!defaultPanel.icon ? mdiViewDashboard : undefined
|
||||
)}
|
||||
${beforeSpacer.map((panel) =>
|
||||
this._renderPanel(
|
||||
panel.url_path,
|
||||
hass.localize(`panel.${panel.title}`) || panel.title,
|
||||
panel.icon,
|
||||
undefined
|
||||
)
|
||||
)}
|
||||
${this._editMode
|
||||
? html`<div id="sortable">
|
||||
${guard([this._hiddenPanels, this._renderEmptySortable], () =>
|
||||
this._renderEmptySortable
|
||||
? ""
|
||||
: this._renderPanels(beforeSpacer)
|
||||
)}
|
||||
</div>`
|
||||
: this._renderPanels(beforeSpacer)}
|
||||
<div class="spacer" disabled></div>
|
||||
|
||||
${afterSpacer.map((panel) =>
|
||||
this._renderPanel(
|
||||
panel.url_path,
|
||||
hass.localize(`panel.${panel.title}`) || panel.title,
|
||||
panel.icon,
|
||||
undefined
|
||||
)
|
||||
)}
|
||||
${this._editMode && this._hiddenPanels.length
|
||||
? html`
|
||||
${this._hiddenPanels.map((url) => {
|
||||
const panel = this.hass.panels[url];
|
||||
return html`<paper-icon-item
|
||||
@click=${this._unhidePanel}
|
||||
class="hidden-panel"
|
||||
>
|
||||
<ha-icon
|
||||
slot="item-icon"
|
||||
.icon=${panel.url_path === "lovelace"
|
||||
? "mdi:view-dashboard"
|
||||
: panel.icon}
|
||||
></ha-icon>
|
||||
<span class="item-text"
|
||||
>${panel.url_path === "lovelace"
|
||||
? hass.localize("panel.states")
|
||||
: hass.localize(`panel.${panel.title}`) ||
|
||||
panel.title}</span
|
||||
>
|
||||
<ha-svg-icon
|
||||
class="hide-panel"
|
||||
.panel=${url}
|
||||
.path=${mdiPlus}
|
||||
></ha-svg-icon>
|
||||
</paper-icon-item>`;
|
||||
})}
|
||||
<div class="spacer" disabled></div>
|
||||
`
|
||||
: ""}
|
||||
${this._renderPanels(afterSpacer)}
|
||||
${this._externalConfig && this._externalConfig.hasSettingsScreen
|
||||
? html`
|
||||
<a
|
||||
@ -295,7 +392,9 @@ class HaSidebar extends LitElement {
|
||||
changedProps.has("narrow") ||
|
||||
changedProps.has("alwaysExpand") ||
|
||||
changedProps.has("_externalConfig") ||
|
||||
changedProps.has("_notifications")
|
||||
changedProps.has("_notifications") ||
|
||||
changedProps.has("_editMode") ||
|
||||
changedProps.has("_renderEmptySortable")
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
@ -361,6 +460,74 @@ class HaSidebar extends LitElement {
|
||||
return this.shadowRoot!.querySelector(".tooltip")! as HTMLDivElement;
|
||||
}
|
||||
|
||||
private async _handleAction(ev: CustomEvent<ActionHandlerDetail>) {
|
||||
if (ev.detail.action !== "hold") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Sortable) {
|
||||
const [sortableImport, sortStylesImport] = await Promise.all([
|
||||
import("sortablejs/modular/sortable.core.esm"),
|
||||
import("./ha-sidebar-sort-styles"),
|
||||
]);
|
||||
|
||||
sortStyles = sortStylesImport.sortStyles;
|
||||
|
||||
Sortable = sortableImport.Sortable;
|
||||
Sortable.mount(sortableImport.OnSpill);
|
||||
Sortable.mount(sortableImport.AutoScroll());
|
||||
}
|
||||
this._editMode = true;
|
||||
|
||||
await this.updateComplete;
|
||||
|
||||
this._createSortable();
|
||||
}
|
||||
|
||||
private _createSortable() {
|
||||
this._sortable = new Sortable(this.shadowRoot!.getElementById("sortable"), {
|
||||
animation: 150,
|
||||
fallbackClass: "sortable-fallback",
|
||||
dataIdAttr: "data-panel",
|
||||
onSort: async () => {
|
||||
this._panelOrder = this._sortable.toArray();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private _closeEditMode() {
|
||||
this._sortable?.destroy();
|
||||
this._sortable = undefined;
|
||||
this._editMode = false;
|
||||
}
|
||||
|
||||
private async _hidePanel(ev: Event) {
|
||||
ev.preventDefault();
|
||||
const panel = (ev.target as any).panel;
|
||||
if (this._hiddenPanels.includes(panel)) {
|
||||
return;
|
||||
}
|
||||
// Make a copy for Memoize
|
||||
this._hiddenPanels = [...this._hiddenPanels, panel];
|
||||
this._renderEmptySortable = true;
|
||||
await this.updateComplete;
|
||||
this._renderEmptySortable = false;
|
||||
}
|
||||
|
||||
private async _unhidePanel(ev: Event) {
|
||||
ev.preventDefault();
|
||||
const index = this._hiddenPanels.indexOf((ev.target as any).panel);
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
this._hiddenPanels.splice(index, 1);
|
||||
// Make a copy for Memoize
|
||||
this._hiddenPanels = [...this._hiddenPanels];
|
||||
this._renderEmptySortable = true;
|
||||
await this.updateComplete;
|
||||
this._renderEmptySortable = false;
|
||||
}
|
||||
|
||||
private _itemMouseEnter(ev: MouseEvent) {
|
||||
// On keypresses on the listbox, we're going to ignore mouse enter events
|
||||
// for 100ms so that we ignore it when pressing down arrow scrolls the
|
||||
@ -457,6 +624,19 @@ class HaSidebar extends LitElement {
|
||||
fireEvent(this, "hass-toggle-menu");
|
||||
}
|
||||
|
||||
private _renderPanels(panels: PanelInfo[]) {
|
||||
return panels.map((panel) =>
|
||||
this._renderPanel(
|
||||
panel.url_path,
|
||||
panel.url_path === "lovelace"
|
||||
? this.hass.localize("panel.states")
|
||||
: this.hass.localize(`panel.${panel.title}`) || panel.title,
|
||||
panel.url_path === "lovelace" ? undefined : panel.icon,
|
||||
panel.url_path === "lovelace" ? mdiViewDashboard : undefined
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private _renderPanel(
|
||||
urlPath: string,
|
||||
title: string | null,
|
||||
@ -480,6 +660,14 @@ class HaSidebar extends LitElement {
|
||||
></ha-svg-icon>`
|
||||
: html`<ha-icon slot="item-icon" .icon=${icon}></ha-icon>`}
|
||||
<span class="item-text">${title}</span>
|
||||
${this._editMode
|
||||
? html`<ha-svg-icon
|
||||
class="hide-panel"
|
||||
.panel=${urlPath}
|
||||
@click=${this._hidePanel}
|
||||
.path=${mdiClose}
|
||||
></ha-svg-icon>`
|
||||
: ""}
|
||||
</paper-icon-item>
|
||||
</a>
|
||||
`;
|
||||
@ -542,11 +730,15 @@ class HaSidebar extends LitElement {
|
||||
}
|
||||
|
||||
.title {
|
||||
width: 100%;
|
||||
display: none;
|
||||
}
|
||||
:host([expanded]) .title {
|
||||
display: initial;
|
||||
}
|
||||
.title mwc-button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
paper-listbox::-webkit-scrollbar {
|
||||
width: 0.4rem;
|
||||
|
@ -15,7 +15,7 @@ import type {
|
||||
} from "../../data/media-player";
|
||||
import { haStyleDialog } from "../../resources/styles";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { createCloseHeading } from "../ha-dialog";
|
||||
import "../ha-dialog";
|
||||
import "./ha-media-player-browse";
|
||||
import { MediaPlayerBrowseDialogParams } from "./show-media-browser-dialog";
|
||||
|
||||
@ -56,18 +56,17 @@ class DialogMediaPlayerBrowse extends LitElement {
|
||||
scrimClickAction
|
||||
escapeKeyAction
|
||||
hideActions
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
this.hass.localize("ui.components.media-browser.media-player-browser")
|
||||
)}
|
||||
flexContent
|
||||
@closed=${this._closeDialog}
|
||||
>
|
||||
<ha-media-player-browse
|
||||
dialog
|
||||
.hass=${this.hass}
|
||||
.entityId=${this._entityId}
|
||||
.action=${this._action!}
|
||||
.mediaContentId=${this._mediaContentId}
|
||||
.mediaContentType=${this._mediaContentType}
|
||||
@close-dialog=${this._closeDialog}
|
||||
@media-picked=${this._mediaPicked}
|
||||
></ha-media-player-browse>
|
||||
</ha-dialog>
|
||||
@ -94,13 +93,20 @@ class DialogMediaPlayerBrowse extends LitElement {
|
||||
--dialog-content-padding: 0;
|
||||
}
|
||||
|
||||
ha-header-bar {
|
||||
--mdc-theme-on-primary: var(--primary-text-color);
|
||||
--mdc-theme-primary: var(--mdc-theme-surface);
|
||||
flex-shrink: 0;
|
||||
border-bottom: 1px solid
|
||||
var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12));
|
||||
}
|
||||
|
||||
@media (min-width: 800px) {
|
||||
ha-dialog {
|
||||
--mdc-dialog-max-width: 800px;
|
||||
}
|
||||
ha-media-player-browse {
|
||||
width: 700px;
|
||||
padding: 20px 24px;
|
||||
}
|
||||
}
|
||||
`,
|
||||
|
@ -2,7 +2,7 @@ import "@material/mwc-button/mwc-button";
|
||||
import "@material/mwc-fab/mwc-fab";
|
||||
import "@material/mwc-list/mwc-list";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiArrowLeft, mdiFolder, mdiPlay, mdiPlus } from "@mdi/js";
|
||||
import { mdiArrowLeft, mdiClose, mdiFolder, mdiPlay, mdiPlus } from "@mdi/js";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import {
|
||||
@ -16,8 +16,11 @@ import {
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import { ifDefined } from "lit-html/directives/if-defined";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { computeRTLDirection } from "../../common/util/compute_rtl";
|
||||
import { debounce } from "../../common/util/debounce";
|
||||
import { browseMediaPlayer, MediaPickedEvent } from "../../data/media-player";
|
||||
import type { MediaPlayerItem } from "../../data/media-player";
|
||||
@ -49,6 +52,12 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
|
||||
@property() public action: "pick" | "play" = "play";
|
||||
|
||||
@property({ type: Boolean }) public hideBack = false;
|
||||
|
||||
@property({ type: Boolean }) public hideTitle = false;
|
||||
|
||||
@property({ type: Boolean }) public dialog = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "narrow", reflect: true })
|
||||
private _narrow = false;
|
||||
|
||||
@ -69,6 +78,15 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
public navigateBack() {
|
||||
this._mediaPlayerItems!.pop();
|
||||
const item = this._mediaPlayerItems!.pop();
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
this._navigate(item);
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._mediaPlayerItems.length) {
|
||||
return html``;
|
||||
@ -90,8 +108,20 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
| MediaPlayerItem
|
||||
| undefined = this._hasExpandableChildren(mostRecentItem.children);
|
||||
|
||||
const showImages = mostRecentItem.children?.some(
|
||||
(child) => child.thumbnail && child.thumbnail !== mostRecentItem.thumbnail
|
||||
);
|
||||
|
||||
const mediaType = this.hass.localize(
|
||||
`ui.components.media-browser.content-type.${mostRecentItem.media_content_type}`
|
||||
);
|
||||
|
||||
return html`
|
||||
<div class="header">
|
||||
<div
|
||||
class="header ${classMap({
|
||||
"no-img": !mostRecentItem.thumbnail,
|
||||
})}"
|
||||
>
|
||||
<div class="header-content">
|
||||
${mostRecentItem.thumbnail
|
||||
? html`
|
||||
@ -123,56 +153,65 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
`
|
||||
: html``}
|
||||
<div class="header-info">
|
||||
<div class="breadcrumb-overflow">
|
||||
<div class="breadcrumb">
|
||||
${previousItem
|
||||
? html`
|
||||
<div
|
||||
class="previous-title"
|
||||
.previous=${true}
|
||||
.item=${previousItem}
|
||||
@click=${this._navigate}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiArrowLeft}></ha-svg-icon>
|
||||
${previousItem.title}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<h1 class="title">${mostRecentItem.title}</h1>
|
||||
<h2 class="subtitle">
|
||||
${this.hass.localize(
|
||||
`ui.components.media-browser.content-type.${mostRecentItem.media_content_type}`
|
||||
)}
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
${mostRecentItem?.can_play &&
|
||||
(!this._narrow || (this._narrow && !mostRecentItem.thumbnail))
|
||||
? html`
|
||||
<div class="actions">
|
||||
<mwc-button
|
||||
raised
|
||||
.item=${mostRecentItem}
|
||||
@click=${this._actionClicked}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
.label=${this.hass.localize(
|
||||
`ui.components.media-browser.${this.action}-media`
|
||||
)}
|
||||
.path=${this.action === "play" ? mdiPlay : mdiPlus}
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
`ui.components.media-browser.${this.action}`
|
||||
)}
|
||||
</mwc-button>
|
||||
${this.hideTitle && (this._narrow || !mostRecentItem.thumbnail)
|
||||
? ""
|
||||
: html`<div class="breadcrumb-overflow">
|
||||
<div class="breadcrumb">
|
||||
${!this.hideBack && previousItem
|
||||
? html`
|
||||
<div
|
||||
class="previous-title"
|
||||
@click=${this.navigateBack}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiArrowLeft}></ha-svg-icon>
|
||||
${previousItem.title}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<h1 class="title">${mostRecentItem.title}</h1>
|
||||
${mediaType
|
||||
? html`<h2 class="subtitle">
|
||||
${mediaType}
|
||||
</h2>`
|
||||
: ""}
|
||||
</div>
|
||||
</div>`}
|
||||
${mostRecentItem?.can_play &&
|
||||
(!mostRecentItem.thumbnail || !this._narrow)
|
||||
? html`
|
||||
<mwc-button
|
||||
raised
|
||||
.item=${mostRecentItem}
|
||||
@click=${this._actionClicked}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
.label=${this.hass.localize(
|
||||
`ui.components.media-browser.${this.action}-media`
|
||||
)}
|
||||
.path=${this.action === "play" ? mdiPlay : mdiPlus}
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
`ui.components.media-browser.${this.action}`
|
||||
)}
|
||||
</mwc-button>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
</div>
|
||||
${this.dialog
|
||||
? html`
|
||||
<mwc-icon-button
|
||||
aria-label=${this.hass.localize("ui.dialogs.generic.close")}
|
||||
@click=${this._closeDialogAction}
|
||||
class="header_button"
|
||||
dir=${computeRTLDirection(this.hass)}
|
||||
>
|
||||
<ha-svg-icon path=${mdiClose}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
${mostRecentItem.children?.length
|
||||
? hasExpandableChildren
|
||||
? html`
|
||||
@ -184,7 +223,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
<div
|
||||
class="child"
|
||||
.item=${child}
|
||||
@click=${this._navigate}
|
||||
@click=${this._navigateForward}
|
||||
>
|
||||
<div class="ha-card-parent">
|
||||
<ha-card
|
||||
@ -235,21 +274,41 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
: html`
|
||||
<mwc-list>
|
||||
${mostRecentItem.children.map(
|
||||
(child) => html`<mwc-list-item
|
||||
(child) => html`
|
||||
<mwc-list-item
|
||||
@click=${this._actionClicked}
|
||||
.item=${child}
|
||||
graphic="icon"
|
||||
graphic="avatar"
|
||||
hasMeta
|
||||
>
|
||||
<span>${child.title}</span>
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.label=${this.hass.localize(
|
||||
`ui.components.media-browser.${this.action}-media`
|
||||
<div
|
||||
class="graphic"
|
||||
style=${ifDefined(
|
||||
showImages && child.thumbnail
|
||||
? `background-image: url(${child.thumbnail})`
|
||||
: undefined
|
||||
)}
|
||||
.path=${this.action === "play" ? mdiPlay : mdiPlus}
|
||||
></ha-svg-icon
|
||||
></mwc-list-item>
|
||||
<li divider role="separator"></li>`
|
||||
slot="graphic"
|
||||
>
|
||||
<mwc-icon-button
|
||||
class="play ${classMap({
|
||||
show: !showImages || !child.thumbnail,
|
||||
})}"
|
||||
.item=${child}
|
||||
.label=${this.hass.localize(
|
||||
`ui.components.media-browser.${this.action}-media`
|
||||
)}
|
||||
@click=${this._actionClicked}
|
||||
>
|
||||
<ha-svg-icon
|
||||
.path=${this.action === "play" ? mdiPlay : mdiPlus}
|
||||
></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
</div>
|
||||
<span>${child.title}</span>
|
||||
</mwc-list-item>
|
||||
<li divider role="separator"></li>
|
||||
`
|
||||
)}
|
||||
</mwc-list>
|
||||
`
|
||||
@ -260,6 +319,11 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
protected firstUpdated(): void {
|
||||
this._measureCard();
|
||||
this._attachObserver();
|
||||
|
||||
this.addEventListener("scroll", this._scroll, { passive: true });
|
||||
this.addEventListener("touchmove", this._scroll, {
|
||||
passive: true,
|
||||
});
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
@ -295,25 +359,23 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private async _navigate(ev: MouseEvent): Promise<void> {
|
||||
private async _navigateForward(ev: MouseEvent): Promise<void> {
|
||||
const target = ev.currentTarget as any;
|
||||
let item: MediaPlayerItem | undefined;
|
||||
|
||||
if (target.previous) {
|
||||
this._mediaPlayerItems!.pop();
|
||||
item = this._mediaPlayerItems!.pop();
|
||||
}
|
||||
|
||||
item = target.item;
|
||||
const item: MediaPlayerItem = target.item;
|
||||
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
this._navigate(item);
|
||||
}
|
||||
|
||||
private async _navigate(item: MediaPlayerItem) {
|
||||
const itemData = await this._fetchData(
|
||||
item.media_content_id,
|
||||
item.media_content_type
|
||||
);
|
||||
|
||||
this.scrollTo(0, 0);
|
||||
this._mediaPlayerItems = [...this._mediaPlayerItems, itemData];
|
||||
}
|
||||
|
||||
@ -332,7 +394,15 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
}
|
||||
|
||||
private _measureCard(): void {
|
||||
this._narrow = this.offsetWidth < 500;
|
||||
this._narrow = (this.dialog ? window.innerWidth : this.offsetWidth) < 450;
|
||||
}
|
||||
|
||||
private _scroll(): void {
|
||||
if (this.scrollTop > (this._narrow ? 224 : 125)) {
|
||||
this.setAttribute("scroll", "");
|
||||
} else if (this.scrollTop === 0) {
|
||||
this.removeAttribute("scroll");
|
||||
}
|
||||
}
|
||||
|
||||
private async _attachObserver(): Promise<void> {
|
||||
@ -350,22 +420,40 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
children.find((item: MediaPlayerItem) => item.can_expand)
|
||||
);
|
||||
|
||||
private _closeDialogAction(): void {
|
||||
fireEvent(this, "close-dialog");
|
||||
}
|
||||
|
||||
static get styles(): CSSResultArray {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
padding: 0px 0px 20px;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
}
|
||||
|
||||
.breadcrumb-overflow {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
.header_button {
|
||||
position: relative;
|
||||
top: 14px;
|
||||
right: -8px;
|
||||
}
|
||||
|
||||
.header {
|
||||
background-color: var(--card-background-color);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 5;
|
||||
padding: 20px 24px 10px;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
@ -380,6 +468,8 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
width: 200px;
|
||||
margin-right: 16px;
|
||||
background-size: cover;
|
||||
border-radius: 4px;
|
||||
transition: width 0.4s, height 0.4s;
|
||||
}
|
||||
|
||||
.header-info {
|
||||
@ -391,9 +481,14 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.header-info .actions {
|
||||
padding-top: 24px;
|
||||
--mdc-theme-primary: var(--primary-color);
|
||||
.header-info mwc-button {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.breadcrumb-overflow {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
@ -404,7 +499,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
}
|
||||
|
||||
.breadcrumb .title {
|
||||
font-size: 48px;
|
||||
font-size: 32px;
|
||||
line-height: 1.2;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
@ -412,6 +507,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.breadcrumb .previous-title {
|
||||
@ -428,17 +524,8 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
font-size: 16px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.divider {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.divider::before {
|
||||
height: 1px;
|
||||
display: block;
|
||||
background-color: var(--divider-color);
|
||||
content: " ";
|
||||
margin-bottom: 0;
|
||||
transition: height 0.5s, margin 0.5s;
|
||||
}
|
||||
|
||||
/* ============= CHILDREN ============= */
|
||||
@ -446,8 +533,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
mwc-list {
|
||||
--mdc-list-vertical-padding: 0;
|
||||
--mdc-theme-text-icon-on-background: var(--secondary-text-color);
|
||||
border: 1px solid var(--divider-color);
|
||||
border-radius: 4px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
mwc-list li:last-child {
|
||||
@ -468,6 +554,10 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
margin: 8px 0px;
|
||||
}
|
||||
|
||||
:host(:not([narrow])) .children {
|
||||
padding: 0px 24px;
|
||||
}
|
||||
|
||||
.child {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -483,7 +573,9 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
width: 100%;
|
||||
padding-bottom: 100%;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
@ -503,7 +595,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
bottom: 4px;
|
||||
right: 4px;
|
||||
transition: all 0.5s;
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
background-color: rgba(var(--rgb-card-background-color), 0.5);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
@ -529,22 +621,46 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
mwc-list-item .graphic {
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
mwc-list-item .graphic .play {
|
||||
opacity: 0;
|
||||
transition: all 0.5s;
|
||||
background-color: rgba(var(--rgb-card-background-color), 0.5);
|
||||
border-radius: 50%;
|
||||
--mdc-icon-button-size: 40px;
|
||||
}
|
||||
|
||||
mwc-list-item:hover .graphic .play {
|
||||
opacity: 1;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
mwc-list-item .graphic .play.show {
|
||||
opacity: 1;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/* ============= Narrow ============= */
|
||||
|
||||
:host([narrow]) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
:host([narrow]) mwc-list {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
:host([narrow]) .breadcrumb .title {
|
||||
font-size: 38px;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
:host([narrow]) .breadcrumb-overflow {
|
||||
align-items: flex-end;
|
||||
:host([narrow]) .header {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
:host([narrow]) .header_button {
|
||||
position: absolute;
|
||||
top: 14px;
|
||||
right: 8px;
|
||||
}
|
||||
|
||||
:host([narrow]) .header-content {
|
||||
@ -556,26 +672,100 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
height: auto;
|
||||
width: 100%;
|
||||
margin-right: 0;
|
||||
padding-bottom: 100%;
|
||||
padding-bottom: 50%;
|
||||
margin-bottom: 8px;
|
||||
position: relative;
|
||||
background-position: center;
|
||||
border-radius: 0;
|
||||
transition: width 0.4s, height 0.4s, padding-bottom 0.4s;
|
||||
}
|
||||
|
||||
:host([narrow]) .header-content .img mwc-fab {
|
||||
mwc-fab {
|
||||
position: absolute;
|
||||
--mdc-theme-secondary: var(--primary-color);
|
||||
bottom: -20px;
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
:host([narrow]) .header-info,
|
||||
:host([narrow]) .header-info mwc-button {
|
||||
margin-top: 16px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
:host([narrow]) .header-info {
|
||||
padding: 20px 24px 10px;
|
||||
}
|
||||
|
||||
:host([narrow]) .media-source,
|
||||
:host([narrow]) .children {
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
:host([narrow]) .children {
|
||||
grid-template-columns: 1fr 1fr !important;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) !important;
|
||||
}
|
||||
|
||||
/* ============= Scroll ============= */
|
||||
|
||||
:host([scroll]) .breadcrumb .subtitle {
|
||||
height: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:host([scroll]) .breadcrumb .title {
|
||||
-webkit-line-clamp: 1;
|
||||
}
|
||||
|
||||
:host([scroll]) .header-info mwc-button,
|
||||
.no-img .header-info mwc-button {
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
:host([scroll][narrow]) .no-img .header-info mwc-button {
|
||||
padding-right: 16px;
|
||||
}
|
||||
|
||||
:host([scroll]) .header-info {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
:host([scroll]) .header-info mwc-button {
|
||||
align-self: center;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
:host([scroll][narrow]) .no-img .header-info {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
:host([scroll][narrow]) .header-info {
|
||||
padding: 20px 24px 10px 24px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:host([scroll]) .header-content {
|
||||
align-items: flex-end;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
:host([scroll]) .header-content .img {
|
||||
height: 75px;
|
||||
width: 75px;
|
||||
}
|
||||
|
||||
:host([scroll][narrow]) .header-content .img {
|
||||
height: 100px;
|
||||
width: 100px;
|
||||
padding-bottom: initial;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
:host([scroll]) mwc-fab {
|
||||
bottom: 4px;
|
||||
right: 4px;
|
||||
--mdc-fab-box-shadow: none;
|
||||
--mdc-theme-secondary: rgba(var(--rgb-primary-color), 0.5);
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@ -44,3 +44,14 @@ export const createAuthForUser = async (
|
||||
username,
|
||||
password,
|
||||
});
|
||||
|
||||
export const adminChangePassword = async (
|
||||
hass: HomeAssistant,
|
||||
userId: string,
|
||||
password: string
|
||||
) =>
|
||||
hass.callWS<void>({
|
||||
type: "config/auth_provider/homeassistant/admin_change_password",
|
||||
user_id: userId,
|
||||
password,
|
||||
});
|
||||
|
@ -3,7 +3,7 @@ import {
|
||||
HassEntityBase,
|
||||
} from "home-assistant-js-websocket";
|
||||
import { navigate } from "../common/navigate";
|
||||
import { HomeAssistant } from "../types";
|
||||
import { HomeAssistant, Context } from "../types";
|
||||
import { DeviceCondition, DeviceTrigger } from "./device_automation";
|
||||
import { Action } from "./script";
|
||||
|
||||
@ -206,3 +206,31 @@ export const getAutomationEditorInitData = () => {
|
||||
inititialAutomationEditorData = undefined;
|
||||
return data;
|
||||
};
|
||||
|
||||
export const subscribeTrigger = (
|
||||
hass: HomeAssistant,
|
||||
onChange: (result: {
|
||||
variables: {
|
||||
trigger: {};
|
||||
};
|
||||
context: Context;
|
||||
}) => void,
|
||||
trigger: Trigger | Trigger[],
|
||||
variables?: {}
|
||||
) =>
|
||||
hass.connection.subscribeMessage(onChange, {
|
||||
type: "subscribe_trigger",
|
||||
trigger,
|
||||
variables,
|
||||
});
|
||||
|
||||
export const testCondition = (
|
||||
hass: HomeAssistant,
|
||||
condition: Condition | Condition[],
|
||||
variables?: {}
|
||||
) =>
|
||||
hass.callWS<{ result: boolean }>({
|
||||
type: "test_condition",
|
||||
condition,
|
||||
variables,
|
||||
});
|
||||
|
@ -9,14 +9,14 @@ interface CloudStatusBase {
|
||||
}
|
||||
|
||||
export interface GoogleEntityConfig {
|
||||
should_expose?: boolean;
|
||||
should_expose?: boolean | null;
|
||||
override_name?: string;
|
||||
aliases?: string[];
|
||||
disable_2fa?: boolean;
|
||||
}
|
||||
|
||||
export interface AlexaEntityConfig {
|
||||
should_expose?: boolean;
|
||||
should_expose?: boolean | null;
|
||||
}
|
||||
|
||||
export interface CertificateInformation {
|
||||
@ -31,9 +31,11 @@ export interface CloudPreferences {
|
||||
remote_enabled: boolean;
|
||||
google_secure_devices_pin: string | undefined;
|
||||
cloudhooks: { [webhookId: string]: CloudWebhook };
|
||||
google_default_expose: string[] | null;
|
||||
google_entity_configs: {
|
||||
[entityId: string]: GoogleEntityConfig;
|
||||
};
|
||||
alexa_default_expose: string[] | null;
|
||||
alexa_entity_configs: {
|
||||
[entityId: string]: AlexaEntityConfig;
|
||||
};
|
||||
@ -106,8 +108,10 @@ export const updateCloudPref = (
|
||||
prefs: {
|
||||
google_enabled?: CloudPreferences["google_enabled"];
|
||||
alexa_enabled?: CloudPreferences["alexa_enabled"];
|
||||
alexa_default_expose?: CloudPreferences["alexa_default_expose"];
|
||||
alexa_report_state?: CloudPreferences["alexa_report_state"];
|
||||
google_report_state?: CloudPreferences["google_report_state"];
|
||||
google_default_expose?: CloudPreferences["google_default_expose"];
|
||||
google_secure_devices_pin?: CloudPreferences["google_secure_devices_pin"];
|
||||
}
|
||||
) =>
|
||||
|
@ -8,6 +8,7 @@ export interface ConfigEntry {
|
||||
state: string;
|
||||
connection_class: string;
|
||||
supports_options: boolean;
|
||||
supports_unload: boolean;
|
||||
}
|
||||
|
||||
export interface ConfigEntryMutableParams {
|
||||
@ -37,6 +38,11 @@ export const deleteConfigEntry = (hass: HomeAssistant, configEntryId: string) =>
|
||||
require_restart: boolean;
|
||||
}>("DELETE", `config/config_entries/entry/${configEntryId}`);
|
||||
|
||||
export const reloadConfigEntry = (hass: HomeAssistant, configEntryId: string) =>
|
||||
hass.callApi<{
|
||||
require_restart: boolean;
|
||||
}>("POST", `config/config_entries/entry/${configEntryId}/reload`);
|
||||
|
||||
export const getConfigEntrySystemOptions = (
|
||||
hass: HomeAssistant,
|
||||
configEntryId: string
|
||||
|
@ -72,6 +72,7 @@ export interface HassioAddonDetails extends HassioAddonInfo {
|
||||
ingress_panel: boolean;
|
||||
ingress_entry: null | string;
|
||||
ingress_url: null | string;
|
||||
watchdog: null | boolean;
|
||||
}
|
||||
|
||||
export interface HassioAddonsInfo {
|
||||
@ -99,6 +100,7 @@ export interface HassioAddonSetOptionParams {
|
||||
auto_update?: boolean;
|
||||
ingress_panel?: boolean;
|
||||
network?: object | null;
|
||||
watchdog?: boolean;
|
||||
}
|
||||
|
||||
export const reloadHassioAddons = async (hass: HomeAssistant) => {
|
||||
|
@ -40,6 +40,10 @@ export const updateOS = async (hass: HomeAssistant) => {
|
||||
return hass.callApi<HassioResponse<void>>("POST", "hassio/os/update");
|
||||
};
|
||||
|
||||
export const configSyncOS = async (hass: HomeAssistant) => {
|
||||
return hass.callApi<HassioResponse<void>>("POST", "hassio/os/config/sync");
|
||||
};
|
||||
|
||||
export const changeHostOptions = async (hass: HomeAssistant, options: any) => {
|
||||
return hass.callApi<HassioResponse<void>>(
|
||||
"POST",
|
||||
|
43
src/data/hassio/network.ts
Normal file
43
src/data/hassio/network.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { hassioApiResultExtractor, HassioResponse } from "./common";
|
||||
|
||||
export interface NetworkInterface {
|
||||
gateway: string;
|
||||
id: string;
|
||||
ip_address: string;
|
||||
address?: string;
|
||||
method: "static" | "dhcp";
|
||||
nameservers: string[] | string;
|
||||
dns?: string[];
|
||||
primary: boolean;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface NetworkInterfaces {
|
||||
[key: string]: NetworkInterface;
|
||||
}
|
||||
|
||||
export interface NetworkInfo {
|
||||
interfaces: NetworkInterfaces;
|
||||
}
|
||||
|
||||
export const fetchNetworkInfo = async (hass: HomeAssistant) => {
|
||||
return hassioApiResultExtractor(
|
||||
await hass.callApi<HassioResponse<NetworkInfo>>(
|
||||
"GET",
|
||||
"hassio/network/info"
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export const updateNetworkInterface = async (
|
||||
hass: HomeAssistant,
|
||||
network_interface: string,
|
||||
options: Partial<NetworkInterface>
|
||||
) => {
|
||||
await hass.callApi<HassioResponse<NetworkInfo>>(
|
||||
"POST",
|
||||
`hassio/network/interface/${network_interface}/update`,
|
||||
options
|
||||
);
|
||||
};
|
@ -35,6 +35,14 @@ export interface SupervisorOptions {
|
||||
addons_repositories?: string[];
|
||||
}
|
||||
|
||||
export const reloadSupervisor = async (hass: HomeAssistant) => {
|
||||
await hass.callApi<HassioResponse<void>>("POST", `hassio/supervisor/reload`);
|
||||
};
|
||||
|
||||
export const updateSupervisor = async (hass: HomeAssistant) => {
|
||||
await hass.callApi<HassioResponse<void>>("POST", `hassio/supervisor/update`);
|
||||
};
|
||||
|
||||
export const fetchHassioHomeAssistantInfo = async (hass: HomeAssistant) => {
|
||||
return hassioApiResultExtractor(
|
||||
await hass.callApi<HassioResponse<HassioHomeAssistantInfo>>(
|
||||
|
@ -318,10 +318,11 @@ export interface WindowWithLovelaceProm extends Window {
|
||||
export interface ActionHandlerOptions {
|
||||
hasHold?: boolean;
|
||||
hasDoubleClick?: boolean;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export interface ActionHandlerDetail {
|
||||
action: string;
|
||||
action: "hold" | "tap" | "double_tap";
|
||||
}
|
||||
|
||||
export type ActionHandlerEvent = HASSDomEvent<ActionHandlerDetail>;
|
||||
|
@ -2,6 +2,7 @@ import { SVGTemplateResult, svg, html, TemplateResult, css } from "lit-element";
|
||||
import { styleMap } from "lit-html/directives/style-map";
|
||||
|
||||
import type { HomeAssistant, WeatherEntity } from "../types";
|
||||
import { roundWithOneDecimal } from "../util/calculate";
|
||||
|
||||
export const weatherSVGs = new Set<string>([
|
||||
"clear-night",
|
||||
@ -135,7 +136,7 @@ export const getSecondaryWeatherAttribute = (
|
||||
return `
|
||||
${hass!.localize(
|
||||
`ui.card.weather.attributes.${attribute}`
|
||||
)} ${value} ${getWeatherUnit(hass!, attribute)}
|
||||
)} ${roundWithOneDecimal(value)} ${getWeatherUnit(hass!, attribute)}
|
||||
`;
|
||||
};
|
||||
|
||||
|
@ -1,21 +1,22 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||
import "../../components/ha-icon-button";
|
||||
import "../../components/ha-circular-progress";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
CSSResultArray,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { computeRTL } from "../../common/util/compute_rtl";
|
||||
import "../../components/ha-circular-progress";
|
||||
import "../../components/ha-dialog";
|
||||
import "../../components/ha-form/ha-form";
|
||||
import "../../components/ha-icon-button";
|
||||
import "../../components/ha-markdown";
|
||||
import {
|
||||
AreaRegistryEntry,
|
||||
@ -35,8 +36,6 @@ import "./step-flow-external";
|
||||
import "./step-flow-form";
|
||||
import "./step-flow-loading";
|
||||
import "./step-flow-pick-handler";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { computeRTL } from "../../common/util/compute_rtl";
|
||||
|
||||
let instance = 0;
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
import "@material/mwc-button";
|
||||
import "../../components/ha-circular-progress";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import {
|
||||
css,
|
||||
@ -12,6 +11,7 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import "../../components/ha-circular-progress";
|
||||
import "../../components/ha-form/ha-form";
|
||||
import type { HaFormSchema } from "../../components/ha-form/ha-form";
|
||||
import "../../components/ha-markdown";
|
||||
@ -91,7 +91,7 @@ class StepFlowForm extends LitElement {
|
||||
|
||||
${!allRequiredInfoFilledIn
|
||||
? html`
|
||||
<paper-tooltip position="left"
|
||||
<paper-tooltip animation-delay="0" position="left"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.not_all_required_fields"
|
||||
)}
|
||||
|
@ -4,27 +4,35 @@ import {
|
||||
CSSResultArray,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "../../components/dialog/ha-paper-dialog";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { createCloseHeading } from "../../components/ha-dialog";
|
||||
import "../../components/ha-switch";
|
||||
import "../../components/ha-formfield";
|
||||
import { domainToName } from "../../data/integration";
|
||||
import { PolymerChangedEvent } from "../../polymer-types";
|
||||
import { haStyleDialog } from "../../resources/styles";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { HassDialog } from "../make-dialog-manager";
|
||||
import { HaDomainTogglerDialogParams } from "./show-dialog-domain-toggler";
|
||||
|
||||
@customElement("dialog-domain-toggler")
|
||||
class DomainTogglerDialog extends LitElement {
|
||||
class DomainTogglerDialog extends LitElement implements HassDialog {
|
||||
public hass!: HomeAssistant;
|
||||
|
||||
@internalProperty() private _params?: HaDomainTogglerDialogParams;
|
||||
|
||||
public async showDialog(params: HaDomainTogglerDialogParams): Promise<void> {
|
||||
public showDialog(params: HaDomainTogglerDialogParams): void {
|
||||
this._params = params;
|
||||
}
|
||||
|
||||
public closeDialog() {
|
||||
this._params = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._params) {
|
||||
return html``;
|
||||
@ -35,46 +43,47 @@ class DomainTogglerDialog extends LitElement {
|
||||
.sort();
|
||||
|
||||
return html`
|
||||
<ha-paper-dialog
|
||||
with-backdrop
|
||||
opened
|
||||
@opened-changed=${this._openedChanged}
|
||||
<ha-dialog
|
||||
open
|
||||
@closed=${this.closeDialog}
|
||||
scrimClickAction
|
||||
escapeKeyAction
|
||||
hideActions
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
this.hass.localize("ui.dialogs.domain_toggler.title")
|
||||
)}
|
||||
>
|
||||
<h2>
|
||||
${this.hass.localize("ui.dialogs.domain_toggler.title")}
|
||||
</h2>
|
||||
<div>
|
||||
${domains.map(
|
||||
(domain) =>
|
||||
html`
|
||||
<div>${domain[0]}</div>
|
||||
<mwc-button .domain=${domain[1]} @click=${this._handleOff}>
|
||||
${this.hass.localize("state.default.off")}
|
||||
</mwc-button>
|
||||
<mwc-button .domain=${domain[1]} @click=${this._handleOn}>
|
||||
${this.hass.localize("state.default.on")}
|
||||
<ha-formfield .label=${domain[0]}>
|
||||
<ha-switch
|
||||
.domain=${domain[1]}
|
||||
.checked=${!this._params!.exposedDomains ||
|
||||
this._params!.exposedDomains.includes(domain[1])}
|
||||
@change=${this._handleSwitch}
|
||||
>
|
||||
</ha-switch>
|
||||
</ha-formfield>
|
||||
<mwc-button .domain=${domain[1]} @click=${this._handleReset}>
|
||||
${this.hass.localize("ui.dialogs.domain_toggler.reset_entities")}
|
||||
</mwc-button>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
</ha-paper-dialog>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _openedChanged(ev: PolymerChangedEvent<boolean>): void {
|
||||
// Closed dialog by clicking on the overlay
|
||||
if (!ev.detail.value) {
|
||||
this._params = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private _handleOff(ev) {
|
||||
this._params!.toggleDomain(ev.currentTarget.domain, false);
|
||||
private _handleSwitch(ev) {
|
||||
this._params!.toggleDomain(ev.currentTarget.domain, ev.target.checked);
|
||||
ev.currentTarget.blur();
|
||||
}
|
||||
|
||||
private _handleOn(ev) {
|
||||
this._params!.toggleDomain(ev.currentTarget.domain, true);
|
||||
private _handleReset(ev) {
|
||||
this._params!.resetDomain(ev.currentTarget.domain);
|
||||
ev.currentTarget.blur();
|
||||
}
|
||||
|
||||
@ -82,8 +91,8 @@ class DomainTogglerDialog extends LitElement {
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-paper-dialog {
|
||||
max-width: 500px;
|
||||
ha-dialog {
|
||||
--mdc-dialog-max-width: 500px;
|
||||
}
|
||||
div {
|
||||
display: grid;
|
||||
|
@ -2,7 +2,9 @@ import { fireEvent } from "../../common/dom/fire_event";
|
||||
|
||||
export interface HaDomainTogglerDialogParams {
|
||||
domains: string[];
|
||||
exposedDomains: string[] | null;
|
||||
toggleDomain: (domain: string, turnOn: boolean) => void;
|
||||
resetDomain: (domain: string) => void;
|
||||
}
|
||||
|
||||
export const loadDomainTogglerDialog = () =>
|
||||
|
@ -55,9 +55,9 @@ class DialogBox extends LitElement {
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
scrimClickAction
|
||||
escapeKeyAction
|
||||
@close=${this._close}
|
||||
?scrimClickAction=${this._params.prompt}
|
||||
?escapeKeyAction=${this._params.prompt}
|
||||
@closed=${this._dismiss}
|
||||
.heading=${this._params.title
|
||||
? this._params.title
|
||||
: this._params.confirmation &&
|
||||
|
@ -5,29 +5,27 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
|
||||
import {
|
||||
SUPPORT_BRIGHTNESS,
|
||||
SUPPORT_COLOR_TEMP,
|
||||
SUPPORT_WHITE_VALUE,
|
||||
SUPPORT_COLOR,
|
||||
SUPPORT_EFFECT,
|
||||
} from "../../../data/light";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import type { HomeAssistant, LightEntity } from "../../../types";
|
||||
|
||||
import "../../../components/ha-attributes";
|
||||
import "../../../components/ha-color-picker";
|
||||
import "../../../components/ha-labeled-slider";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-labeled-slider";
|
||||
import "../../../components/ha-paper-dropdown-menu";
|
||||
import {
|
||||
SUPPORT_BRIGHTNESS,
|
||||
SUPPORT_COLOR,
|
||||
SUPPORT_COLOR_TEMP,
|
||||
SUPPORT_EFFECT,
|
||||
SUPPORT_WHITE_VALUE,
|
||||
} from "../../../data/light";
|
||||
import type { HomeAssistant, LightEntity } from "../../../types";
|
||||
|
||||
interface HueSatColor {
|
||||
h: number;
|
||||
@ -149,7 +147,7 @@ class MoreInfoLight extends LitElement {
|
||||
: ""}
|
||||
<ha-attributes
|
||||
.stateObj=${this.stateObj}
|
||||
extraFilters="brightness,color_temp,white_value,effect_list,effect,hs_color,rgb_color,xy_color,min_mireds,max_mireds,entity_id"
|
||||
extra-filters="brightness,color_temp,white_value,effect_list,effect,hs_color,rgb_color,xy_color,min_mireds,max_mireds,entity_id"
|
||||
></ha-attributes>
|
||||
</div>
|
||||
`;
|
||||
|
@ -174,7 +174,7 @@ class MoreInfoMediaPlayer extends LitElement {
|
||||
>
|
||||
${stateObj.attributes.sound_mode_list.map(
|
||||
(mode) => html`
|
||||
<paper-item itemName=${mode}>${mode}</paper-item>
|
||||
<paper-item .itemName=${mode}>${mode}</paper-item>
|
||||
`
|
||||
)}
|
||||
</paper-listbox>
|
||||
@ -352,21 +352,27 @@ class MoreInfoMediaPlayer extends LitElement {
|
||||
}
|
||||
|
||||
private _handleSourceChanged(e: CustomEvent) {
|
||||
const newVal = e.detail.value;
|
||||
const newVal = e.detail.item.itemName;
|
||||
|
||||
if (!newVal || this.stateObj!.attributes.source === newVal) return;
|
||||
if (!newVal || this.stateObj!.attributes.source === newVal) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.hass.callService("media_player", "select_source", {
|
||||
entity_id: this.stateObj!.entity_id,
|
||||
source: newVal,
|
||||
});
|
||||
}
|
||||
|
||||
private _handleSoundModeChanged(e: CustomEvent) {
|
||||
const newVal = e.detail.value;
|
||||
const newVal = e.detail.item.itemName;
|
||||
|
||||
if (!newVal || this.stateObj?.attributes.sound_mode === newVal) return;
|
||||
if (!newVal || this.stateObj?.attributes.sound_mode === newVal) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.hass.callService("media_player", "select_sound_mode", {
|
||||
entity_id: this.stateObj!.entity_id,
|
||||
sound_mode: newVal,
|
||||
});
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ const VACUUM_COMMANDS: VacuumCommand[] = [
|
||||
},
|
||||
{
|
||||
translationKey: "clean_spot",
|
||||
icon: "hass:broom",
|
||||
icon: "hass:target-variant",
|
||||
serviceName: "clean_spot",
|
||||
isVisible: (stateObj) =>
|
||||
supportsFeature(stateObj, VACUUM_SUPPORT_CLEAN_SPOT),
|
||||
|
@ -43,12 +43,9 @@ export class HuiPersistentNotificationItem extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.datetime="${this.notification.created_at}"
|
||||
></ha-relative-time>
|
||||
<paper-tooltip
|
||||
>${this._computeTooltip(
|
||||
this.hass,
|
||||
this.notification
|
||||
)}</paper-tooltip
|
||||
>
|
||||
<paper-tooltip animation-delay="0">
|
||||
${this._computeTooltip(this.hass, this.notification)}
|
||||
</paper-tooltip>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
@ -11,6 +11,7 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { navigate } from "../common/navigate";
|
||||
import { computeRTLDirection } from "../common/util/compute_rtl";
|
||||
import "../components/data-table/ha-data-table";
|
||||
import type {
|
||||
DataTableColumnContainer,
|
||||
@ -20,7 +21,6 @@ import type {
|
||||
import type { HomeAssistant, Route } from "../types";
|
||||
import "./hass-tabs-subpage";
|
||||
import type { PageNavigation } from "./hass-tabs-subpage";
|
||||
import { computeRTLDirection } from "../common/util/compute_rtl";
|
||||
|
||||
@customElement("hass-tabs-subpage-data-table")
|
||||
export class HaTabsSubpageDataTable extends LitElement {
|
||||
@ -136,7 +136,7 @@ export class HaTabsSubpageDataTable extends LitElement {
|
||||
? html`<div class="active-filters">
|
||||
<div>
|
||||
<ha-icon icon="hass:filter-variant"></ha-icon>
|
||||
<paper-tooltip position="left">
|
||||
<paper-tooltip animation-delay="0" position="left">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.filtering.filtering_by"
|
||||
)}
|
||||
|
@ -4,9 +4,9 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { ifDefined } from "lit-html/directives/if-defined";
|
||||
@ -175,8 +175,8 @@ class HaConfigAreaPage extends LitElement {
|
||||
</a>
|
||||
${!state.attributes.id
|
||||
? html`
|
||||
<paper-tooltip
|
||||
>${this.hass.localize(
|
||||
<paper-tooltip animation-delay="0">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.devices.cant_edit"
|
||||
)}
|
||||
</paper-tooltip>
|
||||
@ -228,8 +228,8 @@ class HaConfigAreaPage extends LitElement {
|
||||
</a>
|
||||
${!state.attributes.id
|
||||
? html`
|
||||
<paper-tooltip
|
||||
>${this.hass.localize(
|
||||
<paper-tooltip animation-delay="0">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.devices.cant_edit"
|
||||
)}
|
||||
</paper-tooltip>
|
||||
|
@ -1,3 +1,5 @@
|
||||
import "@material/mwc-fab";
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import {
|
||||
@ -16,7 +18,8 @@ import {
|
||||
DataTableColumnContainer,
|
||||
RowClickedEvent,
|
||||
} from "../../../components/data-table/ha-data-table";
|
||||
import "@material/mwc-fab";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import {
|
||||
AreaRegistryEntry,
|
||||
createAreaRegistryEntry,
|
||||
@ -26,8 +29,6 @@ import {
|
||||
devicesInArea,
|
||||
} from "../../../data/device_registry";
|
||||
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import "../../../layouts/hass-loading-screen";
|
||||
import "../../../layouts/hass-tabs-subpage-data-table";
|
||||
import { HomeAssistant, Route } from "../../../types";
|
||||
@ -37,7 +38,6 @@ import {
|
||||
loadAreaRegistryDetailDialog,
|
||||
showAreaRegistryDetailDialog,
|
||||
} from "./show-dialog-area-registry-detail";
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
|
||||
@customElement("ha-config-areas-dashboard")
|
||||
export class HaConfigAreasDashboard extends LitElement {
|
||||
|
@ -15,7 +15,7 @@ export class HaConditionAction extends LitElement implements ActionElement {
|
||||
return { condition: "state" };
|
||||
}
|
||||
|
||||
public render() {
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-automation-condition-editor
|
||||
.condition=${this.action}
|
||||
|
@ -16,7 +16,7 @@ export class HaDelayAction extends LitElement implements ActionElement {
|
||||
return { delay: "" };
|
||||
}
|
||||
|
||||
public render() {
|
||||
protected render() {
|
||||
const { delay } = this.action;
|
||||
|
||||
return html`
|
||||
|
@ -1,8 +1,8 @@
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-input/paper-textarea";
|
||||
import { customElement, html, LitElement, property } from "lit-element";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/entity/ha-entity-picker";
|
||||
import "@polymer/paper-input/paper-textarea";
|
||||
import { NumericStateCondition } from "../../../../../data/automation";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import { handleChangeEvent } from "../ha-automation-condition-row";
|
||||
@ -19,7 +19,7 @@ export default class HaNumericStateCondition extends LitElement {
|
||||
};
|
||||
}
|
||||
|
||||
public render() {
|
||||
protected render() {
|
||||
const { value_template, entity_id, below, above } = this.condition;
|
||||
|
||||
return html`
|
||||
|
@ -146,7 +146,7 @@ export class HaAutomationEditor extends LitElement {
|
||||
"ui.panel.config.automation.editor.modes.description",
|
||||
"documentation_link",
|
||||
html`<a
|
||||
href="https://www.home-assistant.io/docs/automation/#automation-modes"
|
||||
href="https://www.home-assistant.io/integrations/automation/#automation-modes"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>${this.hass.localize(
|
||||
|
@ -1,12 +1,13 @@
|
||||
import "../../../components/ha-icon-button";
|
||||
import "@material/mwc-fab";
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
CSSResult,
|
||||
} from "lit-element";
|
||||
import { ifDefined } from "lit-html/directives/if-defined";
|
||||
import memoizeOne from "memoize-one";
|
||||
@ -16,7 +17,8 @@ import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { DataTableColumnContainer } from "../../../components/data-table/ha-data-table";
|
||||
import "../../../components/entity/ha-entity-toggle";
|
||||
import "@material/mwc-fab";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import {
|
||||
AutomationConfig,
|
||||
AutomationEntity,
|
||||
@ -28,8 +30,6 @@ import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../types";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import { showThingtalkDialog } from "./show-dialog-thingtalk";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
|
||||
@customElement("ha-automation-picker")
|
||||
class HaAutomationPicker extends LitElement {
|
||||
@ -138,7 +138,7 @@ class HaAutomationPicker extends LitElement {
|
||||
</a>
|
||||
${!automation.attributes.id
|
||||
? html`
|
||||
<paper-tooltip position="left">
|
||||
<paper-tooltip animation-delay="0" position="left">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.picker.only_editable"
|
||||
)}
|
||||
|
@ -19,7 +19,7 @@ export class HaEventTrigger extends LitElement implements TriggerElement {
|
||||
return { event_type: "", event_data: {} };
|
||||
}
|
||||
|
||||
public render() {
|
||||
protected render() {
|
||||
const { event_type, event_data } = this.trigger;
|
||||
return html`
|
||||
<paper-input
|
||||
|
@ -18,7 +18,7 @@ export default class HaHassTrigger extends LitElement {
|
||||
};
|
||||
}
|
||||
|
||||
public render() {
|
||||
protected render() {
|
||||
const { event } = this.trigger;
|
||||
return html`
|
||||
<label id="eventlabel">
|
||||
|
@ -1,8 +1,8 @@
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-input/paper-textarea";
|
||||
import { customElement, html, LitElement, property } from "lit-element";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/entity/ha-entity-picker";
|
||||
import "@polymer/paper-input/paper-textarea";
|
||||
import { ForDict, NumericStateTrigger } from "../../../../../data/automation";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import { handleChangeEvent } from "../ha-automation-trigger-row";
|
||||
@ -19,7 +19,7 @@ export default class HaNumericStateTrigger extends LitElement {
|
||||
};
|
||||
}
|
||||
|
||||
public render() {
|
||||
protected render() {
|
||||
const { value_template, entity_id, below, above } = this.trigger;
|
||||
let trgFor = this.trigger.for;
|
||||
|
||||
|
@ -1,14 +1,22 @@
|
||||
import "../../../../components/ha-icon-button";
|
||||
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import {
|
||||
mdiCheckboxMarked,
|
||||
mdiCheckboxMultipleMarked,
|
||||
mdiCloseBox,
|
||||
mdiCloseBoxMultiple,
|
||||
} from "@mdi/js";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { computeDomain } from "../../../../common/entity/compute_domain";
|
||||
@ -20,31 +28,28 @@ import {
|
||||
} from "../../../../common/entity/entity_filter";
|
||||
import { compare } from "../../../../common/string/compare";
|
||||
import "../../../../components/entity/state-info";
|
||||
import "../../../../components/ha-button-menu";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-formfield";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-switch";
|
||||
import type { HaSwitch } from "../../../../components/ha-switch";
|
||||
import { AlexaEntity, fetchCloudAlexaEntities } from "../../../../data/alexa";
|
||||
import {
|
||||
AlexaEntityConfig,
|
||||
CloudPreferences,
|
||||
CloudStatusLoggedIn,
|
||||
updateCloudAlexaEntityConfig,
|
||||
updateCloudPref,
|
||||
} from "../../../../data/cloud";
|
||||
import { showDomainTogglerDialog } from "../../../../dialogs/domain-toggler/show-dialog-domain-toggler";
|
||||
import "../../../../layouts/hass-loading-screen";
|
||||
import "../../../../layouts/hass-subpage";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import "../../../../components/ha-formfield";
|
||||
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
|
||||
|
||||
const DEFAULT_CONFIG_EXPOSE = true;
|
||||
const IGNORE_INTERFACES = ["Alexa.EndpointHealth"];
|
||||
|
||||
const configIsExposed = (config: AlexaEntityConfig) =>
|
||||
config.should_expose === undefined
|
||||
? DEFAULT_CONFIG_EXPOSE
|
||||
: config.should_expose;
|
||||
|
||||
@customElement("cloud-alexa")
|
||||
class CloudAlexa extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@ -100,7 +105,10 @@ class CloudAlexa extends LitElement {
|
||||
const stateObj = this.hass.states[entity.entity_id];
|
||||
const config = this._entityConfigs[entity.entity_id] || {};
|
||||
const isExposed = emptyFilter
|
||||
? configIsExposed(config)
|
||||
? this._configIsExposed(entity.entity_id, config)
|
||||
: filterFunc(entity.entity_id);
|
||||
const isDomainExposed = emptyFilter
|
||||
? this._configIsDomainExposed(entity.entity_id)
|
||||
: filterFunc(entity.entity_id);
|
||||
if (isExposed) {
|
||||
selected++;
|
||||
@ -117,33 +125,80 @@ class CloudAlexa extends LitElement {
|
||||
target.push(html`
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<state-info
|
||||
.hass=${this.hass}
|
||||
.stateObj=${stateObj}
|
||||
secondary-line
|
||||
@click=${this._showMoreInfo}
|
||||
>
|
||||
${entity.interfaces
|
||||
.filter((ifc) => !IGNORE_INTERFACES.includes(ifc))
|
||||
.map((ifc) =>
|
||||
ifc.replace("Alexa.", "").replace("Controller", "")
|
||||
)
|
||||
.join(", ")}
|
||||
</state-info>
|
||||
<ha-formfield
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.config.cloud.alexa.expose"
|
||||
)}
|
||||
.dir=${computeRTLDirection(this.hass!)}
|
||||
>
|
||||
<ha-switch
|
||||
.entityId=${entity.entity_id}
|
||||
.disabled=${!emptyFilter}
|
||||
.checked=${isExposed}
|
||||
@change=${this._exposeChanged}
|
||||
<div class="top-line">
|
||||
<state-info
|
||||
.hass=${this.hass}
|
||||
.stateObj=${stateObj}
|
||||
secondary-line
|
||||
@click=${this._showMoreInfo}
|
||||
>
|
||||
</ha-switch>
|
||||
</ha-formfield>
|
||||
${entity.interfaces
|
||||
.filter((ifc) => !IGNORE_INTERFACES.includes(ifc))
|
||||
.map((ifc) => ifc.replace(/(Alexa.|Controller)/g, ""))
|
||||
.join(", ")}
|
||||
</state-info>
|
||||
<ha-button-menu
|
||||
corner="BOTTOM_START"
|
||||
.entityId=${stateObj.entity_id}
|
||||
@action=${this._exposeChanged}
|
||||
>
|
||||
<mwc-icon-button
|
||||
slot="trigger"
|
||||
class=${classMap({
|
||||
exposed: isExposed!,
|
||||
"not-exposed": !isExposed,
|
||||
})}
|
||||
.title=${this.hass!.localize(
|
||||
"ui.panel.config.cloud.alexa.expose"
|
||||
)}
|
||||
>
|
||||
<ha-svg-icon
|
||||
.path=${config.should_expose !== null
|
||||
? isExposed
|
||||
? mdiCheckboxMarked
|
||||
: mdiCloseBox
|
||||
: isDomainExposed
|
||||
? mdiCheckboxMultipleMarked
|
||||
: mdiCloseBoxMultiple}
|
||||
></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<mwc-list-item hasMeta>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.alexa.expose_entity"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
class="exposed"
|
||||
slot="meta"
|
||||
.path=${mdiCheckboxMarked}
|
||||
></ha-svg-icon>
|
||||
</mwc-list-item>
|
||||
<mwc-list-item hasMeta>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.alexa.dont_expose_entity"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
class="not-exposed"
|
||||
slot="meta"
|
||||
.path=${mdiCloseBox}
|
||||
></ha-svg-icon>
|
||||
</mwc-list-item>
|
||||
<mwc-list-item hasMeta>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.alexa.follow_domain"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
class=${classMap({
|
||||
exposed: isDomainExposed,
|
||||
"not-exposed": !isDomainExposed,
|
||||
})}
|
||||
slot="meta"
|
||||
.path=${isDomainExposed
|
||||
? mdiCheckboxMultipleMarked
|
||||
: mdiCloseBoxMultiple}
|
||||
></ha-svg-icon>
|
||||
</mwc-list-item>
|
||||
</ha-button-menu>
|
||||
</div>
|
||||
</div>
|
||||
</ha-card>
|
||||
`);
|
||||
@ -157,17 +212,16 @@ class CloudAlexa extends LitElement {
|
||||
<hass-subpage header="${this.hass!.localize(
|
||||
"ui.panel.config.cloud.alexa.title"
|
||||
)}">
|
||||
<span slot="toolbar-icon">
|
||||
${selected}${!this.narrow ? html` selected ` : ""}
|
||||
</span>
|
||||
${
|
||||
emptyFilter
|
||||
? html`
|
||||
<ha-icon-button
|
||||
<mwc-button
|
||||
slot="toolbar-icon"
|
||||
icon="hass:tune"
|
||||
@click=${this._openDomainToggler}
|
||||
></ha-icon-button>
|
||||
>${this.hass!.localize(
|
||||
"ui.panel.config.cloud.alexa.manage_domains"
|
||||
)}</mwc-button
|
||||
>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
@ -183,11 +237,20 @@ class CloudAlexa extends LitElement {
|
||||
${
|
||||
exposedCards.length > 0
|
||||
? html`
|
||||
<h1>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.alexa.exposed_entities"
|
||||
)}
|
||||
</h1>
|
||||
<div class="header">
|
||||
<h3>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.alexa.exposed_entities"
|
||||
)}
|
||||
</h3>
|
||||
${!this.narrow
|
||||
? this.hass!.localize(
|
||||
"ui.panel.config.cloud.alexa.exposed",
|
||||
"selected",
|
||||
selected
|
||||
)
|
||||
: selected}
|
||||
</div>
|
||||
<div class="content">${exposedCards}</div>
|
||||
`
|
||||
: ""
|
||||
@ -195,11 +258,20 @@ class CloudAlexa extends LitElement {
|
||||
${
|
||||
notExposedCards.length > 0
|
||||
? html`
|
||||
<h1>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.alexa.not_exposed_entities"
|
||||
)}
|
||||
</h1>
|
||||
<div class="header second">
|
||||
<h3>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.alexa.not_exposed_entities"
|
||||
)}
|
||||
</h3>
|
||||
${!this.narrow
|
||||
? this.hass!.localize(
|
||||
"ui.panel.config.cloud.alexa.not_exposed",
|
||||
"selected",
|
||||
this._entities.length - selected
|
||||
)
|
||||
: this._entities.length - selected}
|
||||
</div>
|
||||
<div class="content">${notExposedCards}</div>
|
||||
`
|
||||
: ""
|
||||
@ -239,17 +311,37 @@ class CloudAlexa extends LitElement {
|
||||
fireEvent(this, "hass-more-info", { entityId });
|
||||
}
|
||||
|
||||
private async _exposeChanged(ev: Event) {
|
||||
const entityId = (ev.currentTarget as any).entityId;
|
||||
const newExposed = (ev.target as HaSwitch).checked;
|
||||
await this._updateExposed(entityId, newExposed);
|
||||
private _configIsDomainExposed(entityId: string) {
|
||||
const domain = computeDomain(entityId);
|
||||
return this.cloudStatus.prefs.alexa_default_expose
|
||||
? this.cloudStatus.prefs.alexa_default_expose.includes(domain)
|
||||
: DEFAULT_CONFIG_EXPOSE;
|
||||
}
|
||||
|
||||
private async _updateExposed(entityId: string, newExposed: boolean) {
|
||||
const curExposed = configIsExposed(this._entityConfigs[entityId] || {});
|
||||
if (newExposed === curExposed) {
|
||||
return;
|
||||
private _configIsExposed(entityId: string, config: AlexaEntityConfig) {
|
||||
return config.should_expose === null
|
||||
? this._configIsDomainExposed(entityId)
|
||||
: config.should_expose;
|
||||
}
|
||||
|
||||
private async _exposeChanged(ev: CustomEvent<ActionDetail>) {
|
||||
const entityId = (ev.currentTarget as any).entityId;
|
||||
let newVal: boolean | null = null;
|
||||
switch (ev.detail.index) {
|
||||
case 0:
|
||||
newVal = true;
|
||||
break;
|
||||
case 1:
|
||||
newVal = false;
|
||||
break;
|
||||
case 2:
|
||||
newVal = null;
|
||||
break;
|
||||
}
|
||||
await this._updateExposed(entityId, newVal);
|
||||
}
|
||||
|
||||
private async _updateExposed(entityId: string, newExposed: boolean | null) {
|
||||
await this._updateConfig(entityId, {
|
||||
should_expose: newExposed,
|
||||
});
|
||||
@ -274,16 +366,46 @@ class CloudAlexa extends LitElement {
|
||||
domains: this._entities!.map((entity) =>
|
||||
computeDomain(entity.entity_id)
|
||||
).filter((value, idx, self) => self.indexOf(value) === idx),
|
||||
toggleDomain: (domain, turnOn) => {
|
||||
exposedDomains: this.cloudStatus.prefs.alexa_default_expose,
|
||||
toggleDomain: (domain, expose) => {
|
||||
this._updateDomainExposed(domain, expose);
|
||||
},
|
||||
resetDomain: (domain) => {
|
||||
this._entities!.forEach((entity) => {
|
||||
if (computeDomain(entity.entity_id) === domain) {
|
||||
this._updateExposed(entity.entity_id, turnOn);
|
||||
this._updateExposed(entity.entity_id, null);
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private async _updateDomainExposed(domain: string, expose: boolean) {
|
||||
const defaultExpose =
|
||||
this.cloudStatus.prefs.alexa_default_expose ||
|
||||
this._entities!.map((entity) => computeDomain(entity.entity_id)).filter(
|
||||
(value, idx, self) => self.indexOf(value) === idx
|
||||
);
|
||||
|
||||
if (
|
||||
(expose && defaultExpose.includes(domain)) ||
|
||||
(!expose && !defaultExpose.includes(domain))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (expose) {
|
||||
defaultExpose.push(domain);
|
||||
} else {
|
||||
defaultExpose.splice(defaultExpose.indexOf(domain), 1);
|
||||
}
|
||||
|
||||
await updateCloudPref(this.hass!, {
|
||||
alexa_default_expose: defaultExpose,
|
||||
});
|
||||
fireEvent(this, "ha-refresh-cloud-status");
|
||||
}
|
||||
|
||||
private _ensureStatusReload() {
|
||||
if (this._popstateReloadStatusAttached) {
|
||||
return;
|
||||
@ -306,61 +428,75 @@ class CloudAlexa extends LitElement {
|
||||
this._popstateSyncAttached = true;
|
||||
// Cache parent because by the time popstate happens,
|
||||
// this element is detached
|
||||
// const parent = this.parentElement!;
|
||||
window.addEventListener(
|
||||
"popstate",
|
||||
() => {
|
||||
// We don't have anything yet.
|
||||
// showToast(parent, { message: "Synchronizing changes to Google." });
|
||||
// cloudSyncGoogleAssistant(this.hass);
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
.banner {
|
||||
color: var(--primary-text-color);
|
||||
background-color: var(
|
||||
--ha-card-background,
|
||||
var(--card-background-color, white)
|
||||
);
|
||||
padding: 16px 8px;
|
||||
text-align: center;
|
||||
}
|
||||
h1 {
|
||||
color: var(--primary-text-color);
|
||||
font-size: 24px;
|
||||
letter-spacing: -0.012em;
|
||||
margin-bottom: 0;
|
||||
padding: 0 8px;
|
||||
}
|
||||
.content {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
grid-gap: 8px 8px;
|
||||
padding: 8px;
|
||||
}
|
||||
ha-switch {
|
||||
clear: both;
|
||||
}
|
||||
.card-content {
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
state-info {
|
||||
cursor: pointer;
|
||||
}
|
||||
ha-switch {
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
@media all and (max-width: 450px) {
|
||||
ha-card {
|
||||
max-width: 100%;
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
mwc-list-item > [slot="meta"] {
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
.banner {
|
||||
color: var(--primary-text-color);
|
||||
background-color: var(
|
||||
--ha-card-background,
|
||||
var(--card-background-color, white)
|
||||
);
|
||||
padding: 16px 8px;
|
||||
text-align: center;
|
||||
}
|
||||
.content {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
grid-gap: 8px 8px;
|
||||
padding: 8px;
|
||||
}
|
||||
.card-content {
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
state-info {
|
||||
cursor: pointer;
|
||||
}
|
||||
ha-switch {
|
||||
padding: 8px 0;
|
||||
}
|
||||
.top-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 16px;
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
background: var(--app-header-background-color);
|
||||
}
|
||||
.header.second {
|
||||
border-top: 1px solid var(--divider-color);
|
||||
}
|
||||
.exposed {
|
||||
color: var(--success-color);
|
||||
}
|
||||
.not-exposed {
|
||||
color: var(--error-color);
|
||||
}
|
||||
@media all and (max-width: 450px) {
|
||||
ha-card {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,14 +1,22 @@
|
||||
import "../../../../components/ha-icon-button";
|
||||
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import {
|
||||
mdiCheckboxMarked,
|
||||
mdiCheckboxMultipleMarked,
|
||||
mdiCloseBox,
|
||||
mdiCloseBoxMultiple,
|
||||
} from "@mdi/js";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { computeDomain } from "../../../../common/entity/compute_domain";
|
||||
@ -19,8 +27,12 @@ import {
|
||||
isEmptyFilter,
|
||||
} from "../../../../common/entity/entity_filter";
|
||||
import { compare } from "../../../../common/string/compare";
|
||||
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
|
||||
import "../../../../components/entity/state-info";
|
||||
import "../../../../components/ha-button-menu";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-formfield";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-switch";
|
||||
import type { HaSwitch } from "../../../../components/ha-switch";
|
||||
import {
|
||||
@ -29,6 +41,7 @@ import {
|
||||
cloudSyncGoogleAssistant,
|
||||
GoogleEntityConfig,
|
||||
updateCloudGoogleEntityConfig,
|
||||
updateCloudPref,
|
||||
} from "../../../../data/cloud";
|
||||
import {
|
||||
fetchCloudGoogleEntities,
|
||||
@ -37,18 +50,12 @@ import {
|
||||
import { showDomainTogglerDialog } from "../../../../dialogs/domain-toggler/show-dialog-domain-toggler";
|
||||
import "../../../../layouts/hass-loading-screen";
|
||||
import "../../../../layouts/hass-subpage";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { showToast } from "../../../../util/toast";
|
||||
import "../../../../components/ha-formfield";
|
||||
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
|
||||
|
||||
const DEFAULT_CONFIG_EXPOSE = true;
|
||||
|
||||
const configIsExposed = (config: GoogleEntityConfig) =>
|
||||
config.should_expose === undefined
|
||||
? DEFAULT_CONFIG_EXPOSE
|
||||
: config.should_expose;
|
||||
|
||||
@customElement("cloud-google-assistant")
|
||||
class CloudGoogleAssistant extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@ -104,7 +111,10 @@ class CloudGoogleAssistant extends LitElement {
|
||||
const stateObj = this.hass.states[entity.entity_id];
|
||||
const config = this._entityConfigs[entity.entity_id] || {};
|
||||
const isExposed = emptyFilter
|
||||
? configIsExposed(config)
|
||||
? this._configIsExposed(entity.entity_id, config)
|
||||
: filterFunc(entity.entity_id);
|
||||
const isDomainExposed = emptyFilter
|
||||
? this._configIsDomainExposed(entity.entity_id)
|
||||
: filterFunc(entity.entity_id);
|
||||
if (isExposed) {
|
||||
selected++;
|
||||
@ -121,31 +131,78 @@ class CloudGoogleAssistant extends LitElement {
|
||||
target.push(html`
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<state-info
|
||||
.hass=${this.hass}
|
||||
.stateObj=${stateObj}
|
||||
secondary-line
|
||||
@click=${this._showMoreInfo}
|
||||
>
|
||||
${entity.traits
|
||||
.map((trait) => trait.substr(trait.lastIndexOf(".") + 1))
|
||||
.join(", ")}
|
||||
</state-info>
|
||||
<div>
|
||||
<ha-formfield
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.config.cloud.google.expose"
|
||||
)}
|
||||
.dir=${dir}
|
||||
<div class="top-line">
|
||||
<state-info
|
||||
.hass=${this.hass}
|
||||
.stateObj=${stateObj}
|
||||
secondary-line
|
||||
@click=${this._showMoreInfo}
|
||||
>
|
||||
<ha-switch
|
||||
.entityId=${entity.entity_id}
|
||||
.disabled=${!emptyFilter}
|
||||
.checked=${isExposed}
|
||||
@change=${this._exposeChanged}
|
||||
${entity.traits
|
||||
.map((trait) => trait.substr(trait.lastIndexOf(".") + 1))
|
||||
.join(", ")}
|
||||
</state-info>
|
||||
<ha-button-menu
|
||||
corner="BOTTOM_START"
|
||||
.entityId=${stateObj.entity_id}
|
||||
@action=${this._exposeChanged}
|
||||
>
|
||||
<mwc-icon-button
|
||||
slot="trigger"
|
||||
class=${classMap({
|
||||
exposed: isExposed!,
|
||||
"not-exposed": !isExposed,
|
||||
})}
|
||||
.title=${this.hass!.localize(
|
||||
"ui.panel.config.cloud.google.expose"
|
||||
)}
|
||||
>
|
||||
</ha-switch>
|
||||
</ha-formfield>
|
||||
<ha-svg-icon
|
||||
.path=${config.should_expose !== null
|
||||
? isExposed
|
||||
? mdiCheckboxMarked
|
||||
: mdiCloseBox
|
||||
: isDomainExposed
|
||||
? mdiCheckboxMultipleMarked
|
||||
: mdiCloseBoxMultiple}
|
||||
></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<mwc-list-item hasMeta>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.google.expose_entity"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
class="exposed"
|
||||
slot="meta"
|
||||
.path=${mdiCheckboxMarked}
|
||||
></ha-svg-icon>
|
||||
</mwc-list-item>
|
||||
<mwc-list-item hasMeta>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.google.dont_expose_entity"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
class="not-exposed"
|
||||
slot="meta"
|
||||
.path=${mdiCloseBox}
|
||||
></ha-svg-icon>
|
||||
</mwc-list-item>
|
||||
<mwc-list-item hasMeta>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.google.follow_domain"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
class=${classMap({
|
||||
exposed: isDomainExposed,
|
||||
"not-exposed": !isDomainExposed,
|
||||
})}
|
||||
slot="meta"
|
||||
.path=${isDomainExposed
|
||||
? mdiCheckboxMultipleMarked
|
||||
: mdiCloseBoxMultiple}
|
||||
></ha-svg-icon>
|
||||
</mwc-list-item>
|
||||
</ha-button-menu>
|
||||
</div>
|
||||
${entity.might_2fa
|
||||
? html`
|
||||
@ -178,17 +235,16 @@ class CloudGoogleAssistant extends LitElement {
|
||||
<hass-subpage header="${this.hass!.localize(
|
||||
"ui.panel.config.cloud.google.title"
|
||||
)}">
|
||||
<span slot="toolbar-icon">
|
||||
${selected}${!this.narrow ? html` selected ` : ""}
|
||||
</span>
|
||||
${
|
||||
emptyFilter
|
||||
? html`
|
||||
<ha-icon-button
|
||||
<mwc-button
|
||||
slot="toolbar-icon"
|
||||
icon="hass:tune"
|
||||
@click=${this._openDomainToggler}
|
||||
></ha-icon-button>
|
||||
>${this.hass!.localize(
|
||||
"ui.panel.config.cloud.google.manage_domains"
|
||||
)}</mwc-button
|
||||
>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
@ -204,11 +260,20 @@ class CloudGoogleAssistant extends LitElement {
|
||||
${
|
||||
exposedCards.length > 0
|
||||
? html`
|
||||
<h1>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.google.exposed_entities"
|
||||
)}
|
||||
</h1>
|
||||
<div class="header">
|
||||
<h3>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.google.exposed_entities"
|
||||
)}
|
||||
</h3>
|
||||
${!this.narrow
|
||||
? this.hass!.localize(
|
||||
"ui.panel.config.cloud.alexa.exposed",
|
||||
"selected",
|
||||
selected
|
||||
)
|
||||
: selected}
|
||||
</div>
|
||||
<div class="content">${exposedCards}</div>
|
||||
`
|
||||
: ""
|
||||
@ -216,11 +281,20 @@ class CloudGoogleAssistant extends LitElement {
|
||||
${
|
||||
notExposedCards.length > 0
|
||||
? html`
|
||||
<h1>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.google.not_exposed_entities"
|
||||
)}
|
||||
</h1>
|
||||
<div class="header second">
|
||||
<h3>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.google.not_exposed_entities"
|
||||
)}
|
||||
</h3>
|
||||
${!this.narrow
|
||||
? this.hass!.localize(
|
||||
"ui.panel.config.cloud.alexa.not_exposed",
|
||||
"selected",
|
||||
this._entities.length - selected
|
||||
)
|
||||
: this._entities.length - selected}
|
||||
</div>
|
||||
<div class="content">${notExposedCards}</div>
|
||||
`
|
||||
: ""
|
||||
@ -242,6 +316,19 @@ class CloudGoogleAssistant extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _configIsDomainExposed(entityId: string) {
|
||||
const domain = computeDomain(entityId);
|
||||
return this.cloudStatus.prefs.google_default_expose
|
||||
? this.cloudStatus.prefs.google_default_expose.includes(domain)
|
||||
: DEFAULT_CONFIG_EXPOSE;
|
||||
}
|
||||
|
||||
private _configIsExposed(entityId: string, config: GoogleEntityConfig) {
|
||||
return config.should_expose === null
|
||||
? this._configIsDomainExposed(entityId)
|
||||
: config.should_expose;
|
||||
}
|
||||
|
||||
private async _fetchData() {
|
||||
const entities = await fetchCloudGoogleEntities(this.hass);
|
||||
entities.sort((a, b) => {
|
||||
@ -260,17 +347,24 @@ class CloudGoogleAssistant extends LitElement {
|
||||
fireEvent(this, "hass-more-info", { entityId });
|
||||
}
|
||||
|
||||
private async _exposeChanged(ev: Event) {
|
||||
private async _exposeChanged(ev: CustomEvent<ActionDetail>) {
|
||||
const entityId = (ev.currentTarget as any).entityId;
|
||||
const newExposed = (ev.target as HaSwitch).checked;
|
||||
await this._updateExposed(entityId, newExposed);
|
||||
let newVal: boolean | null = null;
|
||||
switch (ev.detail.index) {
|
||||
case 0:
|
||||
newVal = true;
|
||||
break;
|
||||
case 1:
|
||||
newVal = false;
|
||||
break;
|
||||
case 2:
|
||||
newVal = null;
|
||||
break;
|
||||
}
|
||||
await this._updateExposed(entityId, newVal);
|
||||
}
|
||||
|
||||
private async _updateExposed(entityId: string, newExposed: boolean) {
|
||||
const curExposed = configIsExposed(this._entityConfigs[entityId] || {});
|
||||
if (newExposed === curExposed) {
|
||||
return;
|
||||
}
|
||||
private async _updateExposed(entityId: string, newExposed: boolean | null) {
|
||||
await this._updateConfig(entityId, {
|
||||
should_expose: newExposed,
|
||||
});
|
||||
@ -309,16 +403,46 @@ class CloudGoogleAssistant extends LitElement {
|
||||
domains: this._entities!.map((entity) =>
|
||||
computeDomain(entity.entity_id)
|
||||
).filter((value, idx, self) => self.indexOf(value) === idx),
|
||||
toggleDomain: (domain, turnOn) => {
|
||||
exposedDomains: this.cloudStatus.prefs.google_default_expose,
|
||||
toggleDomain: (domain, expose) => {
|
||||
this._updateDomainExposed(domain, expose);
|
||||
},
|
||||
resetDomain: (domain) => {
|
||||
this._entities!.forEach((entity) => {
|
||||
if (computeDomain(entity.entity_id) === domain) {
|
||||
this._updateExposed(entity.entity_id, turnOn);
|
||||
this._updateExposed(entity.entity_id, null);
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private async _updateDomainExposed(domain: string, expose: boolean) {
|
||||
const defaultExpose =
|
||||
this.cloudStatus.prefs.google_default_expose ||
|
||||
this._entities!.map((entity) => computeDomain(entity.entity_id)).filter(
|
||||
(value, idx, self) => self.indexOf(value) === idx
|
||||
);
|
||||
|
||||
if (
|
||||
(expose && defaultExpose.includes(domain)) ||
|
||||
(!expose && !defaultExpose.includes(domain))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (expose) {
|
||||
defaultExpose.push(domain);
|
||||
} else {
|
||||
defaultExpose.splice(defaultExpose.indexOf(domain), 1);
|
||||
}
|
||||
|
||||
await updateCloudPref(this.hass!, {
|
||||
google_default_expose: defaultExpose,
|
||||
});
|
||||
fireEvent(this, "ha-refresh-cloud-status");
|
||||
}
|
||||
|
||||
private _ensureStatusReload() {
|
||||
if (this._popstateReloadStatusAttached) {
|
||||
return;
|
||||
@ -356,46 +480,66 @@ class CloudGoogleAssistant extends LitElement {
|
||||
);
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
.banner {
|
||||
color: var(--primary-text-color);
|
||||
background-color: var(
|
||||
--ha-card-background,
|
||||
var(--card-background-color, white)
|
||||
);
|
||||
padding: 16px 8px;
|
||||
text-align: center;
|
||||
}
|
||||
h1 {
|
||||
color: var(--primary-text-color);
|
||||
font-size: 24px;
|
||||
letter-spacing: -0.012em;
|
||||
margin-bottom: 0;
|
||||
padding: 0 8px;
|
||||
}
|
||||
.content {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
grid-gap: 8px 8px;
|
||||
padding: 8px;
|
||||
}
|
||||
.card-content {
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
state-info {
|
||||
cursor: pointer;
|
||||
}
|
||||
ha-switch {
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
@media all and (max-width: 450px) {
|
||||
ha-card {
|
||||
max-width: 100%;
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
mwc-list-item > [slot="meta"] {
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
.banner {
|
||||
color: var(--primary-text-color);
|
||||
background-color: var(
|
||||
--ha-card-background,
|
||||
var(--card-background-color, white)
|
||||
);
|
||||
padding: 16px 8px;
|
||||
text-align: center;
|
||||
}
|
||||
.content {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
grid-gap: 8px 8px;
|
||||
padding: 8px;
|
||||
}
|
||||
.card-content {
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
state-info {
|
||||
cursor: pointer;
|
||||
}
|
||||
ha-switch {
|
||||
padding: 8px 0;
|
||||
}
|
||||
.top-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 16px;
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
background: var(--app-header-background-color);
|
||||
}
|
||||
.header.second {
|
||||
border-top: 1px solid var(--divider-color);
|
||||
}
|
||||
.exposed {
|
||||
color: var(--success-color);
|
||||
}
|
||||
.not-exposed {
|
||||
color: var(--error-color);
|
||||
}
|
||||
@media all and (max-width: 450px) {
|
||||
ha-card {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,9 +4,9 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { ifDefined } from "lit-html/directives/if-defined";
|
||||
@ -14,6 +14,7 @@ import memoizeOne from "memoize-one";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { compare } from "../../../common/string/compare";
|
||||
import { slugify } from "../../../common/string/slugify";
|
||||
import "../../../components/entity/ha-battery-icon";
|
||||
import "../../../components/ha-icon-next";
|
||||
import { AreaRegistryEntry } from "../../../data/area_registry";
|
||||
@ -25,8 +26,8 @@ import {
|
||||
} from "../../../data/device_registry";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
findBatteryEntity,
|
||||
findBatteryChargingEntity,
|
||||
findBatteryEntity,
|
||||
updateEntityRegistryEntry,
|
||||
} from "../../../data/entity_registry";
|
||||
import { SceneEntities, showSceneEditor } from "../../../data/scene";
|
||||
@ -35,6 +36,7 @@ import {
|
||||
loadDeviceRegistryDetailDialog,
|
||||
showDeviceRegistryDetailDialog,
|
||||
} from "../../../dialogs/device-registry-detail/show-dialog-device-registry-detail";
|
||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import "../../../layouts/hass-error-screen";
|
||||
import "../../../layouts/hass-tabs-subpage";
|
||||
import { HomeAssistant, Route } from "../../../types";
|
||||
@ -43,8 +45,6 @@ import { configSections } from "../ha-panel-config";
|
||||
import "./device-detail/ha-device-entities-card";
|
||||
import "./device-detail/ha-device-info-card";
|
||||
import { showDeviceAutomationDialog } from "./device-detail/show-dialog-device-automation";
|
||||
import { slugify } from "../../../common/string/slugify";
|
||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
|
||||
export interface EntityRegistryStateEntry extends EntityRegistryEntry {
|
||||
stateName?: string | null;
|
||||
@ -296,8 +296,8 @@ export class HaConfigDevicePage extends LitElement {
|
||||
</a>
|
||||
${!state.attributes.id
|
||||
? html`
|
||||
<paper-tooltip
|
||||
>${this.hass.localize(
|
||||
<paper-tooltip animation-delay="0">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.devices.cant_edit"
|
||||
)}
|
||||
</paper-tooltip>
|
||||
@ -369,7 +369,9 @@ export class HaConfigDevicePage extends LitElement {
|
||||
${!state.attributes.id
|
||||
? html`
|
||||
<paper-tooltip
|
||||
>${this.hass.localize(
|
||||
animation-delay="0"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.devices.cant_edit"
|
||||
)}
|
||||
</paper-tooltip>
|
||||
|
@ -1,9 +1,9 @@
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import memoizeOne from "memoize-one";
|
||||
@ -25,8 +25,8 @@ import {
|
||||
} from "../../../data/device_registry";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
findBatteryEntity,
|
||||
findBatteryChargingEntity,
|
||||
findBatteryEntity,
|
||||
} from "../../../data/entity_registry";
|
||||
import { domainToName } from "../../../data/integration";
|
||||
import "../../../layouts/hass-tabs-subpage-data-table";
|
||||
@ -181,8 +181,8 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
);
|
||||
|
||||
private _columns = memoizeOne(
|
||||
(narrow: boolean): DataTableColumnContainer =>
|
||||
narrow
|
||||
(narrow: boolean): DataTableColumnContainer => {
|
||||
const columns: DataTableColumnContainer = narrow
|
||||
? {
|
||||
name: {
|
||||
title: "Device",
|
||||
@ -199,36 +199,6 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
`;
|
||||
},
|
||||
},
|
||||
battery_entity: {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.devices.data_table.battery"
|
||||
),
|
||||
sortable: true,
|
||||
type: "numeric",
|
||||
width: "90px",
|
||||
template: (
|
||||
batteryEntityPair: DeviceRowData["battery_entity"]
|
||||
) => {
|
||||
const battery =
|
||||
batteryEntityPair && batteryEntityPair[0]
|
||||
? this.hass.states[batteryEntityPair[0]]
|
||||
: undefined;
|
||||
const batteryCharging =
|
||||
batteryEntityPair && batteryEntityPair[1]
|
||||
? this.hass.states[batteryEntityPair[1]]
|
||||
: undefined;
|
||||
return battery
|
||||
? html`
|
||||
${isNaN(battery.state as any) ? "-" : battery.state}%
|
||||
<ha-battery-icon
|
||||
.hass=${this.hass!}
|
||||
.batteryStateObj=${battery}
|
||||
.batteryChargingStateObj=${batteryCharging}
|
||||
></ha-battery-icon>
|
||||
`
|
||||
: html` - `;
|
||||
},
|
||||
},
|
||||
}
|
||||
: {
|
||||
name: {
|
||||
@ -240,70 +210,69 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
grows: true,
|
||||
direction: "asc",
|
||||
},
|
||||
manufacturer: {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.devices.data_table.manufacturer"
|
||||
),
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
width: "15%",
|
||||
},
|
||||
model: {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.devices.data_table.model"
|
||||
),
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
width: "15%",
|
||||
},
|
||||
area: {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.devices.data_table.area"
|
||||
),
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
width: "15%",
|
||||
},
|
||||
integration: {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.devices.data_table.integration"
|
||||
),
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
width: "15%",
|
||||
},
|
||||
battery_entity: {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.devices.data_table.battery"
|
||||
),
|
||||
sortable: true,
|
||||
type: "numeric",
|
||||
width: "15%",
|
||||
maxWidth: "90px",
|
||||
template: (
|
||||
batteryEntityPair: DeviceRowData["battery_entity"]
|
||||
) => {
|
||||
const battery =
|
||||
batteryEntityPair && batteryEntityPair[0]
|
||||
? this.hass.states[batteryEntityPair[0]]
|
||||
: undefined;
|
||||
const batteryCharging =
|
||||
batteryEntityPair && batteryEntityPair[1]
|
||||
? this.hass.states[batteryEntityPair[1]]
|
||||
: undefined;
|
||||
return battery && !isNaN(battery.state as any)
|
||||
? html`
|
||||
${battery.state}%
|
||||
<ha-battery-icon
|
||||
.hass=${this.hass!}
|
||||
.batteryStateObj=${battery}
|
||||
.batteryChargingStateObj=${batteryCharging}
|
||||
></ha-battery-icon>
|
||||
`
|
||||
: html` - `;
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
columns.manufacturer = {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.devices.data_table.manufacturer"
|
||||
),
|
||||
sortable: true,
|
||||
hidden: narrow,
|
||||
filterable: true,
|
||||
width: "15%",
|
||||
};
|
||||
columns.model = {
|
||||
title: this.hass.localize("ui.panel.config.devices.data_table.model"),
|
||||
sortable: true,
|
||||
hidden: narrow,
|
||||
filterable: true,
|
||||
width: "15%",
|
||||
};
|
||||
columns.area = {
|
||||
title: this.hass.localize("ui.panel.config.devices.data_table.area"),
|
||||
sortable: true,
|
||||
hidden: narrow,
|
||||
filterable: true,
|
||||
width: "15%",
|
||||
};
|
||||
columns.integration = {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.devices.data_table.integration"
|
||||
),
|
||||
sortable: true,
|
||||
hidden: narrow,
|
||||
filterable: true,
|
||||
width: "15%",
|
||||
};
|
||||
columns.battery_entity = {
|
||||
title: this.hass.localize("ui.panel.config.devices.data_table.battery"),
|
||||
sortable: true,
|
||||
type: "numeric",
|
||||
width: narrow ? "90px" : "15%",
|
||||
maxWidth: "90px",
|
||||
template: (batteryEntityPair: DeviceRowData["battery_entity"]) => {
|
||||
const battery =
|
||||
batteryEntityPair && batteryEntityPair[0]
|
||||
? this.hass.states[batteryEntityPair[0]]
|
||||
: undefined;
|
||||
const batteryCharging =
|
||||
batteryEntityPair && batteryEntityPair[1]
|
||||
? this.hass.states[batteryEntityPair[1]]
|
||||
: undefined;
|
||||
return battery && !isNaN(battery.state as any)
|
||||
? html`
|
||||
${battery.state}%
|
||||
<ha-battery-icon
|
||||
.hass=${this.hass!}
|
||||
.batteryStateObj=${battery}
|
||||
.batteryChargingStateObj=${batteryCharging}
|
||||
></ha-battery-icon>
|
||||
`
|
||||
: html` - `;
|
||||
},
|
||||
};
|
||||
return columns;
|
||||
}
|
||||
);
|
||||
|
||||
public constructor() {
|
||||
|
@ -1,4 +1,6 @@
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item";
|
||||
import { mdiFilterVariant } from "@mdi/js";
|
||||
import "@polymer/paper-checkbox/paper-checkbox";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
@ -10,9 +12,9 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
@ -56,8 +58,6 @@ import {
|
||||
loadEntityEditorDialog,
|
||||
showEntityEditorDialog,
|
||||
} from "./show-dialog-entity-editor";
|
||||
import { mdiFilterVariant } from "@mdi/js";
|
||||
import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item";
|
||||
|
||||
export interface StateEntity extends EntityRegistryEntry {
|
||||
readonly?: boolean;
|
||||
@ -192,7 +192,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
? "hass:cancel"
|
||||
: "hass:pencil-off"}
|
||||
></ha-icon>
|
||||
<paper-tooltip position="left">
|
||||
<paper-tooltip animation-delay="0" position="left">
|
||||
${entity.restored
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.entities.picker.status.restored"
|
||||
@ -390,7 +390,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
icon="hass:undo"
|
||||
@click=${this._enableSelected}
|
||||
></ha-icon-button>
|
||||
<paper-tooltip for="enable-btn">
|
||||
<paper-tooltip animation-delay="0" for="enable-btn">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.entities.picker.enable_selected.button"
|
||||
)}
|
||||
@ -400,7 +400,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
icon="hass:cancel"
|
||||
@click=${this._disableSelected}
|
||||
></ha-icon-button>
|
||||
<paper-tooltip for="disable-btn">
|
||||
<paper-tooltip animation-delay="0" for="disable-btn">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.entities.picker.disable_selected.button"
|
||||
)}
|
||||
@ -410,7 +410,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
icon="hass:delete"
|
||||
@click=${this._removeSelected}
|
||||
></ha-icon-button>
|
||||
<paper-tooltip for="remove-btn">
|
||||
<paper-tooltip animation-delay="0" for="remove-btn">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.entities.picker.remove_selected.button"
|
||||
)}
|
||||
@ -433,7 +433,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
${this.narrow
|
||||
? html` <div>
|
||||
<ha-icon icon="hass:filter-variant"></ha-icon>
|
||||
<paper-tooltip position="left">
|
||||
<paper-tooltip animation-delay="0" position="left">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.filtering.filtering_by"
|
||||
)}
|
||||
|
@ -6,9 +6,9 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
@ -136,7 +136,7 @@ export class DialogHelperDetail extends LitElement {
|
||||
</paper-icon-item>
|
||||
${!isLoaded
|
||||
? html`
|
||||
<paper-tooltip
|
||||
<paper-tooltip animation-delay="0"
|
||||
>${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.platform_not_loaded",
|
||||
"platform",
|
||||
|
@ -1,3 +1,5 @@
|
||||
import "@material/mwc-fab";
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
import "@polymer/paper-checkbox/paper-checkbox";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
@ -7,9 +9,9 @@ import { HassEntity } from "home-assistant-js-websocket";
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
@ -21,8 +23,8 @@ import {
|
||||
DataTableColumnContainer,
|
||||
RowClickedEvent,
|
||||
} from "../../../components/data-table/ha-data-table";
|
||||
import "@material/mwc-fab";
|
||||
import "../../../components/ha-icon";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import "../../../layouts/hass-loading-screen";
|
||||
import "../../../layouts/hass-tabs-subpage-data-table";
|
||||
import { HomeAssistant, Route } from "../../../types";
|
||||
@ -30,8 +32,6 @@ import { showEntityEditorDialog } from "../entities/show-dialog-entity-editor";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import { HELPER_DOMAINS } from "./const";
|
||||
import { showHelperDetailDialog } from "./show-dialog-helper-detail";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
|
||||
@customElement("ha-config-helpers")
|
||||
export class HaConfigHelpers extends LitElement {
|
||||
@ -110,7 +110,7 @@ export class HaConfigHelpers extends LitElement {
|
||||
style="display:inline-block; position: relative;"
|
||||
>
|
||||
<ha-icon icon="hass:pencil-off"></ha-icon>
|
||||
<paper-tooltip position="left">
|
||||
<paper-tooltip animation-delay="0" position="left">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.entities.picker.status.readonly"
|
||||
)}
|
||||
|
@ -14,6 +14,7 @@ import {
|
||||
ConfigEntry,
|
||||
updateConfigEntry,
|
||||
deleteConfigEntry,
|
||||
reloadConfigEntry,
|
||||
} from "../../../data/config_entries";
|
||||
import { EntityRegistryEntry } from "../../../data/entity_registry";
|
||||
import { DeviceRegistryEntry } from "../../../data/device_registry";
|
||||
@ -28,7 +29,8 @@ import { haStyle } from "../../../resources/styles";
|
||||
import "../../../components/ha-icon-next";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { mdiDotsVertical, mdiOpenInNew } from "@mdi/js";
|
||||
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
||||
import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item";
|
||||
import { shouldHandleRequestSelectedEvent } from "../../../common/mwc/handle-request-selected-event";
|
||||
|
||||
export interface ConfigEntryUpdatedEvent {
|
||||
entry: ConfigEntry;
|
||||
@ -228,7 +230,7 @@ export class HaIntegrationCard extends LitElement {
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
<ha-button-menu corner="BOTTOM_START" @action=${this._handleAction}>
|
||||
<ha-button-menu corner="BOTTOM_START">
|
||||
<mwc-icon-button
|
||||
.title=${this.hass.localize("ui.common.menu")}
|
||||
.label=${this.hass.localize("ui.common.overflow_menu")}
|
||||
@ -236,7 +238,7 @@ export class HaIntegrationCard extends LitElement {
|
||||
>
|
||||
<ha-svg-icon path=${mdiDotsVertical}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<mwc-list-item>
|
||||
<mwc-list-item @request-selected="${this._handleSystemOptions}">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.system_options"
|
||||
)}
|
||||
@ -259,7 +261,17 @@ export class HaIntegrationCard extends LitElement {
|
||||
</mwc-list-item>
|
||||
</a>
|
||||
`}
|
||||
<mwc-list-item class="warning">
|
||||
${item.state === "loaded" && item.supports_unload
|
||||
? html`<mwc-list-item @request-selected="${this._handleReload}">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.reload"
|
||||
)}
|
||||
</mwc-list-item>`
|
||||
: ""}
|
||||
<mwc-list-item
|
||||
class="warning"
|
||||
@request-selected="${this._handleDelete}"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.delete"
|
||||
)}
|
||||
@ -309,17 +321,31 @@ export class HaIntegrationCard extends LitElement {
|
||||
showOptionsFlowDialog(this, ev.target.closest("ha-card").configEntry);
|
||||
}
|
||||
|
||||
private _handleAction(ev: CustomEvent<ActionDetail>) {
|
||||
const configEntry = ((ev.target as HTMLElement).closest("ha-card") as any)
|
||||
.configEntry;
|
||||
switch (ev.detail.index) {
|
||||
case 0:
|
||||
this._showSystemOptions(configEntry);
|
||||
break;
|
||||
case 1:
|
||||
this._removeIntegration(configEntry);
|
||||
break;
|
||||
private _handleReload(ev: CustomEvent<RequestSelectedDetail>): void {
|
||||
if (!shouldHandleRequestSelectedEvent(ev)) {
|
||||
return;
|
||||
}
|
||||
this._reloadIntegration(
|
||||
((ev.target as HTMLElement).closest("ha-card") as any).configEntry
|
||||
);
|
||||
}
|
||||
|
||||
private _handleDelete(ev: CustomEvent<RequestSelectedDetail>): void {
|
||||
if (!shouldHandleRequestSelectedEvent(ev)) {
|
||||
return;
|
||||
}
|
||||
this._removeIntegration(
|
||||
((ev.target as HTMLElement).closest("ha-card") as any).configEntry
|
||||
);
|
||||
}
|
||||
|
||||
private _handleSystemOptions(ev: CustomEvent<RequestSelectedDetail>): void {
|
||||
if (!shouldHandleRequestSelectedEvent(ev)) {
|
||||
return;
|
||||
}
|
||||
this._showSystemOptions(
|
||||
((ev.target as HTMLElement).closest("ha-card") as any).configEntry
|
||||
);
|
||||
}
|
||||
|
||||
private _showSystemOptions(configEntry: ConfigEntry) {
|
||||
@ -353,6 +379,21 @@ export class HaIntegrationCard extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private async _reloadIntegration(configEntry: ConfigEntry) {
|
||||
const entryId = configEntry.entry_id;
|
||||
|
||||
reloadConfigEntry(this.hass, entryId).then((result) => {
|
||||
const locale_key = result.require_restart
|
||||
? "reload_restart_confirm"
|
||||
: "reload_confirm";
|
||||
showAlertDialog(this, {
|
||||
text: this.hass.localize(
|
||||
`ui.panel.config.integrations.config_entry.${locale_key}`
|
||||
),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async _editEntryName(ev) {
|
||||
const configEntry = ev.target.closest("ha-card").configEntry;
|
||||
const newName = await showPromptDialog(this, {
|
||||
|
@ -1,11 +1,12 @@
|
||||
import "@material/mwc-fab";
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
@ -18,6 +19,7 @@ import {
|
||||
} from "../../../../components/data-table/ha-data-table";
|
||||
import "../../../../components/ha-icon";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import {
|
||||
createDashboard,
|
||||
deleteDashboard,
|
||||
@ -33,8 +35,6 @@ import "../../../../layouts/hass-tabs-subpage-data-table";
|
||||
import { HomeAssistant, Route } from "../../../../types";
|
||||
import { lovelaceTabs } from "../ha-config-lovelace";
|
||||
import { showDashboardDetailDialog } from "./show-dialog-lovelace-dashboard-detail";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
|
||||
@customElement("ha-config-lovelace-dashboards")
|
||||
export class HaConfigLovelaceDashboards extends LitElement {
|
||||
@ -76,7 +76,7 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
||||
style="padding-left: 10px;"
|
||||
icon="hass:check-circle-outline"
|
||||
></ha-icon>
|
||||
<paper-tooltip>
|
||||
<paper-tooltip animation-delay="0">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.lovelace.dashboards.default_dashboard`
|
||||
)}
|
||||
|
@ -1,15 +1,15 @@
|
||||
import "@material/mwc-fab";
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
import "@polymer/paper-checkbox/paper-checkbox";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import "@material/mwc-fab";
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
@ -20,6 +20,7 @@ import {
|
||||
RowClickedEvent,
|
||||
} from "../../../../components/data-table/ha-data-table";
|
||||
import "../../../../components/ha-icon";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import {
|
||||
createResource,
|
||||
deleteResource,
|
||||
@ -37,8 +38,6 @@ import { HomeAssistant, Route } from "../../../../types";
|
||||
import { loadLovelaceResources } from "../../../lovelace/common/load-resources";
|
||||
import { lovelaceTabs } from "../ha-config-lovelace";
|
||||
import { showResourceDetailDialog } from "./show-dialog-lovelace-resource-detail";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
|
||||
@customElement("ha-config-lovelace-resources")
|
||||
export class HaConfigLovelaceRescources extends LitElement {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import "../../../components/ha-icon-button";
|
||||
import "@material/mwc-fab";
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import {
|
||||
css,
|
||||
@ -13,8 +14,11 @@ import { ifDefined } from "lit-html/directives/if-defined";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { stateIcon } from "../../../common/entity/state_icon";
|
||||
import { DataTableColumnContainer } from "../../../components/data-table/ha-data-table";
|
||||
import "@material/mwc-fab";
|
||||
import "../../../components/ha-icon";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import { forwardHaptic } from "../../../data/haptics";
|
||||
import { activateScene, SceneEntity } from "../../../data/scene";
|
||||
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
@ -23,10 +27,6 @@ import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../types";
|
||||
import { showToast } from "../../../util/toast";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import "../../../components/ha-icon";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
import { stateIcon } from "../../../common/entity/state_icon";
|
||||
|
||||
@customElement("ha-scene-dashboard")
|
||||
class HaSceneDashboard extends LitElement {
|
||||
@ -117,7 +117,7 @@ class HaSceneDashboard extends LitElement {
|
||||
</a>
|
||||
${!scene.attributes.id
|
||||
? html`
|
||||
<paper-tooltip position="left">
|
||||
<paper-tooltip animation-delay="0" position="left">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.scene.picker.only_editable"
|
||||
)}
|
||||
|
@ -36,6 +36,19 @@ const reloadableDomains = [
|
||||
"input_datetime",
|
||||
"input_select",
|
||||
"template",
|
||||
"universal",
|
||||
"rest",
|
||||
"command_line",
|
||||
"filter",
|
||||
"statistics",
|
||||
"generic",
|
||||
"generic_thermostat",
|
||||
"homekit",
|
||||
"min_max",
|
||||
"history_stats",
|
||||
"trend",
|
||||
"ping",
|
||||
"filesize",
|
||||
];
|
||||
|
||||
@customElement("ha-config-server-control")
|
||||
|
@ -1,20 +1,21 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "../../../components/ha-circular-progress";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { computeRTLDirection } from "../../../common/util/compute_rtl";
|
||||
import "../../../components/ha-circular-progress";
|
||||
import "../../../components/ha-dialog";
|
||||
import "../../../components/ha-switch";
|
||||
import "../../../components/ha-formfield";
|
||||
import "../../../components/ha-switch";
|
||||
import { createAuthForUser } from "../../../data/auth";
|
||||
import {
|
||||
createUser,
|
||||
@ -27,7 +28,6 @@ import { PolymerChangedEvent } from "../../../polymer-types";
|
||||
import { haStyleDialog } from "../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { AddUserDialogParams } from "./show-dialog-add-user";
|
||||
import { computeRTLDirection } from "../../../common/util/compute_rtl";
|
||||
|
||||
@customElement("dialog-add-user")
|
||||
export class DialogAddUser extends LitElement {
|
||||
@ -46,6 +46,8 @@ export class DialogAddUser extends LitElement {
|
||||
|
||||
@internalProperty() private _password?: string;
|
||||
|
||||
@internalProperty() private _passwordConfirm?: string;
|
||||
|
||||
@internalProperty() private _isAdmin?: boolean;
|
||||
|
||||
public showDialog(params: AddUserDialogParams) {
|
||||
@ -53,6 +55,7 @@ export class DialogAddUser extends LitElement {
|
||||
this._name = "";
|
||||
this._username = "";
|
||||
this._password = "";
|
||||
this._passwordConfirm = "";
|
||||
this._isAdmin = false;
|
||||
this._error = undefined;
|
||||
this._loading = false;
|
||||
@ -83,17 +86,20 @@ export class DialogAddUser extends LitElement {
|
||||
${this._error ? html` <div class="error">${this._error}</div> ` : ""}
|
||||
<paper-input
|
||||
class="name"
|
||||
name="name"
|
||||
.label=${this.hass.localize("ui.panel.config.users.add_user.name")}
|
||||
.value=${this._name}
|
||||
required
|
||||
auto-validate
|
||||
autocapitalize="on"
|
||||
.errorMessage=${this.hass.localize("ui.common.error_required")}
|
||||
@value-changed=${this._nameChanged}
|
||||
@value-changed=${this._handleValueChanged}
|
||||
@blur=${this._maybePopulateUsername}
|
||||
></paper-input>
|
||||
|
||||
<paper-input
|
||||
class="username"
|
||||
name="username"
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.users.add_user.username"
|
||||
)}
|
||||
@ -101,20 +107,40 @@ export class DialogAddUser extends LitElement {
|
||||
required
|
||||
auto-validate
|
||||
autocapitalize="none"
|
||||
@value-changed=${this._usernameChanged}
|
||||
@value-changed=${this._handleValueChanged}
|
||||
.errorMessage=${this.hass.localize("ui.common.error_required")}
|
||||
></paper-input>
|
||||
|
||||
<paper-input
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.users.add_user.password"
|
||||
)}
|
||||
type="password"
|
||||
name="password"
|
||||
.value=${this._password}
|
||||
required
|
||||
auto-validate
|
||||
@value-changed=${this._passwordChanged}
|
||||
@value-changed=${this._handleValueChanged}
|
||||
.errorMessage=${this.hass.localize("ui.common.error_required")}
|
||||
></paper-input>
|
||||
|
||||
<paper-input
|
||||
label="${this.hass.localize(
|
||||
"ui.panel.config.users.add_user.password_confirm"
|
||||
)}"
|
||||
name="passwordConfirm"
|
||||
.value=${this._passwordConfirm}
|
||||
@value-changed=${this._handleValueChanged}
|
||||
required
|
||||
type="password"
|
||||
.invalid=${this._password !== "" &&
|
||||
this._passwordConfirm !== "" &&
|
||||
this._passwordConfirm !== this._password}
|
||||
.errorMessage="${this.hass.localize(
|
||||
"ui.panel.config.users.add_user.password_not_match"
|
||||
)}"
|
||||
></paper-input>
|
||||
|
||||
<ha-formfield
|
||||
.label=${this.hass.localize("ui.panel.config.users.editor.admin")}
|
||||
.dir=${computeRTLDirection(this.hass)}
|
||||
@ -147,7 +173,10 @@ export class DialogAddUser extends LitElement {
|
||||
: html`
|
||||
<mwc-button
|
||||
slot="primaryAction"
|
||||
.disabled=${!this._name || !this._username || !this._password}
|
||||
.disabled=${!this._name ||
|
||||
!this._username ||
|
||||
!this._password ||
|
||||
this._password !== this._passwordConfirm}
|
||||
@click=${this._createUser}
|
||||
>
|
||||
${this.hass.localize("ui.panel.config.users.add_user.create")}
|
||||
@ -173,19 +202,10 @@ export class DialogAddUser extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _nameChanged(ev: PolymerChangedEvent<string>) {
|
||||
private _handleValueChanged(ev: PolymerChangedEvent<string>): void {
|
||||
this._error = undefined;
|
||||
this._name = ev.detail.value;
|
||||
}
|
||||
|
||||
private _usernameChanged(ev: PolymerChangedEvent<string>) {
|
||||
this._error = undefined;
|
||||
this._username = ev.detail.value;
|
||||
}
|
||||
|
||||
private _passwordChanged(ev: PolymerChangedEvent<string>) {
|
||||
this._error = undefined;
|
||||
this._password = ev.detail.value;
|
||||
const name = (ev.target as any).name;
|
||||
this[`_${name}`] = ev.detail.value;
|
||||
}
|
||||
|
||||
private async _adminChanged(ev): Promise<void> {
|
||||
|
@ -6,23 +6,28 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { computeRTLDirection } from "../../../common/util/compute_rtl";
|
||||
import { createCloseHeading } from "../../../components/ha-dialog";
|
||||
import "../../../components/ha-switch";
|
||||
import "../../../components/ha-formfield";
|
||||
import "../../../components/ha-switch";
|
||||
import { adminChangePassword } from "../../../data/auth";
|
||||
import {
|
||||
SYSTEM_GROUP_ID_ADMIN,
|
||||
SYSTEM_GROUP_ID_USER,
|
||||
} from "../../../data/user";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showPromptDialog,
|
||||
} from "../../../dialogs/generic/show-dialog-box";
|
||||
import { PolymerChangedEvent } from "../../../polymer-types";
|
||||
import { haStyleDialog } from "../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { UserDetailDialogParams } from "./show-dialog-user-detail";
|
||||
import { computeRTLDirection } from "../../../common/util/compute_rtl";
|
||||
|
||||
@customElement("dialog-user-detail")
|
||||
class DialogUserDetail extends LitElement {
|
||||
@ -134,14 +139,22 @@ class DialogUserDetail extends LitElement {
|
||||
</mwc-button>
|
||||
${user.system_generated
|
||||
? html`
|
||||
<paper-tooltip position="right">
|
||||
<paper-tooltip animation-delay="0" position="right">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.users.editor.system_generated_users_not_removable"
|
||||
)}
|
||||
</paper-tooltip>
|
||||
`
|
||||
: ""}
|
||||
${!user.system_generated && this.hass.user?.is_owner
|
||||
? html`<mwc-button @click=${this._changePassword}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.users.editor.change_password"
|
||||
)}
|
||||
</mwc-button>`
|
||||
: ""}
|
||||
</div>
|
||||
|
||||
<div slot="primaryAction">
|
||||
<mwc-button
|
||||
@click=${this._updateEntry}
|
||||
@ -153,7 +166,7 @@ class DialogUserDetail extends LitElement {
|
||||
</mwc-button>
|
||||
${user.system_generated
|
||||
? html`
|
||||
<paper-tooltip position="left">
|
||||
<paper-tooltip animation-delay="0" position="left">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.users.editor.system_generated_users_not_editable"
|
||||
)}
|
||||
@ -202,6 +215,52 @@ class DialogUserDetail extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private async _changePassword() {
|
||||
const credential = this._params?.entry.credentials.find(
|
||||
(cred) => cred.type === "homeassistant"
|
||||
);
|
||||
if (!credential) {
|
||||
showAlertDialog(this, {
|
||||
title: "No Home Assistant credentials found.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
const newPassword = await showPromptDialog(this, {
|
||||
title: this.hass.localize("ui.panel.config.users.editor.change_password"),
|
||||
inputType: "password",
|
||||
inputLabel: this.hass.localize(
|
||||
"ui.panel.config.users.editor.new_password"
|
||||
),
|
||||
});
|
||||
if (!newPassword) {
|
||||
return;
|
||||
}
|
||||
const confirmPassword = await showPromptDialog(this, {
|
||||
title: this.hass.localize("ui.panel.config.users.editor.change_password"),
|
||||
inputType: "password",
|
||||
inputLabel: this.hass.localize(
|
||||
"ui.panel.config.users.add_user.password_confirm"
|
||||
),
|
||||
});
|
||||
if (!confirmPassword) {
|
||||
return;
|
||||
}
|
||||
if (newPassword !== confirmPassword) {
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.users.add_user.password_not_match"
|
||||
),
|
||||
});
|
||||
return;
|
||||
}
|
||||
await adminChangePassword(this.hass, this._params!.entry.id, newPassword);
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.users.add_user.password_changed"
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
private _close(): void {
|
||||
this._params = undefined;
|
||||
}
|
||||
|
@ -11,9 +11,9 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
@ -198,7 +198,7 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
|
||||
: mdiPencilOff}
|
||||
></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<paper-tooltip position="left">
|
||||
<paper-tooltip animation-delay="0" position="left">
|
||||
${state.entity_id === "zone.home"
|
||||
? this.hass.localize(
|
||||
`ui.panel.config.zone.${
|
||||
|
@ -1,8 +1,9 @@
|
||||
import "@material/mwc-button";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import { safeDump, safeLoad } from "js-yaml";
|
||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||
import "../../../components/buttons/ha-progress-button";
|
||||
import "../../../components/entity/ha-entity-picker";
|
||||
import "../../../components/ha-code-editor";
|
||||
import "../../../components/ha-service-picker";
|
||||
@ -11,7 +12,6 @@ import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
||||
import "../../../styles/polymer-ha-style";
|
||||
import "../../../util/app-localstorage-document";
|
||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||
|
||||
const ERROR_SENTINEL = {};
|
||||
/*
|
||||
@ -34,7 +34,7 @@ class HaPanelDevService extends LocalizeMixin(PolymerElement) {
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
mwc-button {
|
||||
ha-progress-button {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
@ -136,9 +136,13 @@ class HaPanelDevService extends LocalizeMixin(PolymerElement) {
|
||||
error="[[!validJSON]]"
|
||||
on-value-changed="_yamlChanged"
|
||||
></ha-code-editor>
|
||||
<mwc-button on-click="_callService" raised disabled="[[!validJSON]]">
|
||||
<ha-progress-button
|
||||
on-click="_callService"
|
||||
raised
|
||||
disabled="[[!validJSON]]"
|
||||
>
|
||||
[[localize('ui.panel.developer-tools.tabs.services.call_service')]]
|
||||
</mwc-button>
|
||||
</ha-progress-button>
|
||||
</div>
|
||||
|
||||
<template is="dom-if" if="[[!domainService]]">
|
||||
@ -307,7 +311,8 @@ class HaPanelDevService extends LocalizeMixin(PolymerElement) {
|
||||
return ENTITY_COMPONENT_DOMAINS.includes(domain) ? [domain] : null;
|
||||
}
|
||||
|
||||
_callService() {
|
||||
_callService(ev) {
|
||||
const button = ev.target;
|
||||
if (this.parsedJSON === ERROR_SENTINEL) {
|
||||
showAlertDialog(this, {
|
||||
text: this.hass.localize(
|
||||
@ -316,10 +321,17 @@ class HaPanelDevService extends LocalizeMixin(PolymerElement) {
|
||||
this.serviceData
|
||||
),
|
||||
});
|
||||
button.actionError();
|
||||
return;
|
||||
}
|
||||
|
||||
this.hass.callService(this._domain, this._service, this.parsedJSON);
|
||||
this.hass
|
||||
.callService(this._domain, this._service, this.parsedJSON)
|
||||
.then(() => {
|
||||
button.actionSuccess();
|
||||
})
|
||||
.catch(() => {
|
||||
button.actionError();
|
||||
});
|
||||
}
|
||||
|
||||
_fillExampleData() {
|
||||
|
@ -2,6 +2,7 @@ import "@material/mwc-ripple";
|
||||
import type { Ripple } from "@material/mwc-ripple";
|
||||
import { directive, PropertyPart } from "lit-html";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { deepEqual } from "../../../../common/util/deep-equal";
|
||||
import {
|
||||
ActionHandlerDetail,
|
||||
ActionHandlerOptions,
|
||||
@ -17,10 +18,18 @@ interface ActionHandler extends HTMLElement {
|
||||
bind(element: Element, options): void;
|
||||
}
|
||||
interface ActionHandlerElement extends HTMLElement {
|
||||
actionHandler?: boolean;
|
||||
actionHandler?: {
|
||||
options: ActionHandlerOptions;
|
||||
start?: (ev: Event) => void;
|
||||
end?: (ev: Event) => void;
|
||||
handleEnter?: (ev: KeyboardEvent) => void;
|
||||
};
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"action-handler": ActionHandler;
|
||||
}
|
||||
interface HASSDomEvents {
|
||||
action: ActionHandlerDetail;
|
||||
}
|
||||
@ -76,26 +85,45 @@ class ActionHandler extends HTMLElement implements ActionHandler {
|
||||
});
|
||||
}
|
||||
|
||||
public bind(element: ActionHandlerElement, options) {
|
||||
if (element.actionHandler) {
|
||||
public bind(element: ActionHandlerElement, options: ActionHandlerOptions) {
|
||||
if (
|
||||
element.actionHandler &&
|
||||
deepEqual(options, element.actionHandler.options)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
element.actionHandler = true;
|
||||
|
||||
element.addEventListener("contextmenu", (ev: Event) => {
|
||||
const e = ev || window.event;
|
||||
if (e.preventDefault) {
|
||||
e.preventDefault();
|
||||
}
|
||||
if (e.stopPropagation) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
e.cancelBubble = true;
|
||||
e.returnValue = false;
|
||||
return false;
|
||||
});
|
||||
if (element.actionHandler) {
|
||||
element.removeEventListener("touchstart", element.actionHandler.start!);
|
||||
element.removeEventListener("touchend", element.actionHandler.end!);
|
||||
element.removeEventListener("touchcancel", element.actionHandler.end!);
|
||||
|
||||
const start = (ev: Event) => {
|
||||
element.removeEventListener("mousedown", element.actionHandler.start!);
|
||||
element.removeEventListener("click", element.actionHandler.end!);
|
||||
|
||||
element.removeEventListener("keyup", element.actionHandler.handleEnter!);
|
||||
} else {
|
||||
element.addEventListener("contextmenu", (ev: Event) => {
|
||||
const e = ev || window.event;
|
||||
if (e.preventDefault) {
|
||||
e.preventDefault();
|
||||
}
|
||||
if (e.stopPropagation) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
e.cancelBubble = true;
|
||||
e.returnValue = false;
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
element.actionHandler = { options };
|
||||
|
||||
if (options.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
element.actionHandler.start = (ev: Event) => {
|
||||
this.held = false;
|
||||
let x;
|
||||
let y;
|
||||
@ -107,13 +135,19 @@ class ActionHandler extends HTMLElement implements ActionHandler {
|
||||
y = (ev as MouseEvent).pageY;
|
||||
}
|
||||
|
||||
this.timer = window.setTimeout(() => {
|
||||
this.startAnimation(x, y);
|
||||
this.held = true;
|
||||
}, this.holdTime);
|
||||
if (options.hasHold) {
|
||||
this.timer = window.setTimeout(() => {
|
||||
this.startAnimation(x, y);
|
||||
this.held = true;
|
||||
}, this.holdTime);
|
||||
}
|
||||
};
|
||||
|
||||
const end = (ev: Event) => {
|
||||
element.actionHandler.end = (ev: Event) => {
|
||||
// Don't respond on our own generated click
|
||||
if (!ev.isTrusted) {
|
||||
return;
|
||||
}
|
||||
// Prevent mouse event if touch event
|
||||
ev.preventDefault();
|
||||
if (
|
||||
@ -122,9 +156,11 @@ class ActionHandler extends HTMLElement implements ActionHandler {
|
||||
) {
|
||||
return;
|
||||
}
|
||||
clearTimeout(this.timer);
|
||||
this.stopAnimation();
|
||||
this.timer = undefined;
|
||||
if (options.hasHold) {
|
||||
clearTimeout(this.timer);
|
||||
this.stopAnimation();
|
||||
this.timer = undefined;
|
||||
}
|
||||
if (this.held) {
|
||||
fireEvent(element, "action", { action: "hold" });
|
||||
} else if (options.hasDoubleClick) {
|
||||
@ -143,24 +179,30 @@ class ActionHandler extends HTMLElement implements ActionHandler {
|
||||
}
|
||||
} else {
|
||||
fireEvent(element, "action", { action: "tap" });
|
||||
// Fire the click we prevented the action for
|
||||
(ev.target as HTMLElement)?.click();
|
||||
}
|
||||
};
|
||||
|
||||
const handleEnter = (ev: KeyboardEvent) => {
|
||||
element.actionHandler.handleEnter = (ev: KeyboardEvent) => {
|
||||
if (ev.keyCode !== 13) {
|
||||
return;
|
||||
}
|
||||
end(ev);
|
||||
(ev.currentTarget as ActionHandlerElement).actionHandler!.end!(ev);
|
||||
};
|
||||
|
||||
element.addEventListener("touchstart", start, { passive: true });
|
||||
element.addEventListener("touchend", end);
|
||||
element.addEventListener("touchcancel", end);
|
||||
element.addEventListener("touchstart", element.actionHandler.start, {
|
||||
passive: true,
|
||||
});
|
||||
element.addEventListener("touchend", element.actionHandler.end);
|
||||
element.addEventListener("touchcancel", element.actionHandler.end);
|
||||
|
||||
element.addEventListener("mousedown", start, { passive: true });
|
||||
element.addEventListener("click", end);
|
||||
element.addEventListener("mousedown", element.actionHandler.start, {
|
||||
passive: true,
|
||||
});
|
||||
element.addEventListener("click", element.actionHandler.end);
|
||||
|
||||
element.addEventListener("keyup", handleEnter);
|
||||
element.addEventListener("keyup", element.actionHandler.handleEnter);
|
||||
}
|
||||
|
||||
private startAnimation(x: number, y: number) {
|
||||
|
@ -13,6 +13,7 @@ function hasConfigChanged(element: any, changedProps: PropertyValues): boolean {
|
||||
}
|
||||
|
||||
if (
|
||||
oldHass.connected !== element.hass!.connected ||
|
||||
oldHass.themes !== element.hass!.themes ||
|
||||
oldHass.language !== element.hass!.language ||
|
||||
oldHass.localize !== element.hass.localize ||
|
||||
|
@ -159,7 +159,19 @@ export class HuiImage extends LitElement {
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
if (changedProps.has("cameraImage") && this.cameraView !== "live") {
|
||||
if (changedProps.has("hass")) {
|
||||
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||
if (!oldHass || oldHass.connected !== this.hass!.connected) {
|
||||
if (this.hass!.connected && this.cameraView !== "live") {
|
||||
this._updateCameraImageSrc();
|
||||
this._startUpdateCameraInterval();
|
||||
} else if (!this.hass!.connected) {
|
||||
this._stopUpdateCameraInterval();
|
||||
this._cameraImageSrc = undefined;
|
||||
this._loadError = true;
|
||||
}
|
||||
}
|
||||
} else if (changedProps.has("cameraImage") && this.cameraView !== "live") {
|
||||
this._updateCameraImageSrc();
|
||||
this._startUpdateCameraInterval();
|
||||
}
|
||||
|
@ -47,6 +47,7 @@ const cardConfigStruct = object({
|
||||
title: optional(union([string(), boolean()])),
|
||||
theme: optional(string()),
|
||||
show_header_toggle: optional(boolean()),
|
||||
state_color: optional(boolean()),
|
||||
entities: array(entitiesConfigStruct),
|
||||
header: optional(headerFooterConfigStructs),
|
||||
footer: optional(headerFooterConfigStructs),
|
||||
@ -89,33 +90,47 @@ export class HuiEntitiesCardEditor extends LitElement
|
||||
)} (${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.card.config.optional"
|
||||
)})"
|
||||
.value="${this._title}"
|
||||
.configValue="${"title"}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
.value=${this._title}
|
||||
.configValue=${"title"}
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-input>
|
||||
<hui-theme-select-editor
|
||||
.hass=${this.hass}
|
||||
.value="${this._theme}"
|
||||
.configValue="${"theme"}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
.value=${this._theme}
|
||||
.configValue=${"theme"}
|
||||
@value-changed=${this._valueChanged}
|
||||
></hui-theme-select-editor>
|
||||
<ha-formfield
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.card.entities.show_header_toggle"
|
||||
)}
|
||||
.dir=${computeRTLDirection(this.hass)}
|
||||
>
|
||||
<ha-switch
|
||||
.checked="${this._config!.show_header_toggle !== false}"
|
||||
.configValue="${"show_header_toggle"}"
|
||||
@change="${this._valueChanged}"
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
<div class="side-by-side">
|
||||
<ha-formfield
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.card.entities.show_header_toggle"
|
||||
)}
|
||||
.dir=${computeRTLDirection(this.hass)}
|
||||
>
|
||||
<ha-switch
|
||||
.checked=${this._config!.show_header_toggle !== false}
|
||||
.configValue=${"show_header_toggle"}
|
||||
@change=${this._valueChanged}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
<ha-formfield
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.card.generic.state_color"
|
||||
)}
|
||||
.dir=${computeRTLDirection(this.hass)}
|
||||
>
|
||||
<ha-switch
|
||||
.checked=${this._config!.state_color}
|
||||
.configValue=${"state_color"}
|
||||
@change=${this._valueChanged}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
</div>
|
||||
</div>
|
||||
<hui-entity-editor
|
||||
.hass=${this.hass}
|
||||
.entities="${this._configEntities}"
|
||||
@entities-changed="${this._valueChanged}"
|
||||
.entities=${this._configEntities}
|
||||
@entities-changed=${this._valueChanged}
|
||||
></hui-entity-editor>
|
||||
`;
|
||||
}
|
||||
|
@ -47,6 +47,7 @@ const cardConfigStruct = object({
|
||||
show_name: optional(boolean()),
|
||||
show_state: optional(boolean()),
|
||||
show_icon: optional(boolean()),
|
||||
state_color: optional(boolean()),
|
||||
entities: array(entitiesConfigStruct),
|
||||
});
|
||||
|
||||
@ -89,6 +90,10 @@ export class HuiGlanceCardEditor extends LitElement
|
||||
return this._config!.show_state || true;
|
||||
}
|
||||
|
||||
get _state_color(): boolean {
|
||||
return this._config!.state_color ?? true;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass || !this._config) {
|
||||
return html``;
|
||||
@ -105,16 +110,16 @@ export class HuiGlanceCardEditor extends LitElement
|
||||
)} (${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.card.config.optional"
|
||||
)})"
|
||||
.value="${this._title}"
|
||||
.configValue="${"title"}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
.value=${this._title}
|
||||
.configValue=${"title"}
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-input>
|
||||
<div class="side-by-side">
|
||||
<hui-theme-select-editor
|
||||
.hass=${this.hass}
|
||||
.value="${this._theme}"
|
||||
.configValue="${"theme"}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
.value=${this._theme}
|
||||
.configValue=${"theme"}
|
||||
@value-changed=${this._valueChanged}
|
||||
></hui-theme-select-editor>
|
||||
<paper-input
|
||||
.label="${this.hass.localize(
|
||||
@ -123,9 +128,9 @@ export class HuiGlanceCardEditor extends LitElement
|
||||
"ui.panel.lovelace.editor.card.config.optional"
|
||||
)})"
|
||||
type="number"
|
||||
.value="${this._columns}"
|
||||
.configValue="${"columns"}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
.value=${this._columns}
|
||||
.configValue=${"columns"}
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-input>
|
||||
</div>
|
||||
<div class="side-by-side">
|
||||
@ -138,8 +143,8 @@ export class HuiGlanceCardEditor extends LitElement
|
||||
>
|
||||
<ha-switch
|
||||
.checked=${this._config!.show_name !== false}
|
||||
.configValue="${"show_name"}"
|
||||
@change="${this._valueChanged}"
|
||||
.configValue=${"show_name"}
|
||||
@change=${this._valueChanged}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
</div>
|
||||
@ -152,8 +157,8 @@ export class HuiGlanceCardEditor extends LitElement
|
||||
>
|
||||
<ha-switch
|
||||
.checked=${this._config!.show_icon !== false}
|
||||
.configValue="${"show_icon"}"
|
||||
@change="${this._valueChanged}"
|
||||
.configValue=${"show_icon"}
|
||||
@change=${this._valueChanged}
|
||||
>
|
||||
</ha-switch>
|
||||
</ha-formfield>
|
||||
@ -167,18 +172,30 @@ export class HuiGlanceCardEditor extends LitElement
|
||||
>
|
||||
<ha-switch
|
||||
.checked=${this._config!.show_state !== false}
|
||||
.configValue="${"show_state"}"
|
||||
@change="${this._valueChanged}"
|
||||
.configValue=${"show_state"}
|
||||
@change=${this._valueChanged}
|
||||
>
|
||||
</ha-switch>
|
||||
</ha-formfield>
|
||||
</div>
|
||||
</div>
|
||||
<ha-formfield
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.card.generic.state_color"
|
||||
)}
|
||||
.dir=${computeRTLDirection(this.hass)}
|
||||
>
|
||||
<ha-switch
|
||||
.checked=${this._config!.state_color}
|
||||
.configValue=${"state_color"}
|
||||
@change=${this._valueChanged}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
</div>
|
||||
<hui-entity-editor
|
||||
.hass=${this.hass}
|
||||
.entities="${this._configEntities}"
|
||||
@entities-changed="${this._valueChanged}"
|
||||
.entities=${this._configEntities}
|
||||
@entities-changed=${this._valueChanged}
|
||||
></hui-entity-editor>
|
||||
`;
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ export interface EntityFilterEntityConfig extends EntityConfig {
|
||||
}
|
||||
export interface DividerConfig {
|
||||
type: "divider";
|
||||
style: string;
|
||||
style: { [key: string]: string };
|
||||
}
|
||||
export interface SectionConfig {
|
||||
type: "section";
|
||||
|
@ -40,7 +40,7 @@ export const buttonsHeaderFooterConfigStruct = object({
|
||||
export const graphHeaderFooterConfigStruct = object({
|
||||
type: string(),
|
||||
entity: string(),
|
||||
detail: optional(string()),
|
||||
detail: optional(number()),
|
||||
hours_to_show: optional(number()),
|
||||
});
|
||||
|
||||
|
@ -1,34 +1,34 @@
|
||||
import "@material/mwc-button";
|
||||
import "../../layouts/ha-app-layout";
|
||||
import "@polymer/app-layout/app-header/app-header";
|
||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||
import "../../components/ha-icon-button";
|
||||
import "../../components/ha-circular-progress";
|
||||
import { safeDump, safeLoad } from "js-yaml";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import { array, assert, object, optional, string, type } from "superstruct";
|
||||
import { computeRTL } from "../../common/util/compute_rtl";
|
||||
import "../../components/ha-circular-progress";
|
||||
import "../../components/ha-code-editor";
|
||||
import type { HaCodeEditor } from "../../components/ha-code-editor";
|
||||
import "../../components/ha-icon";
|
||||
import "../../components/ha-icon-button";
|
||||
import type { LovelaceConfig } from "../../data/lovelace";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
} from "../../dialogs/generic/show-dialog-box";
|
||||
import "../../layouts/ha-app-layout";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type { Lovelace } from "./types";
|
||||
import { optional, array, string, object, type, assert } from "superstruct";
|
||||
|
||||
const lovelaceStruct = type({
|
||||
title: optional(string()),
|
||||
@ -49,7 +49,7 @@ class LovelaceFullConfigEditor extends LitElement {
|
||||
|
||||
private _generation = 1;
|
||||
|
||||
public render(): TemplateResult | void {
|
||||
protected render(): TemplateResult | void {
|
||||
return html`
|
||||
<ha-app-layout>
|
||||
<app-header slot="header">
|
||||
|
@ -1,10 +1,13 @@
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { styleMap } from "lit-html/directives/style-map";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { DividerConfig, LovelaceRow } from "../entity-rows/types";
|
||||
|
||||
@ -19,13 +22,7 @@ class HuiDividerRow extends LitElement implements LovelaceRow {
|
||||
throw new Error("Error in card configuration.");
|
||||
}
|
||||
|
||||
this._config = {
|
||||
style: {
|
||||
height: "1px",
|
||||
"background-color": "var(--divider-color)",
|
||||
},
|
||||
...config,
|
||||
};
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
@ -33,13 +30,16 @@ class HuiDividerRow extends LitElement implements LovelaceRow {
|
||||
return html``;
|
||||
}
|
||||
|
||||
const el = document.createElement("div");
|
||||
return html`<div style=${styleMap(this._config.style)}></div>`;
|
||||
}
|
||||
|
||||
Object.keys(this._config.style).forEach((prop) => {
|
||||
el.style.setProperty(prop, this._config!.style[prop]);
|
||||
});
|
||||
|
||||
return html` ${el} `;
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
div {
|
||||
height: 1px;
|
||||
background-color: var(--entities-divider-color, var(--divider-color));
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,8 +3,8 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "../../../components/ha-icon";
|
||||
@ -48,7 +48,7 @@ class HuiSectionRow extends LitElement implements LovelaceRow {
|
||||
}
|
||||
.divider {
|
||||
height: 1px;
|
||||
background-color: var(--divider-color);
|
||||
background-color: var(--entities-divider-color, var(--divider-color));
|
||||
margin-left: -16px;
|
||||
margin-right: -16px;
|
||||
margin-top: 8px;
|
||||
|
@ -394,18 +394,6 @@ export class HUIView extends LitElement {
|
||||
left: calc(16px + env(safe-area-inset-left));
|
||||
}
|
||||
|
||||
@media (max-width: 500px) {
|
||||
:host {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.column > * {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 599px) {
|
||||
.column {
|
||||
max-width: 600px;
|
||||
|
@ -1,17 +1,17 @@
|
||||
import "../../components/ha-icon-button";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import { formatDateTime } from "../../common/datetime/format_date_time";
|
||||
import "../../components/ha-card";
|
||||
import { EventsMixin } from "../../mixins/events-mixin";
|
||||
import LocalizeMixin from "../../mixins/localize-mixin";
|
||||
import "../../components/ha-icon-button";
|
||||
import "../../components/ha-settings-row";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
} from "../../dialogs/generic/show-dialog-box";
|
||||
import "../../components/ha-settings-row";
|
||||
import { EventsMixin } from "../../mixins/events-mixin";
|
||||
import LocalizeMixin from "../../mixins/localize-mixin";
|
||||
|
||||
/*
|
||||
* @appliesMixin EventsMixin
|
||||
@ -39,7 +39,7 @@ class HaRefreshTokens extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
<div slot="description">[[_formatLastUsed(item)]]</div>
|
||||
<div>
|
||||
<template is="dom-if" if="[[item.is_current]]">
|
||||
<paper-tooltip position="left"
|
||||
<paper-tooltip animation-delay="0" position="left"
|
||||
>[[localize('ui.panel.profile.refresh_tokens.current_token_tooltip')]]</paper-tooltip
|
||||
>
|
||||
</template>
|
||||
|
@ -6,6 +6,7 @@ export const darkStyles = {
|
||||
"secondary-background-color": "#1e1e1e",
|
||||
"primary-text-color": "#e1e1e1",
|
||||
"secondary-text-color": "#9b9b9b",
|
||||
"disabled-text-color": "#6f6f6f",
|
||||
"app-header-text-color": "#e1e1e1",
|
||||
"app-header-background-color": "#1c1c1c",
|
||||
"switch-unchecked-button-color": "#999999",
|
||||
|
@ -526,7 +526,8 @@
|
||||
}
|
||||
},
|
||||
"domain_toggler": {
|
||||
"title": "Toggle Domains"
|
||||
"title": "Toggle Domains",
|
||||
"reset_entities": "Reset Entities"
|
||||
},
|
||||
"mqtt_device_debug_info": {
|
||||
"title": "{device} debug info",
|
||||
@ -838,7 +839,20 @@
|
||||
"input_number": "Reload input numbers",
|
||||
"input_datetime": "Reload input date times",
|
||||
"input_select": "Reload input selects",
|
||||
"template": "Reload template entities"
|
||||
"template": "Reload template entities",
|
||||
"universal": "Reload universal media player entities",
|
||||
"rest": "Reload rest entities",
|
||||
"command_line": "Reload command line entities",
|
||||
"filter": "Reload filter entities",
|
||||
"statistics": "Reload statistics entities",
|
||||
"generic": "Reload generic IP camera entities",
|
||||
"generic_thermostat": "Reload generic thermostat entities",
|
||||
"homekit": "Reload HomeKit",
|
||||
"min_max": "Reload min/max entities",
|
||||
"history_stats": "Reload history stats entities",
|
||||
"trend": "Reload trend entities",
|
||||
"ping": "Reload ping binary sensor entities",
|
||||
"filesize": "Reload file size entities"
|
||||
},
|
||||
"server_management": {
|
||||
"heading": "Server management",
|
||||
@ -1370,7 +1384,13 @@
|
||||
"title": "Alexa",
|
||||
"banner": "Editing which entities are exposed via this UI is disabled because you have configured entity filters in configuration.yaml.",
|
||||
"exposed_entities": "Exposed entities",
|
||||
"not_exposed_entities": "Not Exposed entities",
|
||||
"not_exposed_entities": "Not exposed entities",
|
||||
"manage_domains": "Manage domains",
|
||||
"expose_entity": "Expose entity",
|
||||
"dont_expose_entity": "Don't expose entity",
|
||||
"follow_domain": "Follow domain",
|
||||
"exposed": "{selected} exposed",
|
||||
"not_exposed": "{selected} not exposed",
|
||||
"expose": "Expose to Alexa"
|
||||
},
|
||||
"dialog_certificate": {
|
||||
@ -1386,7 +1406,13 @@
|
||||
"disable_2FA": "Disable two factor authentication",
|
||||
"banner": "Editing which entities are exposed via this UI is disabled because you have configured entity filters in configuration.yaml.",
|
||||
"exposed_entities": "Exposed entities",
|
||||
"not_exposed_entities": "Not Exposed entities",
|
||||
"not_exposed_entities": "Not exposed entities",
|
||||
"manage_domains": "Manage domains",
|
||||
"expose_entity": "Expose entity",
|
||||
"dont_expose_entity": "Don't expose entity",
|
||||
"follow_domain": "Follow domain",
|
||||
"exposed": "{selected} exposed",
|
||||
"not_exposed": "{selected} not exposed",
|
||||
"sync_to_google": "Synchronizing changes to Google."
|
||||
},
|
||||
"dialog_cloudhook": {
|
||||
@ -1605,7 +1631,10 @@
|
||||
"documentation": "Documentation",
|
||||
"delete": "Delete",
|
||||
"delete_confirm": "Are you sure you want to delete this integration?",
|
||||
"reload": "Reload",
|
||||
"restart_confirm": "Restart Home Assistant to finish removing this integration",
|
||||
"reload_confirm": "The integration was reloaded",
|
||||
"reload_restart_confirm": "Restart Home Assistant to finish reloading this integration",
|
||||
"manuf": "by {manufacturer}",
|
||||
"hub": "Connected via",
|
||||
"firmware": "Firmware: {version}",
|
||||
@ -1646,6 +1675,8 @@
|
||||
"caption": "View user",
|
||||
"name": "Name",
|
||||
"change_password": "Change password",
|
||||
"new_password": "New Password",
|
||||
"password_changed": "The password is changed!",
|
||||
"activate_user": "Activate user",
|
||||
"deactivate_user": "Deactivate user",
|
||||
"delete_user": "Delete user",
|
||||
@ -1666,6 +1697,8 @@
|
||||
"name": "Name",
|
||||
"username": "Username",
|
||||
"password": "Password",
|
||||
"password_confirm": "Confirm Password",
|
||||
"password_not_match": "Passwords don't match",
|
||||
"create": "Create"
|
||||
}
|
||||
},
|
||||
@ -2236,7 +2269,8 @@
|
||||
"url": "Url",
|
||||
"state": "State",
|
||||
"secondary_info_attribute": "Secondary Info Attribute",
|
||||
"search": "Search"
|
||||
"search": "Search",
|
||||
"state_color": "Color icons based on state?"
|
||||
},
|
||||
"map": {
|
||||
"name": "Map",
|
||||
|
@ -193,7 +193,7 @@ export interface Resources {
|
||||
|
||||
export interface Context {
|
||||
id: string;
|
||||
parrent_id?: string;
|
||||
parent_id?: string;
|
||||
user_id?: string;
|
||||
}
|
||||
|
||||
|
23
src/util/calculate.ts
Normal file
23
src/util/calculate.ts
Normal file
@ -0,0 +1,23 @@
|
||||
export const normalize = (value: number, min: number, max: number): number => {
|
||||
if (isNaN(value) || isNaN(min) || isNaN(max)) {
|
||||
// Not a number, return 0
|
||||
return 0;
|
||||
}
|
||||
if (value > max) return max;
|
||||
if (value < min) return min;
|
||||
return value;
|
||||
};
|
||||
|
||||
export const getValueInPercentage = (
|
||||
value: number,
|
||||
min: number,
|
||||
max: number
|
||||
): number => {
|
||||
const newMax = max - min;
|
||||
const newVal = value - min;
|
||||
return (100 * newVal) / newMax;
|
||||
};
|
||||
|
||||
export const roundWithOneDecimal = (value: number): number => {
|
||||
return Math.round(value * 10) / 10;
|
||||
};
|
25
test-mocha/util/calculate.spec.ts
Normal file
25
test-mocha/util/calculate.spec.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import * as assert from "assert";
|
||||
import {
|
||||
getValueInPercentage,
|
||||
normalize,
|
||||
roundWithOneDecimal,
|
||||
} from "../../src/util/calculate";
|
||||
|
||||
describe("Calculate tests", function () {
|
||||
it("Test getValueInPercentage", function () {
|
||||
assert.equal(getValueInPercentage(10, 0, 100), 10);
|
||||
assert.equal(getValueInPercentage(120, 0, 100), 120);
|
||||
assert.equal(getValueInPercentage(-10, 0, 100), -10);
|
||||
assert.equal(getValueInPercentage(10.33333, 0, 100), 10.33333);
|
||||
});
|
||||
it("Test normalize", function () {
|
||||
assert.equal(normalize(10, 0, 100), 10);
|
||||
assert.equal(normalize(1, 10, 100), 10);
|
||||
assert.equal(normalize(100, 0, 10), 10);
|
||||
});
|
||||
it("Test roundWithOneDecimal", function () {
|
||||
assert.equal(roundWithOneDecimal(10), 10);
|
||||
assert.equal(roundWithOneDecimal(10.3), 10.3);
|
||||
assert.equal(roundWithOneDecimal(10.3333), 10.3);
|
||||
});
|
||||
});
|
@ -46,15 +46,15 @@
|
||||
"state_badge": {
|
||||
"alarm_control_panel": {
|
||||
"armed": "Gewapen",
|
||||
"armed_away": "Gewapen",
|
||||
"armed_away": "Aktief",
|
||||
"armed_custom_bypass": "Gewapen",
|
||||
"armed_home": "Gewapen",
|
||||
"armed_night": "Gewapen",
|
||||
"armed_night": "Aktief",
|
||||
"arming": "Bewapen Tans",
|
||||
"disarmed": "Ontwapen",
|
||||
"disarming": "Ontwapen",
|
||||
"pending": "Hangend",
|
||||
"triggered": "Aktief"
|
||||
"triggered": "Lui"
|
||||
},
|
||||
"default": {
|
||||
"entity_not_found": "Entiteit nie gevind nie",
|
||||
@ -646,7 +646,7 @@
|
||||
"create": "SKEP",
|
||||
"default_name": "Nuwe Gebied",
|
||||
"delete": "SKRAP",
|
||||
"update": "OPDATEER"
|
||||
"update": "Opdateer"
|
||||
},
|
||||
"picker": {
|
||||
"create_area": "SKEP GEBIED",
|
||||
@ -694,7 +694,7 @@
|
||||
"wait_template": "Wag Templaat"
|
||||
}
|
||||
},
|
||||
"unsupported_action": "Ongesteunde aksie: {action}"
|
||||
"unsupported_action": "Geen UI-ondersteuning vir aksie nie: {action}"
|
||||
},
|
||||
"alias": "Naam",
|
||||
"conditions": {
|
||||
@ -1350,6 +1350,8 @@
|
||||
"network_stopped": "Z-Wave Netwerk het Gestop"
|
||||
},
|
||||
"node_config": {
|
||||
"config_value": "Konfigurasiewaarde",
|
||||
"header": "Knooppuntkonfigurasieopsies",
|
||||
"set_config_parameter": "Stel Config-parameter in"
|
||||
},
|
||||
"services": {
|
||||
@ -1449,13 +1451,16 @@
|
||||
"humidifier": {
|
||||
"description": "Die Luchtbevochtigerkaart verleen beheer oor u lugbevochtiger-entiteit. Hiermee kan u die humiditeit en modus van die entiteit verander."
|
||||
},
|
||||
"iframe": {
|
||||
"name": "Webblad"
|
||||
},
|
||||
"shopping-list": {
|
||||
"integration_not_loaded": "Hierdie kaart vereis die ' shopping_list ' integrasie wat opgestel moet word."
|
||||
}
|
||||
},
|
||||
"edit_card": {
|
||||
"add": "Voeg Kaart by",
|
||||
"delete": "Skrap",
|
||||
"delete": "Skrap kaart",
|
||||
"edit": "Wysig",
|
||||
"header": "Kaart opstelling",
|
||||
"move": "Skuif",
|
||||
|
@ -419,9 +419,16 @@
|
||||
"unlock": "Desbloquejar"
|
||||
},
|
||||
"media_player": {
|
||||
"browse_media": "Navega pels mitjans",
|
||||
"media_next_track": "Següent",
|
||||
"media_play": "Reprodueix",
|
||||
"media_play_pause": "Reprodueix/pausa",
|
||||
"media_previous_track": "Anterior",
|
||||
"sound_mode": "Mode de so",
|
||||
"source": "Entrada",
|
||||
"text_to_speak": "Text a veu"
|
||||
"text_to_speak": "Text a veu",
|
||||
"turn_off": "Apaga",
|
||||
"turn_on": "Engega"
|
||||
},
|
||||
"persistent_notification": {
|
||||
"dismiss": "Desestimar"
|
||||
@ -554,6 +561,26 @@
|
||||
"loading_history": "Carregant historial d'estats...",
|
||||
"no_history_found": "No s'ha trobat cap historial d'estats."
|
||||
},
|
||||
"media-browser": {
|
||||
"choose-source": "Tria la font",
|
||||
"content-type": {
|
||||
"album": "Àlbum",
|
||||
"artist": "Artista",
|
||||
"library": "Biblioteca",
|
||||
"playlist": "Llista de reproducció",
|
||||
"server": "Servidor"
|
||||
},
|
||||
"media-player-browser": "Navegador del reproductor multimèdia",
|
||||
"no_items": "Sense elements",
|
||||
"pick": "Escull",
|
||||
"pick-media": "Tria mitjans",
|
||||
"play": "Reprodueix",
|
||||
"play-media": "Reprodueix mitjans"
|
||||
},
|
||||
"picture-upload": {
|
||||
"label": "Imatge",
|
||||
"unsupported_format": "Format no compatible, tria una imatge JPEG, PNG o GIF."
|
||||
},
|
||||
"related-items": {
|
||||
"area": "Àrea",
|
||||
"automation": "Part de les següents automatitzacions",
|
||||
@ -574,6 +601,7 @@
|
||||
"week": "{count} {count, plural,\n one {setmana}\n other {setmanes}\n}"
|
||||
},
|
||||
"future": "D'aquí a {time}",
|
||||
"just_now": "Ara mateix",
|
||||
"never": "Mai",
|
||||
"past": "Fa {time}"
|
||||
},
|
||||
@ -656,6 +684,9 @@
|
||||
"required_error_msg": "Aquest camp és obligatori",
|
||||
"yaml_not_editable": "La configuració d'aquesta entitat no es pot editar des de la interfície d'usuari. Només es poden editar des de la interfície aquelles entitats que s'han configurat des d'ella."
|
||||
},
|
||||
"image_cropper": {
|
||||
"crop": "Retalla"
|
||||
},
|
||||
"more_info_control": {
|
||||
"dismiss": "Desestimar el diàleg",
|
||||
"edit": "Edita entitat",
|
||||
@ -892,7 +923,7 @@
|
||||
"wait_template": "Plantilla d'espera"
|
||||
}
|
||||
},
|
||||
"unsupported_action": "Acció {action} no suportada."
|
||||
"unsupported_action": "Acció no suportada per la UI: {action}"
|
||||
},
|
||||
"alias": "Nom",
|
||||
"conditions": {
|
||||
@ -958,7 +989,7 @@
|
||||
"zone": "Zona"
|
||||
}
|
||||
},
|
||||
"unsupported_condition": "Condició {condition} no suportada."
|
||||
"unsupported_condition": "Condició no suportada per la UI: {condition}"
|
||||
},
|
||||
"default_name": "Nova automatització",
|
||||
"description": {
|
||||
@ -993,7 +1024,7 @@
|
||||
"delete_confirm": "Segur que vols eliminar-ho?",
|
||||
"duplicate": "Duplica",
|
||||
"header": "Disparadors",
|
||||
"introduction": "Els activadors són les regles que fan que es dispari una automatització. Pots definir més d'un activador per a cada automatització. Una vegada s'iniciï un activador, el Home Assistant validarà les condicions (si n'hi ha) i finalment cridarà l'acció.",
|
||||
"introduction": "Els activadors són les regles que fan que es dispari una automatització. Pots definir més d'un activador per a cada automatització. Una vegada s'iniciï un activador, Home Assistant validarà les condicions (si n'hi ha) i finalment cridarà l'acció.",
|
||||
"learn_more": "Més informació sobre els activadors",
|
||||
"name": "Disparador",
|
||||
"type_select": "Tipus de disparador",
|
||||
@ -1050,6 +1081,9 @@
|
||||
"sunrise": "a l'Alba",
|
||||
"sunset": "al Capvespre"
|
||||
},
|
||||
"tag": {
|
||||
"label": "Etiqueta"
|
||||
},
|
||||
"template": {
|
||||
"label": "Plantilla",
|
||||
"value_template": "Plantilla de valor"
|
||||
@ -1077,7 +1111,7 @@
|
||||
"zone": "Zona"
|
||||
}
|
||||
},
|
||||
"unsupported_platform": "Plataforma {platform} no suportada."
|
||||
"unsupported_platform": "Plataforma no suportada per la UI: {platform}"
|
||||
},
|
||||
"unsaved_confirm": "Hi ha canvis no desats. Segur que vols sortir?"
|
||||
},
|
||||
@ -1090,7 +1124,7 @@
|
||||
"headers": {
|
||||
"name": "Nom"
|
||||
},
|
||||
"introduction": "L'editor d'automatitzacions et permet crear i editar automatitzacions. Vés a l'enllaç de sota per veure'n les instruccions i assegurar-te que has configurat el Home Assistant correctament.",
|
||||
"introduction": "L'editor d'automatitzacions et permet crear i editar automatitzacions. Vés a l'enllaç de sota per veure'n les instruccions i assegurar-te que has configurat Home Assistant correctament.",
|
||||
"learn_more": "Més informació sobre les automatitzacions",
|
||||
"no_automations": "No s'ha pogut trobar cap automatització editable",
|
||||
"only_editable": "Només es poden editar les automatitzacions definides dins l'arxiu automations.yaml.",
|
||||
@ -1308,6 +1342,7 @@
|
||||
}
|
||||
},
|
||||
"devices": {
|
||||
"add_prompt": "Encara no s'han afegit {name} mitjançant aquest dispositiu. Pots afegir-ne fent clic al botó + a sobre.",
|
||||
"automation": {
|
||||
"actions": {
|
||||
"caption": "Quan es dispara alguna cosa..."
|
||||
@ -1327,6 +1362,7 @@
|
||||
"caption": "Dispositius",
|
||||
"confirm_delete": "Estàs segur que vols eliminar aquest dispositiu?",
|
||||
"confirm_rename_entity_ids": "Vols, també, canviar el nom dels ID's d'entitat de les entitats?",
|
||||
"confirm_rename_entity_ids_warning": "Això no canviarà cap configuració (com automatitzacions, scripts, escenes, Lovelace) que estigui utilitzant aquestes entitats, les hauràs d'actualitzar tu mateix.",
|
||||
"data_table": {
|
||||
"area": "Àrea",
|
||||
"battery": "Bateria",
|
||||
@ -1489,8 +1525,11 @@
|
||||
"no_device": "Entitats sense dispositius",
|
||||
"no_devices": "Aquesta integració no té dispositius.",
|
||||
"options": "Opcions",
|
||||
"reload": "Torna a carregar",
|
||||
"reload_confirm": "La integració s'ha tornar a carregar",
|
||||
"reload_restart_confirm": "Reinicia Home Assistant per acabar de carregar aquesta integració",
|
||||
"rename": "Canvia el nom",
|
||||
"restart_confirm": "Reinicia el Home Assistant per acabar d'eliminar aquesta integració",
|
||||
"restart_confirm": "Reinicia Home Assistant per acabar d'eliminar aquesta integració",
|
||||
"settings_button": "Edita la configuració de {integration}",
|
||||
"system_options": "Opcions de sistema",
|
||||
"system_options_button": "Opcions de sistema de {integration}",
|
||||
@ -1647,7 +1686,11 @@
|
||||
"topic": "tòpic"
|
||||
},
|
||||
"ozw": {
|
||||
"button": "Configura",
|
||||
"common": {
|
||||
"controller": "Controlador",
|
||||
"instance": "Instància",
|
||||
"network": "Xarxa",
|
||||
"node_id": "ID del node",
|
||||
"ozw_instance": "Instància OpenZWave",
|
||||
"zwave": "Z-Wave"
|
||||
@ -1657,9 +1700,74 @@
|
||||
"stage": "Etapa",
|
||||
"zwave_info": "Informació Z-Wave"
|
||||
},
|
||||
"navigation": {
|
||||
"network": "Xarxa",
|
||||
"nodes": "Nodes",
|
||||
"select_instance": "Selecciona instància"
|
||||
},
|
||||
"network_status": {
|
||||
"details": {
|
||||
"driverallnodesqueried": "S'han cosultat tots els nodes",
|
||||
"driverallnodesqueriedsomedead": "S'han consultat tots els nodes. Se n'han trobat alguns morts",
|
||||
"driverawakenodesqueries": "S'han cosultat tots els nodes desperts",
|
||||
"driverfailed": "No s'ha pogut connectar amb el controlador Z-Wave",
|
||||
"driverready": "Iniciant el controlador Z-Wave",
|
||||
"driverremoved": "El controlador s'ha eliminat",
|
||||
"driverreset": "El controlador s'ha restablert",
|
||||
"offline": "OZWDaemon fora de línia",
|
||||
"ready": "A punt per connectar-se",
|
||||
"started": "Connectat a MQTT",
|
||||
"starting": "Connectant a MQTT",
|
||||
"stopped": "OpenZWave aturat"
|
||||
},
|
||||
"offline": "Fora de línia",
|
||||
"online": "En línia",
|
||||
"starting": "Iniciant",
|
||||
"unknown": "Desconegut"
|
||||
},
|
||||
"network": {
|
||||
"header": "Gestió de la xarxa",
|
||||
"introduction": "Gestiona les funcions de tota la xarxa.",
|
||||
"node_count": "{count} nodes"
|
||||
},
|
||||
"node_query_stages": {
|
||||
"associations": "Actualitzant de grups d'associacions i membres",
|
||||
"cacheload": "Carregant informació del fitxer de memòria cau d'OpenZWave. Els nodes amb bateria romandran en aquesta fase fins que es despertin.",
|
||||
"complete": "Procés de consulta completat",
|
||||
"configuration": "Obtenint valors de configuració del node",
|
||||
"dynamic": "Obtenint valors usualment canviants del node",
|
||||
"instances": "Obtenint detalls sobre instàncies i canals compatibles amb el dispositiu",
|
||||
"manufacturerspecific1": "Obtenint fabricant i codis ID de producte del node",
|
||||
"manufacturerspecific2": "Obtenint fabricant i codis ID de producte addicionals del node",
|
||||
"neighbors": "Obtenint llista de nodes veïns",
|
||||
"nodeinfo": "Obtenint classes de comandes compatibles del node",
|
||||
"nodeplusinfo": "Obtenint informació Z-Wave+ del node",
|
||||
"probe": "Comprovant si el node està despert",
|
||||
"protocolinfo": "Obtenint funcions Z-Wave bàsiques del controlador d'aquest node",
|
||||
"session": "Obtenint valors inusualment canviants del node",
|
||||
"static": "Obtenint valors estàtics del dispositiu",
|
||||
"versions": "Obtenint informació de programari i versions de classes de comandes",
|
||||
"wakeup": "Configurant el suport per a cues i missatges"
|
||||
},
|
||||
"refresh_node": {
|
||||
"battery_note": "Si el node funciona amb bateria, assegura't de que estigui actiu abans de continuar",
|
||||
"complete": "Actualització del node completa",
|
||||
"description": "Això farà que OpenZWave torni a consultar el node i n'actualitzi les classes de comandes, funcions i valors.",
|
||||
"node_status": "Estat del node",
|
||||
"step": "Pas"
|
||||
"refreshing_description": "Actualitzant la informació del node...",
|
||||
"start_refresh_button": "Inicia l'actualització",
|
||||
"step": "Pas",
|
||||
"title": "Informació d'actualització del node",
|
||||
"wakeup_header": "Instruccions en despertar de",
|
||||
"wakeup_instructions_source": "Les instruccions en despertar provenen de la base de dades de dispositius de la comunitat OpenZWave."
|
||||
},
|
||||
"select_instance": {
|
||||
"header": "Selecciona una instància d'OpenZWave",
|
||||
"introduction": "Tens més d'una instància d'OpenZWave en funcionament. Quina instància vols gestionar?"
|
||||
},
|
||||
"services": {
|
||||
"add_node": "Afegeix node",
|
||||
"remove_node": "Elimina node"
|
||||
}
|
||||
},
|
||||
"person": {
|
||||
@ -1725,7 +1833,7 @@
|
||||
"headers": {
|
||||
"name": "Nom"
|
||||
},
|
||||
"introduction": "L'editor d'escenes et permet crear i editar escenes. Vés a l'enllaç de sota per veure'n les instruccions i assegurar-te que has configurat el Home Assistant correctament.",
|
||||
"introduction": "L'editor d'escenes et permet crear i editar escenes. Vés a l'enllaç de sota per veure'n les instruccions i assegurar-te que has configurat Home Assistant correctament.",
|
||||
"learn_more": "Més informació sobre les escenes",
|
||||
"no_scenes": "No s'ha pogut trobar cap escena editable",
|
||||
"only_editable": "Només es poden editar les escenes definides dins l'arxiu scenes.yaml.",
|
||||
@ -1772,7 +1880,7 @@
|
||||
"headers": {
|
||||
"name": "Nom"
|
||||
},
|
||||
"introduction": "L'editor de scripts et permet crear i editar scripts. Vés a l'enllaç de sota per veure'n les instruccions i assegurar-te que has configurat el Home Assistant correctament.",
|
||||
"introduction": "L'editor de scripts et permet crear i editar scripts. Vés a l'enllaç de sota per veure'n les instruccions i assegurar-te que has configurat Home Assistant correctament.",
|
||||
"learn_more": "Més informació sobre els scripts",
|
||||
"no_scripts": "No hem trobat cap script editable",
|
||||
"show_info": "Mostra informació sobre l'script",
|
||||
@ -1785,18 +1893,32 @@
|
||||
"section": {
|
||||
"reloading": {
|
||||
"automation": "Actualitza automatitzacions",
|
||||
"command_line": "Torna a carregar entitats de comandes",
|
||||
"core": "Actualitza ubicació i personalitzacions",
|
||||
"filesize": "Torna a carregar entitats de mida de fitxer",
|
||||
"filter": "Torna a carregar entitats de filtre",
|
||||
"generic": "Torna a carregar entitats genèriques de càmera IP",
|
||||
"generic_thermostat": "Torna a carregar entitats genèriques de termòstat",
|
||||
"group": "Actualitza grups",
|
||||
"heading": "Tornant a carregar la configuració",
|
||||
"history_stats": "Torna a carregar entitats d'estadístiques històriques",
|
||||
"homekit": "Torna a carregar HomeKit",
|
||||
"input_boolean": "Actualitza entrades booleanes",
|
||||
"input_datetime": "Actualitza entrades de data i hora",
|
||||
"input_number": "Actualitza entrades numèriques",
|
||||
"input_select": "Actualitza entrades de selecció",
|
||||
"input_text": "Actualitza entrades de text",
|
||||
"introduction": "Algunes parts de Home Assistant es poden actualitzar sense necessitat reiniciar-lo. Si prems actualitza s'esborrarà la configuració YAML actual i se'n carregarà la nova.",
|
||||
"min_max": "Torna a carregar entitats min/max",
|
||||
"person": "Actualitza persones",
|
||||
"ping": "Torna a carregar entitats de sensors binaris de ping",
|
||||
"rest": "Torna a carregar entitats de repòs",
|
||||
"scene": "Actualitza escenes",
|
||||
"script": "Actualitza programes",
|
||||
"statistics": "Torna a carregar entitats estadístiques",
|
||||
"template": "Torna a carregar entitats de plantilla",
|
||||
"trend": "Torna a carregar entitats de tendència",
|
||||
"universal": "Torna a carregar entitats del reproductor universal",
|
||||
"zone": "Actualitza zones"
|
||||
},
|
||||
"server_management": {
|
||||
@ -1816,12 +1938,40 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": {
|
||||
"add_tag": "Afegeix etiqueta",
|
||||
"automation_title": "S'escanegi l'etiqueta {name}",
|
||||
"caption": "Etiquetes",
|
||||
"create_automation": "Crea una automatització amb una etiqueta",
|
||||
"description": "Gestiona les etiquetes",
|
||||
"detail": {
|
||||
"create": "Crea",
|
||||
"create_and_write": "Crea i escriu",
|
||||
"delete": "Elimina",
|
||||
"description": "Descripció",
|
||||
"name": "Nom",
|
||||
"new_tag": "Nova etiqueta",
|
||||
"tag_id": "ID de l'etiqueta",
|
||||
"tag_id_placeholder": "Autogenerat si es deixa buit",
|
||||
"update": "Actualitza"
|
||||
},
|
||||
"edit": "Edita",
|
||||
"headers": {
|
||||
"last_scanned": "Últim escaneig",
|
||||
"name": "Nom"
|
||||
},
|
||||
"never_scanned": "Mai escanejat",
|
||||
"no_tags": "Sense etiquetes",
|
||||
"write": "Escriu"
|
||||
},
|
||||
"users": {
|
||||
"add_user": {
|
||||
"caption": "Afegir usuari",
|
||||
"create": "Crear",
|
||||
"name": "Nom",
|
||||
"password": "Contrasenya",
|
||||
"password_confirm": "Confirma la contrasenya",
|
||||
"password_not_match": "Les contrasenyes no coincideixen",
|
||||
"username": "Nom d'usuari"
|
||||
},
|
||||
"caption": "Usuaris",
|
||||
@ -1838,7 +1988,9 @@
|
||||
"group": "Grup",
|
||||
"id": "ID",
|
||||
"name": "Nom",
|
||||
"new_password": "Nova contrasenya",
|
||||
"owner": "Propietari",
|
||||
"password_changed": "La contrasenya s'ha canviat!",
|
||||
"system_generated": "Generat pel sistema",
|
||||
"system_generated_users_not_editable": "No es poden actualitzar usuaris generats pel sistema.",
|
||||
"system_generated_users_not_removable": "No es poden eliminar usuaris generats pel sistema.",
|
||||
@ -2137,6 +2289,7 @@
|
||||
"description": "Les plantilles es renderitzen mitjançant el motor Jinja2 amb algunes extensions específiques de Home Assistant.",
|
||||
"editor": "Editor de plantilles",
|
||||
"jinja_documentation": "Documentació sobre plantilles amb Jinja2",
|
||||
"reset": "Restableix a la plantilla de demostració",
|
||||
"template_extensions": "Extensions de plantilla de Home Assistant",
|
||||
"title": "Plantilla",
|
||||
"unknown_error_template": "Error desconegut renderitzant plantilla"
|
||||
@ -2218,6 +2371,10 @@
|
||||
"description": "La targeta botó et permet afegir botons per realitzar diferents tasques.",
|
||||
"name": "Botó"
|
||||
},
|
||||
"calendar": {
|
||||
"description": "La targeta Calendari mostra un calendari que inclou visualitzacions de dia, setmana i llista",
|
||||
"name": "Calendari"
|
||||
},
|
||||
"conditional": {
|
||||
"card": "Targeta",
|
||||
"change_type": "Canvia el tipus",
|
||||
@ -2283,6 +2440,7 @@
|
||||
"show_name": "Mostra nom?",
|
||||
"show_state": "Mostra estat?",
|
||||
"state": "Estat",
|
||||
"state_color": "Color de les icones basat en l'estat?",
|
||||
"tap_action": "Acció en tocar",
|
||||
"theme": "Tema",
|
||||
"title": "Títol",
|
||||
@ -2672,6 +2830,7 @@
|
||||
"intro": "Hola {name}, benvingut/uda a Home Assistant. Quin nom t'agradaria posar a la teva casa?",
|
||||
"intro_location": "Voldirem saber la zona on vius. Aquesta informació servirà per poder mostrar certa informació i configurar automatitzacions relatives a la posició del sol. Aquestes dades mai es compartiran fora de la teva xarxa personal.",
|
||||
"intro_location_detect": "Et podem ajudar a completar aquesta informació fent una única sol·licitud a un servei extern.",
|
||||
"location_name": "Nom de la instal·lació de Home Assistant",
|
||||
"location_name_default": "Casa"
|
||||
},
|
||||
"integration": {
|
||||
|
@ -105,7 +105,7 @@
|
||||
"triggered": "Spuštěno"
|
||||
},
|
||||
"automation": {
|
||||
"off": "Neaktivní",
|
||||
"off": "Vypnuto",
|
||||
"on": "Aktivní"
|
||||
},
|
||||
"binary_sensor": {
|
||||
@ -115,7 +115,7 @@
|
||||
},
|
||||
"cold": {
|
||||
"off": "Normální",
|
||||
"on": "Chladné"
|
||||
"on": "Studený"
|
||||
},
|
||||
"connectivity": {
|
||||
"off": "Odpojeno",
|
||||
@ -139,7 +139,7 @@
|
||||
},
|
||||
"heat": {
|
||||
"off": "Normální",
|
||||
"on": "Horké"
|
||||
"on": "Horký"
|
||||
},
|
||||
"lock": {
|
||||
"off": "Zamčeno",
|
||||
@ -191,8 +191,8 @@
|
||||
}
|
||||
},
|
||||
"calendar": {
|
||||
"off": "Neaktivní",
|
||||
"on": "Aktivní"
|
||||
"off": "Vypnuto",
|
||||
"on": "Zapnuto"
|
||||
},
|
||||
"camera": {
|
||||
"idle": "Nečinný",
|
||||
@ -205,7 +205,7 @@
|
||||
"fan_only": "Pouze ventilátor",
|
||||
"heat": "Topení",
|
||||
"heat_cool": "Vytápění/Chlazení",
|
||||
"off": "Neaktivní"
|
||||
"off": "Vypnuto"
|
||||
},
|
||||
"configurator": {
|
||||
"configure": "Konfigurovat",
|
||||
@ -213,9 +213,9 @@
|
||||
},
|
||||
"cover": {
|
||||
"closed": "Zavřeno",
|
||||
"closing": "Zavírání",
|
||||
"closing": "Zavírá se",
|
||||
"open": "Otevřeno",
|
||||
"opening": "Otevírání",
|
||||
"opening": "Otvírá se",
|
||||
"stopped": "Zastaveno"
|
||||
},
|
||||
"default": {
|
||||
@ -228,30 +228,30 @@
|
||||
"not_home": "Pryč"
|
||||
},
|
||||
"fan": {
|
||||
"off": "Neaktivní",
|
||||
"on": "Aktivní"
|
||||
"off": "Vypnuto",
|
||||
"on": "Zapnuto"
|
||||
},
|
||||
"group": {
|
||||
"closed": "Zavřeno",
|
||||
"closing": "Zavírání",
|
||||
"closing": "Zavírá se",
|
||||
"home": "Doma",
|
||||
"locked": "Zamčeno",
|
||||
"not_home": "Pryč",
|
||||
"off": "Neaktivní",
|
||||
"off": "Vypnuto",
|
||||
"ok": "V pořádku",
|
||||
"on": "Aktivní",
|
||||
"on": "Zapnuto",
|
||||
"open": "Otevřeno",
|
||||
"opening": "Otevírání",
|
||||
"opening": "Otvírá se",
|
||||
"problem": "Problém",
|
||||
"stopped": "Zastaveno",
|
||||
"unlocked": "Odemčeno"
|
||||
},
|
||||
"input_boolean": {
|
||||
"off": "Neaktivní",
|
||||
"on": "Aktivní"
|
||||
"on": "Zapnuto"
|
||||
},
|
||||
"light": {
|
||||
"off": "Neaktivní",
|
||||
"off": "Vypnuto",
|
||||
"on": "Aktivní"
|
||||
},
|
||||
"lock": {
|
||||
@ -260,8 +260,8 @@
|
||||
},
|
||||
"media_player": {
|
||||
"idle": "Nečinný",
|
||||
"off": "Neaktivní",
|
||||
"on": "Aktivní",
|
||||
"off": "Vypnuto",
|
||||
"on": "Zapnuto",
|
||||
"paused": "Pozastaveno",
|
||||
"playing": "Přehrávání",
|
||||
"standby": "Pohotovostní režim"
|
||||
@ -274,27 +274,27 @@
|
||||
"problem": "Problém"
|
||||
},
|
||||
"remote": {
|
||||
"off": "Neaktivní",
|
||||
"on": "Aktivní"
|
||||
"off": "Vypnuto",
|
||||
"on": "Zapnuto"
|
||||
},
|
||||
"scene": {
|
||||
"scening": "Scenérie"
|
||||
},
|
||||
"script": {
|
||||
"off": "Neaktivní",
|
||||
"on": "Aktivní"
|
||||
"off": "Vypnuto",
|
||||
"on": "Zapnuto"
|
||||
},
|
||||
"sensor": {
|
||||
"off": "Neaktivní",
|
||||
"on": "Aktivní"
|
||||
"off": "Vypnuto",
|
||||
"on": "Zapnuto"
|
||||
},
|
||||
"sun": {
|
||||
"above_horizon": "Nad horizontem",
|
||||
"below_horizon": "Za horizontem"
|
||||
},
|
||||
"switch": {
|
||||
"off": "Neaktivní",
|
||||
"on": "Aktivní"
|
||||
"off": "Vypnuto",
|
||||
"on": "Zapnuto"
|
||||
},
|
||||
"timer": {
|
||||
"active": "aktivní",
|
||||
@ -306,8 +306,8 @@
|
||||
"docked": "Ve stanici",
|
||||
"error": "Chyba",
|
||||
"idle": "Nečinný",
|
||||
"off": "Off",
|
||||
"on": "On",
|
||||
"off": "Vypnuto",
|
||||
"on": "Zapnuto",
|
||||
"paused": "Pozastaveno",
|
||||
"returning": "Návrat do stanice"
|
||||
},
|
||||
@ -419,9 +419,16 @@
|
||||
"unlock": "Odemknout"
|
||||
},
|
||||
"media_player": {
|
||||
"browse_media": "Procházet média",
|
||||
"media_next_track": "Další",
|
||||
"media_play": "Přehrát",
|
||||
"media_play_pause": "Přehrát/pozastavit",
|
||||
"media_previous_track": "Předchozí",
|
||||
"sound_mode": "Režim zvuku",
|
||||
"source": "Zdroj",
|
||||
"text_to_speak": "Převod textu na řeč"
|
||||
"text_to_speak": "Převod textu na řeč",
|
||||
"turn_off": "Vypnout",
|
||||
"turn_on": "Zapnout"
|
||||
},
|
||||
"persistent_notification": {
|
||||
"dismiss": "Zavřít"
|
||||
@ -554,6 +561,22 @@
|
||||
"loading_history": "Historie stavu se načítá...",
|
||||
"no_history_found": "Historie stavu chybí."
|
||||
},
|
||||
"media-browser": {
|
||||
"choose-source": "Zvolte zdroj",
|
||||
"content-type": {
|
||||
"album": "Album",
|
||||
"artist": "Umělec",
|
||||
"library": "Knihovna",
|
||||
"playlist": "Seznam skladeb",
|
||||
"server": "Server"
|
||||
},
|
||||
"media-player-browser": "Prohlížeč přehrávače médií",
|
||||
"no_items": "Žádné položky",
|
||||
"pick": "Vybrat",
|
||||
"pick-media": "Vybrat média",
|
||||
"play": "Přehrát",
|
||||
"play-media": "Přehrát média"
|
||||
},
|
||||
"picture-upload": {
|
||||
"label": "Obrázek",
|
||||
"unsupported_format": "Nepodporovaný formát, prosím vyberte obrázek typu JPEG, PNG nebo GIF."
|
||||
@ -578,6 +601,7 @@
|
||||
"week": "{count} {count, plural,\none {týden}\nother {týdnů}\n}"
|
||||
},
|
||||
"future": "Za {time}",
|
||||
"just_now": "Právě teď",
|
||||
"never": "Nikdy",
|
||||
"past": "Před {time}"
|
||||
},
|
||||
@ -762,8 +786,8 @@
|
||||
},
|
||||
"duration": {
|
||||
"day": "{count} {count, plural,\none {den}\nfew {dny}\nother {dnů}\n}",
|
||||
"hour": "{count} {count, plural,\n one {hodinou}\n other {hodiny}\n}",
|
||||
"minute": "{count} {count, plural,\none {minutou}\nother {minuty}\n}",
|
||||
"hour": "{count} {count, plural,\n one {hodina}\n few {hodiny}\n other {hodin}\n}",
|
||||
"minute": "{count} {count, plural,\none {minuta}\nfew {minuty}\nother {minut}\n}",
|
||||
"second": "{count} {count, plural,\none {sekunda}\nfew {sekundy}\nother {sekund}\n}",
|
||||
"week": "{count} {count, plural,\none {týden}\nfew {týdny}\nother {týdnů}\n}"
|
||||
},
|
||||
@ -796,7 +820,7 @@
|
||||
"link_profile_page": "stránka vašeho profilu"
|
||||
},
|
||||
"areas": {
|
||||
"caption": "Registr oblastí",
|
||||
"caption": "Oblasti",
|
||||
"data_table": {
|
||||
"area": "Oblast",
|
||||
"devices": "Zařízení"
|
||||
@ -805,7 +829,7 @@
|
||||
"confirmation_text": "Všechna zařízení v této oblasti budou nastavena jako nepřiřazena.",
|
||||
"confirmation_title": "Opravdu chcete tuto oblast smazat?"
|
||||
},
|
||||
"description": "Přehled všech oblastí ve vaší domácnosti.",
|
||||
"description": "Správa oblastí ve vaší domácnosti",
|
||||
"editor": {
|
||||
"area_id": "ID oblasti",
|
||||
"create": "VYTVOŘIT",
|
||||
@ -814,11 +838,11 @@
|
||||
"name": "Název",
|
||||
"name_required": "Název je povinný",
|
||||
"unknown_error": "Neznámá chyba",
|
||||
"update": "UPRAVIT"
|
||||
"update": "Aktualizovat"
|
||||
},
|
||||
"picker": {
|
||||
"create_area": "Vytvořit oblast",
|
||||
"header": "Registr oblastí",
|
||||
"header": "Oblasti",
|
||||
"integrations_page": "Stránka integrací",
|
||||
"introduction": "Oblasti se používají k uspořádání zařízení podle místa kde jsou. Tato informace bude použita k organizaci rozhraní, k nastavení oprávnění a v integraci s ostatnímy systémy.",
|
||||
"introduction2": "Pro přídání zařízení do oblasti přejděte na stránku integrací pomocí odkazu níže tam klikněte na nakonfigurovanou integraci abyste se dostali na kartu zažízení.",
|
||||
@ -827,7 +851,7 @@
|
||||
},
|
||||
"automation": {
|
||||
"caption": "Automatizace",
|
||||
"description": "Vytvářejte a upravujte automatizace",
|
||||
"description": "Správa automatizací",
|
||||
"editor": {
|
||||
"actions": {
|
||||
"add": "Přidat akci",
|
||||
@ -899,7 +923,7 @@
|
||||
"wait_template": "Šablona pro čekání"
|
||||
}
|
||||
},
|
||||
"unsupported_action": "Nepodporovaná akce: {action}"
|
||||
"unsupported_action": "Akce {action} není podporována v uživatelském rozhraní"
|
||||
},
|
||||
"alias": "Název",
|
||||
"conditions": {
|
||||
@ -965,7 +989,7 @@
|
||||
"zone": "Zóna"
|
||||
}
|
||||
},
|
||||
"unsupported_condition": "Nepodporovaná podmínka: {condition}"
|
||||
"unsupported_condition": "Podmínka {condition} není podporována v uživatelském rozhraní"
|
||||
},
|
||||
"default_name": "Nová automatizace",
|
||||
"description": {
|
||||
@ -1087,7 +1111,7 @@
|
||||
"zone": "Zóna"
|
||||
}
|
||||
},
|
||||
"unsupported_platform": "Nepodporovaná platforma: {platform}"
|
||||
"unsupported_platform": "Platforma {platform} není podporována v uživatelském rozhraní"
|
||||
},
|
||||
"unsaved_confirm": "Máte neuložené změny. Opravdu chcete odejít?"
|
||||
},
|
||||
@ -1100,7 +1124,7 @@
|
||||
"headers": {
|
||||
"name": "Název"
|
||||
},
|
||||
"introduction": "Editor automatizací umožňuje vytvářet a upravovat automatizace. Přečtěte si prosím [pokyny] (https://home-assistant.io/docs/automation/editor/), abyste se ujistili, že jste aplikaci Home Assistant nakonfigurovali správně.",
|
||||
"introduction": "Editor automatizací umožňuje vytvářet a upravovat automatizace. Přečtěte si prosím [pokyny] (https://home-assistant.io/docs/automation/editor/), abyste se ujistili, že jste Home Assistant nakonfigurovali správně.",
|
||||
"learn_more": "Další informace o automatizacích",
|
||||
"no_automations": "Nemohli jsme najít žádné upravitelné automatizace",
|
||||
"only_editable": "Upravitelné mohou být pouze automatizace definované v automations.yaml.",
|
||||
@ -1271,7 +1295,7 @@
|
||||
},
|
||||
"core": {
|
||||
"caption": "Obecné",
|
||||
"description": "Ověřte konfigurační soubor a spravujte server",
|
||||
"description": "Změny obecné konfigurace Home Assistant",
|
||||
"section": {
|
||||
"core": {
|
||||
"core_config": {
|
||||
@ -1291,7 +1315,7 @@
|
||||
"unit_system_imperial": "Imperiální",
|
||||
"unit_system_metric": "Metrický"
|
||||
},
|
||||
"header": "Konfigurace a správa serveru",
|
||||
"header": "Obecná konfigurace",
|
||||
"introduction": "Moc dobře víme, že změna konfigurace může být velmi únavným procesem. Tato sekce se proto pokusí udělat váš život alespoň trochu jednodušší."
|
||||
}
|
||||
}
|
||||
@ -1314,10 +1338,11 @@
|
||||
"warning": {
|
||||
"include_link": "zahrnout customize.yaml",
|
||||
"include_sentence": "Zdá se, že configuration.yaml správně nefunguje",
|
||||
"not_applied": "Změny zde provedené jsou zapsány, ale nebudou použity po opětovném načtení konfigurace, pokud není zahrnut do umístění."
|
||||
"not_applied": "Změny zde provedené jsou zapsány, ale nebudou použity při novém načtení konfigurace, pokud není \"customize\" konfigurace nastavena v globální konfiguraci."
|
||||
}
|
||||
},
|
||||
"devices": {
|
||||
"add_prompt": "Pomocí tohoto zařízení dosud nebyl přidán žádný {name}. Můžete ho přidat kliknutím na tlačítko + výše.",
|
||||
"automation": {
|
||||
"actions": {
|
||||
"caption": "Když je něco spuštěno ..."
|
||||
@ -1353,7 +1378,7 @@
|
||||
"device_info": "Informace o zařízení",
|
||||
"device_not_found": "Zařízení nebylo nalezeno.",
|
||||
"entities": {
|
||||
"add_entities_lovelace": "Přidat všechny entity zařízení do Lovelace",
|
||||
"add_entities_lovelace": "Přidat do Lovelace",
|
||||
"disabled_entities": "+{count} {count, plural,\n one {zakázaná entita}\n other {zakázaných entit}\n}",
|
||||
"entities": "Entity",
|
||||
"hide_disabled": "Skrýt zakázané",
|
||||
@ -1378,8 +1403,8 @@
|
||||
"update": "Aktualizovat"
|
||||
},
|
||||
"entities": {
|
||||
"caption": "Registr entit",
|
||||
"description": "Přehled všech známých entit",
|
||||
"caption": "Entity",
|
||||
"description": "Správa známých entit",
|
||||
"picker": {
|
||||
"disable_selected": {
|
||||
"button": "Zakázat vybrané",
|
||||
@ -1397,7 +1422,7 @@
|
||||
"show_readonly": "Zobrazit entity jen pro čtení",
|
||||
"show_unavailable": "Zobrazit nedostupné entity"
|
||||
},
|
||||
"header": "Registr entit",
|
||||
"header": "Entity",
|
||||
"headers": {
|
||||
"entity_id": "ID entity",
|
||||
"integration": "Integrace",
|
||||
@ -1405,7 +1430,7 @@
|
||||
"status": "Stav"
|
||||
},
|
||||
"introduction": "Homa Assistant uchovává registr všech entit, které kdy viděl a mohou být jednoznačně identifikovány. Každá z těchto entit bude mít přiděleno ID, které bude rezervováno pouze pro tuto entitu.",
|
||||
"introduction2": "Pomocí registru entit můžete přepsat název, změnit identifikátor entity nebo odebrat entitu. Poznámka: odebrání entity z registru entit nesmaže entitu. Pro smazání přejděte na stránku integrací pomocí odkazu níže.",
|
||||
"introduction2": "Pomocí registru entit můžete přepsat název, změnit identifikátor entity nebo odebrat entitu.",
|
||||
"remove_selected": {
|
||||
"button": "Odstranit vybrané",
|
||||
"confirm_partly_text": "Můžete odebrat pouze {removable} z vybraných {selected} entit. Entity lze odebrat pouze v případě, že integrace již entity neposkytuje. Občas je třeba restartovat Home Assistant před tím, než je možné odstranit entity ze smazané integrace. Opravdu chcete odebrat odstranitelné entity?",
|
||||
@ -1500,6 +1525,9 @@
|
||||
"no_device": "Entity bez zařízení",
|
||||
"no_devices": "Tato integrace nemá žádná zařízení.",
|
||||
"options": "Možnosti",
|
||||
"reload": "Nově načíst",
|
||||
"reload_confirm": "Integrace byla nově načtena",
|
||||
"reload_restart_confirm": "Restartujte Home Assistant pro nové načtení této integrace",
|
||||
"rename": "Přejmenovat",
|
||||
"restart_confirm": "Restartujte Home Assistant pro odstranění této integrace",
|
||||
"settings_button": "Upravit nastavení pro {integration}",
|
||||
@ -1524,14 +1552,14 @@
|
||||
},
|
||||
"configure": "Konfigurovat",
|
||||
"configured": "Zkonfigurováno",
|
||||
"description": "Spravovat připojená zařízení a služby",
|
||||
"description": "Správa integrací",
|
||||
"details": "Podrobnosti o integraci",
|
||||
"discovered": "Objeveno",
|
||||
"home_assistant_website": "stránky Home Assistant",
|
||||
"ignore": {
|
||||
"confirm_delete_ignore": "To způsobí, že se integrace znovu objeví ve vašich zjištěných integracích. Může to vyžadovat restartování nebo to může nějakou dobu trvat.",
|
||||
"confirm_delete_ignore_title": "Přestat ignorovat {name}?",
|
||||
"confirm_ignore": "Opravdu nechcete tuto integraci nastavit? Tuto akci můžete vrátit zpět klepnutím na 'Zobrazit ignorované integrace' v nabídce vpravo nahoře.",
|
||||
"confirm_ignore": "Opravdu nechcete tuto integraci nastavit? Tuto akci můžete vrátit zpět klepnutím na \"Zobrazit ignorované integrace\" v nabídce vpravo nahoře.",
|
||||
"confirm_ignore_title": "Ignorovat objevený {name}?",
|
||||
"hide_ignored": "Skrýt ignorované integrace",
|
||||
"ignore": "Ignorovat",
|
||||
@ -1552,7 +1580,7 @@
|
||||
"rename_input_label": "Název položky",
|
||||
"search": "Hledat integraci"
|
||||
},
|
||||
"introduction": "Zde je možné konfigurovat vaše komponenty a Home Assistant.\nZ uživatelského rozhraní sice zatím není možné konfigurovat vše, ale pracujeme na tom.",
|
||||
"introduction": "Zde je možné konfigurovat vaše komponenty a Home Assistant.\nZatím není možné vše konfigurovat přímo z uživatelského rozhraní, ale pracujeme na tom.",
|
||||
"logs": {
|
||||
"caption": "Logy",
|
||||
"clear": "Zrušit",
|
||||
@ -1573,7 +1601,7 @@
|
||||
"cant_edit_yaml": "Dashboardy definované v YAML nelze upravovat z uživatelského rozhraní. Změňte je v configuration.yaml.",
|
||||
"caption": "Dashboardy",
|
||||
"conf_mode": {
|
||||
"storage": "Řízeno UI",
|
||||
"storage": "Řízeno uživatelským rozhraním",
|
||||
"yaml": "Soubor YAML"
|
||||
},
|
||||
"confirm_delete": "Opravdu chcete odstranit tento dashboard?",
|
||||
@ -1608,7 +1636,7 @@
|
||||
"open": "Otevřít"
|
||||
}
|
||||
},
|
||||
"description": "Nakonfigurujte své Lovelace Dashboardy",
|
||||
"description": "Správa Lovelace Dashboardů",
|
||||
"resources": {
|
||||
"cant_edit_yaml": "Používáte Lovelace v režimu YAML, proto nemůžete spravovat své zdroje prostřednictvím uživatelského rozhraní. Spravujte je v souboru configuration.yaml.",
|
||||
"caption": "Zdroje",
|
||||
@ -1658,11 +1686,88 @@
|
||||
"topic": "téma"
|
||||
},
|
||||
"ozw": {
|
||||
"button": "Konfigurovat",
|
||||
"common": {
|
||||
"controller": "Ovladač",
|
||||
"instance": "Instance",
|
||||
"network": "Síť",
|
||||
"node_id": "ID uzlu",
|
||||
"ozw_instance": "Instance OpenZWave",
|
||||
"zwave": "Z-Wave"
|
||||
},
|
||||
"device_info": {
|
||||
"node_failed": "Uzel selhal",
|
||||
"stage": "Fáze",
|
||||
"zwave_info": "Informace o Z-Wave"
|
||||
},
|
||||
"navigation": {
|
||||
"network": "Síť",
|
||||
"nodes": "Uzly",
|
||||
"select_instance": "Vyberte instanci"
|
||||
},
|
||||
"network_status": {
|
||||
"details": {
|
||||
"driverallnodesqueried": "Všechny uzly byly zkontaktovány",
|
||||
"driverallnodesqueriedsomedead": "Všechny uzly byly zkontaktovány. Některé uzly byly nalezeny mrtvé",
|
||||
"driverawakenodesqueries": "Všechny probuzené uzly byly zkontaktovány",
|
||||
"driverfailed": "Nepodařilo se připojit k ovladači Z-Wave",
|
||||
"driverready": "Inicializuji ovladač Z-Wave",
|
||||
"driverremoved": "Ovladač byl odstraněn",
|
||||
"driverreset": "Ovladač byl resetován",
|
||||
"offline": "OZWDaemon je offline",
|
||||
"ready": "Připraveno k připojení",
|
||||
"started": "Připojeno k MQTT",
|
||||
"starting": "Připojování k MQTT",
|
||||
"stopped": "OpenZWave zastaven"
|
||||
},
|
||||
"offline": "Offline",
|
||||
"online": "Online",
|
||||
"starting": "Spouštění",
|
||||
"unknown": "Nezjištěno"
|
||||
},
|
||||
"network": {
|
||||
"header": "Správa sítě",
|
||||
"introduction": "Správa funkcí pro celou síť",
|
||||
"node_count": "{count} uzlů"
|
||||
},
|
||||
"node_query_stages": {
|
||||
"associations": "Obnovuji přidružené skupiny a členství",
|
||||
"cacheload": "Načítám informace ze souboru mezipaměti OpenZWave. Uzly baterií zůstanou v této fázi, dokud se uzel neprobudí.",
|
||||
"complete": "Proces komunikace je dokončen",
|
||||
"configuration": "Získávám konfiguraci z uzlu",
|
||||
"dynamic": "Získávám často se měnící hodnoty z uzlu",
|
||||
"instances": "Získávám podrobnosti o tom, jaké instance nebo kanály zařízení podporuje",
|
||||
"manufacturerspecific1": "Získávám z uzlu kody výrobce a produktů",
|
||||
"manufacturerspecific2": "Získávám z uzlu další kódy výrobce a produktů",
|
||||
"neighbors": "Získávám seznam sousedů uzlu",
|
||||
"nodeinfo": "Získávám z uzlu podporované typy příkazů",
|
||||
"nodeplusinfo": "Získávám informace o Z-Wave + z uzlu",
|
||||
"probe": "Kontroluji, zda je uzel probuzený/živý",
|
||||
"protocolinfo": "Získávám z ovladače Z-Wave základní schopnosti tohoto uzlu",
|
||||
"session": "Získávám zřídka se měnící hodnoty z uzlu",
|
||||
"static": "Získávám statické hodnoty ze zařízení",
|
||||
"versions": "Získávám informace o verzích firmwaru a typech příkazů",
|
||||
"wakeup": "Nastavuji podporu pro probouzecí fronty a zprávy"
|
||||
},
|
||||
"refresh_node": {
|
||||
"step": "Krok"
|
||||
"battery_note": "Pokud je uzel napájen z baterie, nezapomeňte jej probudit, než budete pokračovat",
|
||||
"complete": "Obnova uzlu dokončena",
|
||||
"description": "Toto řekne OpenZWave, aby znovu provedl komunikaci s uzlem a aktualizoval typy příkazů, schopnosti a hodnoty uzlu.",
|
||||
"node_status": "Stav uzlu",
|
||||
"refreshing_description": "Aktualizuji informací o uzlu...",
|
||||
"start_refresh_button": "Spustit Obnovení",
|
||||
"step": "Krok",
|
||||
"title": "Aktualizovat informace o uzlu",
|
||||
"wakeup_header": "Pokyny pro probuzení pro",
|
||||
"wakeup_instructions_source": "Pokyny k probuzení pocházejí z komunitní databáze zařízení OpenZWave."
|
||||
},
|
||||
"select_instance": {
|
||||
"header": "Vyberte instanci OpenZWave",
|
||||
"introduction": "Máte více než jednu instanci OpenZWave. Kterou instanci chcete spravovat?"
|
||||
},
|
||||
"services": {
|
||||
"add_node": "Přidat uzel",
|
||||
"remove_node": "Odebrat uzel"
|
||||
}
|
||||
},
|
||||
"person": {
|
||||
@ -1671,7 +1776,7 @@
|
||||
"confirm_delete": "Opravdu chcete odstranit tuto osobu?",
|
||||
"confirm_delete2": "Všechna zařízení patřící této osobě budou nastavena jako nepřiřazena.",
|
||||
"create_person": "Vytvořit osobu",
|
||||
"description": "Spravujte osoby, které Home Assistant sleduje.",
|
||||
"description": "Správa osob, které Home Assistant sleduje",
|
||||
"detail": {
|
||||
"create": "Vytvořit",
|
||||
"delete": "Smazat",
|
||||
@ -1694,7 +1799,7 @@
|
||||
"scene": {
|
||||
"activated": "Aktivovaná scéna {name}.",
|
||||
"caption": "Scény",
|
||||
"description": "Vytváření a úpravy scén",
|
||||
"description": "Správa scén",
|
||||
"editor": {
|
||||
"default_name": "Nová scéna",
|
||||
"devices": {
|
||||
@ -1737,8 +1842,8 @@
|
||||
}
|
||||
},
|
||||
"script": {
|
||||
"caption": "Skript",
|
||||
"description": "Vytvářejte a upravujte skripty",
|
||||
"caption": "Skripty",
|
||||
"description": "Správa skriptů",
|
||||
"editor": {
|
||||
"alias": "Název",
|
||||
"default_name": "Nový skript",
|
||||
@ -1787,20 +1892,34 @@
|
||||
"description": "Restart a zastavení serveru Home Asistent",
|
||||
"section": {
|
||||
"reloading": {
|
||||
"automation": "Znovu načíst automatizace",
|
||||
"core": "Znovu načíst umístění a přizpůsobení",
|
||||
"group": "Znovu načíst skupiny",
|
||||
"automation": "Nově načíst automatizace",
|
||||
"command_line": "Nově načíst entity integrace Command line",
|
||||
"core": "Nově načíst umístění a přizpůsobení",
|
||||
"filesize": "Nově načíst entity integrace File size",
|
||||
"filter": "Nově načíst entity integrace Filter",
|
||||
"generic": "Nově načíst entity integrace Generic IP camera",
|
||||
"generic_thermostat": "Nově načíst entity integrace Generic thermostat",
|
||||
"group": "Nově načíst skupiny",
|
||||
"heading": "Konfigurace se načítá",
|
||||
"input_boolean": "Znovu načíst pomocníky - přepínače",
|
||||
"input_datetime": "Znovu načíst pomocníky - data/časy",
|
||||
"input_number": "Znovu načíst pomocníky - čísla",
|
||||
"input_select": "Znovu načíst pomocníky - výběry",
|
||||
"input_text": "Znovu načíst pomocníky - texty",
|
||||
"introduction": "Některé části Home Assistant lze načíst bez nutnosti restartování. Volba načtení zahodí jejich aktuální konfiguraci a načte novou.",
|
||||
"person": "Znovu načíst osoby",
|
||||
"scene": "Znovu načíst scény",
|
||||
"script": "Znovu načíst skripty",
|
||||
"zone": "Znovu načíst zóny"
|
||||
"history_stats": "Nově načíst entity integrace History stats",
|
||||
"homekit": "Nově načíst entity integrace HomeKit",
|
||||
"input_boolean": "Nově načíst pomocníky - přepínače",
|
||||
"input_datetime": "Nově načíst pomocníky - data/časy",
|
||||
"input_number": "Nově načíst pomocníky - čísla",
|
||||
"input_select": "Nově načíst pomocníky - výběry",
|
||||
"input_text": "Nově načíst pomocníky - texty",
|
||||
"introduction": "Některé části Home Assistant lze nově načíst bez nutnosti restartování. Nové načtení zahodí jejich aktuální konfiguraci a načte novou.",
|
||||
"min_max": "Nově načíst entity integrace Min/Max",
|
||||
"person": "Nově načíst osoby",
|
||||
"ping": "Nově načíst entity integrace Ping",
|
||||
"rest": "Nově načíst entity integrace Rest",
|
||||
"scene": "Nově načíst scény",
|
||||
"script": "Nově načíst skripty",
|
||||
"statistics": "Nově načíst entity integrace Statistics",
|
||||
"template": "Nově načíst entity integrace Template",
|
||||
"trend": "Nově načíst entity integrace Trend",
|
||||
"universal": "Nově načíst entity integrace Universal media player",
|
||||
"zone": "Nově načíst zóny"
|
||||
},
|
||||
"server_management": {
|
||||
"confirm_restart": "Opravdu chcete restartovat Home Assistant?",
|
||||
@ -1841,6 +1960,7 @@
|
||||
"last_scanned": "Naposledy naskenováno",
|
||||
"name": "Název"
|
||||
},
|
||||
"never_scanned": "Nikdy naskenováno",
|
||||
"no_tags": "Žádné značky",
|
||||
"write": "Zapsat"
|
||||
},
|
||||
@ -1850,6 +1970,8 @@
|
||||
"create": "Vytvořit",
|
||||
"name": "Jméno",
|
||||
"password": "Heslo",
|
||||
"password_confirm": "Potvrzení hesla",
|
||||
"password_not_match": "Hesla se neshodují",
|
||||
"username": "Uživatelské jméno"
|
||||
},
|
||||
"caption": "Uživatelé",
|
||||
@ -1866,7 +1988,9 @@
|
||||
"group": "Skupina",
|
||||
"id": "ID",
|
||||
"name": "Jméno",
|
||||
"new_password": "Nové heslo",
|
||||
"owner": "Vlastník",
|
||||
"password_changed": "Heslo změněno!",
|
||||
"system_generated": "Generovaný systémem",
|
||||
"system_generated_users_not_editable": "Nelze aktualizovat uživatele generované systémem.",
|
||||
"system_generated_users_not_removable": "Nelze odebrat uživatele generované systémem.",
|
||||
@ -1950,7 +2074,7 @@
|
||||
"create_group": "Zigbee Home Automation - Vytvoření skupiny",
|
||||
"create_group_details": "Zadejte požadované podrobnosti pro vytvoření nové zigbee skupiny",
|
||||
"creating_group": "Vytváření skupiny",
|
||||
"description": "Vytvoření a úprava Zigbee skupin",
|
||||
"description": "Správa skupin Zigbee",
|
||||
"group_details": "Zde jsou všechny podrobnosti o vybrané skupině Zigbee.",
|
||||
"group_id": "ID skupiny",
|
||||
"group_info": "Informace o skupině",
|
||||
@ -2085,7 +2209,7 @@
|
||||
"heal_network": "Vyléčit síť",
|
||||
"heal_node": "Uzdravit uzel",
|
||||
"node_info": "Informace o uzlu",
|
||||
"print_node": "Otisk nodu",
|
||||
"print_node": "Otisk uzlu",
|
||||
"refresh_entity": "Znovu načíst Entitu",
|
||||
"refresh_node": "Obnovit uzel",
|
||||
"remove_failed_node": "Odebrat selhaný uzel",
|
||||
@ -2165,6 +2289,7 @@
|
||||
"description": "Šablony jsou vykreslovány pomocí Jinja2 šablonového enginu s některými specifickými rozšířeními pro Home Assistant.",
|
||||
"editor": "Editor šablon",
|
||||
"jinja_documentation": "Dokumentace šablony Jinja2",
|
||||
"reset": "Obnovit ukázkovou šablonu",
|
||||
"template_extensions": "Rozšíření šablony Home Assistant",
|
||||
"title": "Šablony",
|
||||
"unknown_error_template": "Šablona vykreslování neznámých chyb"
|
||||
@ -2246,6 +2371,10 @@
|
||||
"description": "Karta Tlačítko umožňuje přidat tlačítka k provádění úkolů.",
|
||||
"name": "Tlačítko"
|
||||
},
|
||||
"calendar": {
|
||||
"description": "Karta Kalendář zobrazuje kalendář včetně zobrazení dnů, týdnů a seznamů",
|
||||
"name": "Kalendář"
|
||||
},
|
||||
"conditional": {
|
||||
"card": "Karta",
|
||||
"change_type": "Změnit typ",
|
||||
@ -2311,6 +2440,7 @@
|
||||
"show_name": "Zobrazit název?",
|
||||
"show_state": "Zobrazit stav?",
|
||||
"state": "Stav",
|
||||
"state_color": "Barevné ikony dle stavu?",
|
||||
"tap_action": "Akce při stisknutí",
|
||||
"theme": "Motiv",
|
||||
"title": "Název",
|
||||
@ -2336,7 +2466,7 @@
|
||||
},
|
||||
"iframe": {
|
||||
"description": "Karta Webová stránka umožňuje vložit oblíbenou webovou stránku přímo do Home Assistanta.",
|
||||
"name": "iFrame"
|
||||
"name": "Webová stránka"
|
||||
},
|
||||
"light": {
|
||||
"description": "Karta Světlo umožňuje změnit jas světla.",
|
||||
@ -2412,13 +2542,13 @@
|
||||
"edit_card": {
|
||||
"add": "Přidat kartu",
|
||||
"confirm_cancel": "Opravdu chcete zahodit změny?",
|
||||
"delete": "Odstranit",
|
||||
"delete": "Smazat kartu",
|
||||
"duplicate": "Duplikovat Kartu",
|
||||
"edit": "Upravit",
|
||||
"header": "Konfigurace karty",
|
||||
"move": "Přesunout",
|
||||
"options": "Více možností",
|
||||
"pick_card": "Vyberte kartu, kterou chcete přidat.",
|
||||
"pick_card": "Kterou kartu chcete přidat?",
|
||||
"pick_card_view_title": "Kterou kartu byste chtěli přidat do svého {name} pohledu?",
|
||||
"show_code_editor": "Zobrazit editor kódu",
|
||||
"show_visual_editor": "Zobrazit vizuální editor",
|
||||
@ -2450,19 +2580,19 @@
|
||||
"header": "Upravit UI",
|
||||
"menu": {
|
||||
"open": "Otevřít Lovelace menu",
|
||||
"raw_editor": "Editor zdrojového kódu"
|
||||
"raw_editor": "Editor kódu konfigurace"
|
||||
},
|
||||
"migrate": {
|
||||
"header": "Konfigurace není kompatibilní",
|
||||
"migrate": "Migrovat konfiguraci",
|
||||
"para_migrate": "Home Assistant může automaticky přidávat ID ke všem kartám a pohledům stisknutím tlačítka \"Migrovat konfiguraci\".",
|
||||
"para_migrate": "Home Assistant může automaticky přidat ID ke všem vašim kartám a pohledům stisknutím tlačítka \"Migrovat konfiguraci\".",
|
||||
"para_no_id": "Tento prvek nemá ID. Přidejte k tomuto prvku ID v 'ui-lovelace.yaml'."
|
||||
},
|
||||
"move_card": {
|
||||
"header": "Vyberte pohled, do kterého chcete kartu přesunout"
|
||||
},
|
||||
"raw_editor": {
|
||||
"confirm_remove_config_text": "Pokud odeberete nastavení Lovelace, automaticky vygenerujeme vaše zobrazení Lovelace s vašimi oblastmi a zařízeními.",
|
||||
"confirm_remove_config_text": "Pokud odeberete konfiguraci uživatelského rozhraní Lovelace, automaticky vygenerujeme pohledy Lovelace s vašimi oblastmi a zařízeními.",
|
||||
"confirm_remove_config_title": "Opravdu chcete odstranit nastavení Lovelace? Automaticky vygenerujeme vaše zobrazení Lovelace s vašimi oblastmi a zařízeními.",
|
||||
"confirm_unsaved_changes": "Máte neuložené změny. Opravdu chcete odejít?",
|
||||
"confirm_unsaved_comments": "Vaše konfigurace obsahuje komentáře, které se neuloží. Chcete pokračovat?",
|
||||
@ -2481,7 +2611,7 @@
|
||||
"close": "Zavřít",
|
||||
"empty_config": "Začít s prázdným dashboardem",
|
||||
"header": "Převzít kontrolu nad vaší Lovelace UI",
|
||||
"para": "Ve výchozím nastavení bude Home Assistant spravovat vaše uživatelské rozhraní – aktualizovat jej při přidání nové entity nebo Lovelace komponenty. Pokud převezmete kontrolu, nebudeme již provádět změny automaticky za vás.",
|
||||
"para": "Tento dashboard momentálně spravuje Home Assistant. Je automaticky aktualizován při přidání nové entity nebo Lovelace komponenty. Pokud převezmete kontrolu, nebudeme již provádět změny automaticky za vás. Vždy si můžete vytvořit nový dashboard na hraní.",
|
||||
"para_sure": "Opravdu chcete převzít kontrolu nad uživalským rozhraním ?",
|
||||
"save": "Převzít kontrolu",
|
||||
"yaml_config": "Abyste mohli snadněji začít, zde aktuální konfigurace tohoto dashboardu:",
|
||||
@ -2507,15 +2637,15 @@
|
||||
},
|
||||
"menu": {
|
||||
"close": "Zavřít",
|
||||
"configure_ui": "Konfigurovat UI",
|
||||
"configure_ui": "Upravit Dashboard",
|
||||
"exit_edit_mode": "Ukončit režim úprav uživatelského rozhraní",
|
||||
"help": "Pomoc",
|
||||
"refresh": "Obnovit",
|
||||
"reload_resources": "Opětovné načtení prostředků"
|
||||
"reload_resources": "Nově načíst zdroje"
|
||||
},
|
||||
"reload_lovelace": "Znovu načíst Lovelace",
|
||||
"reload_lovelace": "Nově načíst Lovelace",
|
||||
"reload_resources": {
|
||||
"refresh_body": "Chcete-li dokončit opětovné načtení, musíte stránku aktualizovat, chcete ji nyní aktualizovat?",
|
||||
"refresh_body": "Chcete-li dokončit nové načtení, musíte stránku aktualizovat. Chcete ji nyní aktualizovat?",
|
||||
"refresh_header": "Chcete aktualizovat stránku?"
|
||||
},
|
||||
"unused_entities": {
|
||||
@ -2616,7 +2746,7 @@
|
||||
"data": {
|
||||
"password": "Heslo API"
|
||||
},
|
||||
"description": "Zadejte API heslo v http config"
|
||||
"description": "Zadejte heslo pro API ve své HTTP konfiguraci"
|
||||
},
|
||||
"mfa": {
|
||||
"data": {
|
||||
@ -2700,6 +2830,7 @@
|
||||
"intro": "Dobrý den, {name} , vítejte v Home Assistant. Jak byste chtěli pojmenovat svůj domov?",
|
||||
"intro_location": "Rádi bychom věděli, kde žijete. Tyto informace pomohou při zobrazování informací a určování přesné polohy slunce. Tato data nejsou nikdy sdílena mimo vaši síť.",
|
||||
"intro_location_detect": "Můžeme vám pomoci vyplnit tyto informace jednorázovým požadavkem na externí službu.",
|
||||
"location_name": "Název vaší instalace Home Assistant",
|
||||
"location_name_default": "Domov"
|
||||
},
|
||||
"integration": {
|
||||
@ -2830,7 +2961,7 @@
|
||||
"shopping-list": {
|
||||
"add_item": "Přidat položku",
|
||||
"clear_completed": "Vymazat nakoupené",
|
||||
"microphone_tip": "Klepněte na mikrofon vpravo nahoře a řekněte \"Add candy to my shopping list\""
|
||||
"microphone_tip": "Klepněte na mikrofon vpravo nahoře a řekněte nebo napište \"Add candy to my shopping list\""
|
||||
}
|
||||
},
|
||||
"sidebar": {
|
||||
|
@ -733,6 +733,9 @@
|
||||
"no_area": "Dim Ardal",
|
||||
"no_device": "Endidau heb ddyfeisiau",
|
||||
"no_devices": "Tydi'r integreiddad yma ddim efo dyfeisiadau.",
|
||||
"reload": "Ail-lwytho",
|
||||
"reload_confirm": "Cafodd yr integreiddiad ei ail-lwytho",
|
||||
"reload_restart_confirm": "Ailgychwyn Home Assistant i ddarfod yr integreiddiadau",
|
||||
"restart_confirm": "Ailgychwyn Home Assistant i ddarfod dileu'r integreiddiad hwn"
|
||||
},
|
||||
"config_flow": {
|
||||
@ -783,6 +786,52 @@
|
||||
"mqtt": {
|
||||
"title": "MQTT"
|
||||
},
|
||||
"ozw": {
|
||||
"button": "Ffurfweddu",
|
||||
"common": {
|
||||
"controller": "Rheolwr",
|
||||
"instance": "Enghraifft",
|
||||
"network": "Rhwydwaith"
|
||||
},
|
||||
"navigation": {
|
||||
"network": "Rhwydwaith",
|
||||
"nodes": "Nodau",
|
||||
"select_instance": "Dewis Enghraifft"
|
||||
},
|
||||
"network_status": {
|
||||
"details": {
|
||||
"driverallnodesqueried": "Holwyd pob nod",
|
||||
"driverallnodesqueriedsomedead": "Holwyd pob nod. Cafwyd hyd i rai nodau yn farw",
|
||||
"driverawakenodesqueries": "Holwyd pob nod effro",
|
||||
"driverfailed": "Wedi methu cysylltu â rheolydd Z-Wave",
|
||||
"driverready": "Cychwyn y rheolydd Z-Wave",
|
||||
"driverremoved": "Mae'r gyrrwr wedi'i diddymu",
|
||||
"driverreset": "Mae'r gyrrwr wedi cael ei ailosod",
|
||||
"offline": "OZWDaemon all-lein",
|
||||
"ready": "Barod i gysylltu",
|
||||
"started": "Cysylltu â MQTT",
|
||||
"starting": "Cysylltu â MQTT",
|
||||
"stopped": "Stopiodd OpenZWave"
|
||||
},
|
||||
"offline": "All-lein",
|
||||
"online": "Ar-lein",
|
||||
"starting": "Cychwyn",
|
||||
"unknown": "Anhysbys"
|
||||
},
|
||||
"network": {
|
||||
"header": "Rheolaeth Rhwydwaith",
|
||||
"introduction": "Rheoli swyddogaethau rhwydwaith-lydan",
|
||||
"node_count": "nodau {count}"
|
||||
},
|
||||
"select_instance": {
|
||||
"header": "Dewiswch Enghraifft OpenZWave",
|
||||
"introduction": "Mae gennych fwy nag un enghraifft OpenZWave yn rhedeg. Pa enghraifft hoffech chi ei rheoli?"
|
||||
},
|
||||
"services": {
|
||||
"add_node": "Ychwanegu Nod",
|
||||
"remove_node": "Tynnu Nod"
|
||||
}
|
||||
},
|
||||
"person": {
|
||||
"caption": "Pobl",
|
||||
"description": "Rheoli'r pobl mae Home Assistant yn tracio.",
|
||||
@ -838,11 +887,16 @@
|
||||
"section": {
|
||||
"reloading": {
|
||||
"automation": "Ail-lwytho awtomeiddiadau",
|
||||
"command_line": "Ail-lwytho endidau llinell orchymyn",
|
||||
"core": "Ail-lwytho craidd",
|
||||
"filter": "Ail-lwytho endidau hidlo",
|
||||
"group": "Ail-lwytho grwpiau",
|
||||
"heading": "Ffurfweddiad yn ail-lwytho",
|
||||
"introduction": "Gall rhai rhannau o Home Assistant ail-lwytho heb orfod ailgychwyn. Bydd taro ail-lwytho yn dadlwytho eu cyfluniad cyfredol a llwytho'r un newydd.",
|
||||
"script": "Ail-lwytho sgriptiau"
|
||||
"rest": "Ail-lwytho endidau gorffwys",
|
||||
"script": "Ail-lwytho sgriptiau",
|
||||
"statistics": "Ail-lwytho endidau ystadegau",
|
||||
"universal": "Ail-lwytho endidau chwaraewyr cyfryngau cyffredinol"
|
||||
},
|
||||
"server_management": {
|
||||
"heading": "Rheoli gweinydd",
|
||||
@ -945,6 +999,7 @@
|
||||
"title": "Cyflerau"
|
||||
},
|
||||
"templates": {
|
||||
"reset": "Ailosod i dempled demo",
|
||||
"title": "Templedi"
|
||||
}
|
||||
}
|
||||
@ -995,6 +1050,10 @@
|
||||
"button": {
|
||||
"description": "Mae'r cerdyn Botwm yn caniatáu ichi ychwanegu botymau i gyflawni tasgau."
|
||||
},
|
||||
"calendar": {
|
||||
"description": "Mae'r cerdyn Calendr yn dangos calendr gan gynnwys gweddlun dydd, wythnos a restr",
|
||||
"name": "Calendr"
|
||||
},
|
||||
"conditional": {
|
||||
"card": "Cerdyn",
|
||||
"change_type": "Newid math",
|
||||
@ -1281,6 +1340,7 @@
|
||||
"intro": "Helo {name}, croeso i Home Assistant. Sut hoffech enwi eich tŷ?",
|
||||
"intro_location": "Hoffem wybod ble rydych chi'n byw. Bydd y wybodaeth yn helpu i arddangos gwybodaeth a sefydlu awtomeiddiadau haul. Tydi'r data byth yn cael ei rannu thu allan i'ch rhwydwaith.",
|
||||
"intro_location_detect": "Gallwn eich helpu i lenwi'r wybodaeth hon drwy wneud cais un-tro i wasanaeth allanol.",
|
||||
"location_name": "Enw eich gosodiad Home Assistant",
|
||||
"location_name_default": "Hafan"
|
||||
},
|
||||
"integration": {
|
||||
|
@ -352,7 +352,7 @@
|
||||
"arm_away": "Aktivieren - Unterwegs",
|
||||
"arm_custom_bypass": "Benutzerdefinierter Bypass",
|
||||
"arm_home": "Aktivieren - Zuhause",
|
||||
"arm_night": "Nacht aktiviert",
|
||||
"arm_night": "Aktivieren - Nacht",
|
||||
"clear_code": "Löschen",
|
||||
"code": "Code",
|
||||
"disarm": "Deaktivieren"
|
||||
@ -419,9 +419,12 @@
|
||||
"unlock": "Entriegeln"
|
||||
},
|
||||
"media_player": {
|
||||
"media_play": "Abspielen",
|
||||
"sound_mode": "Sound-Modus",
|
||||
"source": "Quelle",
|
||||
"text_to_speak": "Text zum Sprechen"
|
||||
"text_to_speak": "Text zum Sprechen",
|
||||
"turn_off": "Ausschalten",
|
||||
"turn_on": "Einschalten"
|
||||
},
|
||||
"persistent_notification": {
|
||||
"dismiss": "Ausblenden"
|
||||
@ -554,6 +557,17 @@
|
||||
"loading_history": "Lade Zustandsverlauf...",
|
||||
"no_history_found": "Kein Zustandsverlauf gefunden."
|
||||
},
|
||||
"media-browser": {
|
||||
"choose-source": "Quelle wählen",
|
||||
"content-type": {
|
||||
"album": "Album",
|
||||
"artist": "Künstler",
|
||||
"library": "Bibliothek",
|
||||
"playlist": "Playlist",
|
||||
"server": "Server"
|
||||
},
|
||||
"play": "Abspielen"
|
||||
},
|
||||
"related-items": {
|
||||
"area": "Bereich",
|
||||
"automation": "Teil der folgenden Automatisierungen",
|
||||
@ -574,6 +588,7 @@
|
||||
"week": "{count} {count, plural,\none {Woche}\nother {Wochen}\n}"
|
||||
},
|
||||
"future": "In {time}",
|
||||
"just_now": "Gerade jetzt",
|
||||
"never": "Noch nie",
|
||||
"past": "Vor {time}"
|
||||
},
|
||||
@ -833,6 +848,9 @@
|
||||
"name": "Aktion",
|
||||
"type_select": "Aktionstyp",
|
||||
"type": {
|
||||
"choose": {
|
||||
"label": "Auswählen"
|
||||
},
|
||||
"condition": {
|
||||
"label": "Bedingung"
|
||||
},
|
||||
@ -852,6 +870,21 @@
|
||||
"label": "Ereignis auslösen",
|
||||
"service_data": "Dienstdaten"
|
||||
},
|
||||
"repeat": {
|
||||
"label": "Wiederholen",
|
||||
"type_select": "Wiederholungstyp",
|
||||
"type": {
|
||||
"count": {
|
||||
"label": "Anzahl"
|
||||
},
|
||||
"until": {
|
||||
"label": "Bis"
|
||||
},
|
||||
"while": {
|
||||
"label": "Während"
|
||||
}
|
||||
}
|
||||
},
|
||||
"scene": {
|
||||
"label": "Szene aktivieren"
|
||||
},
|
||||
@ -1298,7 +1331,7 @@
|
||||
},
|
||||
"cant_edit": "Du kannst nur Elemente bearbeiten, die in der Benutzeroberfläche erstellt wurden.",
|
||||
"caption": "Geräte",
|
||||
"confirm_delete": "Möchtest Du dieses Gerät wirklich löschen?",
|
||||
"confirm_delete": "Möchtest du dieses Gerät wirklich löschen?",
|
||||
"confirm_rename_entity_ids": "Möchten Sie auch die Entitäts-IDs Ihrer Entitäten umbenennen?",
|
||||
"data_table": {
|
||||
"area": "Bereich",
|
||||
@ -1370,7 +1403,7 @@
|
||||
"introduction2": "Verwenden Sie die Entitätsregistrierung, um den Namen zu überschreiben, die Entität-ID zu ändern oder den Eintrag aus Home Assistant zu entfernen. Beachten Sie, dass das Entfernen des Entitätsregistrierungs-Eintrags die Entität nicht löscht. Folgen Sie dazu dem Link unten und entfernen Sie ihn in der Integrationsseite.",
|
||||
"remove_selected": {
|
||||
"button": "Ausgewählte entfernen",
|
||||
"confirm_partly_text": "Du kannst nur {removable} der ausgewählten {selected} Entitäten entfernen. Entitäten können nur entfernt werden, wenn die Integration die Entitäten nicht mehr bereitstellt. Manchmal musst du Home Assistant neu starten, bevor du die Entitäten einer entfernten Integration entfernen kannst. Möchtest Du die entfernbaren Entitäten wirklich entfernen?",
|
||||
"confirm_partly_text": "Du kannst nur {removable} der ausgewählten {selected} Entitäten entfernen. Entitäten können nur entfernt werden, wenn die Integration die Entitäten nicht mehr bereitstellt. Manchmal musst du Home Assistant neu starten, bevor du die Entitäten einer entfernten Integration entfernen kannst. Möchtest du die entfernbaren Entitäten wirklich entfernen?",
|
||||
"confirm_partly_title": "Es können nur {number} ausgewählte Objekte entfernt werden.",
|
||||
"confirm_text": "Du solltest sie aus deiner Lovelace-Konfiguration und deinen Automatisierungen entfernen, wenn sie diese Entitäten enthalten.",
|
||||
"confirm_title": "Möchtest du {number} Entitäten entfernen?"
|
||||
@ -1507,9 +1540,9 @@
|
||||
"no_integrations": "Du hast anscheinend noch keine Integrationen konfiguriert. Klicke auf die Schaltfläche unten, um Deine erste Integration hinzuzufügen!",
|
||||
"none": "Noch nichts konfiguriert",
|
||||
"none_found": "Keine Integrationen gefunden",
|
||||
"none_found_detail": "Passe Deine Suchkriterien an.",
|
||||
"none_found_detail": "Passe deine Suchkriterien an.",
|
||||
"note_about_integrations": "Nicht alle Integrationen können über die Benutzeroberfläche konfiguriert werden.",
|
||||
"note_about_website_reference": "Weitere Informationen findest Du auf der ",
|
||||
"note_about_website_reference": "Weitere Informationen findest du auf der ",
|
||||
"rename_dialog": "Bearbeite den Namen dieses Konfigurationseintrags",
|
||||
"rename_input_label": "Eintragsname",
|
||||
"search": "Such-Integrationen"
|
||||
@ -1595,7 +1628,7 @@
|
||||
},
|
||||
"no_resources": "keine Ressourcen"
|
||||
},
|
||||
"refresh_body": "Die Seite muss aktualisiert werden, um das Entfernen abzuschließen. Möchtest Du sie jetzt aktualisieren?",
|
||||
"refresh_body": "Die Seite muss aktualisiert werden, um das Entfernen abzuschließen. Möchtest du sie jetzt aktualisieren?",
|
||||
"refresh_header": "Möchtest du aktualisieren?",
|
||||
"types": {
|
||||
"css": "Stylesheet",
|
||||
@ -1620,8 +1653,23 @@
|
||||
"topic": "Topic"
|
||||
},
|
||||
"ozw": {
|
||||
"common": {
|
||||
"instance": "Instanz"
|
||||
},
|
||||
"device_info": {
|
||||
"zwave_info": "Z-Wave Infos"
|
||||
},
|
||||
"navigation": {
|
||||
"network": "Netzwerk",
|
||||
"select_instance": "Instanz auswählen"
|
||||
},
|
||||
"network_status": {
|
||||
"offline": "Offline",
|
||||
"online": "Online",
|
||||
"unknown": "Unbekannt"
|
||||
},
|
||||
"network": {
|
||||
"header": "Netzwerkverwaltung"
|
||||
}
|
||||
},
|
||||
"person": {
|
||||
@ -1778,6 +1826,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": {
|
||||
"detail": {
|
||||
"name": "Name"
|
||||
},
|
||||
"headers": {
|
||||
"name": "Name"
|
||||
},
|
||||
"never_scanned": "Nie gescannt"
|
||||
},
|
||||
"users": {
|
||||
"add_user": {
|
||||
"caption": "Benutzer hinzufügen",
|
||||
@ -1938,7 +1995,7 @@
|
||||
"name": "Name",
|
||||
"new_zone": "Neue Zone",
|
||||
"passive": "Passiv",
|
||||
"passive_note": "Passive Zonen sind im Frontend versteckt und werden nicht als Ort für Device Tracker verwendet. Dies ist nützlich, wenn Du sie nur für Automatisierungen verwenden möchtest.",
|
||||
"passive_note": "Passive Zonen sind im Frontend versteckt und werden nicht als Ort für Device Tracker verwendet. Dies ist nützlich, wenn du sie nur für Automatisierungen verwenden möchtest.",
|
||||
"radius": "Radius",
|
||||
"required_error_msg": "Dieses Feld ist erforderlich",
|
||||
"update": "Aktualisieren"
|
||||
@ -1948,7 +2005,7 @@
|
||||
"go_to_core_config": "Zur allgemeinen Konfiguration gehen?",
|
||||
"home_zone_core_config": "Der Standort deiner Homezone kann auf der allgemeinen Konfigurationsseite bearbeitet werden. Der Radius der Homezone kann vom Frontend aus noch nicht bearbeitet werden. Möchtest du zur allgemeinen Konfiguration gehen?",
|
||||
"introduction": "Mit Zonen kannst du bestimmte Regionen auf der Erde angeben. Befindet sich eine Person in einer Zone, übernimmt der Zustand den Namen aus der Zone. Zonen können auch als Auslöser oder Bedingung in Automatisierungs-Setups verwendet werden.",
|
||||
"no_zones_created_yet": "Es sieht so aus, als hättest Du noch keine Zonen erstellt."
|
||||
"no_zones_created_yet": "Es sieht so aus, als hättest du noch keine Zonen erstellt."
|
||||
},
|
||||
"zwave": {
|
||||
"button": "Konfigurieren",
|
||||
@ -2099,6 +2156,7 @@
|
||||
"description": "Vorlagen werden durch die Jinja2-Template-Engine mit einigen für Home Assistant spezifischen Erweiterungen gerendert.",
|
||||
"editor": "Vorlageneditor",
|
||||
"jinja_documentation": "Jinja2 Template Dokumentation",
|
||||
"reset": "Zurücksetzen auf Demo-Vorlage",
|
||||
"template_extensions": "Home Assistant-Vorlagenerweiterungen",
|
||||
"title": "Vorlage",
|
||||
"unknown_error_template": "Unbekannter Fehler beim Rendern der Vorlage"
|
||||
@ -2180,6 +2238,9 @@
|
||||
"description": "Mit der Schaltflächen-Karte kannst du Schaltflächen hinzufügen, um Aufgaben auszuführen.",
|
||||
"name": "Schaltfläche"
|
||||
},
|
||||
"calendar": {
|
||||
"name": "Kalender"
|
||||
},
|
||||
"conditional": {
|
||||
"card": "Karte",
|
||||
"change_type": "Typ ändern",
|
||||
@ -2206,7 +2267,7 @@
|
||||
"name": "Entität Filter"
|
||||
},
|
||||
"entity": {
|
||||
"description": "Mit der Entitätskarte erhältst Du einen schnellen Überblick über den Status Ihrer Entität.",
|
||||
"description": "Mit der Entitätskarte erhältst du einen schnellen Überblick über den Status Ihrer Entität.",
|
||||
"name": "Entität"
|
||||
},
|
||||
"gauge": {
|
||||
@ -2233,7 +2294,7 @@
|
||||
"icon_height": "Symbol Höhe",
|
||||
"image": "Bildpfad",
|
||||
"manual": "Manuell",
|
||||
"manual_description": "Möchtest du eine benutzerdefinierte Karte hinzufügen, oder den Yaml-Code von Hand bearbeiten?",
|
||||
"manual_description": "Möchtest du eine benutzerdefinierte Karte hinzufügen oder den YAML-Code von Hand bearbeiten?",
|
||||
"maximum": "Maximum",
|
||||
"minimum": "Minimum",
|
||||
"name": "Name",
|
||||
@ -2416,8 +2477,8 @@
|
||||
"para_sure": "Bist du dir sicher, dass du die Benutzeroberfläche selbst verwalten möchtest?",
|
||||
"save": "Kontrolle übernehmen",
|
||||
"yaml_config": "Um dir den Einstieg zu erleichtern, findest du hier die aktuelle Konfiguration dieses Dashboards:",
|
||||
"yaml_control": "Um die Kontrolle im YAML-Modus zu übernehmen, erstelle eine YAML-Datei mit dem Namen, den Du in Deiner Konfiguration für dieses Dashboard angegeben hast, oder mit der Standardeinstellung 'ui-lovelace.yaml'.",
|
||||
"yaml_mode": "Du verwendest den YAML-Modus für dieses Dashboard. Dies bedeutet, dass Du Deine Lovelace-Konfiguration nicht über die Benutzeroberfläche ändern kannst. Wenn Du dieses Dashboard über die Benutzeroberfläche verwalten möchtest, entferne \"mode: yaml\" aus Deiner Lovelace-Konfiguration in \"configuration.yaml\"."
|
||||
"yaml_control": "Um die Kontrolle im YAML-Modus zu übernehmen, erstelle eine YAML-Datei mit dem Namen, den du in Deiner Konfiguration für dieses Dashboard angegeben hast, oder mit der Standardeinstellung 'ui-lovelace.yaml'.",
|
||||
"yaml_mode": "Du verwendest den YAML-Modus für dieses Dashboard. Dies bedeutet, dass Du Deine Lovelace-Konfiguration nicht über die Benutzeroberfläche ändern kannst. Wenn du dieses Dashboard über die Benutzeroberfläche verwalten möchtest, entferne \"mode: yaml\" aus Deiner Lovelace-Konfiguration in \"configuration.yaml\"."
|
||||
},
|
||||
"suggest_card": {
|
||||
"add": "Zu Lovelace hinzufügen",
|
||||
@ -2442,8 +2503,8 @@
|
||||
},
|
||||
"reload_lovelace": "Benutzeroberfläche neu laden",
|
||||
"reload_resources": {
|
||||
"refresh_body": "Du musst die Seite aktualisieren, um das Neuladen abzuschließen. Möchtest Du sie jetzt aktualisieren?",
|
||||
"refresh_header": "Möchtest Du aktualisieren?"
|
||||
"refresh_body": "Du musst die Seite aktualisieren, um das Neuladen abzuschließen. Möchtest du sie jetzt aktualisieren?",
|
||||
"refresh_header": "Möchtest du aktualisieren?"
|
||||
},
|
||||
"unused_entities": {
|
||||
"available_entities": "Dies sind die Entitäten, die du zur Verfügung hast, die aber noch nicht in deiner Lovelace-Benutzeroberfläche enthalten sind.",
|
||||
@ -2627,6 +2688,7 @@
|
||||
"intro": "Hallo {name}, willkommen bei Home Assistant. Wie möchten Sie Ihre Haus benennen?",
|
||||
"intro_location": "Wir würden gerne wissen, wo Sie wohnen. Diese Daten helfen bei der Anzeige von Informationen und der Einrichtung von Sonnenstands-basierten Automatisierungen. Diese Daten werden niemals außerhalb Ihres Netzwerks weitergegeben.",
|
||||
"intro_location_detect": "Wir können helfen, diese Informationen auszufüllen, indem wir eine einmalige Anfrage an einen externen Dienstleister richten.",
|
||||
"location_name": "Name deiner Home Assistant Installation",
|
||||
"location_name_default": "Home"
|
||||
},
|
||||
"integration": {
|
||||
@ -2738,6 +2800,7 @@
|
||||
"themes": {
|
||||
"accent_color": "Akzentfarbe",
|
||||
"dark_mode": {
|
||||
"auto": "Automatisch",
|
||||
"dark": "Dunkel",
|
||||
"light": "Hell"
|
||||
},
|
||||
|
@ -419,9 +419,16 @@
|
||||
"unlock": "Ξεκλείδωμα"
|
||||
},
|
||||
"media_player": {
|
||||
"browse_media": "Αναζήτηση πολυμέσων",
|
||||
"media_next_track": "Επόμενο",
|
||||
"media_play": "Αναπαραγωγή",
|
||||
"media_play_pause": "Αναπαραγωγή/παύση",
|
||||
"media_previous_track": "Προηγούμενο",
|
||||
"sound_mode": "Λειτουργία ήχου",
|
||||
"source": "Πηγή",
|
||||
"text_to_speak": "Κείμενο προς εκφώνηση"
|
||||
"text_to_speak": "Κείμενο προς εκφώνηση",
|
||||
"turn_off": "Απενεργοποίηση",
|
||||
"turn_on": "Ενεργοποίηση"
|
||||
},
|
||||
"persistent_notification": {
|
||||
"dismiss": "Απόρριψη"
|
||||
@ -502,8 +509,11 @@
|
||||
"error_required": "Υποχρεωτικό",
|
||||
"loading": "Φόρτωση",
|
||||
"menu": "Μενού",
|
||||
"next": "Επόμενο",
|
||||
"no": "Όχι",
|
||||
"overflow_menu": "Μενού υπερχείλισης",
|
||||
"previous": "Προηγούμενο",
|
||||
"refresh": "Ανανέωση",
|
||||
"save": "Αποθήκευση",
|
||||
"successfully_deleted": "Η διαγραφή ολοκληρώθηκε με επιτυχία",
|
||||
"successfully_saved": "Αποθηκεύτηκε με επιτυχία",
|
||||
@ -551,6 +561,21 @@
|
||||
"loading_history": "Φόρτωση ιστορικού κατάστασης …",
|
||||
"no_history_found": "Δεν έχει βρεθεί ιστορικό κατάστασης."
|
||||
},
|
||||
"media-browser": {
|
||||
"choose-source": "Επιλέξτε Πηγή",
|
||||
"content-type": {
|
||||
"album": "Άλμπουμ",
|
||||
"artist": "Καλλιτέχνης",
|
||||
"library": "Βιβλιοθήκη",
|
||||
"server": "Διακομιστής"
|
||||
},
|
||||
"media-player-browser": "Πρόγραμμα περιήγησης πολυμέσων",
|
||||
"no_items": "Χωρίς στοιχεία",
|
||||
"pick": "Επιλέξετε",
|
||||
"pick-media": "Επιλογή μέσων",
|
||||
"play": "Αναπαραγωγή",
|
||||
"play-media": "Αναπαραγωγή πολυμέσων"
|
||||
},
|
||||
"relative_time": {
|
||||
"duration": {
|
||||
"day": "{count} {count, plural,\n one {μέρα}\n other {μέρες}\n}",
|
||||
@ -560,6 +585,7 @@
|
||||
"week": "{count} {count, plural,\n one {εβδομάδα}\n other {εβδομάδες}\n}"
|
||||
},
|
||||
"future": "Σε {time}",
|
||||
"just_now": "Μόλις τώρα",
|
||||
"never": "Ποτέ",
|
||||
"past": "{time} πριν"
|
||||
},
|
||||
@ -976,6 +1002,9 @@
|
||||
"sunrise": "Ανατολή ηλίου",
|
||||
"sunset": "Δύση ηλίου"
|
||||
},
|
||||
"tag": {
|
||||
"label": "Ετικέτα"
|
||||
},
|
||||
"template": {
|
||||
"label": "Πρότυπο",
|
||||
"value_template": "Τιμή πρότυπου"
|
||||
@ -1252,6 +1281,7 @@
|
||||
"caption": "Συσκευές",
|
||||
"confirm_delete": "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτή τη συσκευή;",
|
||||
"confirm_rename_entity_ids": "Θέλετε επίσης να μετονομάσετε τα αναγνωριστικά οντότητας των οντοτήτων σας;",
|
||||
"confirm_rename_entity_ids_warning": "Αυτό δεν θα αλλάξει καμία ρύθμιση παραμέτρων (όπως αυτοματισμούς, σενάρια, σκηνές, Lovelace) που χρησιμοποιεί αυτήν τη στιγμή αυτές τις οντότητες, θα πρέπει να τις ενημερώσετε μόνοι σας.",
|
||||
"data_table": {
|
||||
"area": "Περιοχή",
|
||||
"battery": "Μπαταρία",
|
||||
@ -1370,6 +1400,7 @@
|
||||
"caption": "Ενσωματώσεις",
|
||||
"config_entry": {
|
||||
"area": "Στην {area}",
|
||||
"delete": "Διαγραφή",
|
||||
"delete_button": "Διαγραφή {integration}",
|
||||
"delete_confirm": "Είστε σίγουρος ότι θέλετε να διαγραφεί αυτή η ενοποίηση;",
|
||||
"device_unavailable": "συσκευή μη διαθέσιμη",
|
||||
@ -1381,8 +1412,11 @@
|
||||
"no_area": "Καμία περιοχή",
|
||||
"no_device": "Οντότητες χωρίς συσκευές",
|
||||
"no_devices": "Αυτή η ενοποίηση δεν έχει συσκευές.",
|
||||
"options": "Επιλογές",
|
||||
"rename": "Μετονομασία",
|
||||
"restart_confirm": "Επανεκκινήστε το Home Assistant για να ολοκληρώσετε την κατάργηση αυτής της ενοποίησης",
|
||||
"settings_button": "Επεξεργασία ρυθμίσεων για {integration}",
|
||||
"system_options": "Επιλογές συστήματος",
|
||||
"system_options_button": "Επιλογές συστήματος για {integration}",
|
||||
"unnamed_entry": "Ανώνυμη καταχώριση"
|
||||
},
|
||||
@ -1420,8 +1454,11 @@
|
||||
"integration_not_found": "Η ενσωμάτωση δε βρέθηκε.",
|
||||
"new": "Ρυθμίστε νέα ενοποίηση",
|
||||
"none": "Δεν υπάρχει διαμόρφωση ακόμα",
|
||||
"none_found": "Δεν βρέθηκαν ενσωματώσεις",
|
||||
"none_found_detail": "Προσαρμόστε τα κριτήρια αναζήτησης.",
|
||||
"note_about_integrations": "Δεν μπορούν όλες οι ενσωματώσεις να διαμορφωθούν από το UI ακόμη.",
|
||||
"note_about_website_reference": "Περισσότερα είναι διαθέσιμα στο",
|
||||
"rename_input_label": "Όνομα καταχώρησης",
|
||||
"search": "Αναζήτηση ενσωματώσεων"
|
||||
},
|
||||
"introduction": "Εδώ είναι δυνατή η διαμόρφωση του Home Assistant και των εξαρτημάτων. Δεν είναι δυνατή η διαμόρφωση όλων από την διεπαφή χρήστη (UI) αλλά εργαζόμαστε πάνω σε αυτό.",
|
||||
@ -1443,8 +1480,10 @@
|
||||
"cant_edit_default": "Δεν είναι δυνατή η επεξεργασία του τυπικού πίνακα ελέγχου Lovelace από τη διεπαφή χρήστη. Μπορείτε να το αποκρύψετε ορίζοντας έναν άλλο πίνακα ελέγχου ως προεπιλογή.",
|
||||
"cant_edit_yaml": "Δεν είναι δυνατή η επεξεργασία πινάκων εργαλείων που ορίζονται στο YAML από το περιβάλλον εργασίας χρήστη. Αλλάξτε τα στο configuration.yaml.",
|
||||
"caption": "Επισκόπηση",
|
||||
"confirm_delete": "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτόν τον πίνακα ελέγχου;",
|
||||
"default_dashboard": "Αυτή είναι η προεπιλεγμένη επισκόπηση",
|
||||
"detail": {
|
||||
"dismiss": "Κλείστε",
|
||||
"icon": "Εικονίδιο",
|
||||
"title": "Τίτλος",
|
||||
"title_required": "Απαιτείται τίτλος.",
|
||||
@ -1452,7 +1491,8 @@
|
||||
},
|
||||
"picker": {
|
||||
"headers": {
|
||||
"default": "Προεπιλογή"
|
||||
"default": "Προεπιλογή",
|
||||
"filename": "Όνομα_αρχείου"
|
||||
},
|
||||
"open": "Άνοιγμα"
|
||||
}
|
||||
@ -1490,6 +1530,36 @@
|
||||
"stage": "Στάδιο",
|
||||
"zwave_info": "Πληροφορίες Z-Wave"
|
||||
},
|
||||
"navigation": {
|
||||
"network": "Δίκτυο",
|
||||
"nodes": "Κόμβοι",
|
||||
"select_instance": "Επιλέξτε στιγμιότυπο "
|
||||
},
|
||||
"network_status": {
|
||||
"details": {
|
||||
"driverallnodesqueried": "Όλοι οι κόμβοι έχουν ερωτηθεί.",
|
||||
"driverallnodesqueriedsomedead": "Όλοι οι κόμβοι έχουν ερωτηθεί. Μερικοί κόμβοι βρέθηκαν να μην αποκρίνονται.",
|
||||
"driverawakenodesqueries": "Όλοι οι ενεργοποιημένοι κόμβοι έχουν ερωτηθεί. ",
|
||||
"driverfailed": "Αποτυχία σύνδεσης με τον ελεγκτή Z-Wave",
|
||||
"driverready": "Προετοιμασία του ελεγκτή Z-Wave",
|
||||
"driverremoved": "Το πρόγραμμα οδήγησης έχει αφαιρεθεί",
|
||||
"driverreset": "Έγινε επαναφορά του προγράμματος οδήγησης",
|
||||
"offline": "OZWDaemon εκτός σύνδεσης",
|
||||
"ready": "Έτοιμος για σύνδεση",
|
||||
"started": "Συνδεδεμένος με MQTT",
|
||||
"starting": "Σύνδεση με MQTT",
|
||||
"stopped": "Το OpenZWave σταμάτησε"
|
||||
},
|
||||
"offline": "Εκτός σύνδεσης",
|
||||
"online": "Σε σύνδεση",
|
||||
"starting": "Εκκίνηση",
|
||||
"unknown": "Άγνωστο"
|
||||
},
|
||||
"network": {
|
||||
"header": "Διαχείριση δικτύου",
|
||||
"introduction": "Διαχείριση λειτουργιών δικτύου.",
|
||||
"node_count": "{count} κόμβοι"
|
||||
},
|
||||
"node_query_stages": {
|
||||
"complete": "Η διαδικασία της συνέντευξης ολοκληρώθηκε",
|
||||
"configuration": "Λήψη τιμών διαμόρφωσης από τον κόμβο",
|
||||
@ -1511,6 +1581,14 @@
|
||||
"step": "Βήμα",
|
||||
"title": "Ανανέωση πληροφοριών κόμβου",
|
||||
"wakeup_header": "Οδηγίες εκκίνησης απο"
|
||||
},
|
||||
"select_instance": {
|
||||
"header": "Επιλέξτε ένα στιγμιότυπο OpenZWave",
|
||||
"introduction": "Έχετε περισσότερα από ένα στιγμιότυπα OpenZWave σε ενέργεια. Ποιό στιγμιότυπο θέλετε να διαχειριστείτε;"
|
||||
},
|
||||
"services": {
|
||||
"add_node": "Προσθήκη κόμβου",
|
||||
"remove_node": "Κατάργηση κόμβου"
|
||||
}
|
||||
},
|
||||
"person": {
|
||||
@ -1586,7 +1664,7 @@
|
||||
},
|
||||
"script": {
|
||||
"caption": "Δέσμη ενεργειών",
|
||||
"description": "Δημιουργήσετε και να επεξεργαστείτε δέσμες ενεργειών",
|
||||
"description": "Δημιουργήσετε και επεξεργαστείτε δέσμες ενεργειών",
|
||||
"editor": {
|
||||
"alias": "Όνομα",
|
||||
"default_name": "Νέα δέσμη ενεργειών",
|
||||
@ -1665,6 +1743,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": {
|
||||
"detail": {
|
||||
"create": "Δημιουργία",
|
||||
"create_and_write": "Δημιουργία και εγγραφή",
|
||||
"delete": "Διαγραφή",
|
||||
"description": "Περιγραφή",
|
||||
"name": "Όνομα",
|
||||
"new_tag": "Νέα ετικέτα",
|
||||
"update": "Ενημέρωση"
|
||||
}
|
||||
},
|
||||
"users": {
|
||||
"add_user": {
|
||||
"caption": "Προσθήκη χρήστη",
|
||||
@ -1840,6 +1929,7 @@
|
||||
"exclude_entity": "Εξαίρεση αυτής της οντότητας από τον Home Assistant",
|
||||
"group": "Ομάδα",
|
||||
"header": "Διαχείριση κόμβων Z-Wave",
|
||||
"introduction": "Εκτελέστε εντολές Z-Wave που επηρεάζουν έναν μόνο κόμβο. Επιλέξτε έναν κόμβο για να δείτε μια λίστα με τις διαθέσιμες εντολές.",
|
||||
"max_associations": "Μέγιστες ενώσεις:",
|
||||
"node_group_associations": "Κόμβοι συσχετίσεων ομάδων",
|
||||
"node_protection": "Προστασία κόμβου",
|
||||
@ -1944,6 +2034,7 @@
|
||||
"description": "Τα πρότυπα μετατρέπονται χρησιμοποιώντας τη μηχανή πρότυπου Jinja2 με ορισμένες εξειδικευμένες επεκτάσεις του Home Assistant.",
|
||||
"editor": "Πρόγραμμα επεξεργασίας προτύπων",
|
||||
"jinja_documentation": "Έγγραφα πρότυπου Jinja2",
|
||||
"reset": "Επαναφορά στο πρότυπο επίδειξης",
|
||||
"template_extensions": "Επεκτάσεις προτύπου Home Assistant",
|
||||
"title": "Πρότυπα",
|
||||
"unknown_error_template": "Άγνωστο σφάλμα ερμηνείας προτύπου"
|
||||
@ -2013,6 +2104,10 @@
|
||||
"available_states": "Διαθέσιμες λειτουργίες",
|
||||
"name": "Πίνακας συναγερμών"
|
||||
},
|
||||
"calendar": {
|
||||
"description": "Η κάρτα \"Ημερολόγιο\" εμφανίζει ένα ημερολόγιο που περιλαμβάνει προβολές ημέρας, εβδομάδας και λίστας",
|
||||
"name": "Ημερολόγιο"
|
||||
},
|
||||
"conditional": {
|
||||
"card": "Κάρτα",
|
||||
"conditions": "Συνθήκες",
|
||||
@ -2264,6 +2359,7 @@
|
||||
"warning": {
|
||||
"entity_non_numeric": "Η οντότητα δεν είναι αριθμητική: {entity}",
|
||||
"entity_not_found": "Η οντότητα δεν είναι διαθέσιμη: {entity}",
|
||||
"entity_unavailable": "{entity} δεν είναι διαθέσιμο προς το παρόν",
|
||||
"starting": "Το Home Assistant ξεκινά, ενδέχεται να μην είναι ακόμη διαθέσιμα όλα"
|
||||
}
|
||||
},
|
||||
@ -2424,6 +2520,7 @@
|
||||
"intro": "Γεια σου {name}, καλώς ήρθες στο Home Assistant. Πώς θα ήθελες να αναφέρεσαι στο σπίτι σου;",
|
||||
"intro_location": "Θα θέλαμε να μάθουμε πού ζεις. Αυτές οι πληροφορίες θα βοηθήσουν στην προβολή πληροφοριών και στη ρύθμιση αυτοματισμών που βασίζονται στη θέση ηλίου. Αυτά τα δεδομένα δεν μοιράζονται ποτέ εκτός του δικτύου σας.",
|
||||
"intro_location_detect": "Μπορούμε να σε βοηθήσουμε να συμπληρώσεις αυτές τις πληροφορίες, κάνοντας μια εφάπαξ αίτηση σε μια εξωτερική υπηρεσία.",
|
||||
"location_name": "Όνομα της εγκατάστασης σας του Home Assistant",
|
||||
"location_name_default": "Σπίτι"
|
||||
},
|
||||
"integration": {
|
||||
|
@ -419,9 +419,16 @@
|
||||
"unlock": "Unlock"
|
||||
},
|
||||
"media_player": {
|
||||
"browse_media": "Browse media",
|
||||
"media_next_track": "Next",
|
||||
"media_play": "Play",
|
||||
"media_play_pause": "Play/pause",
|
||||
"media_previous_track": "Previous",
|
||||
"sound_mode": "Sound mode",
|
||||
"source": "Source",
|
||||
"text_to_speak": "Text to speak"
|
||||
"text_to_speak": "Text to speak",
|
||||
"turn_off": "Turn off",
|
||||
"turn_on": "Turn on"
|
||||
},
|
||||
"persistent_notification": {
|
||||
"dismiss": "Dismiss"
|
||||
@ -554,6 +561,22 @@
|
||||
"loading_history": "Loading state history...",
|
||||
"no_history_found": "No state history found."
|
||||
},
|
||||
"media-browser": {
|
||||
"choose-source": "Choose Source",
|
||||
"content-type": {
|
||||
"album": "Album",
|
||||
"artist": "Artist",
|
||||
"library": "Library",
|
||||
"playlist": "Playlist",
|
||||
"server": "Server"
|
||||
},
|
||||
"media-player-browser": "Media Player Browser",
|
||||
"no_items": "No items",
|
||||
"pick": "Pick",
|
||||
"pick-media": "Pick Media",
|
||||
"play": "Play",
|
||||
"play-media": "Play Media"
|
||||
},
|
||||
"picture-upload": {
|
||||
"label": "Picture",
|
||||
"unsupported_format": "Unsupported format, please choose a JPEG, PNG or GIF image."
|
||||
@ -578,6 +601,7 @@
|
||||
"week": "{count} {count, plural,\n one {week}\n other {weeks}\n}"
|
||||
},
|
||||
"future": "In {time}",
|
||||
"just_now": "Just now",
|
||||
"never": "Never",
|
||||
"past": "{time} ago"
|
||||
},
|
||||
@ -1318,6 +1342,7 @@
|
||||
}
|
||||
},
|
||||
"devices": {
|
||||
"add_prompt": "No {name} have been added using this device yet. You can add one by clicking the + button above.",
|
||||
"automation": {
|
||||
"actions": {
|
||||
"caption": "When something is triggered..."
|
||||
@ -1500,6 +1525,9 @@
|
||||
"no_device": "Entities without devices",
|
||||
"no_devices": "This integration has no devices.",
|
||||
"options": "Options",
|
||||
"reload": "Reload",
|
||||
"reload_confirm": "The integration was reloaded",
|
||||
"reload_restart_confirm": "Restart Home Assistant to finish reloading this integration",
|
||||
"rename": "Rename",
|
||||
"restart_confirm": "Restart Home Assistant to finish removing this integration",
|
||||
"settings_button": "Edit settings for {integration}",
|
||||
@ -1658,7 +1686,11 @@
|
||||
"topic": "topic"
|
||||
},
|
||||
"ozw": {
|
||||
"button": "Configure",
|
||||
"common": {
|
||||
"controller": "Controller",
|
||||
"instance": "Instance",
|
||||
"network": "Network",
|
||||
"node_id": "Node ID",
|
||||
"ozw_instance": "OpenZWave Instance",
|
||||
"zwave": "Z-Wave"
|
||||
@ -1668,6 +1700,36 @@
|
||||
"stage": "Stage",
|
||||
"zwave_info": "Z-Wave Info"
|
||||
},
|
||||
"navigation": {
|
||||
"network": "Network",
|
||||
"nodes": "Nodes",
|
||||
"select_instance": "Select Instance"
|
||||
},
|
||||
"network_status": {
|
||||
"details": {
|
||||
"driverallnodesqueried": "All nodes have been queried",
|
||||
"driverallnodesqueriedsomedead": "All nodes have been queried. Some nodes were found dead",
|
||||
"driverawakenodesqueries": "All awake nodes have been queried",
|
||||
"driverfailed": "Failed to connect to Z-Wave controller",
|
||||
"driverready": "Initializing the Z-Wave controller",
|
||||
"driverremoved": "The driver has been removed",
|
||||
"driverreset": "The driver has been reset",
|
||||
"offline": "OZWDaemon offline",
|
||||
"ready": "Ready to connect",
|
||||
"started": "Connected to MQTT",
|
||||
"starting": "Connecting to MQTT",
|
||||
"stopped": "OpenZWave stopped"
|
||||
},
|
||||
"offline": "Offline",
|
||||
"online": "Online",
|
||||
"starting": "Starting",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"network": {
|
||||
"header": "Network Management",
|
||||
"introduction": "Manage network-wide functions.",
|
||||
"node_count": "{count} nodes"
|
||||
},
|
||||
"node_query_stages": {
|
||||
"associations": "Refreshing association groups and memberships",
|
||||
"cacheload": "Loading information from the OpenZWave cache file. Battery nodes will stay at this stage until the node wakes up.",
|
||||
@ -1698,6 +1760,14 @@
|
||||
"title": "Refresh Node Information",
|
||||
"wakeup_header": "Wakeup Instructions for",
|
||||
"wakeup_instructions_source": "Wakeup instructions are sourced from the OpenZWave community device database."
|
||||
},
|
||||
"select_instance": {
|
||||
"header": "Select an OpenZWave Instance",
|
||||
"introduction": "You have more than one OpenZWave instance running. Which instance would you like to manage?"
|
||||
},
|
||||
"services": {
|
||||
"add_node": "Add Node",
|
||||
"remove_node": "Remove Node"
|
||||
}
|
||||
},
|
||||
"person": {
|
||||
@ -1823,18 +1893,32 @@
|
||||
"section": {
|
||||
"reloading": {
|
||||
"automation": "Reload automations",
|
||||
"command_line": "Reload command line entities",
|
||||
"core": "Reload location & customizations",
|
||||
"filesize": "Reload file size entities",
|
||||
"filter": "Reload filter entities",
|
||||
"generic": "Reload generic IP camera entities",
|
||||
"generic_thermostat": "Reload generic thermostat entities",
|
||||
"group": "Reload groups",
|
||||
"heading": "YAML configuration reloading",
|
||||
"history_stats": "Reload history stats entities",
|
||||
"homekit": "Reload HomeKit",
|
||||
"input_boolean": "Reload input booleans",
|
||||
"input_datetime": "Reload input date times",
|
||||
"input_number": "Reload input numbers",
|
||||
"input_select": "Reload input selects",
|
||||
"input_text": "Reload input texts",
|
||||
"introduction": "Some parts of Home Assistant can reload without requiring a restart. Hitting reload will unload their current YAML configuration and load the new one.",
|
||||
"min_max": "Reload min/max entities",
|
||||
"person": "Reload persons",
|
||||
"ping": "Reload ping binary sensor entities",
|
||||
"rest": "Reload rest entities",
|
||||
"scene": "Reload scenes",
|
||||
"script": "Reload scripts",
|
||||
"statistics": "Reload statistics entities",
|
||||
"template": "Reload template entities",
|
||||
"trend": "Reload trend entities",
|
||||
"universal": "Reload universal media player entities",
|
||||
"zone": "Reload zones"
|
||||
},
|
||||
"server_management": {
|
||||
@ -1876,6 +1960,7 @@
|
||||
"last_scanned": "Last scanned",
|
||||
"name": "Name"
|
||||
},
|
||||
"never_scanned": "Never scanned",
|
||||
"no_tags": "No tags",
|
||||
"write": "Write"
|
||||
},
|
||||
@ -1885,6 +1970,8 @@
|
||||
"create": "Create",
|
||||
"name": "Name",
|
||||
"password": "Password",
|
||||
"password_confirm": "Confirm Password",
|
||||
"password_not_match": "Passwords don't match",
|
||||
"username": "Username"
|
||||
},
|
||||
"caption": "Users",
|
||||
@ -1901,7 +1988,9 @@
|
||||
"group": "Group",
|
||||
"id": "ID",
|
||||
"name": "Name",
|
||||
"new_password": "New Password",
|
||||
"owner": "Owner",
|
||||
"password_changed": "The password is changed!",
|
||||
"system_generated": "System generated",
|
||||
"system_generated_users_not_editable": "Unable to update system generated users.",
|
||||
"system_generated_users_not_removable": "Unable to remove system generated users.",
|
||||
@ -2200,6 +2289,7 @@
|
||||
"description": "Templates are rendered using the Jinja2 template engine with some Home Assistant specific extensions.",
|
||||
"editor": "Template editor",
|
||||
"jinja_documentation": "Jinja2 template documentation",
|
||||
"reset": "Reset to demo template",
|
||||
"template_extensions": "Home Assistant template extensions",
|
||||
"title": "Template",
|
||||
"unknown_error_template": "Unknown error rendering template"
|
||||
@ -2281,6 +2371,10 @@
|
||||
"description": "The Button card allows you to add buttons to perform tasks.",
|
||||
"name": "Button"
|
||||
},
|
||||
"calendar": {
|
||||
"description": "The Calendar card displays a calendar including day, week and list views",
|
||||
"name": "Calendar"
|
||||
},
|
||||
"conditional": {
|
||||
"card": "Card",
|
||||
"change_type": "Change type",
|
||||
@ -2346,6 +2440,7 @@
|
||||
"show_name": "Show Name?",
|
||||
"show_state": "Show State?",
|
||||
"state": "State",
|
||||
"state_color": "Color icons based on state?",
|
||||
"tap_action": "Tap Action",
|
||||
"theme": "Theme",
|
||||
"title": "Title",
|
||||
@ -2735,6 +2830,7 @@
|
||||
"intro": "Hello {name}, welcome to Home Assistant. How would you like to name your home?",
|
||||
"intro_location": "We would like to know where you live. This information will help with displaying information and setting up sun-based automations. This data is never shared outside of your network.",
|
||||
"intro_location_detect": "We can help you fill in this information by making a one-time request to an external service.",
|
||||
"location_name": "Name of your Home Assistant installation",
|
||||
"location_name_default": "Home"
|
||||
},
|
||||
"integration": {
|
||||
|
@ -13,15 +13,9 @@
|
||||
},
|
||||
"panel": {
|
||||
"calendar": "Calendario",
|
||||
"config": "",
|
||||
"developer_tools": "Herramientas para desarrolladores",
|
||||
"history": "",
|
||||
"logbook": "",
|
||||
"mailbox": "",
|
||||
"map": "",
|
||||
"profile": "Perfil",
|
||||
"shopping_list": "Lista de compras",
|
||||
"states": ""
|
||||
"shopping_list": "Lista de compras"
|
||||
},
|
||||
"state_attributes": {
|
||||
"climate": {
|
||||
@ -1000,7 +994,6 @@
|
||||
"start": "Inicio"
|
||||
},
|
||||
"mqtt": {
|
||||
"label": "",
|
||||
"payload": "Payload (opcional)",
|
||||
"topic": "Topic"
|
||||
},
|
||||
@ -1616,7 +1609,6 @@
|
||||
"start_listening": "Comenzar a escuchar",
|
||||
"stop_listening": "Deja de escuchar",
|
||||
"subscribe_to": "Tema para suscribirse",
|
||||
"title": "",
|
||||
"topic": "tema"
|
||||
},
|
||||
"person": {
|
||||
@ -1947,7 +1939,6 @@
|
||||
},
|
||||
"zwave": {
|
||||
"button": "Configurar",
|
||||
"caption": "",
|
||||
"common": {
|
||||
"index": "Índice",
|
||||
"instance": "Instancia",
|
||||
|
@ -419,9 +419,16 @@
|
||||
"unlock": "Desbloquear"
|
||||
},
|
||||
"media_player": {
|
||||
"browse_media": "Explorar medios",
|
||||
"media_next_track": "Siguiente",
|
||||
"media_play": "Reproducir",
|
||||
"media_play_pause": "Reproducir/pausa",
|
||||
"media_previous_track": "Anterior",
|
||||
"sound_mode": "Modo de sonido",
|
||||
"source": "Fuente",
|
||||
"text_to_speak": "Texto para hablar"
|
||||
"text_to_speak": "Texto para hablar",
|
||||
"turn_off": "Apagar",
|
||||
"turn_on": "Encender"
|
||||
},
|
||||
"persistent_notification": {
|
||||
"dismiss": "Descartar"
|
||||
@ -554,6 +561,22 @@
|
||||
"loading_history": "Cargando historial de estado...",
|
||||
"no_history_found": "No se encontró historial de estado."
|
||||
},
|
||||
"media-browser": {
|
||||
"choose-source": "Elige la fuente",
|
||||
"content-type": {
|
||||
"album": "Álbum",
|
||||
"artist": "Artista",
|
||||
"library": "Biblioteca",
|
||||
"playlist": "Lista de reproducción",
|
||||
"server": "Servidor"
|
||||
},
|
||||
"media-player-browser": "Navegador del Reproductor Multimedia",
|
||||
"no_items": "No hay elementos",
|
||||
"pick": "Elegir",
|
||||
"pick-media": "Elegir medio",
|
||||
"play": "Reproducir",
|
||||
"play-media": "Reproducir medio"
|
||||
},
|
||||
"picture-upload": {
|
||||
"label": "Imagen",
|
||||
"unsupported_format": "Formato no soportado, por favor, selecciona una imagen JPEG, PNG o GIF."
|
||||
@ -578,6 +601,7 @@
|
||||
"week": "{count} {count, plural,\none {semana}\nother {semanas}\n}"
|
||||
},
|
||||
"future": "En {time}",
|
||||
"just_now": "Ahora mismo",
|
||||
"never": "Nunca",
|
||||
"past": "Hace {time}"
|
||||
},
|
||||
@ -1318,6 +1342,7 @@
|
||||
}
|
||||
},
|
||||
"devices": {
|
||||
"add_prompt": "Todavía no se ha añadido ningún {name} usando este dispositivo. Puedes añadir uno pulsando en el botón + de arriba.",
|
||||
"automation": {
|
||||
"actions": {
|
||||
"caption": "Cuando algo se activa...."
|
||||
@ -1337,7 +1362,7 @@
|
||||
"caption": "Dispositivos",
|
||||
"confirm_delete": "¿Estás seguro de que quieres eliminar este dispositivo?",
|
||||
"confirm_rename_entity_ids": "¿También quieres cambiar el nombre de los identificadores de entidad de tus entidades?",
|
||||
"confirm_rename_entity_ids_warning": "Esto no cambiará ninguna configuración (como automatizaciones, scripts, escenas, Lovelace) que estén utilizando actualmente estas entidades, tendrá que actualizarlas usted mismo.",
|
||||
"confirm_rename_entity_ids_warning": "Esto no cambiará ninguna configuración (como automatizaciones, scripts, escenas, Lovelace) que estén utilizando actualmente estas entidades, tendrás que actualizarlas tú mismo.",
|
||||
"data_table": {
|
||||
"area": "Área",
|
||||
"battery": "Batería",
|
||||
@ -1500,6 +1525,9 @@
|
||||
"no_device": "Entidades sin dispositivos",
|
||||
"no_devices": "Esta integración no tiene dispositivos.",
|
||||
"options": "Opciones",
|
||||
"reload": "Recargar",
|
||||
"reload_confirm": "La integración se ha recargado",
|
||||
"reload_restart_confirm": "Reinicia Home Assistant para terminar de recargar esta integración",
|
||||
"rename": "Renombrar",
|
||||
"restart_confirm": "Reinicia Home Assistant para terminar de eliminar esta integración.",
|
||||
"settings_button": "Editar configuración para {integration}",
|
||||
@ -1658,7 +1686,11 @@
|
||||
"topic": "tema"
|
||||
},
|
||||
"ozw": {
|
||||
"button": "Configurar",
|
||||
"common": {
|
||||
"controller": "Controlador",
|
||||
"instance": "Instancia",
|
||||
"network": "Red",
|
||||
"node_id": "ID del Nodo",
|
||||
"ozw_instance": "Instancia OpenZWave",
|
||||
"zwave": "Z-Wave"
|
||||
@ -1668,6 +1700,36 @@
|
||||
"stage": "Etapa",
|
||||
"zwave_info": "Información Z-Wave"
|
||||
},
|
||||
"navigation": {
|
||||
"network": "Red",
|
||||
"nodes": "Nodos",
|
||||
"select_instance": "Seleccionar Instancia"
|
||||
},
|
||||
"network_status": {
|
||||
"details": {
|
||||
"driverallnodesqueried": "Se han consultado todos los nodos.",
|
||||
"driverallnodesqueriedsomedead": "Se han consultado todos los nodos. Se encontraron algunos nodos muertos",
|
||||
"driverawakenodesqueries": "Se han consultado todos los nodos despiertos",
|
||||
"driverfailed": "No se pudo conectar con el controlador Z-Wave",
|
||||
"driverready": "Iniciando el controlador Z-Wave",
|
||||
"driverremoved": "El controlador ha sido eliminado",
|
||||
"driverreset": "El controlador se ha reiniciado",
|
||||
"offline": "OZWDaemon desconectado",
|
||||
"ready": "Listo para conectar",
|
||||
"started": "Conectado a MQTT",
|
||||
"starting": "Conectando con MQTT",
|
||||
"stopped": "OpenZWave detenido"
|
||||
},
|
||||
"offline": "Desconectado",
|
||||
"online": "En línea",
|
||||
"starting": "Iniciando",
|
||||
"unknown": "Desconocido"
|
||||
},
|
||||
"network": {
|
||||
"header": "Administración de la Red",
|
||||
"introduction": "Gestionar las funciones de toda la red.",
|
||||
"node_count": "{count} nodos"
|
||||
},
|
||||
"node_query_stages": {
|
||||
"associations": "Refrescando grupos de asociaciones y miembros",
|
||||
"cacheload": "Cargando información del archivo de caché de OpenZWave. Los nodos con batería permanecerán en esta etapa hasta que el nodo se active.",
|
||||
@ -1698,6 +1760,14 @@
|
||||
"title": "Refrescar Información del Nodo",
|
||||
"wakeup_header": "Instrucciones para despertar a",
|
||||
"wakeup_instructions_source": "Las instrucciones para despertarlo se obtienen de la base de datos de dispositivos de la comunidad OpenZWave."
|
||||
},
|
||||
"select_instance": {
|
||||
"header": "Selecciona una instancia de OpenZWave",
|
||||
"introduction": "Tienes más de una instancia de OpenZWave en ejecución. ¿Qué instancia te gustaría gestionar?"
|
||||
},
|
||||
"services": {
|
||||
"add_node": "Añadir Nodo",
|
||||
"remove_node": "Eliminar Nodo"
|
||||
}
|
||||
},
|
||||
"person": {
|
||||
@ -1823,18 +1893,32 @@
|
||||
"section": {
|
||||
"reloading": {
|
||||
"automation": "Recargar automatizaciones",
|
||||
"command_line": "Recargar entidades de línea de comandos",
|
||||
"core": "Recargar ubicación y personalizaciones",
|
||||
"filesize": "Recargar entidades de tamaño de archivo",
|
||||
"filter": "Recargar entidades de filtro",
|
||||
"generic": "Recargar entidades de cámara IP genéricas",
|
||||
"generic_thermostat": "Recargar entidades de termostato genéricas",
|
||||
"group": "Recargar grupos",
|
||||
"heading": "Recargando la configuración YAML",
|
||||
"history_stats": "Recargar entidades de estadísticas del historial",
|
||||
"homekit": "Recargar HomeKit",
|
||||
"input_boolean": "Recargar los campos booleanos",
|
||||
"input_datetime": "Recargar los campos de fecha y hora",
|
||||
"input_number": "Recargar los campos numéricos",
|
||||
"input_select": "Recargar los campos desplegables",
|
||||
"input_text": "Recargar los campos de texto",
|
||||
"introduction": "Algunas partes de Home Assistant pueden recargarse sin necesidad de reiniciar. Al pulsar en recargar se descartará la configuración YAML actual y se cargará la nueva.",
|
||||
"min_max": "Recargar entidades min/max",
|
||||
"person": "Recargar personas",
|
||||
"ping": "Recargar entidades de sensor binario de ping",
|
||||
"rest": "Recargar entidades rest",
|
||||
"scene": "Recargar escenas",
|
||||
"script": "Recargar scripts",
|
||||
"statistics": "Recargar entidades de estadísticas",
|
||||
"template": "Recargar entidades de plantilla",
|
||||
"trend": "Recargar entidades de tendencia",
|
||||
"universal": "Recargar entidades de reproductor multimedia universal",
|
||||
"zone": "Recargar zonas"
|
||||
},
|
||||
"server_management": {
|
||||
@ -1876,6 +1960,7 @@
|
||||
"last_scanned": "Última vez escaneada",
|
||||
"name": "Nombre"
|
||||
},
|
||||
"never_scanned": "Nunca escaneado",
|
||||
"no_tags": "Sin etiquetas",
|
||||
"write": "Escribir"
|
||||
},
|
||||
@ -1885,6 +1970,8 @@
|
||||
"create": "Crear",
|
||||
"name": "Nombre",
|
||||
"password": "Contraseña",
|
||||
"password_confirm": "Confirmar contraseña",
|
||||
"password_not_match": "Las contraseñas no coinciden",
|
||||
"username": "Nombre de usuario"
|
||||
},
|
||||
"caption": "Usuarios",
|
||||
@ -1901,7 +1988,9 @@
|
||||
"group": "Grupo",
|
||||
"id": "ID",
|
||||
"name": "Nombre",
|
||||
"new_password": "Nueva contraseña",
|
||||
"owner": "Propietario",
|
||||
"password_changed": "¡La contraseña ha cambiado!",
|
||||
"system_generated": "Generado por el sistema",
|
||||
"system_generated_users_not_editable": "No se pueden actualizar los usuarios generados por el sistema.",
|
||||
"system_generated_users_not_removable": "No se pueden eliminar los usuarios generados por el sistema.",
|
||||
@ -2200,6 +2289,7 @@
|
||||
"description": "Las plantillas se muestran utilizando el motor de plantillas Jinja2 con algunas extensiones específicas de Home Assistant.",
|
||||
"editor": "Editor de plantillas",
|
||||
"jinja_documentation": "Documentación de plantilla Jinja2",
|
||||
"reset": "Reiniciar a la plantilla de demostración",
|
||||
"template_extensions": "Extensiones de plantilla de Home Assistant",
|
||||
"title": "Plantillas",
|
||||
"unknown_error_template": "Error desconocido al mostrar la plantilla"
|
||||
@ -2281,6 +2371,10 @@
|
||||
"description": "La tarjeta Botón te permite agregar botones para realizar tareas.",
|
||||
"name": "Botón"
|
||||
},
|
||||
"calendar": {
|
||||
"description": "La tarjeta Calendario muestra un calendario que incluye vistas de día, semana y lista",
|
||||
"name": "Calendario"
|
||||
},
|
||||
"conditional": {
|
||||
"card": "Tarjeta",
|
||||
"change_type": "Cambiar el tipo",
|
||||
@ -2346,6 +2440,7 @@
|
||||
"show_name": "¿Mostrar nombre?",
|
||||
"show_state": "¿Mostrar estado?",
|
||||
"state": "Estado",
|
||||
"state_color": "¿Iconos de colores según el estado?",
|
||||
"tap_action": "Acción de toque",
|
||||
"theme": "Tema",
|
||||
"title": "Título",
|
||||
@ -2735,6 +2830,7 @@
|
||||
"intro": "Hola {name}, bienvenido a Home Assistant. ¿Cómo te gustaría llamar a tu casa?",
|
||||
"intro_location": "Nos gustaría saber dónde vives. Esta información ayudará a mostrar información y a configurar automatizaciones basadas en el sol. Estos datos nunca se comparten fuera de tu red.",
|
||||
"intro_location_detect": "Podemos ayudarte a completar esta información haciendo una solicitud única a un servicio externo.",
|
||||
"location_name": "Nombre de tu instalación de Home Assistant",
|
||||
"location_name_default": "Casa"
|
||||
},
|
||||
"integration": {
|
||||
|
@ -923,10 +923,6 @@
|
||||
"at": "Kell",
|
||||
"label": "Aeg"
|
||||
},
|
||||
"webhook": {
|
||||
"label": "",
|
||||
"webhook_id": ""
|
||||
},
|
||||
"zone": {
|
||||
"enter": "Sisenemine",
|
||||
"entity": "Asukohaga olem",
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user